@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname,
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
6
6
|
import { getIsContainerized } from "../config/env-registry.js";
|
|
7
7
|
import { getConfig } from "../config/loader.js";
|
|
8
8
|
import { loadSkillCatalog, resolveSkillSelector } from "../config/skills.js";
|
|
9
9
|
import { indexCatalogById } from "../skills/include-graph.js";
|
|
10
|
-
import {
|
|
11
|
-
isSkillSourcePath,
|
|
12
|
-
normalizeDirPath,
|
|
13
|
-
normalizeFilePath,
|
|
14
|
-
} from "../skills/path-classifier.js";
|
|
10
|
+
import { normalizeFilePath } from "../skills/path-classifier.js";
|
|
15
11
|
import { computeTransitiveSkillVersionHash } from "../skills/transitive-version-hash.js";
|
|
16
12
|
import { computeSkillVersionHash } from "../skills/version-hash.js";
|
|
17
13
|
import type { ManifestOverride } from "../tools/execution-target.js";
|
|
@@ -21,16 +17,20 @@ import {
|
|
|
21
17
|
} from "../tools/network/url-safety.js";
|
|
22
18
|
import { getTool } from "../tools/registry.js";
|
|
23
19
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} from "
|
|
20
|
+
type ApprovalContext,
|
|
21
|
+
DefaultApprovalPolicy,
|
|
22
|
+
resolveThreshold,
|
|
23
|
+
} from "./approval-policy.js";
|
|
24
|
+
import { bashRiskClassifier } from "./bash-risk-classifier.js";
|
|
25
|
+
import { fileRiskClassifier } from "./file-risk-classifier.js";
|
|
26
|
+
import { type RiskAssessment, riskToRiskLevel } from "./risk-types.js";
|
|
28
27
|
import {
|
|
29
28
|
buildShellAllowlistOptions,
|
|
30
29
|
buildShellCommandCandidates,
|
|
31
30
|
cachedParse,
|
|
32
31
|
type ParsedCommand,
|
|
33
32
|
} from "./shell-identity.js";
|
|
33
|
+
import { skillLoadRiskClassifier } from "./skill-risk-classifier.js";
|
|
34
34
|
import { findHighestPriorityRule, onRulesChanged } from "./trust-store.js";
|
|
35
35
|
import {
|
|
36
36
|
type AllowlistOption,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
RiskLevel,
|
|
40
40
|
type ScopeOption,
|
|
41
41
|
} from "./types.js";
|
|
42
|
+
import { webRiskClassifier } from "./web-risk-classifier.js";
|
|
42
43
|
import { isWorkspaceScopedInvocation } from "./workspace-policy.js";
|
|
43
44
|
|
|
44
45
|
// ── Risk classification cache ────────────────────────────────────────────────
|
|
@@ -48,10 +49,35 @@ import { isWorkspaceScopedInvocation } from "./workspace-policy.js";
|
|
|
48
49
|
// Invalidated when trust rules change since risk classification for file tools
|
|
49
50
|
// depends on skill source path checks which reference config, but the core
|
|
50
51
|
// risk logic is input-deterministic.
|
|
52
|
+
/** The result of classifyRisk(): a risk level with an optional human-readable reason. */
|
|
53
|
+
export interface RiskClassification {
|
|
54
|
+
level: RiskLevel;
|
|
55
|
+
/** Human-readable explanation of why this risk level was assigned. */
|
|
56
|
+
reason?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
const RISK_CACHE_MAX = 256;
|
|
52
|
-
const riskCache = new Map<string,
|
|
60
|
+
const riskCache = new Map<string, RiskClassification>();
|
|
53
61
|
let riskCacheInvalidationHookRegistered = false;
|
|
54
62
|
|
|
63
|
+
// ── Assessment cache ─────────────────────────────────────────────────────────
|
|
64
|
+
// Stores the full RiskAssessment from classifier-backed tools so that
|
|
65
|
+
// generateAllowlistOptions() can read classifier-produced allowlistOptions
|
|
66
|
+
// without re-classifying. Keyed on (toolName, inputHash) — a simpler key
|
|
67
|
+
// than the full risk cache since generateAllowlistOptions() does not receive
|
|
68
|
+
// workingDir or manifestOverride. Cleared alongside the risk cache.
|
|
69
|
+
const assessmentCache = new Map<string, RiskAssessment>();
|
|
70
|
+
|
|
71
|
+
function assessmentCacheKey(
|
|
72
|
+
toolName: string,
|
|
73
|
+
input: Record<string, unknown>,
|
|
74
|
+
): string {
|
|
75
|
+
const { reason: _reason, activity: _activity, ...cacheableInput } = input;
|
|
76
|
+
const inputJson = JSON.stringify(cacheableInput);
|
|
77
|
+
const hash = createHash("sha256").update(inputJson).digest("hex");
|
|
78
|
+
return `${toolName}\0${hash}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
function riskCacheKey(
|
|
56
82
|
toolName: string,
|
|
57
83
|
input: Record<string, unknown>,
|
|
@@ -76,6 +102,7 @@ function riskCacheKey(
|
|
|
76
102
|
/** Clear the risk classification cache. Called when trust rules change. */
|
|
77
103
|
function clearRiskCache(): void {
|
|
78
104
|
riskCache.clear();
|
|
105
|
+
assessmentCache.clear();
|
|
79
106
|
}
|
|
80
107
|
|
|
81
108
|
function ensureRiskCacheInvalidationHook(): void {
|
|
@@ -86,319 +113,8 @@ function ensureRiskCacheInvalidationHook(): void {
|
|
|
86
113
|
onRulesChanged(clearRiskCache);
|
|
87
114
|
}
|
|
88
115
|
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
"ls",
|
|
92
|
-
"cat",
|
|
93
|
-
"head",
|
|
94
|
-
"tail",
|
|
95
|
-
"less",
|
|
96
|
-
"more",
|
|
97
|
-
"wc",
|
|
98
|
-
"file",
|
|
99
|
-
"stat",
|
|
100
|
-
"grep",
|
|
101
|
-
"rg",
|
|
102
|
-
"ag",
|
|
103
|
-
"ack",
|
|
104
|
-
"find",
|
|
105
|
-
"fd",
|
|
106
|
-
"which",
|
|
107
|
-
"where",
|
|
108
|
-
"whereis",
|
|
109
|
-
"type",
|
|
110
|
-
"echo",
|
|
111
|
-
"printf",
|
|
112
|
-
"date",
|
|
113
|
-
"cal",
|
|
114
|
-
"uptime",
|
|
115
|
-
"whoami",
|
|
116
|
-
"hostname",
|
|
117
|
-
"uname",
|
|
118
|
-
"pwd",
|
|
119
|
-
"realpath",
|
|
120
|
-
"dirname",
|
|
121
|
-
"basename",
|
|
122
|
-
"git",
|
|
123
|
-
"node",
|
|
124
|
-
"bun",
|
|
125
|
-
"deno",
|
|
126
|
-
"npm",
|
|
127
|
-
"npx",
|
|
128
|
-
"yarn",
|
|
129
|
-
"pnpm",
|
|
130
|
-
"python",
|
|
131
|
-
"python3",
|
|
132
|
-
"pip",
|
|
133
|
-
"pip3",
|
|
134
|
-
"man",
|
|
135
|
-
"help",
|
|
136
|
-
"info",
|
|
137
|
-
"env",
|
|
138
|
-
"printenv",
|
|
139
|
-
"set",
|
|
140
|
-
"diff",
|
|
141
|
-
"sort",
|
|
142
|
-
"uniq",
|
|
143
|
-
"cut",
|
|
144
|
-
"tr",
|
|
145
|
-
"tee",
|
|
146
|
-
"xargs",
|
|
147
|
-
"jq",
|
|
148
|
-
"yq",
|
|
149
|
-
"http",
|
|
150
|
-
"dig",
|
|
151
|
-
"nslookup",
|
|
152
|
-
"ping",
|
|
153
|
-
"tree",
|
|
154
|
-
"du",
|
|
155
|
-
"df",
|
|
156
|
-
]);
|
|
157
|
-
|
|
158
|
-
// High-risk shell programs / patterns
|
|
159
|
-
const HIGH_RISK_PROGRAMS = new Set([
|
|
160
|
-
"sudo",
|
|
161
|
-
"su",
|
|
162
|
-
"doas",
|
|
163
|
-
"dd",
|
|
164
|
-
"mkfs",
|
|
165
|
-
"fdisk",
|
|
166
|
-
"parted",
|
|
167
|
-
"mount",
|
|
168
|
-
"umount",
|
|
169
|
-
"systemctl",
|
|
170
|
-
"service",
|
|
171
|
-
"launchctl",
|
|
172
|
-
"useradd",
|
|
173
|
-
"userdel",
|
|
174
|
-
"usermod",
|
|
175
|
-
"groupadd",
|
|
176
|
-
"groupdel",
|
|
177
|
-
"iptables",
|
|
178
|
-
"ufw",
|
|
179
|
-
"firewall-cmd",
|
|
180
|
-
"reboot",
|
|
181
|
-
"shutdown",
|
|
182
|
-
"halt",
|
|
183
|
-
"poweroff",
|
|
184
|
-
"kill",
|
|
185
|
-
"killall",
|
|
186
|
-
"pkill",
|
|
187
|
-
]);
|
|
188
|
-
|
|
189
|
-
// Git subcommands that are low-risk (read-only)
|
|
190
|
-
const LOW_RISK_GIT_SUBCOMMANDS = new Set([
|
|
191
|
-
"status",
|
|
192
|
-
"log",
|
|
193
|
-
"diff",
|
|
194
|
-
"show",
|
|
195
|
-
"branch",
|
|
196
|
-
"tag",
|
|
197
|
-
"remote",
|
|
198
|
-
"stash",
|
|
199
|
-
"blame",
|
|
200
|
-
"shortlog",
|
|
201
|
-
"describe",
|
|
202
|
-
"rev-parse",
|
|
203
|
-
"ls-files",
|
|
204
|
-
"ls-tree",
|
|
205
|
-
"cat-file",
|
|
206
|
-
"reflog",
|
|
207
|
-
]);
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Classify risk for `assistant` CLI subcommands. Multi-word subcommands
|
|
211
|
-
* (e.g. `assistant oauth token`) are matched by walking the positional args.
|
|
212
|
-
*/
|
|
213
|
-
function classifyAssistantSubcommand(args: string[]): RiskLevel {
|
|
214
|
-
const sub = firstPositionalArg(args);
|
|
215
|
-
if (!sub) return RiskLevel.Low;
|
|
216
|
-
|
|
217
|
-
if (sub === "oauth") {
|
|
218
|
-
const oauthSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
|
|
219
|
-
if (oauthSub === "token") return RiskLevel.High;
|
|
220
|
-
if (oauthSub === "mode") {
|
|
221
|
-
// `oauth mode --set` is high risk; bare `oauth mode` (read) is low.
|
|
222
|
-
// Match both `--set value` (two tokens) and `--set=value` (one token).
|
|
223
|
-
if (args.some((a) => a === "--set" || a.startsWith("--set=")))
|
|
224
|
-
return RiskLevel.High;
|
|
225
|
-
return RiskLevel.Low;
|
|
226
|
-
}
|
|
227
|
-
if (oauthSub === "request") return RiskLevel.Medium;
|
|
228
|
-
if (oauthSub === "connect" || oauthSub === "disconnect")
|
|
229
|
-
return RiskLevel.Medium;
|
|
230
|
-
return RiskLevel.Low;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (sub === "credentials") {
|
|
234
|
-
const credSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
|
|
235
|
-
if (credSub === "reveal") return RiskLevel.High;
|
|
236
|
-
if (credSub === "set" || credSub === "delete") return RiskLevel.High;
|
|
237
|
-
return RiskLevel.Low;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (sub === "keys") {
|
|
241
|
-
const keysSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
|
|
242
|
-
if (keysSub === "set" || keysSub === "delete") return RiskLevel.High;
|
|
243
|
-
return RiskLevel.Low;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (sub === "trust") {
|
|
247
|
-
const trustSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
|
|
248
|
-
if (trustSub === "remove" || trustSub === "clear") return RiskLevel.High;
|
|
249
|
-
return RiskLevel.Low;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return RiskLevel.Low;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Commands that wrap another program — the real program appears as the first
|
|
256
|
-
// non-flag argument. When one of these is the segment program we look through
|
|
257
|
-
// its args to find the effective program (e.g. `env curl …` → curl).
|
|
258
|
-
const WRAPPER_PROGRAMS = new Set([
|
|
259
|
-
"env",
|
|
260
|
-
"nice",
|
|
261
|
-
"nohup",
|
|
262
|
-
"time",
|
|
263
|
-
"command",
|
|
264
|
-
"exec",
|
|
265
|
-
"strace",
|
|
266
|
-
"ltrace",
|
|
267
|
-
"ionice",
|
|
268
|
-
"taskset",
|
|
269
|
-
"timeout",
|
|
270
|
-
]);
|
|
271
|
-
|
|
272
|
-
// `env` flags that consume the next positional argument as their value.
|
|
273
|
-
// Without this, `env -u curl echo` would incorrectly identify `curl` (the
|
|
274
|
-
// value of -u) as the wrapped program instead of `echo`.
|
|
275
|
-
const ENV_VALUE_FLAGS = new Set(["-u", "--unset", "-C", "--chdir"]);
|
|
276
|
-
|
|
277
|
-
// `timeout` flags that consume the next positional argument as their value.
|
|
278
|
-
const TIMEOUT_VALUE_FLAGS = new Set(["-s", "--signal", "-k", "--kill-after"]);
|
|
279
|
-
|
|
280
|
-
// Wrapper programs where the first non-flag positional argument is a
|
|
281
|
-
// configuration value (duration, CPU mask), not the wrapped program name.
|
|
282
|
-
// For these wrappers, the second non-flag positional is the real program.
|
|
283
|
-
const WRAPPER_SKIP_FIRST_POSITIONAL = new Set(["timeout", "taskset"]);
|
|
284
|
-
|
|
285
|
-
// `git` global flags that consume the next positional argument as their value.
|
|
286
|
-
// Without this, `git -C status commit` would incorrectly identify `status`
|
|
287
|
-
// (the directory path) as the subcommand instead of `commit`.
|
|
288
|
-
const GIT_VALUE_FLAGS = new Set([
|
|
289
|
-
"-C",
|
|
290
|
-
"-c",
|
|
291
|
-
"--git-dir",
|
|
292
|
-
"--work-tree",
|
|
293
|
-
"--namespace",
|
|
294
|
-
"--super-prefix",
|
|
295
|
-
"--config-env",
|
|
296
|
-
]);
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Return the first non-flag argument from an argument list, optionally
|
|
300
|
-
* skipping value-taking flags. Flags are arguments that start with `-`.
|
|
301
|
-
* This is used to skip global options (e.g. `--verbose`, `-h`, `-C <path>`)
|
|
302
|
-
* when extracting the subcommand from CLIs like `git`, `vellum`, and
|
|
303
|
-
* `assistant`.
|
|
304
|
-
*
|
|
305
|
-
* When `valueFlags` is provided, any flag in that set causes the next
|
|
306
|
-
* argument to be skipped as well (it is the flag's value, not a positional).
|
|
307
|
-
*/
|
|
308
|
-
function firstPositionalArg(
|
|
309
|
-
args: string[],
|
|
310
|
-
valueFlags?: Set<string>,
|
|
311
|
-
): string | undefined {
|
|
312
|
-
for (let i = 0; i < args.length; i++) {
|
|
313
|
-
const arg = args[i];
|
|
314
|
-
if (arg.startsWith("-")) {
|
|
315
|
-
if (valueFlags?.has(arg)) i++; // skip the next arg (the flag's value)
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
return arg;
|
|
319
|
-
}
|
|
320
|
-
return undefined;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Bare filenames that `rm` is allowed to delete at Medium risk (instead of
|
|
324
|
-
// High) so workspace-scoped allow rules can approve them without the
|
|
325
|
-
// dangerous `allowHighRisk` flag. Only matches when the args contain no
|
|
326
|
-
// flags and exactly one of these filenames.
|
|
327
|
-
const RM_SAFE_BARE_FILES = new Set(["BOOTSTRAP.md", "UPDATES.md"]);
|
|
328
|
-
|
|
329
|
-
function isRmOfKnownSafeFile(args: string[]): boolean {
|
|
330
|
-
if (args.length !== 1) return false;
|
|
331
|
-
const target = args[0];
|
|
332
|
-
if (target.startsWith("-") || target.includes("/")) return false;
|
|
333
|
-
return RM_SAFE_BARE_FILES.has(target);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Given a segment whose program is a known wrapper, return the first
|
|
338
|
-
* non-flag argument (i.e. the wrapped program name). Returns `undefined`
|
|
339
|
-
* when no suitable argument is found.
|
|
340
|
-
*
|
|
341
|
-
* Handles `env` specially: skips `VAR=value` pairs and value-taking flags
|
|
342
|
-
* like `-u NAME` and `-C DIR`.
|
|
343
|
-
*
|
|
344
|
-
* Handles `timeout` and `taskset` specially: their first non-flag positional
|
|
345
|
-
* argument is a duration or CPU mask, not the wrapped program. The second
|
|
346
|
-
* non-flag positional is the real program.
|
|
347
|
-
*/
|
|
348
|
-
function getWrappedProgram(seg: {
|
|
349
|
-
program: string;
|
|
350
|
-
args: string[];
|
|
351
|
-
}): string | undefined {
|
|
352
|
-
const isEnv = seg.program === "env";
|
|
353
|
-
const isTimeout = seg.program === "timeout";
|
|
354
|
-
const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
|
|
355
|
-
let skippedFirstPositional = false;
|
|
356
|
-
for (let i = 0; i < seg.args.length; i++) {
|
|
357
|
-
const arg = seg.args[i];
|
|
358
|
-
if (arg.startsWith("-")) {
|
|
359
|
-
if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++; // skip the value argument
|
|
360
|
-
if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++; // skip the value argument
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
if (isEnv && arg.includes("=")) continue; // skip env VAR=value pairs
|
|
364
|
-
if (skipFirst && !skippedFirstPositional) {
|
|
365
|
-
skippedFirstPositional = true;
|
|
366
|
-
continue; // skip the duration/CPU mask
|
|
367
|
-
}
|
|
368
|
-
return arg;
|
|
369
|
-
}
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Like `getWrappedProgram`, but also returns the remaining args after the
|
|
375
|
-
* wrapped program name. This allows callers to propagate subcommand-aware
|
|
376
|
-
* classification (e.g. `env assistant oauth token` → classify `oauth token`).
|
|
377
|
-
*/
|
|
378
|
-
function getWrappedProgramWithArgs(seg: {
|
|
379
|
-
program: string;
|
|
380
|
-
args: string[];
|
|
381
|
-
}): { program: string; args: string[] } | undefined {
|
|
382
|
-
const isEnv = seg.program === "env";
|
|
383
|
-
const isTimeout = seg.program === "timeout";
|
|
384
|
-
const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
|
|
385
|
-
let skippedFirstPositional = false;
|
|
386
|
-
for (let i = 0; i < seg.args.length; i++) {
|
|
387
|
-
const arg = seg.args[i];
|
|
388
|
-
if (arg.startsWith("-")) {
|
|
389
|
-
if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++;
|
|
390
|
-
if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++;
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
if (isEnv && arg.includes("=")) continue;
|
|
394
|
-
if (skipFirst && !skippedFirstPositional) {
|
|
395
|
-
skippedFirstPositional = true;
|
|
396
|
-
continue; // skip the duration/CPU mask
|
|
397
|
-
}
|
|
398
|
-
return { program: arg, args: seg.args.slice(i + 1) };
|
|
399
|
-
}
|
|
400
|
-
return undefined;
|
|
401
|
-
}
|
|
116
|
+
// ── Approval policy singleton ────────────────────────────────────────────────
|
|
117
|
+
const defaultApprovalPolicy = new DefaultApprovalPolicy();
|
|
402
118
|
|
|
403
119
|
function getStringField(
|
|
404
120
|
input: Record<string, unknown>,
|
|
@@ -582,11 +298,7 @@ async function buildCommandCandidates(
|
|
|
582
298
|
return [`${toolName}:${skillId}`];
|
|
583
299
|
}
|
|
584
300
|
|
|
585
|
-
if (
|
|
586
|
-
toolName === "web_fetch" ||
|
|
587
|
-
toolName === "browser_navigate" ||
|
|
588
|
-
toolName === "network_request"
|
|
589
|
-
) {
|
|
301
|
+
if (toolName === "web_fetch" || toolName === "network_request") {
|
|
590
302
|
const rawUrl = getStringField(input, "url").trim();
|
|
591
303
|
const candidates: string[] = [];
|
|
592
304
|
|
|
@@ -663,7 +375,7 @@ export async function classifyRisk(
|
|
|
663
375
|
preParsed?: ParsedCommand,
|
|
664
376
|
manifestOverride?: ManifestOverride,
|
|
665
377
|
signal?: AbortSignal,
|
|
666
|
-
): Promise<
|
|
378
|
+
): Promise<RiskClassification> {
|
|
667
379
|
signal?.throwIfAborted();
|
|
668
380
|
ensureRiskCacheInvalidationHook();
|
|
669
381
|
|
|
@@ -682,13 +394,98 @@ export async function classifyRisk(
|
|
|
682
394
|
}
|
|
683
395
|
}
|
|
684
396
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
397
|
+
// ── Bash/host_bash: delegate to the registry-driven BashRiskClassifier ────
|
|
398
|
+
let result: RiskClassification;
|
|
399
|
+
let classifierAssessment: RiskAssessment | undefined;
|
|
400
|
+
if (toolName === "bash" || toolName === "host_bash") {
|
|
401
|
+
const command = ((input.command as string) ?? "").trim();
|
|
402
|
+
if (!command) {
|
|
403
|
+
result = { level: RiskLevel.Low };
|
|
404
|
+
} else {
|
|
405
|
+
const assessment = await bashRiskClassifier.classify({
|
|
406
|
+
command,
|
|
407
|
+
toolName: toolName as "bash" | "host_bash",
|
|
408
|
+
});
|
|
409
|
+
classifierAssessment = assessment;
|
|
410
|
+
result = {
|
|
411
|
+
level: riskToRiskLevel(assessment.riskLevel),
|
|
412
|
+
reason: assessment.reason,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// ── File tools: delegate to FileRiskClassifier ──────────────────────────
|
|
417
|
+
else if (
|
|
418
|
+
[
|
|
419
|
+
"file_read",
|
|
420
|
+
"file_write",
|
|
421
|
+
"file_edit",
|
|
422
|
+
"host_file_read",
|
|
423
|
+
"host_file_write",
|
|
424
|
+
"host_file_edit",
|
|
425
|
+
].includes(toolName)
|
|
426
|
+
) {
|
|
427
|
+
const filePath = getStringField(input, "path", "file_path");
|
|
428
|
+
const isHostTool = toolName.startsWith("host_");
|
|
429
|
+
const assessment = await fileRiskClassifier.classify({
|
|
430
|
+
toolName: toolName as
|
|
431
|
+
| "file_read"
|
|
432
|
+
| "file_write"
|
|
433
|
+
| "file_edit"
|
|
434
|
+
| "host_file_read"
|
|
435
|
+
| "host_file_write"
|
|
436
|
+
| "host_file_edit",
|
|
437
|
+
filePath,
|
|
438
|
+
workingDir: isHostTool ? "/" : (workingDir ?? process.cwd()),
|
|
439
|
+
});
|
|
440
|
+
classifierAssessment = assessment;
|
|
441
|
+
result = {
|
|
442
|
+
level: riskToRiskLevel(assessment.riskLevel),
|
|
443
|
+
reason: assessment.reason,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// ── Web tools: delegate to WebRiskClassifier ────────────────────────────
|
|
447
|
+
else if (["web_fetch", "network_request", "web_search"].includes(toolName)) {
|
|
448
|
+
const assessment = await webRiskClassifier.classify({
|
|
449
|
+
toolName: toolName as "web_fetch" | "network_request" | "web_search",
|
|
450
|
+
url: getStringField(input, "url"),
|
|
451
|
+
allowPrivateNetwork: input.allow_private_network === true,
|
|
452
|
+
});
|
|
453
|
+
classifierAssessment = assessment;
|
|
454
|
+
result = {
|
|
455
|
+
level: riskToRiskLevel(assessment.riskLevel),
|
|
456
|
+
reason: assessment.reason,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
// ── Skill tools: delegate to SkillLoadRiskClassifier ────────────────────
|
|
460
|
+
else if (
|
|
461
|
+
["skill_load", "scaffold_managed_skill", "delete_managed_skill"].includes(
|
|
462
|
+
toolName,
|
|
463
|
+
)
|
|
464
|
+
) {
|
|
465
|
+
const assessment = await skillLoadRiskClassifier.classify({
|
|
466
|
+
toolName: toolName as
|
|
467
|
+
| "skill_load"
|
|
468
|
+
| "scaffold_managed_skill"
|
|
469
|
+
| "delete_managed_skill",
|
|
470
|
+
skillSelector: getStringField(input, "skill", "skill_id").trim(),
|
|
471
|
+
});
|
|
472
|
+
classifierAssessment = assessment;
|
|
473
|
+
result = {
|
|
474
|
+
level: riskToRiskLevel(assessment.riskLevel),
|
|
475
|
+
reason: assessment.reason,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
// ── Remaining tools: fall through to registry-based classification ──────
|
|
479
|
+
else {
|
|
480
|
+
result = {
|
|
481
|
+
level: await classifyRiskFromRegistry(
|
|
482
|
+
toolName,
|
|
483
|
+
input,
|
|
484
|
+
workingDir,
|
|
485
|
+
manifestOverride,
|
|
486
|
+
),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
692
489
|
|
|
693
490
|
// Proxied bash commands route through the credential proxy which handles
|
|
694
491
|
// per-request approval separately. Cap the bash tool's own risk at Medium
|
|
@@ -696,9 +493,9 @@ export async function classifyRisk(
|
|
|
696
493
|
if (
|
|
697
494
|
toolName === "bash" &&
|
|
698
495
|
input.network_mode === "proxied" &&
|
|
699
|
-
result === RiskLevel.High
|
|
496
|
+
result.level === RiskLevel.High
|
|
700
497
|
) {
|
|
701
|
-
result = RiskLevel.Medium;
|
|
498
|
+
result = { level: RiskLevel.Medium, reason: result.reason };
|
|
702
499
|
}
|
|
703
500
|
|
|
704
501
|
if (cacheKey) {
|
|
@@ -709,237 +506,26 @@ export async function classifyRisk(
|
|
|
709
506
|
riskCache.set(cacheKey, result);
|
|
710
507
|
}
|
|
711
508
|
|
|
509
|
+
// Store the full assessment in a separate cache keyed on (toolName, input)
|
|
510
|
+
// so generateAllowlistOptions() can retrieve classifier-produced options.
|
|
511
|
+
if (classifierAssessment) {
|
|
512
|
+
const aKey = assessmentCacheKey(toolName, input);
|
|
513
|
+
if (assessmentCache.size >= RISK_CACHE_MAX) {
|
|
514
|
+
const oldest = assessmentCache.keys().next().value;
|
|
515
|
+
if (oldest !== undefined) assessmentCache.delete(oldest);
|
|
516
|
+
}
|
|
517
|
+
assessmentCache.set(aKey, classifierAssessment);
|
|
518
|
+
}
|
|
519
|
+
|
|
712
520
|
return result;
|
|
713
521
|
}
|
|
714
522
|
|
|
715
|
-
async function
|
|
523
|
+
async function classifyRiskFromRegistry(
|
|
716
524
|
toolName: string,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
preParsed?: ParsedCommand,
|
|
525
|
+
_input: Record<string, unknown>,
|
|
526
|
+
_workingDir?: string,
|
|
720
527
|
manifestOverride?: ManifestOverride,
|
|
721
528
|
): Promise<RiskLevel> {
|
|
722
|
-
if (toolName === "file_read") {
|
|
723
|
-
const filePath = getStringField(input, "path", "file_path");
|
|
724
|
-
if (isActorTokenSigningKeyPath(filePath, workingDir)) {
|
|
725
|
-
return RiskLevel.High;
|
|
726
|
-
}
|
|
727
|
-
return RiskLevel.Low;
|
|
728
|
-
}
|
|
729
|
-
if (toolName === "file_write" || toolName === "file_edit") {
|
|
730
|
-
const filePath = getStringField(input, "path", "file_path");
|
|
731
|
-
if (
|
|
732
|
-
filePath &&
|
|
733
|
-
isSkillSourcePath(
|
|
734
|
-
resolve(workingDir ?? process.cwd(), filePath),
|
|
735
|
-
getConfig().skills.load.extraDirs,
|
|
736
|
-
)
|
|
737
|
-
) {
|
|
738
|
-
return RiskLevel.High;
|
|
739
|
-
}
|
|
740
|
-
if (filePath) {
|
|
741
|
-
const normalizedHooksDir = normalizeDirPath(getWorkspaceHooksDir());
|
|
742
|
-
const normalizedPath = normalizeFilePath(
|
|
743
|
-
resolve(workingDir ?? process.cwd(), filePath),
|
|
744
|
-
);
|
|
745
|
-
const hooksDirNoTrailingSlash = normalizedHooksDir.slice(0, -1);
|
|
746
|
-
if (
|
|
747
|
-
normalizedPath === hooksDirNoTrailingSlash ||
|
|
748
|
-
normalizedPath.startsWith(normalizedHooksDir)
|
|
749
|
-
) {
|
|
750
|
-
return RiskLevel.High;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
return RiskLevel.Low;
|
|
754
|
-
}
|
|
755
|
-
if (toolName === "web_search") return RiskLevel.Low;
|
|
756
|
-
if (toolName === "web_fetch") {
|
|
757
|
-
// Private-network fetches are High risk so that blanket allow rules
|
|
758
|
-
// (including the starter bundle) cannot silently bypass the prompt.
|
|
759
|
-
return input.allow_private_network === true
|
|
760
|
-
? RiskLevel.High
|
|
761
|
-
: RiskLevel.Low;
|
|
762
|
-
}
|
|
763
|
-
if (toolName === "browser_navigate") {
|
|
764
|
-
return input.allow_private_network === true
|
|
765
|
-
? RiskLevel.High
|
|
766
|
-
: RiskLevel.Low;
|
|
767
|
-
}
|
|
768
|
-
// All other browser tools are low risk — the browser is sandboxed and user-visible.
|
|
769
|
-
if (toolName.startsWith("browser_")) return RiskLevel.Low;
|
|
770
|
-
// Proxy-authenticated network requests are Medium risk — they carry injected
|
|
771
|
-
// credentials and the user should approve the target host/origin.
|
|
772
|
-
if (toolName === "network_request") return RiskLevel.Medium;
|
|
773
|
-
if (toolName === "skill_load") return RiskLevel.Low;
|
|
774
|
-
|
|
775
|
-
// Skill mutation tools are always High risk — they write or delete persistent
|
|
776
|
-
// skill source code. These tools moved from core tool registry to bundled
|
|
777
|
-
// skills, but their security classification must remain High regardless of
|
|
778
|
-
// whether they appear in the tool registry.
|
|
779
|
-
if (
|
|
780
|
-
toolName === "scaffold_managed_skill" ||
|
|
781
|
-
toolName === "delete_managed_skill"
|
|
782
|
-
) {
|
|
783
|
-
return RiskLevel.High;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// Escalate host file mutations targeting skill source paths to High risk.
|
|
787
|
-
// The host variants fall through to the tool registry (Medium) by default,
|
|
788
|
-
// but writing to skill source code is a privilege-escalation vector.
|
|
789
|
-
if (toolName === "host_file_write" || toolName === "host_file_edit") {
|
|
790
|
-
const filePath = getStringField(input, "path", "file_path");
|
|
791
|
-
if (
|
|
792
|
-
filePath &&
|
|
793
|
-
isSkillSourcePath(resolve(filePath), getConfig().skills.load.extraDirs)
|
|
794
|
-
) {
|
|
795
|
-
return RiskLevel.High;
|
|
796
|
-
}
|
|
797
|
-
if (filePath) {
|
|
798
|
-
const normalizedHooksDir = normalizeDirPath(getWorkspaceHooksDir());
|
|
799
|
-
const normalizedPath = normalizeFilePath(resolve(filePath));
|
|
800
|
-
const hooksDirNoTrailingSlash = normalizedHooksDir.slice(0, -1);
|
|
801
|
-
if (
|
|
802
|
-
normalizedPath === hooksDirNoTrailingSlash ||
|
|
803
|
-
normalizedPath.startsWith(normalizedHooksDir)
|
|
804
|
-
) {
|
|
805
|
-
return RiskLevel.High;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
// Fall through to the tool registry default (Medium) below.
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (toolName === "bash" || toolName === "host_bash") {
|
|
812
|
-
const command = (input.command as string) ?? "";
|
|
813
|
-
if (!command.trim()) return RiskLevel.Low;
|
|
814
|
-
|
|
815
|
-
const parsed = preParsed ?? (await cachedParse(command));
|
|
816
|
-
|
|
817
|
-
// Dangerous patterns → High
|
|
818
|
-
if (parsed.dangerousPatterns.length > 0) return RiskLevel.High;
|
|
819
|
-
|
|
820
|
-
// Opaque constructs → at least Medium (never Low)
|
|
821
|
-
if (parsed.hasOpaqueConstructs) return RiskLevel.Medium;
|
|
822
|
-
|
|
823
|
-
// Check each segment
|
|
824
|
-
let maxRisk = RiskLevel.Low;
|
|
825
|
-
|
|
826
|
-
for (const seg of parsed.segments) {
|
|
827
|
-
const prog = seg.program;
|
|
828
|
-
|
|
829
|
-
if (HIGH_RISK_PROGRAMS.has(prog)) return RiskLevel.High;
|
|
830
|
-
|
|
831
|
-
if (prog === "rm") {
|
|
832
|
-
// Only downgrade rm of known safe workspace files for sandboxed bash.
|
|
833
|
-
// host_bash has a global ask rule that would prompt Medium-risk
|
|
834
|
-
// commands, so rm on the host must always require explicit approval.
|
|
835
|
-
if (toolName === "bash" && isRmOfKnownSafeFile(seg.args)) {
|
|
836
|
-
maxRisk = RiskLevel.Medium;
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
return RiskLevel.High;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
if (
|
|
843
|
-
prog === "chmod" ||
|
|
844
|
-
prog === "chown" ||
|
|
845
|
-
prog === "chgrp" ||
|
|
846
|
-
prog === "sed" ||
|
|
847
|
-
prog === "awk"
|
|
848
|
-
) {
|
|
849
|
-
maxRisk = RiskLevel.Medium;
|
|
850
|
-
continue;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// curl/wget can download and execute arbitrary code from the internet.
|
|
854
|
-
// Also catch wrapped invocations like `env curl …` or `nice wget …`.
|
|
855
|
-
if (prog === "curl" || prog === "wget") {
|
|
856
|
-
maxRisk = RiskLevel.Medium;
|
|
857
|
-
continue;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
if (WRAPPER_PROGRAMS.has(prog)) {
|
|
861
|
-
// `command -v` and `command -V` are read-only lookups (print where
|
|
862
|
-
// a command lives) — don't escalate to high risk for those.
|
|
863
|
-
if (
|
|
864
|
-
prog === "command" &&
|
|
865
|
-
seg.args.length > 0 &&
|
|
866
|
-
(seg.args[0] === "-v" || seg.args[0] === "-V")
|
|
867
|
-
) {
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
const wrapped = getWrappedProgram(seg);
|
|
871
|
-
if (wrapped === "rm") return RiskLevel.High;
|
|
872
|
-
if (wrapped && HIGH_RISK_PROGRAMS.has(wrapped)) return RiskLevel.High;
|
|
873
|
-
if (wrapped === "curl" || wrapped === "wget") {
|
|
874
|
-
maxRisk = RiskLevel.Medium;
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
877
|
-
// Propagate subcommand-aware classification for wrapped git/assistant
|
|
878
|
-
if (wrapped === "git") {
|
|
879
|
-
const wrappedWithArgs = getWrappedProgramWithArgs(seg);
|
|
880
|
-
if (wrappedWithArgs) {
|
|
881
|
-
const subcommand = firstPositionalArg(
|
|
882
|
-
wrappedWithArgs.args,
|
|
883
|
-
GIT_VALUE_FLAGS,
|
|
884
|
-
);
|
|
885
|
-
if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
|
|
886
|
-
continue;
|
|
887
|
-
}
|
|
888
|
-
maxRisk = RiskLevel.Medium;
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (wrapped === "assistant") {
|
|
893
|
-
const wrappedWithArgs = getWrappedProgramWithArgs(seg);
|
|
894
|
-
if (wrappedWithArgs) {
|
|
895
|
-
const assistantRisk = classifyAssistantSubcommand(
|
|
896
|
-
wrappedWithArgs.args,
|
|
897
|
-
);
|
|
898
|
-
if (assistantRisk === RiskLevel.High) return RiskLevel.High;
|
|
899
|
-
if (assistantRisk === RiskLevel.Medium) {
|
|
900
|
-
maxRisk = RiskLevel.Medium;
|
|
901
|
-
}
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (prog === "git") {
|
|
908
|
-
const subcommand = firstPositionalArg(seg.args, GIT_VALUE_FLAGS);
|
|
909
|
-
if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
|
|
910
|
-
// Stay at current risk
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
// Non-read-only git commands are medium
|
|
914
|
-
maxRisk = RiskLevel.Medium;
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (prog === "assistant") {
|
|
919
|
-
const assistantRisk = classifyAssistantSubcommand(seg.args);
|
|
920
|
-
if (assistantRisk === RiskLevel.High) return RiskLevel.High;
|
|
921
|
-
if (assistantRisk === RiskLevel.Medium) {
|
|
922
|
-
maxRisk = RiskLevel.Medium;
|
|
923
|
-
}
|
|
924
|
-
continue;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
if (!LOW_RISK_PROGRAMS.has(prog)) {
|
|
928
|
-
// Unknown program → medium
|
|
929
|
-
if (maxRisk === RiskLevel.Low) {
|
|
930
|
-
maxRisk = RiskLevel.Medium;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// If no segments could be extracted, treat as opaque
|
|
936
|
-
if (parsed.segments.length === 0) {
|
|
937
|
-
return RiskLevel.Medium;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
return maxRisk;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
529
|
// Check the tool registry for a declared default risk level
|
|
944
530
|
const tool = getTool(toolName);
|
|
945
531
|
if (tool) return tool.defaultRiskLevel;
|
|
@@ -959,27 +545,6 @@ async function classifyRiskUncached(
|
|
|
959
545
|
return RiskLevel.Medium;
|
|
960
546
|
}
|
|
961
547
|
|
|
962
|
-
function isActorTokenSigningKeyPath(
|
|
963
|
-
filePath: string | undefined,
|
|
964
|
-
workingDir?: string,
|
|
965
|
-
): boolean {
|
|
966
|
-
if (!filePath) return false;
|
|
967
|
-
const cwd = workingDir ?? process.cwd();
|
|
968
|
-
const resolvedPath = resolve(cwd, filePath);
|
|
969
|
-
// Include both the per-instance protected dir AND the legacy global
|
|
970
|
-
// ~/.vellum/protected path so upgraded machines with a host-wide signing
|
|
971
|
-
// key still classify reads as High risk.
|
|
972
|
-
const signingKeyPaths = Array.from(
|
|
973
|
-
new Set([
|
|
974
|
-
join(homedir(), ".vellum", "protected", "actor-token-signing-key"),
|
|
975
|
-
join(getProtectedDir(), "actor-token-signing-key"),
|
|
976
|
-
join(getDeprecatedDir(), "actor-token-signing-key"),
|
|
977
|
-
resolve(cwd, "deprecated", "actor-token-signing-key"),
|
|
978
|
-
]),
|
|
979
|
-
);
|
|
980
|
-
return signingKeyPaths.includes(resolvedPath);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
548
|
export async function check(
|
|
984
549
|
toolName: string,
|
|
985
550
|
input: Record<string, unknown>,
|
|
@@ -999,7 +564,7 @@ export async function check(
|
|
|
999
564
|
}
|
|
1000
565
|
}
|
|
1001
566
|
|
|
1002
|
-
const risk = await classifyRisk(
|
|
567
|
+
const { level: risk, reason: riskReason } = await classifyRisk(
|
|
1003
568
|
toolName,
|
|
1004
569
|
input,
|
|
1005
570
|
workingDir,
|
|
@@ -1024,129 +589,55 @@ export async function check(
|
|
|
1024
589
|
policyContext,
|
|
1025
590
|
);
|
|
1026
591
|
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
// Third-party skill-origin tools default to prompting when no trust rule
|
|
1069
|
-
// matches, regardless of risk level. Bundled skill tools are first-party
|
|
1070
|
-
// and trusted, so they fall through to the normal risk-based policy.
|
|
1071
|
-
// When manifestOverride is present, the tool comes from a skill manifest
|
|
1072
|
-
// but isn't registered — treat it as a third-party skill tool.
|
|
1073
|
-
if (!matchedRule) {
|
|
1074
|
-
const tool = getTool(toolName);
|
|
1075
|
-
if (tool?.origin === "skill" && !tool.ownerSkillBundled) {
|
|
1076
|
-
return {
|
|
1077
|
-
decision: "prompt",
|
|
1078
|
-
reason: "Skill tool: requires approval by default",
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
if (!tool && manifestOverride) {
|
|
1082
|
-
return {
|
|
1083
|
-
decision: "prompt",
|
|
1084
|
-
reason: "Skill tool: requires approval by default",
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// In strict mode, every tool without an explicit matching rule must be
|
|
1090
|
-
// prompted — there is no implicit auto-allow for any risk level.
|
|
1091
|
-
// This explicitly covers skill_load: activating a skill can grant the
|
|
1092
|
-
// agent new capabilities, so in strict mode users must approve each
|
|
1093
|
-
// skill load via an exact-version or wildcard trust rule.
|
|
1094
|
-
const permissionsMode = getConfig().permissions.mode;
|
|
1095
|
-
|
|
1096
|
-
if (permissionsMode === "strict" && !matchedRule) {
|
|
1097
|
-
return {
|
|
1098
|
-
decision: "prompt",
|
|
1099
|
-
reason: `Strict mode: no matching rule, requires approval`,
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// Workspace mode: auto-allow workspace-scoped operations that don't have
|
|
1104
|
-
// an explicit rule, but only when risk is Low. Medium and High risk operations
|
|
1105
|
-
// fall through to risk-based policy and always require approval.
|
|
1106
|
-
if (
|
|
1107
|
-
permissionsMode === "workspace" &&
|
|
1108
|
-
!matchedRule &&
|
|
1109
|
-
risk === RiskLevel.Low
|
|
1110
|
-
) {
|
|
1111
|
-
// Outside a container, bash runs on the host — don't auto-allow
|
|
1112
|
-
if (toolName === "bash" && !getIsContainerized()) {
|
|
1113
|
-
// Fall through to risk-based policy below
|
|
1114
|
-
} else if (isWorkspaceScopedInvocation(toolName, input, workingDir)) {
|
|
1115
|
-
return {
|
|
1116
|
-
decision: "allow",
|
|
1117
|
-
reason: "Workspace mode: workspace-scoped operation auto-allowed",
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
// Auto-allow low-risk bundled skill tools even without explicit trust rules.
|
|
1123
|
-
// These are first-party tools with a vetted risk declaration — applying the
|
|
1124
|
-
// same policy as the per-tool default allow rules for browser tools, but
|
|
1125
|
-
// generically so every new bundled skill benefits automatically.
|
|
1126
|
-
// This block must come AFTER the strict mode check so that strict mode
|
|
1127
|
-
// still prompts for bundled skill tools without explicit rules.
|
|
1128
|
-
if (!matchedRule && risk === RiskLevel.Low) {
|
|
1129
|
-
const tool = getTool(toolName);
|
|
1130
|
-
if (tool?.origin === "skill" && tool.ownerSkillBundled) {
|
|
1131
|
-
return {
|
|
1132
|
-
decision: "allow",
|
|
1133
|
-
reason: "Bundled skill tool: low risk, auto-allowed",
|
|
1134
|
-
};
|
|
592
|
+
// Build approval context from local variables
|
|
593
|
+
const tool = getTool(toolName);
|
|
594
|
+
const config = getConfig();
|
|
595
|
+
const resolvedThreshold = resolveThreshold(
|
|
596
|
+
config.permissions.autoApproveUpTo,
|
|
597
|
+
policyContext?.executionContext,
|
|
598
|
+
);
|
|
599
|
+
const approvalContext: ApprovalContext = {
|
|
600
|
+
riskLevel: risk,
|
|
601
|
+
toolName,
|
|
602
|
+
matchedRule: matchedRule ?? undefined,
|
|
603
|
+
permissionsMode: config.permissions.mode,
|
|
604
|
+
isContainerized: getIsContainerized(),
|
|
605
|
+
isWorkspaceScoped:
|
|
606
|
+
risk === RiskLevel.Low
|
|
607
|
+
? isWorkspaceScopedInvocation(toolName, input, workingDir)
|
|
608
|
+
: false,
|
|
609
|
+
toolOrigin:
|
|
610
|
+
tool?.origin === "skill" ? "skill" : tool ? "builtin" : undefined,
|
|
611
|
+
isSkillBundled: tool?.ownerSkillBundled ?? false,
|
|
612
|
+
hasManifestOverride: !!manifestOverride,
|
|
613
|
+
autoApproveUpTo: resolvedThreshold,
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// Delegate the allow/prompt/deny decision to the approval policy
|
|
617
|
+
const approvalDecision = defaultApprovalPolicy.evaluate(approvalContext);
|
|
618
|
+
|
|
619
|
+
// Enrich the reason with the classifier's explanation when available.
|
|
620
|
+
// For risk-based fallback decisions (prompt/deny from High/Medium risk),
|
|
621
|
+
// incorporate the classifier reason so the user sees *why* the command
|
|
622
|
+
// was classified at that level (e.g. "High risk (Recursive force delete): requires approval").
|
|
623
|
+
let enrichedReason = approvalDecision.reason;
|
|
624
|
+
if (riskReason && !approvalDecision.matchedRule) {
|
|
625
|
+
const riskLabelMatch = enrichedReason.match(
|
|
626
|
+
/^(High|Medium|Low|high|medium|low) risk(.*)/i,
|
|
627
|
+
);
|
|
628
|
+
if (riskLabelMatch) {
|
|
629
|
+
const capitalizedLabel =
|
|
630
|
+
riskLabelMatch[1].charAt(0).toUpperCase() +
|
|
631
|
+
riskLabelMatch[1].slice(1).toLowerCase();
|
|
632
|
+
enrichedReason = `${capitalizedLabel} risk (${riskReason})${riskLabelMatch[2]}`;
|
|
1135
633
|
}
|
|
1136
634
|
}
|
|
1137
635
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (risk === RiskLevel.Low) {
|
|
1146
|
-
return { decision: "allow", reason: "Low risk: auto-allowed" };
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
return { decision: "prompt", reason: `${risk} risk: requires approval` };
|
|
636
|
+
return {
|
|
637
|
+
decision: approvalDecision.decision,
|
|
638
|
+
reason: enrichedReason,
|
|
639
|
+
matchedRule: approvalDecision.matchedRule,
|
|
640
|
+
};
|
|
1150
641
|
}
|
|
1151
642
|
|
|
1152
643
|
const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
|
@@ -1157,7 +648,6 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
|
|
1157
648
|
host_file_write: "host file writes",
|
|
1158
649
|
host_file_edit: "host file edits",
|
|
1159
650
|
web_fetch: "URL fetches",
|
|
1160
|
-
browser_navigate: "browser navigations",
|
|
1161
651
|
network_request: "network requests",
|
|
1162
652
|
};
|
|
1163
653
|
|
|
@@ -1185,6 +675,10 @@ function shellAllowlistStrategy(
|
|
|
1185
675
|
input: Record<string, unknown>,
|
|
1186
676
|
): Promise<AllowlistOption[]> {
|
|
1187
677
|
const command = ((input.command as string) ?? "").trim();
|
|
678
|
+
// TODO(phase-3): Wire RiskAssessment.scopeOptions into permission prompts
|
|
679
|
+
// and retire buildShellAllowlistOptions + buildShellCommandCandidates from
|
|
680
|
+
// shell-identity.ts. The classifier's generateScopeOptions produces the
|
|
681
|
+
// canonical scope ladder; this legacy path should not diverge further.
|
|
1188
682
|
return buildShellAllowlistOptions(command);
|
|
1189
683
|
}
|
|
1190
684
|
|
|
@@ -1366,7 +860,6 @@ const ALLOWLIST_STRATEGIES: Record<string, AllowlistStrategy> = {
|
|
|
1366
860
|
host_file_write: fileAllowlistStrategy,
|
|
1367
861
|
host_file_edit: fileAllowlistStrategy,
|
|
1368
862
|
web_fetch: urlAllowlistStrategy,
|
|
1369
|
-
browser_navigate: urlAllowlistStrategy,
|
|
1370
863
|
network_request: urlAllowlistStrategy,
|
|
1371
864
|
scaffold_managed_skill: managedSkillAllowlistStrategy,
|
|
1372
865
|
delete_managed_skill: managedSkillAllowlistStrategy,
|
|
@@ -1380,6 +873,22 @@ export async function generateAllowlistOptions(
|
|
|
1380
873
|
): Promise<AllowlistOption[]> {
|
|
1381
874
|
signal?.throwIfAborted();
|
|
1382
875
|
|
|
876
|
+
// Check if a classifier already produced allowlist options during
|
|
877
|
+
// classifyRisk(). If so, return those directly — avoids duplicate
|
|
878
|
+
// computation and keeps scope option generation unified with risk
|
|
879
|
+
// classification.
|
|
880
|
+
const aKey = assessmentCacheKey(toolName, input);
|
|
881
|
+
const cachedAssessment = assessmentCache.get(aKey);
|
|
882
|
+
if (
|
|
883
|
+
cachedAssessment?.allowlistOptions &&
|
|
884
|
+
cachedAssessment.allowlistOptions.length > 0
|
|
885
|
+
) {
|
|
886
|
+
return cachedAssessment.allowlistOptions;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Fall back to the per-tool strategy function for tools that don't have
|
|
890
|
+
// classifier-produced options (e.g. bash tools use the shell identity
|
|
891
|
+
// strategy, or when the cache was missed).
|
|
1383
892
|
if (Object.hasOwn(ALLOWLIST_STRATEGIES, toolName)) {
|
|
1384
893
|
return ALLOWLIST_STRATEGIES[toolName](toolName, input);
|
|
1385
894
|
}
|