@vellumai/assistant 0.6.5 → 0.7.0
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 +29 -1
- package/ARCHITECTURE.md +60 -53
- package/Dockerfile +25 -3
- package/README.md +8 -10
- package/__tests__/permissions/gateway-threshold-reader.test.ts +277 -0
- package/bun.lock +306 -119
- package/docs/architecture/integrations.md +32 -39
- package/docs/architecture/memory.md +26 -120
- package/docs/architecture/security.md +22 -36
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/credential-execution-service.md +7 -5
- package/docs/plugins.md +761 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +17 -45
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/bun.lock +25 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/knip.json +8 -22
- package/node_modules/@vellumai/ces-client/bun.lock +33 -0
- package/node_modules/@vellumai/ces-client/package.json +25 -0
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +631 -0
- package/node_modules/@vellumai/ces-client/src/__tests__/package-boundary.test.ts +138 -0
- package/node_modules/@vellumai/ces-client/src/credential-rpc.ts +13 -0
- package/node_modules/@vellumai/ces-client/src/http-credentials.ts +296 -0
- package/node_modules/@vellumai/ces-client/src/http-log-export.ts +111 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +43 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +445 -0
- package/node_modules/@vellumai/credential-storage/src/__tests__/package-boundary.test.ts +32 -6
- package/node_modules/@vellumai/egress-proxy/src/__tests__/package-boundary.test.ts +32 -1
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/node_modules/@vellumai/gateway-client/bun.lock +39 -0
- package/node_modules/@vellumai/gateway-client/package.json +23 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +343 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/package-boundary.test.ts +140 -0
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +422 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +35 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +331 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +131 -0
- package/node_modules/@vellumai/gateway-client/tsconfig.json +20 -0
- package/node_modules/@vellumai/{ces-contracts → service-contracts}/bun.lock +1 -1
- package/node_modules/@vellumai/{ces-contracts → service-contracts}/package.json +4 -2
- package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/__tests__/contracts.test.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +155 -0
- package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +23 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +25 -0
- package/node_modules/@vellumai/{ces-contracts/src/index.ts → service-contracts/src/transport.ts} +6 -28
- package/node_modules/@vellumai/service-contracts/src/trust-rules.ts +116 -0
- package/node_modules/@vellumai/service-contracts/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +891 -0
- package/node_modules/@vellumai/skill-host-contracts/bun.lock +24 -0
- package/node_modules/@vellumai/skill-host-contracts/package.json +18 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +91 -0
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +1348 -0
- package/node_modules/@vellumai/skill-host-contracts/src/index.ts +6 -0
- package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +11 -0
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +32 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +333 -0
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +444 -0
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +12 -0
- package/openapi.yaml +3135 -692
- package/package.json +13 -7
- package/scripts/check-circular-deps.ts +80 -0
- package/scripts/generate-openapi.ts +24 -7
- package/{src/memory/graph/inspect.ts → scripts/memory-inspect.ts} +28 -28
- package/src/__tests__/access-request-decision.test.ts +2 -11
- package/src/__tests__/acp-session.test.ts +4 -150
- package/src/__tests__/actor-token-service.test.ts +17 -678
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +2 -6
- package/src/__tests__/agent-loop-override-profile.test.ts +404 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -4
- package/src/__tests__/agent-wake-override-profile.test.ts +261 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -1
- package/src/__tests__/anthropic-provider.test.ts +127 -15
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/app-routes-csp.test.ts +106 -55
- package/src/__tests__/approval-cascade.test.ts +10 -357
- package/src/__tests__/approval-conversation-turn.test.ts +3 -8
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +1 -1
- package/src/__tests__/approval-primitive.test.ts +2 -1
- package/src/__tests__/approval-routes-http.test.ts +34 -451
- package/src/__tests__/assistant-events-sse-hardening.test.ts +73 -80
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -3
- package/src/__tests__/attachment-upload-trusted-source.test.ts +139 -0
- package/src/__tests__/attachments-store.test.ts +46 -1
- package/src/__tests__/audit-log-rotation.test.ts +2 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +9 -20
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/background-shell-bash.test.ts +227 -0
- package/src/__tests__/background-shell-host-bash.test.ts +474 -0
- package/src/__tests__/background-tool-registry.test.ts +145 -0
- package/src/__tests__/background-tool-routes.test.ts +175 -0
- package/src/__tests__/btw-routes.test.ts +147 -183
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/call-controller.test.ts +15 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -1
- package/src/__tests__/call-domain.test.ts +2 -2
- package/src/__tests__/call-pointer-messages.test.ts +11 -13
- package/src/__tests__/call-recovery.test.ts +2 -1
- package/src/__tests__/call-routes-http.test.ts +3 -14
- package/src/__tests__/call-store.test.ts +2 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +31 -62
- package/src/__tests__/canonical-guardian-store.test.ts +2 -2
- package/src/__tests__/catalog-cache.test.ts +69 -0
- package/src/__tests__/catalog-files.test.ts +0 -26
- package/src/__tests__/ces-rpc-credential-backend.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +79 -49
- package/src/__tests__/channel-approval.test.ts +9 -7
- package/src/__tests__/channel-approvals.test.ts +9 -180
- package/src/__tests__/channel-delivery-store.test.ts +11 -10
- package/src/__tests__/channel-guardian.test.ts +14 -25
- package/src/__tests__/channel-readiness-service.test.ts +8 -6
- package/src/__tests__/channel-reply-delivery.test.ts +3 -19
- package/src/__tests__/channel-retry-sweep.test.ts +2 -5
- package/src/__tests__/checker.test.ts +447 -3806
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +208 -0
- package/src/__tests__/cli.test.ts +1 -38
- package/src/__tests__/compaction-events.test.ts +500 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -7
- package/src/__tests__/config-model-image-provider.test.ts +109 -0
- package/src/__tests__/config-schema-cmd.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +25 -203
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -25
- package/src/__tests__/contact-store-user-file.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +71 -18
- package/src/__tests__/contacts-write.test.ts +6 -61
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-search-agent-protocol.test.ts +230 -0
- package/src/__tests__/context-search-agent-runner.test.ts +998 -0
- package/src/__tests__/context-search-conversations-source.test.ts +320 -0
- package/src/__tests__/context-search-fanout.test.ts +380 -0
- package/src/__tests__/context-search-memory-source.test.ts +311 -0
- package/src/__tests__/context-search-pkb-source.test.ts +444 -0
- package/src/__tests__/context-search-types.test.ts +95 -0
- package/src/__tests__/context-search-workspace-source.test.ts +545 -0
- package/src/__tests__/context-window-manager.test.ts +380 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +14 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +631 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +41 -32
- package/src/__tests__/conversation-agent-loop.test.ts +54 -143
- package/src/__tests__/conversation-analysis-routes.test.ts +60 -82
- package/src/__tests__/conversation-attachments.test.ts +9 -20
- package/src/__tests__/conversation-attention-store.test.ts +2 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +4 -2
- package/src/__tests__/conversation-clear-safety.test.ts +53 -95
- package/src/__tests__/conversation-confirmation-signals.test.ts +7 -40
- package/src/__tests__/conversation-crud-inference-profile.test.ts +54 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +63 -157
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-disk-view.test.ts +5 -4
- package/src/__tests__/conversation-fork-crud.test.ts +26 -55
- package/src/__tests__/conversation-fork-route.test.ts +5 -74
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +128 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +216 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +4 -95
- package/src/__tests__/conversation-key-store-disk-view.test.ts +2 -1
- package/src/__tests__/conversation-lifecycle.test.ts +0 -1
- package/src/__tests__/conversation-list-source.test.ts +2 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -1
- package/src/__tests__/conversation-pairing.test.ts +174 -11
- package/src/__tests__/conversation-pre-run-repair.test.ts +137 -294
- package/src/__tests__/conversation-process-callsite.test.ts +3 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +22 -8
- package/src/__tests__/conversation-queue.test.ts +30 -47
- package/src/__tests__/conversation-routes-disk-view.test.ts +131 -103
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +80 -55
- package/src/__tests__/conversation-routes-slash-commands.test.ts +83 -12
- package/src/__tests__/conversation-runtime-assembly.test.ts +196 -194
- package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-slash-commands.test.ts +6 -43
- package/src/__tests__/conversation-slash-queue.test.ts +7 -3
- package/src/__tests__/conversation-slash-unknown.test.ts +25 -3
- package/src/__tests__/conversation-speed-override.test.ts +6 -2
- package/src/__tests__/conversation-starter-routes.test.ts +177 -55
- package/src/__tests__/conversation-starters-cadence.test.ts +2 -2
- package/src/__tests__/conversation-store.test.ts +2 -375
- package/src/__tests__/conversation-title-service.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +42 -3
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +6 -6
- package/src/__tests__/conversation-unread-route.test.ts +1 -1
- package/src/__tests__/conversation-usage.test.ts +3 -2
- package/src/__tests__/conversation-wipe.test.ts +2 -103
- package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +3 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -2
- package/src/__tests__/conversations-defer-cli.test.ts +150 -0
- package/src/__tests__/credential-execution-admin-cli.test.ts +1 -1
- package/src/__tests__/credential-execution-api-key-propagation.test.ts +2 -2
- package/src/__tests__/credential-execution-approval-bridge.test.ts +22 -289
- package/src/__tests__/credential-execution-client.test.ts +1 -1
- package/src/__tests__/credential-execution-managed-contract.test.ts +1 -1
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +16 -2
- package/src/__tests__/credentials-cli.test.ts +45 -21
- package/src/__tests__/daemon-credential-client.test.ts +23 -108
- package/src/__tests__/db-acp-history.test.ts +284 -0
- package/src/__tests__/db-activation-state.test.ts +240 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +2 -1
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +248 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +2 -1
- package/src/__tests__/db-memory-graph-event-date-repair.test.ts +116 -0
- package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +132 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/delete-propagation.test.ts +3 -2
- package/src/__tests__/deterministic-verification-control-plane.test.ts +39 -32
- package/src/__tests__/dm-backfill.test.ts +3 -2
- package/src/__tests__/edit-propagation.test.ts +5 -7
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +1 -1
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/events-client-registration.test.ts +297 -0
- package/src/__tests__/file-write-tool.test.ts +2 -4
- package/src/__tests__/filing-service.test.ts +144 -17
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/followup-tools.test.ts +2 -1
- package/src/__tests__/gateway-client-managed-outbound.test.ts +8 -12
- package/src/__tests__/gateway-only-enforcement.test.ts +2 -6
- package/src/__tests__/gateway-only-guard.test.ts +4 -3
- package/src/__tests__/gemini-provider.test.ts +276 -10
- package/src/__tests__/graph-extraction-event-date.test.ts +30 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -1
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -2
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +9 -9
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -1
- package/src/__tests__/guardian-action-store.test.ts +2 -1
- package/src/__tests__/guardian-action-sweep.test.ts +9 -8
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +21 -118
- package/src/__tests__/guardian-dispatch.test.ts +14 -11
- package/src/__tests__/guardian-grant-minting.test.ts +9 -15
- package/src/__tests__/guardian-outbound-http.test.ts +71 -106
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +34 -90
- package/src/__tests__/guardian-routing-state.test.ts +14 -22
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +253 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +8 -4
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/heartbeat-service.test.ts +39 -21
- package/src/__tests__/helpers/call-route-handler.ts +72 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +161 -0
- package/src/__tests__/helpers/gateway-classify-mock.ts +67 -0
- package/src/__tests__/helpers/mock-logger.ts +36 -0
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/home-state-routes.test.ts +10 -31
- package/src/__tests__/host-browser-e2e-cloud.test.ts +309 -1
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +12 -2
- package/src/__tests__/host-browser-routes.test.ts +36 -91
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +10 -2
- package/src/__tests__/host-proxy-interface.test.ts +38 -4
- package/src/__tests__/host-shell-tool.test.ts +2 -4
- package/src/__tests__/host-transfer-pending-interactions.test.ts +160 -0
- package/src/__tests__/host-transfer-proxy.test.ts +733 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +20 -11
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +3 -2
- package/src/__tests__/injector-chain.test.ts +525 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +41 -206
- package/src/__tests__/install-skill-routing.test.ts +1 -1
- package/src/__tests__/intent-routing.test.ts +0 -26
- package/src/__tests__/invite-redemption-service.test.ts +2 -1
- package/src/__tests__/invite-routes-http.test.ts +80 -12
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -1
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +2 -1
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +157 -0
- package/src/__tests__/list-messages-attachments.test.ts +52 -55
- package/src/__tests__/list-messages-page-latest.test.ts +283 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +16 -17
- package/src/__tests__/llm-call-pipeline.test.ts +284 -0
- package/src/__tests__/llm-context-normalization.test.ts +69 -4
- package/src/__tests__/llm-context-route-provider.test.ts +39 -113
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -1
- package/src/__tests__/llm-resolver.test.ts +211 -0
- package/src/__tests__/llm-schema.test.ts +56 -0
- package/src/__tests__/llm-usage-store.test.ts +2 -1
- package/src/__tests__/log-export-workspace.test.ts +28 -17
- package/src/__tests__/mcp-abort-signal.test.ts +2 -3
- package/src/__tests__/mcp-client-auth.test.ts +2 -3
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-admin-recall.test.ts +221 -0
- package/src/__tests__/memory-recall-log-store.test.ts +2 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +399 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +3 -1
- package/src/__tests__/migration-cross-version-compatibility.test.ts +14 -13
- package/src/__tests__/migration-export-http.test.ts +17 -17
- package/src/__tests__/migration-export-to-gcs.test.ts +491 -0
- package/src/__tests__/migration-import-commit-http.test.ts +16 -16
- package/src/__tests__/migration-import-from-gcs.test.ts +533 -0
- package/src/__tests__/migration-import-from-url.test.ts +21 -91
- package/src/__tests__/migration-import-preflight-http.test.ts +13 -13
- package/src/__tests__/migration-jobs-status.test.ts +164 -0
- package/src/__tests__/migration-validate-http.test.ts +48 -83
- package/src/__tests__/mock-gateway-ipc.ts +32 -62
- package/src/__tests__/model-intents.test.ts +16 -1
- package/src/__tests__/nl-approval-parser.test.ts +13 -17
- package/src/__tests__/non-member-access-request.test.ts +13 -5
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-guardian-path.test.ts +15 -8
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +109 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +57 -55
- package/src/__tests__/oauth-apps-routes.test.ts +77 -123
- package/src/__tests__/oauth-cli.test.ts +28 -13
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +6 -6
- package/src/__tests__/oauth-providers-routes.test.ts +81 -103
- package/src/__tests__/oauth-store.test.ts +44 -77
- package/src/__tests__/oauth2-gateway-transport.test.ts +6 -3
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/openai-provider.test.ts +105 -6
- package/src/__tests__/openai-responses-provider.test.ts +146 -4
- package/src/__tests__/openrouter-provider-only.test.ts +22 -4
- package/src/__tests__/overflow-reduce-pipeline.test.ts +671 -0
- package/src/__tests__/permission-types.test.ts +3 -18
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +378 -0
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +27 -20
- package/src/__tests__/platform.test.ts +10 -59
- package/src/__tests__/playbook-execution.test.ts +2 -1
- package/src/__tests__/playbook-tools.test.ts +2 -1
- package/src/__tests__/plugin-bootstrap.test.ts +529 -0
- package/src/__tests__/plugin-registry.test.ts +303 -0
- package/src/__tests__/plugin-route-contribution.test.ts +294 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +292 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +195 -14
- package/src/__tests__/profiler-routes.test.ts +112 -177
- package/src/__tests__/provider-send-message-override-profile.test.ts +223 -0
- package/src/__tests__/proxy-approval-callback.test.ts +6 -493
- package/src/__tests__/qdrant-collection-migration.test.ts +7 -7
- package/src/__tests__/reaction-persistence.test.ts +4 -2
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +0 -2
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +1 -2
- package/src/__tests__/relay-server.test.ts +19 -4
- package/src/__tests__/require-fresh-approval.test.ts +19 -168
- package/src/__tests__/resolve-trust-class.test.ts +2 -1
- package/src/__tests__/retry-thinking-tool-choice.test.ts +19 -7
- package/src/__tests__/retry-verbosity-normalization.test.ts +139 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +26 -6
- package/src/__tests__/runtime-events-sse-parity.test.ts +12 -13
- package/src/__tests__/runtime-events-sse.test.ts +13 -21
- package/src/__tests__/schedule-routes.test.ts +304 -77
- package/src/__tests__/schedule-store.test.ts +119 -1
- package/src/__tests__/schedule-tools.test.ts +2 -1
- package/src/__tests__/scheduler-recurrence.test.ts +16 -71
- package/src/__tests__/scheduler-reuse-conversation.test.ts +12 -51
- package/src/__tests__/scheduler-wake.test.ts +356 -0
- package/src/__tests__/scoped-approval-grants.test.ts +2 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -1
- package/src/__tests__/secret-detection-handler.test.ts +2 -19
- package/src/__tests__/secret-ingress-http.test.ts +38 -21
- package/src/__tests__/secret-routes-managed-proxy.test.ts +46 -102
- package/src/__tests__/secret-scanner-executor.test.ts +1 -2
- package/src/__tests__/send-endpoint-busy.test.ts +38 -25
- package/src/__tests__/sequence-store.test.ts +2 -1
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/service-contracts-import-guard.test.ts +185 -0
- package/src/__tests__/set-permission-mode.test.ts +0 -10
- package/src/__tests__/settings-routes.test.ts +35 -68
- package/src/__tests__/skill-boundary-guard.test.ts +105 -0
- package/src/__tests__/skill-load-inline-command.test.ts +2 -2
- package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
- package/src/__tests__/skill-runtime-path.test.ts +64 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +11 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +1 -3
- package/src/__tests__/slack-reaction-approvals.test.ts +4 -4
- package/src/__tests__/slack-share-routes.test.ts +37 -72
- package/src/__tests__/subagent-call-site-routing.test.ts +79 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +20 -28
- package/src/__tests__/subagent-notify-parent.test.ts +6 -29
- package/src/__tests__/subagent-role-registry.test.ts +3 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +52 -104
- package/src/__tests__/subagent-tools.test.ts +0 -1
- package/src/__tests__/suggestion-routes.test.ts +149 -57
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-management-tools.test.ts +2 -1
- package/src/__tests__/task-memory-cleanup.test.ts +3 -1
- package/src/__tests__/task-scheduler.test.ts +5 -16
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/terminal-tools.test.ts +5 -314
- package/src/__tests__/thread-backfill.test.ts +3 -2
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +484 -0
- package/src/__tests__/tool-approval-handler.test.ts +21 -63
- package/src/__tests__/tool-audit-listener.test.ts +3 -3
- package/src/__tests__/tool-domain-event-publisher.test.ts +3 -3
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +429 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +61 -4
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +28 -56
- package/src/__tests__/tool-executor.test.ts +434 -1604
- package/src/__tests__/tool-grant-request-escalation.test.ts +90 -311
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/trust-context-guards.test.ts +1 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +7 -15
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +178 -354
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
- package/src/__tests__/trusted-contact-verification.test.ts +2 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -1
- package/src/__tests__/twilio-routes.test.ts +25 -66
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -7
- package/src/__tests__/usage-routes.test.ts +73 -90
- package/src/__tests__/user-plugin-loader.test.ts +233 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -2
- package/src/__tests__/verification-control-plane-policy.test.ts +95 -14
- package/src/__tests__/voice-ingress-preflight.test.ts +5 -5
- package/src/__tests__/voice-invite-redemption.test.ts +2 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +3 -3
- package/src/__tests__/voice-session-bridge.test.ts +285 -106
- package/src/__tests__/volume-security-guard.test.ts +0 -2
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +3 -1
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +2 -1
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +1 -1
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-052-seed-default-inference-profiles.test.ts +260 -0
- package/src/__tests__/workspace-migration-053-release-notes-acp-codex.test.ts +225 -0
- package/src/__tests__/workspace-migration-054-seed-recall-callsite.test.ts +235 -0
- package/src/__tests__/workspace-migration-055-release-notes-agentic-recall.test.ts +128 -0
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +232 -0
- package/src/__tests__/workspace-migration-acp-sessions-ui.test.ts +144 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +1 -1
- package/src/__tests__/workspace-migration-memory-v2-init.test.ts +274 -0
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-policy.test.ts +21 -3
- package/src/acp/__tests__/client-handler.test.ts +64 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +62 -0
- package/src/acp/__tests__/helpers/which-stub.ts +45 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +366 -0
- package/src/acp/__tests__/session-manager-startup.test.ts +159 -0
- package/src/acp/__tests__/session-manager.test.ts +83 -0
- package/src/acp/client-handler.ts +23 -139
- package/src/acp/resolve-agent.test.ts +291 -0
- package/src/acp/resolve-agent.ts +176 -0
- package/src/acp/session-manager.ts +166 -7
- package/src/acp/types.ts +2 -50
- package/src/agent/loop.ts +365 -104
- package/src/agent/message-types.ts +0 -2
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/__tests__/guardian-feed-event.test.ts +296 -0
- package/src/approvals/approval-primitive.ts +3 -20
- package/src/approvals/guardian-decision-primitive.ts +37 -68
- package/src/approvals/guardian-request-resolvers.ts +109 -103
- package/src/avatar/character-components.ts +6 -6
- package/src/{config/bundled-skills/settings/tools → avatar}/identity-avatar.ts +1 -1
- package/src/backup/__tests__/backup-worker.test.ts +2 -15
- package/src/backup/__tests__/paths.test.ts +3 -2
- package/src/backup/backup-worker.ts +3 -24
- package/src/backup/paths.ts +2 -18
- package/src/backup/restore.ts +7 -11
- package/src/browser/__tests__/operations.test.ts +0 -35
- package/src/browser/operations.ts +1 -47
- package/src/bundler/app-compiler.ts +84 -1
- package/src/bundler/package-resolver.ts +2 -6
- package/src/calls/active-call-lease.ts +1 -1
- package/src/calls/call-constants.ts +1 -1
- package/src/calls/call-controller.ts +1 -5
- package/src/calls/call-domain.ts +14 -14
- package/src/calls/call-pointer-messages.ts +4 -9
- package/src/calls/call-state.ts +2 -2
- package/src/calls/call-store.ts +2 -1
- package/src/calls/guardian-action-sweep.ts +9 -25
- package/src/calls/guardian-dispatch.ts +1 -20
- package/src/calls/media-stream-audio-transcode.ts +2 -41
- package/src/calls/media-stream-server.ts +2 -3
- package/src/calls/media-stream-stt-session.ts +1 -3
- package/src/calls/relay-access-wait.ts +5 -8
- package/src/calls/relay-server.ts +15 -18
- package/src/calls/relay-setup-router.ts +2 -2
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/twilio-rest.ts +1 -1
- package/src/calls/twilio-routes.ts +160 -78
- package/src/calls/voice-control-protocol.ts +10 -10
- package/src/calls/voice-ingress-preflight.ts +2 -2
- package/src/calls/voice-session-bridge.ts +137 -42
- package/src/channels/__tests__/types.test.ts +28 -6
- package/src/channels/permission-profiles.ts +2 -72
- package/src/channels/types.ts +48 -30
- package/src/cli/AGENTS.md +1 -0
- package/src/cli/__tests__/notifications.test.ts +92 -214
- package/src/cli/commands/__tests__/attachment.test.ts +14 -8
- package/src/cli/commands/__tests__/backup.test.ts +4 -15
- package/src/cli/commands/__tests__/browser.test.ts +36 -31
- package/src/cli/commands/__tests__/cache.test.ts +23 -18
- package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
- package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +396 -0
- package/src/cli/commands/__tests__/task.test.ts +36 -35
- package/src/cli/commands/__tests__/trust.test.ts +602 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +14 -14
- package/src/cli/commands/__tests__/ui.test.ts +17 -17
- package/src/cli/commands/__tests__/watchers.test.ts +29 -29
- package/src/cli/commands/__tests__/webhooks.test.ts +544 -0
- package/src/cli/commands/attachment.ts +12 -8
- package/src/cli/commands/auth.ts +1 -1
- package/src/cli/commands/avatar.ts +192 -9
- package/src/cli/commands/backup.ts +16 -46
- package/src/cli/commands/browser.ts +52 -4
- package/src/cli/commands/cache.ts +7 -5
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/clients.ts +137 -0
- package/src/cli/commands/completions.ts +3 -10
- package/src/cli/commands/contacts.ts +10 -10
- package/src/cli/commands/conversations-defer.ts +364 -0
- package/src/cli/commands/conversations-import.ts +2 -3
- package/src/cli/commands/conversations.ts +115 -57
- package/src/cli/commands/credential-execution.ts +1 -1
- package/src/cli/commands/credentials.ts +139 -5
- package/src/cli/commands/default-action.ts +1 -1
- package/src/cli/commands/domain.ts +2 -2
- package/src/cli/commands/email.ts +7 -7
- package/src/cli/commands/image-generation.ts +33 -34
- package/src/cli/commands/keys.ts +2 -2
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory-v2.ts +343 -0
- package/src/cli/commands/memory.ts +8 -8
- package/src/cli/commands/notifications.ts +87 -121
- package/src/cli/commands/oauth/__tests__/connect.test.ts +23 -5
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/mode.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/token.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +4 -4
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/shared.ts +29 -2
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -6
- package/src/cli/commands/platform/__tests__/connect.test.ts +23 -11
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +22 -10
- package/src/cli/commands/platform/__tests__/status.test.ts +22 -10
- package/src/cli/commands/platform/connect.ts +3 -3
- package/src/cli/commands/platform/disconnect.ts +4 -6
- package/src/cli/commands/platform/index.ts +12 -10
- package/src/cli/commands/routes.ts +7 -1
- package/src/cli/commands/sequence.ts +7 -7
- package/src/cli/commands/skills.ts +189 -84
- package/src/cli/commands/task.ts +12 -10
- package/src/cli/commands/trust.ts +460 -162
- package/src/cli/commands/ui.ts +3 -3
- package/src/cli/commands/usage.ts +10 -5
- package/src/cli/commands/watchers.ts +8 -8
- package/src/cli/commands/webhooks.ts +270 -0
- package/src/cli/lib/daemon-avatar-client.ts +37 -0
- package/src/cli/lib/daemon-credential-client.ts +27 -189
- package/src/cli/lib/ipc-params.ts +22 -0
- package/src/cli/program.ts +29 -29
- package/src/cli.ts +1 -61
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/acp-defaults.test.ts +57 -0
- package/src/config/acp-defaults.ts +40 -0
- package/src/config/acp-schema.ts +1 -1
- package/src/config/assistant-feature-flags.ts +18 -142
- package/src/config/bundled-skills/acp/SKILL.md +44 -16
- package/src/config/bundled-skills/acp/TOOLS.json +45 -1
- package/src/config/bundled-skills/{screen-watch/tools/start-screen-watch.ts → acp/tools/acp-list-agents.ts} +2 -2
- package/src/config/bundled-skills/acp/tools/acp-steer.ts +12 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +25 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +31 -44
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +6 -6
- package/src/config/bundled-skills/media-processing/services/reduce.ts +0 -13
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/gmail-mime-helpers.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-skills/settings/SKILL.md +2 -17
- package/src/config/bundled-skills/settings/TOOLS.json +0 -56
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-tool-registry.ts +4 -21
- package/src/config/env.ts +7 -8
- package/src/config/feature-flag-registry.json +25 -17
- package/src/config/llm-resolver.ts +51 -33
- package/src/config/loader.ts +12 -15
- package/src/config/schema.ts +22 -70
- package/src/config/schemas/__tests__/filing.test.ts +58 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +186 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/filing.ts +12 -0
- package/src/config/schemas/host-browser.ts +2 -2
- package/src/config/schemas/inference.ts +0 -2
- package/src/config/schemas/ingress.ts +1 -1
- package/src/config/schemas/llm.ts +51 -10
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory-v2.ts +176 -0
- package/src/config/schemas/memory.ts +2 -0
- package/src/config/schemas/security.ts +0 -60
- package/src/config/schemas/services.ts +46 -7
- package/src/config/schemas/tts.ts +11 -0
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +95 -6
- package/src/config/types.ts +0 -41
- package/src/contacts/contact-store.ts +2 -2
- package/src/contacts/contacts-write.ts +0 -38
- package/src/contacts/types.ts +8 -10
- package/src/context/__tests__/compact-prompt.test.ts +27 -9
- package/src/context/prompts/compact.md +26 -12
- package/src/context/token-estimator.ts +1 -1
- package/src/context/tool-result-truncation.ts +4 -64
- package/src/context/window-manager.ts +191 -17
- package/src/credential-execution/approval-bridge.ts +7 -69
- package/src/credential-execution/client.ts +17 -422
- package/src/credential-execution/feature-gates.ts +1 -2
- package/src/credential-execution/managed-catalog.ts +1 -1
- package/src/credential-health/credential-health-service.ts +20 -7
- package/src/daemon/__tests__/conversation-feed-event.test.ts +304 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/__tests__/daemon-skill-host.test.ts +272 -0
- package/src/daemon/__tests__/meet-host-supervisor.test.ts +587 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +463 -0
- package/src/daemon/approval-generators.ts +2 -14
- package/src/daemon/classifier.ts +0 -106
- package/src/daemon/config-watcher.ts +14 -56
- package/src/daemon/connection-policy.ts +0 -14
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +120 -28
- package/src/daemon/conversation-agent-loop.ts +1113 -701
- package/src/daemon/conversation-attachments.ts +5 -81
- package/src/daemon/conversation-error.ts +9 -5
- package/src/daemon/conversation-history.ts +11 -20
- package/src/daemon/conversation-launch.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +37 -19
- package/src/daemon/conversation-messaging.ts +1 -1
- package/src/daemon/conversation-notifiers.ts +3 -111
- package/src/daemon/conversation-process.ts +23 -20
- package/src/daemon/conversation-runtime-assembly.ts +530 -471
- package/src/daemon/conversation-slash.ts +4 -160
- package/src/daemon/conversation-store.ts +368 -0
- package/src/daemon/conversation-surfaces.ts +5 -4
- package/src/daemon/conversation-tool-setup.ts +49 -161
- package/src/daemon/conversation.ts +126 -217
- package/src/daemon/daemon-control.ts +3 -3
- package/src/daemon/daemon-skill-host.ts +262 -0
- package/src/daemon/external-plugins-bootstrap.ts +532 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/handlers/config-channels.ts +2 -2
- package/src/daemon/handlers/config-embeddings.ts +1 -1
- package/src/daemon/handlers/config-ingress.ts +24 -2
- package/src/daemon/handlers/config-model.test.ts +17 -0
- package/src/daemon/handlers/config-model.ts +18 -52
- package/src/daemon/handlers/config-telegram.ts +6 -53
- package/src/daemon/handlers/config-voice.ts +1 -1
- package/src/daemon/handlers/conversations.ts +22 -156
- package/src/daemon/handlers/recording.ts +1 -1
- package/src/daemon/handlers/shared.ts +34 -35
- package/src/daemon/handlers/skills.ts +20 -24
- package/src/daemon/host-transfer-proxy.ts +500 -0
- package/src/daemon/lifecycle.ts +56 -326
- package/src/daemon/meet-host-startup.ts +51 -0
- package/src/daemon/meet-host-supervisor.ts +781 -0
- package/src/daemon/meet-manifest-loader.ts +410 -0
- package/src/daemon/memory-v2-startup.ts +35 -0
- package/src/daemon/message-protocol.ts +4 -7
- package/src/daemon/message-types/acp.ts +1 -0
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +65 -2
- package/src/daemon/message-types/host-transfer.ts +41 -0
- package/src/daemon/message-types/integrations.ts +6 -0
- package/src/daemon/message-types/messages.ts +26 -14
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +0 -6
- package/src/daemon/message-types/shared.ts +5 -2
- package/src/daemon/message-types/subagents.ts +2 -1
- package/src/daemon/message-types/workspace.ts +0 -2
- package/src/daemon/pkb-reminder-builder.test.ts +13 -12
- package/src/daemon/pkb-reminder-builder.ts +8 -16
- package/src/daemon/process-message.ts +616 -0
- package/src/daemon/providers-setup.ts +14 -6
- package/src/daemon/server.ts +79 -1272
- package/src/daemon/shutdown-handlers.ts +3 -13
- package/src/daemon/startup-error.ts +1 -1
- package/src/daemon/tool-side-effects.ts +14 -56
- package/src/daemon/trust-context.ts +32 -0
- package/src/daemon/wake-target-adapter.ts +223 -0
- package/src/email/feature-gate.ts +1 -1
- package/src/events/domain-events.ts +1 -8
- package/src/events/tool-audit-listener.ts +2 -8
- package/src/events/tool-metrics-listener.ts +1 -4
- package/src/filing/filing-service.ts +194 -54
- package/src/followups/followup-store.ts +3 -71
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +52 -8
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +18 -1
- package/src/home/__tests__/rollup-producer.test.ts +67 -2
- package/src/home/assistant-feed-authoring.ts +8 -1
- package/src/home/emit-feed-event.ts +7 -0
- package/src/home/feed-types.ts +42 -3
- package/src/home/relationship-state-writer.ts +1 -1
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/home/rewrite-feed-title.ts +58 -0
- package/src/home/rollup-producer.ts +16 -3
- package/src/inbound/platform-callback-registration.ts +1 -17
- package/src/ipc/__tests__/attachment-ipc.test.ts +128 -66
- package/src/ipc/__tests__/browser-ipc.test.ts +75 -51
- package/src/ipc/__tests__/cache-ipc.test.ts +52 -107
- package/src/ipc/__tests__/cli-ipc.test.ts +9 -6
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +254 -0
- package/src/ipc/__tests__/skill-server.test.ts +182 -0
- package/src/ipc/__tests__/socket-path.test.ts +44 -37
- package/src/ipc/__tests__/ui-request-route.test.ts +241 -216
- package/src/ipc/__tests__/watcher-ipc.test.ts +33 -33
- package/src/ipc/assistant-server.ts +450 -0
- package/src/ipc/cli-client.ts +3 -3
- package/src/ipc/gateway-client.test.ts +131 -0
- package/src/ipc/gateway-client.ts +98 -120
- package/src/ipc/ipc-framing.ts +281 -0
- package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +152 -0
- package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +219 -0
- package/src/ipc/routes/db-proxy.ts +73 -0
- package/src/ipc/routes/route-adapter.ts +32 -0
- package/src/ipc/routes/trust-rules.test.ts +218 -0
- package/src/ipc/skill-ipc-types.ts +13 -0
- package/src/ipc/skill-routes/__tests__/config.test.ts +146 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +402 -0
- package/src/ipc/skill-routes/__tests__/identity.test.ts +81 -0
- package/src/ipc/skill-routes/__tests__/log.test.ts +133 -0
- package/src/ipc/skill-routes/__tests__/memory.test.ts +178 -0
- package/src/ipc/skill-routes/__tests__/platform.test.ts +111 -0
- package/src/ipc/skill-routes/__tests__/providers.test.ts +265 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +361 -0
- package/src/ipc/skill-routes/config.ts +47 -0
- package/src/ipc/skill-routes/events.ts +131 -0
- package/src/ipc/skill-routes/identity.ts +34 -0
- package/src/ipc/skill-routes/index.ts +37 -0
- package/src/ipc/skill-routes/log.ts +40 -0
- package/src/ipc/skill-routes/memory.ts +76 -0
- package/src/ipc/skill-routes/platform.ts +39 -0
- package/src/ipc/skill-routes/providers.ts +163 -0
- package/src/ipc/skill-routes/registries.ts +393 -0
- package/src/ipc/skill-server.ts +771 -0
- package/src/ipc/skill-socket-path.ts +20 -0
- package/src/ipc/socket-cleanup.ts +92 -0
- package/src/ipc/socket-path.ts +55 -48
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +374 -0
- package/src/live-voice/__tests__/live-voice-archive.test.ts +525 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +473 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +359 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +179 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +349 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +244 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +337 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +337 -0
- package/src/live-voice/__tests__/protocol.test.ts +295 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +421 -0
- package/src/live-voice/live-voice-archive.ts +758 -0
- package/src/live-voice/live-voice-metrics.ts +472 -0
- package/src/live-voice/live-voice-session-manager.ts +222 -0
- package/src/live-voice/live-voice-session.ts +1144 -0
- package/src/live-voice/live-voice-tts.ts +260 -0
- package/src/live-voice/protocol.ts +524 -0
- package/src/mcp/client.ts +2 -2
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +4 -28
- package/src/memory/__tests__/auto-analysis-guard.test.ts +2 -2
- package/src/memory/__tests__/conversation-analyze-job.test.ts +7 -62
- package/src/memory/__tests__/conversation-group-migration.test.ts +2 -2
- package/src/memory/__tests__/find-analysis-conversation.test.ts +2 -1
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +235 -0
- package/src/memory/admin.ts +65 -7
- package/src/memory/app-git-service.ts +0 -14
- package/src/memory/attachments-store.ts +14 -16
- package/src/memory/auto-analysis-enqueue.ts +2 -17
- package/src/memory/canonical-guardian-store.ts +2 -1
- package/src/memory/channel-verification-sessions.ts +1 -1
- package/src/memory/checkpoints.ts +1 -1
- package/src/memory/context-search/agent-protocol.ts +424 -0
- package/src/memory/context-search/agent-runner.ts +1295 -0
- package/src/memory/context-search/format.ts +160 -0
- package/src/memory/context-search/limits.ts +106 -0
- package/src/memory/context-search/search.ts +387 -0
- package/src/memory/context-search/sources/conversations.ts +278 -0
- package/src/memory/context-search/sources/memory.ts +90 -0
- package/src/memory/context-search/sources/pkb.ts +468 -0
- package/src/memory/context-search/sources/workspace.ts +1255 -0
- package/src/memory/context-search/types.ts +49 -0
- package/src/memory/conversation-analyze-job.ts +3 -24
- package/src/memory/conversation-attention-store.ts +1 -1
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +117 -145
- package/src/memory/conversation-directories.ts +1 -11
- package/src/memory/conversation-display-order-migration.ts +11 -2
- package/src/memory/conversation-group-migration.ts +20 -4
- package/src/memory/conversation-key-store.ts +3 -4
- package/src/memory/conversation-queries.ts +69 -29
- package/src/memory/conversation-starter-validation.ts +88 -0
- package/src/memory/conversation-starters-cadence.ts +1 -1
- package/src/memory/conversation-title-service.ts +27 -1
- package/src/memory/db-init.ts +22 -4
- package/src/memory/db-maintenance.ts +1 -1
- package/src/memory/delivery-channels.ts +1 -14
- package/src/memory/delivery-crud.ts +2 -32
- package/src/memory/delivery-status.ts +1 -1
- package/src/memory/embedding-gemini.test.ts +44 -5
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/external-conversation-store.ts +1 -1
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +412 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +225 -0
- package/src/memory/graph/bootstrap.test.ts +277 -0
- package/src/memory/graph/bootstrap.ts +10 -6
- package/src/memory/graph/capability-seed.ts +3 -3
- package/src/memory/graph/compaction.ts +1 -1
- package/src/memory/graph/consolidation.ts +13 -10
- package/src/memory/graph/conversation-graph-memory.ts +151 -1
- package/src/memory/graph/decay.ts +1 -1
- package/src/memory/graph/extraction.ts +63 -23
- package/src/memory/graph/graph-memory-state-store.ts +1 -1
- package/src/memory/graph/graph-search.test.ts +95 -2
- package/src/memory/graph/graph-search.ts +22 -7
- package/src/memory/graph/image-ref-utils.ts +1 -1
- package/src/memory/graph/retriever.test.ts +158 -4
- package/src/memory/graph/retriever.ts +27 -8
- package/src/memory/graph/store.test.ts +2 -1
- package/src/memory/graph/store.ts +1 -1
- package/src/memory/graph/tool-handlers.ts +73 -247
- package/src/memory/graph/tools.ts +35 -53
- package/src/memory/group-crud.ts +1 -2
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +1 -1
- package/src/memory/guardian-rate-limits.ts +1 -1
- package/src/memory/indexer.ts +43 -17
- package/src/memory/invite-store.ts +1 -1
- package/src/memory/job-handlers/backfill.ts +1 -1
- package/src/memory/job-handlers/cleanup.ts +2 -1
- package/src/memory/job-handlers/conversation-starters.ts +18 -10
- package/src/memory/job-handlers/embedding.test.ts +2 -1
- package/src/memory/job-handlers/embedding.ts +1 -1
- package/src/memory/job-handlers/index-maintenance.ts +1 -1
- package/src/memory/job-handlers/summarization.ts +3 -3
- package/src/memory/job-utils.ts +3 -3
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +362 -0
- package/src/memory/jobs/embed-concept-page.ts +210 -0
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -1
- package/src/memory/jobs-store.ts +10 -2
- package/src/memory/jobs-worker.ts +58 -5
- package/src/memory/lifecycle-events-store.ts +1 -1
- package/src/memory/llm-request-log-store.ts +1 -1
- package/src/memory/llm-usage-store.ts +1 -1
- package/src/memory/media-store.ts +1 -1
- package/src/memory/memory-recall-log-store.ts +1 -1
- package/src/memory/migrations/038-actor-token-records.ts +3 -0
- package/src/memory/migrations/039-actor-refresh-token-records.ts +3 -0
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/226-schedule-wake-conversation-id.ts +11 -0
- package/src/memory/migrations/227-add-conversation-inference-profile.ts +18 -0
- package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +27 -0
- package/src/memory/migrations/229-delete-private-conversations.test.ts +1087 -0
- package/src/memory/migrations/229-delete-private-conversations.ts +210 -0
- package/src/memory/migrations/230-acp-session-history.ts +41 -0
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +128 -0
- package/src/memory/migrations/232-activation-state.ts +38 -0
- package/src/memory/migrations/index.ts +14 -0
- package/src/memory/migrations/registry.ts +7 -0
- package/src/memory/pkb/pkb-index.test.ts +5 -5
- package/src/memory/pkb/pkb-reconcile.test.ts +5 -5
- package/src/memory/pkb/pkb-search.test.ts +148 -7
- package/src/memory/pkb/pkb-search.ts +65 -30
- package/src/memory/published-pages-store.ts +1 -1
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +25 -0
- package/src/memory/schema/acp.ts +30 -0
- package/src/memory/schema/conversations.ts +1 -1
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/infrastructure.ts +2 -32
- package/src/memory/schema/memory-graph.ts +36 -14
- package/src/memory/schema/oauth.ts +4 -1
- package/src/memory/scoped-approval-grants.ts +2 -1
- package/src/memory/search/semantic.ts +2 -2
- package/src/memory/shared-app-links-store.ts +2 -1
- package/src/memory/tool-usage-store.ts +1 -1
- package/src/memory/trace-event-store.ts +2 -1
- package/src/memory/turn-events-store.ts +1 -1
- package/src/memory/v2/__tests__/activation-store.test.ts +202 -0
- package/src/memory/v2/__tests__/activation.test.ts +956 -0
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +610 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +395 -0
- package/src/memory/v2/__tests__/edges.test.ts +435 -0
- package/src/memory/v2/__tests__/injection.test.ts +792 -0
- package/src/memory/v2/__tests__/migration.test.ts +812 -0
- package/src/memory/v2/__tests__/page-store.test.ts +334 -0
- package/src/memory/v2/__tests__/qdrant.test.ts +438 -0
- package/src/memory/v2/__tests__/sim.test.ts +549 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +85 -0
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +657 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +351 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +441 -0
- package/src/memory/v2/activation-store.ts +109 -0
- package/src/memory/v2/activation.ts +490 -0
- package/src/memory/v2/backfill-jobs.ts +442 -0
- package/src/memory/v2/consolidation-job.ts +304 -0
- package/src/memory/v2/edges.ts +217 -0
- package/src/memory/v2/injection.ts +307 -0
- package/src/memory/v2/migration.ts +654 -0
- package/src/memory/v2/now-text.ts +38 -0
- package/src/memory/v2/page-store.ts +245 -0
- package/src/memory/v2/prompts/consolidation.ts +185 -0
- package/src/memory/v2/prompts/sweep.ts +56 -0
- package/src/memory/v2/qdrant.ts +342 -0
- package/src/memory/v2/sim.ts +206 -0
- package/src/memory/v2/skill-content.ts +42 -0
- package/src/memory/v2/skill-qdrant.ts +395 -0
- package/src/memory/v2/skill-store.ts +128 -0
- package/src/memory/v2/sweep-job.ts +298 -0
- package/src/memory/v2/types.ts +116 -0
- package/src/memory/validation.ts +1 -1
- package/src/messaging/providers/index.ts +262 -0
- package/src/messaging/providers/slack/api.ts +242 -0
- package/src/messaging/providers/slack/message-metadata.ts +1 -1
- package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
- package/src/messaging/providers/slack/render-transcript.ts +58 -0
- package/src/messaging/providers/slack/send.ts +383 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +4 -42
- package/src/messaging/providers/telegram-bot/api.ts +253 -0
- package/src/messaging/providers/telegram-bot/client.ts +17 -58
- package/src/messaging/providers/telegram-bot/send.ts +232 -0
- package/src/messaging/providers/whatsapp/adapter.ts +4 -36
- package/src/messaging/providers/whatsapp/api.ts +319 -0
- package/src/messaging/providers/whatsapp/client.ts +4 -48
- package/src/messaging/providers/whatsapp/send.ts +209 -0
- package/src/notifications/adapters/slack.ts +5 -23
- package/src/notifications/adapters/telegram.ts +8 -29
- package/src/notifications/conversation-candidates.ts +1 -1
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/conversation-seed-composer.ts +12 -6
- package/src/notifications/copy-composer.ts +1 -6
- package/src/notifications/decision-engine.ts +1 -1
- package/src/notifications/decisions-store.ts +1 -1
- package/src/notifications/deliveries-store.ts +2 -1
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/events-store.ts +1 -13
- package/src/notifications/preferences-store.ts +1 -1
- package/src/notifications/signal.ts +1 -11
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/connection-resolver.ts +11 -2
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +32 -15
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -108
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/outbound-proxy/index.ts +0 -1
- package/src/permissions/approval-policy.test.ts +398 -106
- package/src/permissions/approval-policy.ts +134 -108
- package/src/permissions/checker.test.ts +632 -0
- package/src/permissions/checker.ts +280 -345
- package/src/permissions/gateway-threshold-reader.ts +177 -0
- package/src/permissions/ipc-risk-types.ts +95 -0
- package/src/permissions/prompter.ts +8 -9
- package/src/permissions/risk-types.ts +24 -153
- package/src/permissions/types.ts +19 -47
- package/src/permissions/workspace-policy.ts +10 -7
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +488 -0
- package/src/plugins/defaults/llm-call.ts +79 -0
- package/src/plugins/defaults/memory-retrieval.ts +221 -0
- package/src/plugins/defaults/overflow-reduce.ts +185 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +103 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +301 -0
- package/src/plugins/types.ts +1133 -0
- package/src/plugins/user-loader.ts +203 -0
- package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +197 -0
- package/src/prompts/persona-resolver.ts +2 -4
- package/src/prompts/system-prompt.ts +39 -0
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/providers/__tests__/provider-env-vars.test.ts +0 -21
- package/src/providers/__tests__/retry-callsite.test.ts +3 -6
- package/src/providers/anthropic/client.ts +71 -19
- package/src/providers/call-site-routing.ts +7 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/gemini/client.ts +96 -22
- package/src/providers/managed-proxy/context.ts +0 -12
- package/src/providers/model-catalog.ts +123 -25
- package/src/providers/model-intents.ts +6 -7
- package/src/providers/openai/chat-completions-provider.ts +37 -7
- package/src/providers/openai/responses-provider.ts +39 -4
- package/src/providers/openrouter/client.ts +9 -6
- package/src/providers/provider-env-vars.ts +4 -12
- package/src/providers/provider-send-message.ts +16 -11
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +52 -23
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +1 -1
- package/src/providers/speech-to-text/openai-whisper.ts +3 -6
- package/src/providers/speech-to-text/provider-catalog.ts +75 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
- package/src/providers/speech-to-text/xai-realtime.ts +39 -14
- package/src/providers/speech-to-text/xai.ts +5 -5
- package/src/providers/thinking-config.ts +34 -0
- package/src/providers/types.ts +22 -10
- package/src/runtime/AGENTS.md +27 -17
- package/src/runtime/__tests__/agent-wake.test.ts +33 -9
- package/src/runtime/__tests__/client-registry.test.ts +271 -0
- package/src/runtime/__tests__/interactive-ui.test.ts +157 -246
- package/src/runtime/access-request-helper.ts +9 -20
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/agent-wake.ts +174 -68
- package/src/runtime/approval-conversation-turn.ts +2 -15
- package/src/runtime/approval-message-composer.ts +11 -60
- package/src/runtime/assistant-event.ts +18 -66
- package/src/runtime/auth/__tests__/guard-tests.test.ts +6 -30
- package/src/runtime/auth/__tests__/middleware.test.ts +10 -10
- package/src/runtime/auth/__tests__/route-policy.test.ts +0 -8
- package/src/runtime/auth/context.ts +9 -0
- package/src/runtime/auth/middleware.ts +4 -4
- package/src/runtime/auth/route-policy.ts +195 -4
- package/src/runtime/auth/token-service.ts +1 -100
- package/src/runtime/capability-tokens.ts +89 -313
- package/src/runtime/channel-approval-types.ts +1 -6
- package/src/runtime/channel-approvals.ts +7 -79
- package/src/runtime/channel-readiness-service.ts +2 -2
- package/src/runtime/channel-reply-delivery.ts +2 -8
- package/src/runtime/channel-retry-sweep.ts +20 -17
- package/src/runtime/client-registry.ts +254 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +2 -7
- package/src/runtime/gateway-client.ts +37 -378
- package/src/runtime/guardian-action-grant-minter.ts +2 -3
- package/src/runtime/guardian-action-message-composer.ts +11 -52
- package/src/runtime/guardian-action-service.ts +19 -7
- package/src/runtime/guardian-decision-types.ts +4 -65
- package/src/runtime/guardian-reply-router.ts +10 -19
- package/src/runtime/guardian-vellum-migration.ts +5 -64
- package/src/runtime/http-errors.ts +3 -0
- package/src/runtime/http-router.ts +50 -7
- package/src/runtime/http-server.ts +345 -1041
- package/src/runtime/http-types.ts +15 -100
- package/src/runtime/interactive-ui-types.ts +145 -0
- package/src/runtime/interactive-ui.ts +38 -196
- package/src/runtime/invite-redemption-service.ts +1 -1
- package/src/runtime/invite-redemption-templates.ts +1 -1
- package/src/runtime/local-actor-identity.ts +13 -43
- package/src/runtime/message-composer-types.ts +134 -0
- package/src/runtime/middleware/rate-limiter.ts +1 -1
- package/src/runtime/middleware/request-logger.ts +5 -2
- package/src/runtime/migrations/__tests__/job-registry.test.ts +346 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +16 -0
- package/src/runtime/migrations/job-registry.ts +281 -0
- package/src/runtime/migrations/vbundle-builder.ts +4 -26
- package/src/runtime/migrations/vbundle-importer.ts +1 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -13
- package/src/runtime/migrations/vbundle-tar-stream.ts +11 -3
- package/src/runtime/nl-approval-parser.ts +16 -21
- package/src/runtime/pending-interactions.ts +29 -12
- package/src/runtime/routes/__tests__/acp-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/backup-routes.test.ts +204 -320
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +72 -4
- package/src/runtime/routes/__tests__/stt-routes.test.ts +182 -223
- package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +230 -0
- package/src/{ipc/__tests__/task-ipc.test.ts → runtime/routes/__tests__/task-routes.test.ts} +116 -96
- package/src/runtime/routes/__tests__/tts-routes.test.ts +185 -289
- package/src/runtime/routes/access-request-decision.ts +25 -50
- package/src/runtime/routes/acp-routes.test.ts +371 -0
- package/src/runtime/routes/acp-routes.ts +392 -166
- package/src/runtime/routes/app-management-routes.ts +464 -660
- package/src/runtime/routes/app-routes.ts +192 -177
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
- package/src/runtime/routes/approval-routes.ts +133 -434
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +24 -84
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +3 -10
- package/src/runtime/routes/attachment-routes.ts +409 -253
- package/src/runtime/routes/audio-routes.ts +51 -18
- package/src/runtime/routes/avatar-routes.ts +82 -75
- package/src/runtime/routes/background-tool-routes.ts +94 -0
- package/src/runtime/routes/backup-routes.ts +154 -336
- package/src/runtime/routes/brain-graph-routes.ts +83 -110
- package/src/runtime/routes/browser-routes.ts +141 -0
- package/src/runtime/routes/btw-routes.ts +62 -106
- package/src/runtime/routes/cache-routes.ts +96 -0
- package/src/runtime/routes/call-routes.ts +208 -247
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +1 -1
- package/src/runtime/routes/channel-delivery-routes.ts +25 -27
- package/src/runtime/routes/channel-readiness-routes.ts +83 -120
- package/src/runtime/routes/channel-route-definitions.ts +62 -0
- package/src/runtime/routes/channel-route-shared.ts +14 -18
- package/src/runtime/routes/channel-verification-routes.ts +207 -187
- package/src/runtime/routes/client-routes.ts +48 -0
- package/src/runtime/routes/contact-routes.ts +533 -407
- package/src/runtime/routes/conversation-analysis-routes.ts +48 -49
- package/src/runtime/routes/conversation-attention-routes.ts +55 -67
- package/src/runtime/routes/conversation-list-routes.ts +265 -0
- package/src/runtime/routes/conversation-management-routes.ts +626 -715
- package/src/runtime/routes/conversation-query-routes.ts +510 -460
- package/src/runtime/routes/conversation-routes.ts +652 -457
- package/src/runtime/routes/conversation-starter-routes.ts +121 -71
- package/src/runtime/routes/credential-prompt-routes.ts +124 -0
- package/src/runtime/routes/debug-routes.ts +34 -39
- package/src/runtime/routes/defer-routes.ts +230 -0
- package/src/runtime/routes/diagnostics-routes.ts +79 -70
- package/src/runtime/routes/documents-routes.ts +117 -106
- package/src/runtime/routes/errors.ts +132 -0
- package/src/runtime/routes/events-routes.ts +97 -58
- package/src/runtime/routes/filing-routes.ts +65 -78
- package/src/runtime/routes/global-search-routes.ts +51 -57
- package/src/runtime/routes/group-routes.ts +199 -181
- package/src/runtime/routes/guardian-action-routes.ts +103 -169
- package/src/runtime/routes/guardian-approval-interception.ts +27 -58
- package/src/runtime/routes/guardian-approval-prompt.ts +10 -21
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +2 -6
- package/src/runtime/routes/guardian-expiry-sweep.ts +19 -36
- package/src/runtime/routes/heartbeat-routes.ts +194 -209
- package/src/runtime/routes/home-feed-routes.ts +85 -187
- package/src/runtime/routes/home-state-routes.ts +27 -24
- package/src/runtime/routes/host-bash-routes.ts +42 -52
- package/src/runtime/routes/host-browser-routes.ts +38 -69
- package/src/runtime/routes/host-cu-routes.ts +74 -70
- package/src/runtime/routes/host-file-routes.ts +50 -60
- package/src/runtime/routes/host-transfer-routes.ts +220 -0
- package/src/runtime/routes/http-adapter.ts +172 -0
- package/src/runtime/routes/identity-routes.ts +83 -79
- package/src/runtime/routes/inbound-conversation.ts +11 -18
- package/src/runtime/routes/inbound-message-handler.ts +162 -123
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +79 -138
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +54 -90
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +25 -50
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +5 -6
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -24
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -10
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -4
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +3 -3
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +19 -26
- package/src/runtime/routes/index.ts +197 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +25 -32
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +22 -31
- package/src/runtime/routes/integrations/slack/channel.ts +69 -66
- package/src/runtime/routes/integrations/slack/share.ts +49 -58
- package/src/runtime/routes/integrations/telegram.ts +91 -74
- package/src/runtime/routes/integrations/twilio.ts +163 -240
- package/src/runtime/routes/integrations/vercel.ts +57 -54
- package/src/runtime/routes/interface-routes.ts +43 -0
- package/src/runtime/routes/internal-oauth-routes.ts +56 -0
- package/src/runtime/routes/internal-twilio-routes.ts +46 -0
- package/src/runtime/routes/llm-context-normalization.ts +4 -2
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/log-export-routes.ts +90 -100
- package/src/runtime/routes/memory-item-routes.test.ts +153 -175
- package/src/runtime/routes/memory-item-routes.ts +243 -323
- package/src/runtime/routes/memory-v2-routes.ts +193 -0
- package/src/runtime/routes/migration-rollback-routes.ts +167 -212
- package/src/runtime/routes/migration-routes.ts +877 -377
- package/src/runtime/routes/notification-routes.ts +199 -70
- package/src/runtime/routes/oauth-apps.ts +254 -251
- package/src/runtime/routes/oauth-providers.ts +66 -57
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +224 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +60 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +250 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +195 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +159 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +207 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +175 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +27 -0
- package/src/runtime/routes/playground/force-compact.ts +60 -0
- package/src/runtime/routes/playground/guard.ts +36 -0
- package/src/runtime/routes/playground/helpers.ts +103 -0
- package/src/runtime/routes/playground/index.ts +18 -0
- package/src/runtime/routes/playground/inject-failures.ts +143 -0
- package/src/runtime/routes/playground/reset-circuit.ts +89 -0
- package/src/runtime/routes/playground/seed-conversation.ts +113 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +74 -0
- package/src/runtime/routes/playground/state.ts +77 -0
- package/src/runtime/routes/profiler-routes.ts +132 -167
- package/src/runtime/routes/ps-routes.ts +120 -0
- package/src/runtime/routes/recording-routes.ts +197 -258
- package/src/runtime/routes/rename-conversation-routes.ts +89 -0
- package/src/runtime/routes/schedule-routes.ts +284 -207
- package/src/runtime/routes/secret-routes.ts +219 -265
- package/src/runtime/routes/secrets-deps.ts +24 -0
- package/src/runtime/routes/settings-routes.ts +361 -441
- package/src/runtime/routes/skills-routes.ts +434 -469
- package/src/runtime/routes/stt-routes.ts +196 -206
- package/src/runtime/routes/subagents-routes.ts +125 -141
- package/src/runtime/routes/suggest-trust-rule-routes.ts +244 -0
- package/src/runtime/routes/surface-action-routes.ts +135 -190
- package/src/runtime/routes/surface-content-routes.ts +84 -118
- package/src/runtime/routes/task-routes.ts +354 -0
- package/src/runtime/routes/telemetry-routes.ts +33 -49
- package/src/runtime/routes/trace-event-routes.ts +55 -74
- package/src/runtime/routes/trust-rules-routes.ts +147 -239
- package/src/runtime/routes/tts-routes.ts +187 -169
- package/src/runtime/routes/types.ts +139 -0
- package/src/{ipc/routes/ui-request.ts → runtime/routes/ui-request-routes.ts} +23 -17
- package/src/runtime/routes/upgrade-broadcast-routes.ts +156 -197
- package/src/runtime/routes/usage-routes.ts +143 -169
- package/src/runtime/routes/user-routes.ts +102 -18
- package/src/runtime/routes/wake-conversation-routes.ts +49 -0
- package/src/{ipc/routes/watcher.ts → runtime/routes/watcher-routes.ts} +84 -39
- package/src/runtime/routes/wipe-conversation-routes.ts +89 -0
- package/src/runtime/routes/work-items-routes.test.ts +10 -20
- package/src/runtime/routes/work-items-routes.ts +418 -433
- package/src/runtime/routes/workspace-commit-routes.ts +30 -61
- package/src/runtime/routes/workspace-routes.test.ts +254 -381
- package/src/runtime/routes/workspace-routes.ts +238 -246
- package/src/runtime/runtime-mode.ts +8 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +80 -118
- package/src/runtime/services/analyze-conversation.ts +14 -41
- package/src/runtime/services/conversation-serializer.ts +181 -0
- package/src/runtime/skill-route-registry.ts +75 -15
- package/src/runtime/trust-context-resolver.ts +3 -2
- package/src/runtime/verification-outbound-actions.ts +13 -49
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +70 -2
- package/src/schedule/scheduler.ts +149 -8
- package/src/security/ces-credential-client.ts +32 -169
- package/src/security/ces-rpc-credential-backend.ts +1 -1
- package/src/security/credential-backend.ts +6 -6
- package/src/security/oauth-completion-page.ts +1 -1
- package/src/security/oauth2.ts +3 -6
- package/src/sequence/analytics.ts +1 -1
- package/src/sequence/guardrails.ts +3 -3
- package/src/sequence/store.ts +2 -1
- package/src/signals/bash.ts +1 -1
- package/src/signals/event-stream.ts +1 -1
- package/src/skills/catalog-cache.ts +19 -5
- package/src/skills/catalog-files.ts +0 -5
- package/src/skills/catalog-install.ts +28 -18
- package/src/skills/category-inference.ts +0 -11
- package/src/skills/clawhub.ts +2 -2
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/remote-skill-policy.ts +6 -7
- package/src/subagent/index.ts +2 -6
- package/src/subagent/manager.ts +27 -23
- package/src/subagent/types.ts +9 -0
- package/src/tasks/SPEC.md +2 -2
- package/src/tasks/task-compiler.ts +1 -1
- package/src/tasks/task-runner.ts +2 -22
- package/src/tasks/task-store.ts +1 -1
- package/src/tools/acp/list-agents.test.ts +115 -0
- package/src/tools/acp/list-agents.ts +31 -0
- package/src/tools/acp/spawn.test.ts +379 -0
- package/src/tools/acp/spawn.ts +142 -62
- package/src/tools/acp/steer.test.ts +101 -0
- package/src/tools/acp/steer.ts +38 -0
- package/src/tools/background-tool-registry.ts +98 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
- package/src/tools/browser/browser-execution.ts +122 -26
- package/src/tools/browser/browser-manager.ts +1 -8
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +1 -1
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +3 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/browser/cdp-client/types.ts +4 -1
- package/src/tools/computer-use/definitions.ts +1 -1
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -2
- package/src/tools/credential-execution/manage-secure-command-tool.ts +1 -1
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -2
- package/src/tools/credentials/broker-types.ts +2 -1
- package/src/tools/document/editor-template.ts +1 -1
- package/src/tools/execution-timeout.ts +1 -1
- package/src/tools/executor.ts +123 -76
- package/src/tools/host-filesystem/transfer.test.ts +268 -0
- package/src/tools/host-filesystem/transfer.ts +234 -0
- package/src/tools/host-terminal/host-shell.ts +189 -11
- package/src/tools/mcp/mcp-tool-factory.ts +1 -1
- package/src/tools/memory/register.test.ts +161 -1
- package/src/tools/memory/register.ts +19 -34
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/permission-checker.ts +103 -255
- package/src/tools/policy-context.ts +5 -8
- package/src/tools/registry.ts +156 -4
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +13 -154
- package/src/tools/shared/shell-output.ts +4 -1
- package/src/tools/side-effects.ts +2 -2
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/subagent/spawn.ts +35 -11
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/terminal/safe-env.ts +9 -1
- package/src/tools/terminal/shell.ts +161 -31
- package/src/tools/tool-approval-handler.ts +4 -70
- package/src/tools/tool-input-summary.ts +10 -0
- package/src/tools/types.ts +157 -151
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/debounce.ts +0 -21
- package/src/util/errors.ts +0 -8
- package/src/util/log-redact.ts +0 -1
- package/src/util/platform.ts +85 -119
- package/src/util/pricing.ts +135 -9
- package/src/watcher/engine.ts +42 -20
- package/src/watcher/watcher-store.ts +2 -1
- package/src/work-items/work-item-store.ts +1 -1
- package/src/workspace/git-service.ts +1 -6
- package/src/workspace/migrations/006-services-config.ts +11 -4
- package/src/workspace/migrations/017-seed-persona-dirs.ts +1 -1
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -1
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +3 -4
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/052-seed-default-inference-profiles.ts +150 -0
- package/src/workspace/migrations/053-release-notes-acp-codex.ts +107 -0
- package/src/workspace/migrations/054-seed-recall-callsite.ts +102 -0
- package/src/workspace/migrations/055-release-notes-agentic-recall.ts +63 -0
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +65 -0
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +98 -0
- package/src/workspace/migrations/058-release-notes-acp-sessions-ui.ts +71 -0
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +53 -0
- package/src/workspace/migrations/060-memory-v2-init.ts +53 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +1 -1
- package/src/workspace/migrations/registry.ts +30 -0
- package/src/workspace/migrations/runner.ts +2 -2
- package/src/workspace/provider-commit-message-generator.ts +1 -1
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +0 -471
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +0 -436
- package/src/__tests__/cli-command-risk-guard.test.ts +0 -368
- package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
- package/src/__tests__/config-watcher-feature-flags.test.ts +0 -211
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/conversation-approval-overrides.test.ts +0 -207
- package/src/__tests__/conversation-host-access-routes.test.ts +0 -229
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +0 -226
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +0 -167
- package/src/__tests__/ephemeral-permissions.test.ts +0 -474
- package/src/__tests__/extension-id-sync-guard.test.ts +0 -241
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +0 -374
- package/src/__tests__/native-host-marker-sync-guard.test.ts +0 -157
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/pairing-concurrent.test.ts +0 -84
- package/src/__tests__/pairing-routes.test.ts +0 -181
- package/src/__tests__/parser.test.ts +0 -595
- package/src/__tests__/permission-checker-host-gate.test.ts +0 -512
- package/src/__tests__/permission-controls-v2-flag.test.ts +0 -55
- package/src/__tests__/permission-mode.test.ts +0 -89
- package/src/__tests__/provider-env-vars-scope.test.ts +0 -52
- package/src/__tests__/risk-classifier-parity.test.ts +0 -230
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/__tests__/shell-identity.test.ts +0 -370
- package/src/__tests__/shell-parser-fuzz.test.ts +0 -629
- package/src/__tests__/shell-parser-property.test.ts +0 -936
- package/src/__tests__/starter-bundle.test.ts +0 -173
- package/src/__tests__/stt-catalog-parity.test.ts +0 -282
- package/src/__tests__/task-runner.test.ts +0 -224
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -357
- package/src/__tests__/trust-store-pattern-matches.test.ts +0 -29
- package/src/__tests__/trust-store.test.ts +0 -2013
- package/src/__tests__/v2-consent-policy.test.ts +0 -103
- package/src/browser/identifiers.ts +0 -51
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/cli/db.ts +0 -1
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +0 -40
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +0 -64
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +0 -88
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +0 -127
- package/src/daemon/approved-devices-store.ts +0 -110
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/external-skills-bootstrap.ts +0 -41
- package/src/daemon/message-types/trust.ts +0 -71
- package/src/daemon/pairing-store.ts +0 -229
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/ipc/cli-server.ts +0 -252
- package/src/ipc/routes/attachment.ts +0 -114
- package/src/ipc/routes/browser-context.ts +0 -61
- package/src/ipc/routes/browser.ts +0 -96
- package/src/ipc/routes/cache.ts +0 -96
- package/src/ipc/routes/index.ts +0 -21
- package/src/ipc/routes/task-queue.ts +0 -226
- package/src/ipc/routes/task.ts +0 -173
- package/src/ipc/routes/wake-conversation.ts +0 -19
- package/src/memory/db.ts +0 -23
- package/src/oauth/scope-policy.ts +0 -89
- package/src/permissions/bash-risk-classifier.test.ts +0 -1208
- package/src/permissions/bash-risk-classifier.ts +0 -707
- package/src/permissions/command-registry.test.ts +0 -535
- package/src/permissions/command-registry.ts +0 -825
- package/src/permissions/defaults.ts +0 -313
- package/src/permissions/file-risk-classifier.test.ts +0 -535
- package/src/permissions/file-risk-classifier.ts +0 -274
- package/src/permissions/permission-mode.ts +0 -24
- package/src/permissions/shell-identity.ts +0 -337
- package/src/permissions/skill-risk-classifier.test.ts +0 -311
- package/src/permissions/skill-risk-classifier.ts +0 -214
- package/src/permissions/trust-client.ts +0 -359
- package/src/permissions/trust-store-interface.ts +0 -100
- package/src/permissions/trust-store.ts +0 -1330
- package/src/permissions/v2-consent-policy.ts +0 -87
- package/src/permissions/web-risk-classifier.test.ts +0 -170
- package/src/permissions/web-risk-classifier.ts +0 -89
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +0 -715
- package/src/runtime/__tests__/capability-tokens.test.ts +0 -258
- package/src/runtime/actor-refresh-token-store.ts +0 -156
- package/src/runtime/actor-token-store.ts +0 -207
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -264
- package/src/runtime/auth/credential-service.ts +0 -352
- package/src/runtime/conversation-approval-overrides.ts +0 -86
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/browser-extension-pair-routes.ts +0 -556
- package/src/runtime/routes/channel-routes.ts +0 -112
- package/src/runtime/routes/contact-routes.test.ts +0 -298
- package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -175
- package/src/runtime/routes/guardian-refresh-routes.ts +0 -79
- package/src/runtime/routes/invite-routes.ts +0 -280
- package/src/runtime/routes/pairing-routes.ts +0 -431
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +0 -67
- package/src/runtime/services/analyze-deps-singleton.ts +0 -32
- package/src/signals/shotgun.ts +0 -203
- package/src/tasks/ephemeral-permissions.ts +0 -55
- package/src/tools/terminal/parser.ts +0 -623
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
- package/src/types/qrcode.d.ts +0 -13
- package/src/util/network-info.ts +0 -55
- /package/node_modules/@vellumai/{ces-contracts → ces-client}/tsconfig.json +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/__tests__/grants.test.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/error.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/grants.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/handles.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/rendering.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/rpc.ts +0 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
afterAll,
|
|
3
|
-
afterEach,
|
|
4
2
|
beforeEach,
|
|
5
3
|
describe,
|
|
6
4
|
expect,
|
|
7
5
|
mock,
|
|
8
|
-
spyOn,
|
|
9
6
|
test,
|
|
10
7
|
} from "bun:test";
|
|
11
8
|
|
|
@@ -13,14 +10,11 @@ import type {
|
|
|
13
10
|
AllowlistOption,
|
|
14
11
|
PolicyContext,
|
|
15
12
|
ScopeOption,
|
|
16
|
-
TrustRule,
|
|
17
13
|
} from "../permissions/types.js";
|
|
18
14
|
import { RiskLevel } from "../permissions/types.js";
|
|
19
15
|
import type {
|
|
20
16
|
Tool,
|
|
21
17
|
ToolExecutionResult,
|
|
22
|
-
ToolLifecycleEvent,
|
|
23
|
-
ToolPermissionPromptEvent,
|
|
24
18
|
} from "../tools/types.js";
|
|
25
19
|
|
|
26
20
|
const mockConfig = {
|
|
@@ -50,9 +44,7 @@ const mockConfig = {
|
|
|
50
44
|
action: "warn" as const,
|
|
51
45
|
entropyThreshold: 4.0,
|
|
52
46
|
},
|
|
53
|
-
permissions: {
|
|
54
|
-
mode: "workspace" as const,
|
|
55
|
-
},
|
|
47
|
+
permissions: {},
|
|
56
48
|
};
|
|
57
49
|
|
|
58
50
|
let fakeToolResult: ToolExecutionResult = { content: "ok", isError: false };
|
|
@@ -86,8 +78,16 @@ let checkFnOverride:
|
|
|
86
78
|
/** Override for generateScopeOptions — when set, returns this value instead of the default. */
|
|
87
79
|
let scopeOptionsOverride: ScopeOption[] | undefined;
|
|
88
80
|
|
|
89
|
-
/**
|
|
90
|
-
let
|
|
81
|
+
/** Override for getCachedAssessment — when set, returns this value. */
|
|
82
|
+
let cachedAssessmentOverride:
|
|
83
|
+
| {
|
|
84
|
+
riskLevel: string;
|
|
85
|
+
reason: string;
|
|
86
|
+
scopeOptions: Array<{ pattern: string; label: string }>;
|
|
87
|
+
directoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
88
|
+
matchType: string;
|
|
89
|
+
}
|
|
90
|
+
| undefined;
|
|
91
91
|
|
|
92
92
|
mock.module("../config/loader.js", () => ({
|
|
93
93
|
getConfig: () => mockConfig,
|
|
@@ -127,6 +127,7 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
127
127
|
],
|
|
128
128
|
generateScopeOptions: () =>
|
|
129
129
|
scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
|
|
130
|
+
getCachedAssessment: () => cachedAssessmentOverride,
|
|
130
131
|
}));
|
|
131
132
|
|
|
132
133
|
// Mock every export so downstream test files that dynamically import modules
|
|
@@ -163,7 +164,6 @@ mock.module("../tools/terminal/sandbox.js", () => ({
|
|
|
163
164
|
}));
|
|
164
165
|
|
|
165
166
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
166
|
-
import * as trustStore from "../permissions/trust-store.js";
|
|
167
167
|
import { isSideEffectTool, ToolExecutor } from "../tools/executor.js";
|
|
168
168
|
import type { ToolContext } from "../tools/types.js";
|
|
169
169
|
|
|
@@ -185,10 +185,6 @@ function makePrompter(): PermissionPrompter {
|
|
|
185
185
|
} as unknown as PermissionPrompter;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
afterAll(() => {
|
|
189
|
-
mock.restore();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
188
|
describe("ToolExecutor allowedToolNames gating", () => {
|
|
193
189
|
beforeEach(() => {
|
|
194
190
|
fakeToolResult = { content: "ok", isError: false };
|
|
@@ -196,10 +192,7 @@ describe("ToolExecutor allowedToolNames gating", () => {
|
|
|
196
192
|
getToolOverride = undefined;
|
|
197
193
|
checkResultOverride = undefined;
|
|
198
194
|
checkFnOverride = undefined;
|
|
199
|
-
|
|
200
|
-
addRuleSpy.mockRestore();
|
|
201
|
-
addRuleSpy = undefined;
|
|
202
|
-
}
|
|
195
|
+
cachedAssessmentOverride = undefined;
|
|
203
196
|
});
|
|
204
197
|
|
|
205
198
|
test("executes normally when allowedToolNames is not set", async () => {
|
|
@@ -272,10 +265,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
272
265
|
getToolOverride = undefined;
|
|
273
266
|
checkResultOverride = undefined;
|
|
274
267
|
checkFnOverride = undefined;
|
|
275
|
-
|
|
276
|
-
addRuleSpy.mockRestore();
|
|
277
|
-
addRuleSpy = undefined;
|
|
278
|
-
}
|
|
268
|
+
cachedAssessmentOverride = undefined;
|
|
279
269
|
});
|
|
280
270
|
|
|
281
271
|
test("passes PolicyContext with executionTarget for skill-origin tools", async () => {
|
|
@@ -303,14 +293,14 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
303
293
|
const result = await executor.execute(
|
|
304
294
|
"skill_tool",
|
|
305
295
|
{ action: "run" },
|
|
306
|
-
makeContext(),
|
|
296
|
+
makeContext({ requireFreshApproval: true }),
|
|
307
297
|
);
|
|
308
298
|
|
|
309
299
|
expect(result.isError).toBe(false);
|
|
310
300
|
expect(lastCheckArgs).toBeDefined();
|
|
311
301
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
302
|
+
conversationId: "conversation-1",
|
|
312
303
|
executionContext: "conversation",
|
|
313
|
-
ephemeralRules: undefined,
|
|
314
304
|
executionTarget: "sandbox",
|
|
315
305
|
});
|
|
316
306
|
});
|
|
@@ -323,14 +313,14 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
323
313
|
const result = await executor.execute(
|
|
324
314
|
"file_read",
|
|
325
315
|
{ path: "test.txt" },
|
|
326
|
-
makeContext(),
|
|
316
|
+
makeContext({ requireFreshApproval: true }),
|
|
327
317
|
);
|
|
328
318
|
|
|
329
319
|
expect(result.isError).toBe(false);
|
|
330
320
|
expect(lastCheckArgs).toBeDefined();
|
|
331
321
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
322
|
+
conversationId: "conversation-1",
|
|
332
323
|
executionContext: "conversation",
|
|
333
|
-
ephemeralRules: undefined,
|
|
334
324
|
});
|
|
335
325
|
});
|
|
336
326
|
|
|
@@ -356,14 +346,14 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
356
346
|
const result = await executor.execute(
|
|
357
347
|
"file_read",
|
|
358
348
|
{ path: "test.txt" },
|
|
359
|
-
makeContext(),
|
|
349
|
+
makeContext({ requireFreshApproval: true }),
|
|
360
350
|
);
|
|
361
351
|
|
|
362
352
|
expect(result.isError).toBe(false);
|
|
363
353
|
expect(lastCheckArgs).toBeDefined();
|
|
364
354
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
355
|
+
conversationId: "conversation-1",
|
|
365
356
|
executionContext: "conversation",
|
|
366
|
-
ephemeralRules: undefined,
|
|
367
357
|
});
|
|
368
358
|
});
|
|
369
359
|
|
|
@@ -392,14 +382,14 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
392
382
|
const result = await executor.execute(
|
|
393
383
|
"host_skill_tool",
|
|
394
384
|
{ action: "run" },
|
|
395
|
-
makeContext(),
|
|
385
|
+
makeContext({ requireFreshApproval: true }),
|
|
396
386
|
);
|
|
397
387
|
|
|
398
388
|
expect(result.isError).toBe(false);
|
|
399
389
|
expect(lastCheckArgs).toBeDefined();
|
|
400
390
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
391
|
+
conversationId: "conversation-1",
|
|
401
392
|
executionContext: "conversation",
|
|
402
|
-
ephemeralRules: undefined,
|
|
403
393
|
executionTarget: "host",
|
|
404
394
|
});
|
|
405
395
|
});
|
|
@@ -425,1054 +415,225 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
425
415
|
};
|
|
426
416
|
|
|
427
417
|
const executor = new ToolExecutor(makePrompter());
|
|
428
|
-
const result = await executor.execute("no_target_tool", {}, makeContext());
|
|
418
|
+
const result = await executor.execute("no_target_tool", {}, makeContext({ requireFreshApproval: true }));
|
|
429
419
|
|
|
430
420
|
expect(result.isError).toBe(false);
|
|
431
421
|
expect(lastCheckArgs).toBeDefined();
|
|
432
422
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
423
|
+
conversationId: "conversation-1",
|
|
433
424
|
executionContext: "conversation",
|
|
434
|
-
ephemeralRules: undefined,
|
|
435
425
|
executionTarget: undefined,
|
|
436
426
|
});
|
|
437
427
|
});
|
|
438
428
|
});
|
|
439
429
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
function makePrompterWithDecision(
|
|
444
|
-
decision: string,
|
|
445
|
-
selectedPattern?: string,
|
|
446
|
-
selectedScope?: string,
|
|
447
|
-
): PermissionPrompter {
|
|
448
|
-
return {
|
|
449
|
-
prompt: async () => ({ decision, selectedPattern, selectedScope }),
|
|
450
|
-
resolveConfirmation: () => {},
|
|
451
|
-
updateSender: () => {},
|
|
452
|
-
dispose: () => {},
|
|
453
|
-
} as unknown as PermissionPrompter;
|
|
454
|
-
}
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// isSideEffectTool classifier
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
455
433
|
|
|
456
|
-
describe("
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
434
|
+
describe("isSideEffectTool", () => {
|
|
435
|
+
describe("returns true for side-effect tools", () => {
|
|
436
|
+
const sideEffectTools = [
|
|
437
|
+
"file_write",
|
|
438
|
+
"file_edit",
|
|
439
|
+
"host_file_write",
|
|
440
|
+
"host_file_edit",
|
|
441
|
+
"bash",
|
|
442
|
+
"host_bash",
|
|
443
|
+
"web_fetch",
|
|
444
|
+
"document_create",
|
|
445
|
+
"document_update",
|
|
446
|
+
"schedule_create",
|
|
447
|
+
"schedule_update",
|
|
448
|
+
"schedule_delete",
|
|
449
|
+
];
|
|
450
|
+
|
|
451
|
+
for (const toolName of sideEffectTools) {
|
|
452
|
+
test(toolName, () => {
|
|
453
|
+
expect(isSideEffectTool(toolName)).toBe(true);
|
|
454
|
+
});
|
|
467
455
|
}
|
|
468
456
|
});
|
|
469
457
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
);
|
|
492
|
-
return addRuleSpy;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
test("always_allow for a skill tool captures execution target in the rule", async () => {
|
|
496
|
-
checkResultOverride = { decision: "prompt", reason: "test prompt" };
|
|
497
|
-
const spy = setupAddRuleSpy();
|
|
498
|
-
|
|
499
|
-
getToolOverride = (name: string) => {
|
|
500
|
-
if (name === "unknown_tool") return undefined;
|
|
501
|
-
return {
|
|
502
|
-
name,
|
|
503
|
-
description: "skill tool",
|
|
504
|
-
category: "skill",
|
|
505
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
506
|
-
origin: "skill" as const,
|
|
507
|
-
ownerSkillId: "my-skill-42",
|
|
508
|
-
ownerSkillVersionHash: "sha256-deadbeef",
|
|
509
|
-
executionTarget: "sandbox" as const,
|
|
510
|
-
getDefinition: () => ({
|
|
511
|
-
name,
|
|
512
|
-
description: "skill tool",
|
|
513
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
514
|
-
}),
|
|
515
|
-
execute: async () => fakeToolResult,
|
|
516
|
-
};
|
|
517
|
-
};
|
|
458
|
+
describe("returns false for non-side-effect tools", () => {
|
|
459
|
+
const readOnlyTools = [
|
|
460
|
+
"file_read",
|
|
461
|
+
"memory_recall",
|
|
462
|
+
"memory_manage",
|
|
463
|
+
"web_search",
|
|
464
|
+
"browser_navigate",
|
|
465
|
+
"browser_click",
|
|
466
|
+
"browser_type",
|
|
467
|
+
"browser_press_key",
|
|
468
|
+
"browser_close",
|
|
469
|
+
"browser_attach",
|
|
470
|
+
"browser_detach",
|
|
471
|
+
"browser_fill_credential",
|
|
472
|
+
"browser_snapshot",
|
|
473
|
+
"browser_screenshot",
|
|
474
|
+
"browser_wait_for",
|
|
475
|
+
"browser_extract",
|
|
476
|
+
"skill_load",
|
|
477
|
+
"schedule_list",
|
|
478
|
+
];
|
|
518
479
|
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const result = await executor.execute(
|
|
526
|
-
"skill_tool",
|
|
527
|
-
{ action: "run" },
|
|
528
|
-
makeContext(),
|
|
529
|
-
);
|
|
480
|
+
for (const toolName of readOnlyTools) {
|
|
481
|
+
test(toolName, () => {
|
|
482
|
+
expect(isSideEffectTool(toolName)).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
});
|
|
530
486
|
|
|
531
|
-
|
|
532
|
-
expect(
|
|
533
|
-
|
|
534
|
-
spy.mock.calls[0];
|
|
535
|
-
expect(tool).toBe("skill_tool");
|
|
536
|
-
expect(pattern).toBe("skill_tool:*");
|
|
537
|
-
expect(scope).toBe("/tmp/project");
|
|
538
|
-
expect(decision).toBe("allow");
|
|
539
|
-
expect(options).toBeDefined();
|
|
540
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
487
|
+
test("returns false for unknown tool names", () => {
|
|
488
|
+
expect(isSideEffectTool("nonexistent_tool")).toBe(false);
|
|
489
|
+
expect(isSideEffectTool("")).toBe(false);
|
|
541
490
|
});
|
|
542
491
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
492
|
+
describe("action-aware classification for mixed-action tools", () => {
|
|
493
|
+
test("credential_store store is a side-effect", () => {
|
|
494
|
+
expect(isSideEffectTool("credential_store", { action: "store" })).toBe(
|
|
495
|
+
true,
|
|
496
|
+
);
|
|
497
|
+
});
|
|
546
498
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
category: "skill",
|
|
553
|
-
defaultRiskLevel: RiskLevel.High,
|
|
554
|
-
origin: "skill" as const,
|
|
555
|
-
ownerSkillId: "dangerous-skill",
|
|
556
|
-
ownerSkillVersionHash: "sha256-abc",
|
|
557
|
-
executionTarget: "host" as const,
|
|
558
|
-
getDefinition: () => ({
|
|
559
|
-
name,
|
|
560
|
-
description: "high-risk skill tool",
|
|
561
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
562
|
-
}),
|
|
563
|
-
execute: async () => fakeToolResult,
|
|
564
|
-
};
|
|
565
|
-
};
|
|
499
|
+
test("credential_store delete is a side-effect", () => {
|
|
500
|
+
expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
|
|
501
|
+
true,
|
|
502
|
+
);
|
|
503
|
+
});
|
|
566
504
|
|
|
567
|
-
|
|
568
|
-
"
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
);
|
|
572
|
-
const executor = new ToolExecutor(prompter);
|
|
573
|
-
const result = await executor.execute("risky_tool", {}, makeContext());
|
|
505
|
+
test("credential_store prompt is a side-effect", () => {
|
|
506
|
+
expect(isSideEffectTool("credential_store", { action: "prompt" })).toBe(
|
|
507
|
+
true,
|
|
508
|
+
);
|
|
509
|
+
});
|
|
574
510
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
expect(options.executionTarget).toBe("host");
|
|
511
|
+
test("credential_store list is NOT a side-effect", () => {
|
|
512
|
+
expect(isSideEffectTool("credential_store", { action: "list" })).toBe(
|
|
513
|
+
false,
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("credential_store without input is NOT a side-effect", () => {
|
|
518
|
+
expect(isSideEffectTool("credential_store")).toBe(false);
|
|
519
|
+
});
|
|
585
520
|
});
|
|
521
|
+
});
|
|
586
522
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// forcePromptSideEffects enforcement (PR 30)
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
590
526
|
|
|
591
|
-
|
|
527
|
+
describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
528
|
+
let promptCalled: boolean;
|
|
529
|
+
|
|
530
|
+
beforeEach(() => {
|
|
531
|
+
fakeToolResult = { content: "ok", isError: false };
|
|
532
|
+
lastCheckArgs = undefined;
|
|
592
533
|
getToolOverride = undefined;
|
|
534
|
+
checkResultOverride = undefined;
|
|
535
|
+
checkFnOverride = undefined;
|
|
536
|
+
cachedAssessmentOverride = undefined;
|
|
537
|
+
promptCalled = false;
|
|
538
|
+
});
|
|
593
539
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
540
|
+
/**
|
|
541
|
+
* Prompter that tracks whether it was called and always allows.
|
|
542
|
+
*/
|
|
543
|
+
function makeTrackingPrompter(): PermissionPrompter {
|
|
544
|
+
return {
|
|
545
|
+
prompt: async () => {
|
|
546
|
+
promptCalled = true;
|
|
547
|
+
return { decision: "allow" as const };
|
|
548
|
+
},
|
|
549
|
+
resolveConfirmation: () => {},
|
|
550
|
+
updateSender: () => {},
|
|
551
|
+
dispose: () => {},
|
|
552
|
+
} as unknown as PermissionPrompter;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
test("side-effect tool with allow rule is forced to prompt when forcePromptSideEffects is true", async () => {
|
|
556
|
+
// check() returns allow (simulating a matched trust rule)
|
|
557
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
558
|
+
|
|
559
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
600
560
|
const result = await executor.execute(
|
|
601
561
|
"bash",
|
|
602
|
-
{ command: "
|
|
603
|
-
makeContext(),
|
|
562
|
+
{ command: "echo hello" },
|
|
563
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
604
564
|
);
|
|
605
565
|
|
|
606
566
|
expect(result.isError).toBe(false);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
spy.mock.calls[0];
|
|
610
|
-
expect(tool).toBe("bash");
|
|
611
|
-
expect(pattern).toBe("git *");
|
|
612
|
-
expect(scope).toBe("/tmp/project");
|
|
613
|
-
expect(decision).toBe("allow");
|
|
614
|
-
// No options since there's no execution target for core tools
|
|
615
|
-
expect(options).toBeUndefined();
|
|
567
|
+
// The prompter must have been called despite the allow rule
|
|
568
|
+
expect(promptCalled).toBe(true);
|
|
616
569
|
});
|
|
617
570
|
|
|
618
|
-
test("
|
|
619
|
-
checkResultOverride = {
|
|
620
|
-
|
|
571
|
+
test("deny decision is preserved (not converted to prompt) even with forcePromptSideEffects", async () => {
|
|
572
|
+
checkResultOverride = {
|
|
573
|
+
decision: "deny",
|
|
574
|
+
reason: "Policy denies this tool",
|
|
575
|
+
};
|
|
621
576
|
|
|
622
|
-
const
|
|
623
|
-
"always_allow",
|
|
624
|
-
undefined,
|
|
625
|
-
"/tmp/project",
|
|
626
|
-
);
|
|
627
|
-
const executor = new ToolExecutor(prompter);
|
|
577
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
628
578
|
const result = await executor.execute(
|
|
629
|
-
"
|
|
630
|
-
{
|
|
631
|
-
makeContext(),
|
|
579
|
+
"bash",
|
|
580
|
+
{ command: "rm -rf /" },
|
|
581
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
632
582
|
);
|
|
633
583
|
|
|
634
|
-
|
|
635
|
-
expect(
|
|
584
|
+
// Should be denied, not prompted
|
|
585
|
+
expect(result.isError).toBe(true);
|
|
586
|
+
expect(result.content).toBe("Policy denies this tool");
|
|
587
|
+
expect(promptCalled).toBe(false);
|
|
636
588
|
});
|
|
637
589
|
|
|
638
|
-
test("
|
|
639
|
-
|
|
640
|
-
|
|
590
|
+
test("non-side-effect tool is unchanged even with forcePromptSideEffects", async () => {
|
|
591
|
+
// check() returns allow for a read-only tool
|
|
592
|
+
checkResultOverride = { decision: "allow", reason: "Allowed by default" };
|
|
641
593
|
|
|
642
|
-
const
|
|
643
|
-
"always_allow",
|
|
644
|
-
"file_read:*",
|
|
645
|
-
undefined,
|
|
646
|
-
);
|
|
647
|
-
const executor = new ToolExecutor(prompter);
|
|
594
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
648
595
|
const result = await executor.execute(
|
|
649
596
|
"file_read",
|
|
650
|
-
{ path: "
|
|
651
|
-
makeContext(),
|
|
597
|
+
{ path: "README.md" },
|
|
598
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
652
599
|
);
|
|
653
600
|
|
|
654
601
|
expect(result.isError).toBe(false);
|
|
655
|
-
|
|
602
|
+
// Prompter should NOT be called — file_read is not a side-effect tool
|
|
603
|
+
expect(promptCalled).toBe(false);
|
|
656
604
|
});
|
|
657
605
|
|
|
658
|
-
test("
|
|
659
|
-
checkResultOverride = { decision: "
|
|
660
|
-
scopeOptionsOverride = [];
|
|
661
|
-
const spy = setupAddRuleSpy();
|
|
606
|
+
test("side-effect tool is auto-allowed when forcePromptSideEffects is false", async () => {
|
|
607
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
662
608
|
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
"
|
|
666
|
-
|
|
609
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
610
|
+
const result = await executor.execute(
|
|
611
|
+
"file_write",
|
|
612
|
+
{ path: "test.txt", content: "data" },
|
|
613
|
+
makeContext({ forcePromptSideEffects: false }),
|
|
667
614
|
);
|
|
668
|
-
const executor = new ToolExecutor(prompter);
|
|
669
|
-
const result = await executor.execute("some_tool", {}, makeContext());
|
|
670
615
|
|
|
671
616
|
expect(result.isError).toBe(false);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
expect(scope).toBe("everywhere");
|
|
617
|
+
// No prompt — standard behavior when forcePromptSideEffects is off
|
|
618
|
+
expect(promptCalled).toBe(false);
|
|
675
619
|
});
|
|
676
620
|
|
|
677
|
-
test("
|
|
678
|
-
checkResultOverride = { decision: "
|
|
679
|
-
const spy = setupAddRuleSpy();
|
|
680
|
-
getToolOverride = undefined;
|
|
621
|
+
test("side-effect tool is auto-allowed when forcePromptSideEffects is undefined", async () => {
|
|
622
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
681
623
|
|
|
682
|
-
const
|
|
683
|
-
"always_allow",
|
|
684
|
-
"sudo *",
|
|
685
|
-
"everywhere",
|
|
686
|
-
);
|
|
687
|
-
const executor = new ToolExecutor(prompter);
|
|
624
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
688
625
|
const result = await executor.execute(
|
|
689
|
-
"
|
|
690
|
-
{
|
|
691
|
-
makeContext(),
|
|
626
|
+
"file_edit",
|
|
627
|
+
{ path: "test.txt", old_string: "a", new_string: "b" },
|
|
628
|
+
makeContext(), // forcePromptSideEffects not set
|
|
692
629
|
);
|
|
693
630
|
|
|
694
631
|
expect(result.isError).toBe(false);
|
|
695
|
-
expect(
|
|
696
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
697
|
-
// Core tools have no executionTarget, so ruleOptions is empty → undefined
|
|
698
|
-
expect(options).toBeUndefined();
|
|
632
|
+
expect(promptCalled).toBe(false);
|
|
699
633
|
});
|
|
700
634
|
|
|
701
|
-
test("
|
|
702
|
-
checkResultOverride = { decision: "
|
|
703
|
-
const spy = setupAddRuleSpy();
|
|
704
|
-
|
|
705
|
-
getToolOverride = (name: string) => {
|
|
706
|
-
if (name === "unknown_tool") return undefined;
|
|
707
|
-
return {
|
|
708
|
-
name,
|
|
709
|
-
description: "host skill tool",
|
|
710
|
-
category: "skill",
|
|
711
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
712
|
-
origin: "skill" as const,
|
|
713
|
-
ownerSkillId: "host-skill",
|
|
714
|
-
ownerSkillVersionHash: "host-hash-v1",
|
|
715
|
-
executionTarget: "host" as const,
|
|
716
|
-
getDefinition: () => ({
|
|
717
|
-
name,
|
|
718
|
-
description: "host skill tool",
|
|
719
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
720
|
-
}),
|
|
721
|
-
execute: async () => fakeToolResult,
|
|
722
|
-
};
|
|
723
|
-
};
|
|
724
|
-
|
|
725
|
-
const prompter = makePrompterWithDecision(
|
|
726
|
-
"always_allow",
|
|
727
|
-
"host_action:*",
|
|
728
|
-
"/tmp/project",
|
|
729
|
-
);
|
|
730
|
-
const executor = new ToolExecutor(prompter);
|
|
731
|
-
const result = await executor.execute(
|
|
732
|
-
"host_action",
|
|
733
|
-
{ action: "click" },
|
|
734
|
-
makeContext(),
|
|
735
|
-
);
|
|
736
|
-
|
|
737
|
-
expect(result.isError).toBe(false);
|
|
738
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
739
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
740
|
-
expect(options).toBeDefined();
|
|
741
|
-
expect(options.executionTarget).toBe("host");
|
|
742
|
-
});
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
describe("ToolExecutor strict mode + high-risk integration (PR 25)", () => {
|
|
746
|
-
beforeEach(() => {
|
|
747
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
748
|
-
lastCheckArgs = undefined;
|
|
749
|
-
getToolOverride = undefined;
|
|
750
|
-
checkResultOverride = undefined;
|
|
751
|
-
checkFnOverride = undefined;
|
|
752
|
-
if (addRuleSpy) {
|
|
753
|
-
addRuleSpy.mockRestore();
|
|
754
|
-
addRuleSpy = undefined;
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
function setupAddRuleSpy() {
|
|
759
|
-
addRuleSpy = spyOn(trustStore, "addRule").mockImplementation(
|
|
760
|
-
(
|
|
761
|
-
tool: string,
|
|
762
|
-
pattern: string,
|
|
763
|
-
scope: string,
|
|
764
|
-
decision = "allow",
|
|
765
|
-
priority = 100,
|
|
766
|
-
options?: { executionTarget?: string },
|
|
767
|
-
) => {
|
|
768
|
-
return {
|
|
769
|
-
id: "spy-rule-id",
|
|
770
|
-
tool,
|
|
771
|
-
pattern,
|
|
772
|
-
scope,
|
|
773
|
-
decision,
|
|
774
|
-
priority,
|
|
775
|
-
createdAt: Date.now(),
|
|
776
|
-
...options,
|
|
777
|
-
} as TrustRule;
|
|
778
|
-
},
|
|
779
|
-
);
|
|
780
|
-
return addRuleSpy;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
test("always_allow creates rule with execution target for high-risk skill tool", async () => {
|
|
784
|
-
checkResultOverride = {
|
|
785
|
-
decision: "prompt",
|
|
786
|
-
reason: "High risk: always requires approval",
|
|
787
|
-
};
|
|
788
|
-
const spy = setupAddRuleSpy();
|
|
789
|
-
|
|
790
|
-
getToolOverride = (name: string) => {
|
|
791
|
-
if (name === "unknown_tool") return undefined;
|
|
792
|
-
return {
|
|
793
|
-
name,
|
|
794
|
-
description: "high-risk skill tool",
|
|
795
|
-
category: "skill",
|
|
796
|
-
defaultRiskLevel: RiskLevel.High,
|
|
797
|
-
origin: "skill" as const,
|
|
798
|
-
ownerSkillId: "deploy-skill",
|
|
799
|
-
ownerSkillVersionHash: "sha256-deploy-v1",
|
|
800
|
-
executionTarget: "host" as const,
|
|
801
|
-
getDefinition: () => ({
|
|
802
|
-
name,
|
|
803
|
-
description: "high-risk skill tool",
|
|
804
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
805
|
-
}),
|
|
806
|
-
execute: async () => fakeToolResult,
|
|
807
|
-
};
|
|
808
|
-
};
|
|
809
|
-
|
|
810
|
-
const prompter = makePrompterWithDecision(
|
|
811
|
-
"always_allow",
|
|
812
|
-
"deploy_tool:*",
|
|
813
|
-
"everywhere",
|
|
814
|
-
);
|
|
815
|
-
const executor = new ToolExecutor(prompter);
|
|
816
|
-
const result = await executor.execute(
|
|
817
|
-
"deploy_tool",
|
|
818
|
-
{ target: "prod" },
|
|
819
|
-
makeContext(),
|
|
820
|
-
);
|
|
821
|
-
|
|
822
|
-
expect(result.isError).toBe(false);
|
|
823
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
824
|
-
const [tool, pattern, scope, decision, _priority, options] =
|
|
825
|
-
spy.mock.calls[0];
|
|
826
|
-
expect(tool).toBe("deploy_tool");
|
|
827
|
-
expect(pattern).toBe("deploy_tool:*");
|
|
828
|
-
expect(scope).toBe("everywhere");
|
|
829
|
-
expect(decision).toBe("allow");
|
|
830
|
-
// The key integration assertion: execution target is captured
|
|
831
|
-
expect(options.executionTarget).toBeDefined();
|
|
832
|
-
expect(options.executionTarget).toBe("host");
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
test("always_allow creates rule with execution target for skill tool", async () => {
|
|
836
|
-
checkResultOverride = { decision: "prompt", reason: "test prompt" };
|
|
837
|
-
const spy = setupAddRuleSpy();
|
|
838
|
-
|
|
839
|
-
getToolOverride = (name: string) => {
|
|
840
|
-
if (name === "unknown_tool") return undefined;
|
|
841
|
-
return {
|
|
842
|
-
name,
|
|
843
|
-
description: "high-risk skill tool",
|
|
844
|
-
category: "skill",
|
|
845
|
-
defaultRiskLevel: RiskLevel.High,
|
|
846
|
-
origin: "skill" as const,
|
|
847
|
-
ownerSkillId: "risky-skill",
|
|
848
|
-
ownerSkillVersionHash: "sha256-risky",
|
|
849
|
-
executionTarget: "sandbox" as const,
|
|
850
|
-
getDefinition: () => ({
|
|
851
|
-
name,
|
|
852
|
-
description: "high-risk skill tool",
|
|
853
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
854
|
-
}),
|
|
855
|
-
execute: async () => fakeToolResult,
|
|
856
|
-
};
|
|
857
|
-
};
|
|
858
|
-
|
|
859
|
-
// User chooses always_allow — rule is created with execution target.
|
|
860
|
-
const prompter = makePrompterWithDecision(
|
|
861
|
-
"always_allow",
|
|
862
|
-
"risky_op:*",
|
|
863
|
-
"/tmp/project",
|
|
864
|
-
);
|
|
865
|
-
const executor = new ToolExecutor(prompter);
|
|
866
|
-
const result = await executor.execute("risky_op", {}, makeContext());
|
|
867
|
-
|
|
868
|
-
expect(result.isError).toBe(false);
|
|
869
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
870
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
871
|
-
expect(options).toBeDefined();
|
|
872
|
-
// executionTarget should be present
|
|
873
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
874
|
-
// Rule should have execution target
|
|
875
|
-
// allowHighRisk is no longer persisted
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
test("executor forwards policyContext to check() for version-bound skill tool", async () => {
|
|
879
|
-
getToolOverride = (name: string) => {
|
|
880
|
-
if (name === "unknown_tool") return undefined;
|
|
881
|
-
return {
|
|
882
|
-
name,
|
|
883
|
-
description: "versioned skill tool",
|
|
884
|
-
category: "skill",
|
|
885
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
886
|
-
origin: "skill" as const,
|
|
887
|
-
ownerSkillId: "versioned-skill",
|
|
888
|
-
ownerSkillVersionHash: "v3:content-hash-xyz",
|
|
889
|
-
executionTarget: "sandbox" as const,
|
|
890
|
-
getDefinition: () => ({
|
|
891
|
-
name,
|
|
892
|
-
description: "versioned skill tool",
|
|
893
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
894
|
-
}),
|
|
895
|
-
execute: async () => fakeToolResult,
|
|
896
|
-
};
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
const executor = new ToolExecutor(makePrompter());
|
|
900
|
-
await executor.execute("versioned_tool", { action: "test" }, makeContext());
|
|
901
|
-
|
|
902
|
-
expect(lastCheckArgs).toBeDefined();
|
|
903
|
-
expect(lastCheckArgs!.policyContext).toEqual({
|
|
904
|
-
executionContext: "conversation",
|
|
905
|
-
ephemeralRules: undefined,
|
|
906
|
-
executionTarget: "sandbox",
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
// ── Skill mutation approval regression tests (PR 30) ──────────
|
|
911
|
-
|
|
912
|
-
test("always_allow for skill source write creates rule with execution target", async () => {
|
|
913
|
-
checkResultOverride = {
|
|
914
|
-
decision: "prompt",
|
|
915
|
-
reason: "High risk: always requires approval",
|
|
916
|
-
};
|
|
917
|
-
const spy = setupAddRuleSpy();
|
|
918
|
-
|
|
919
|
-
getToolOverride = (name: string) => {
|
|
920
|
-
if (name === "unknown_tool") return undefined;
|
|
921
|
-
return {
|
|
922
|
-
name,
|
|
923
|
-
description: "skill tool that writes to skill source",
|
|
924
|
-
category: "skill",
|
|
925
|
-
defaultRiskLevel: RiskLevel.High,
|
|
926
|
-
origin: "skill" as const,
|
|
927
|
-
ownerSkillId: "code-editor-skill",
|
|
928
|
-
ownerSkillVersionHash: "sha256-v1-original",
|
|
929
|
-
executionTarget: "sandbox" as const,
|
|
930
|
-
getDefinition: () => ({
|
|
931
|
-
name,
|
|
932
|
-
description: "skill source writer",
|
|
933
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
934
|
-
}),
|
|
935
|
-
execute: async () => fakeToolResult,
|
|
936
|
-
};
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
const prompter = makePrompterWithDecision(
|
|
940
|
-
"always_allow",
|
|
941
|
-
"file_write:*/skills/**",
|
|
942
|
-
"everywhere",
|
|
943
|
-
);
|
|
944
|
-
const executor = new ToolExecutor(prompter);
|
|
945
|
-
const result = await executor.execute(
|
|
946
|
-
"file_write",
|
|
947
|
-
{ path: "/tmp/skills/my-skill/executor.ts" },
|
|
948
|
-
makeContext(),
|
|
949
|
-
);
|
|
950
|
-
|
|
951
|
-
expect(result.isError).toBe(false);
|
|
952
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
953
|
-
const [tool, pattern, scope, decision, , options] = spy.mock.calls[0];
|
|
954
|
-
expect(tool).toBe("file_write");
|
|
955
|
-
expect(pattern).toBe("file_write:*/skills/**");
|
|
956
|
-
expect(scope).toBe("everywhere");
|
|
957
|
-
expect(decision).toBe("allow");
|
|
958
|
-
expect(options.executionTarget).toBeDefined();
|
|
959
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
test("always_allow for skill source write creates rule with execution target (baseline)", async () => {
|
|
963
|
-
checkResultOverride = {
|
|
964
|
-
decision: "prompt",
|
|
965
|
-
reason: "High risk: always requires approval",
|
|
966
|
-
};
|
|
967
|
-
const spy = setupAddRuleSpy();
|
|
968
|
-
|
|
969
|
-
getToolOverride = (name: string) => {
|
|
970
|
-
if (name === "unknown_tool") return undefined;
|
|
971
|
-
return {
|
|
972
|
-
name,
|
|
973
|
-
description: "skill tool that writes to skill source",
|
|
974
|
-
category: "skill",
|
|
975
|
-
defaultRiskLevel: RiskLevel.High,
|
|
976
|
-
origin: "skill" as const,
|
|
977
|
-
ownerSkillId: "editor-skill",
|
|
978
|
-
ownerSkillVersionHash: "sha256-editor-v1",
|
|
979
|
-
executionTarget: "sandbox" as const,
|
|
980
|
-
getDefinition: () => ({
|
|
981
|
-
name,
|
|
982
|
-
description: "skill source writer",
|
|
983
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
984
|
-
}),
|
|
985
|
-
execute: async () => fakeToolResult,
|
|
986
|
-
};
|
|
987
|
-
};
|
|
988
|
-
|
|
989
|
-
// User chooses always_allow
|
|
990
|
-
const prompter = makePrompterWithDecision(
|
|
991
|
-
"always_allow",
|
|
992
|
-
"file_write:*/skills/**",
|
|
993
|
-
"/tmp/project",
|
|
994
|
-
);
|
|
995
|
-
const executor = new ToolExecutor(prompter);
|
|
996
|
-
const result = await executor.execute(
|
|
997
|
-
"file_write",
|
|
998
|
-
{ path: "/tmp/skills/my-skill/executor.ts" },
|
|
999
|
-
makeContext(),
|
|
1000
|
-
);
|
|
1001
|
-
|
|
1002
|
-
expect(result.isError).toBe(false);
|
|
1003
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1004
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
1005
|
-
expect(options).toBeDefined();
|
|
1006
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
1007
|
-
// Execution target is captured from the tool context
|
|
1008
|
-
// allowHighRisk is no longer persisted
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
test("skill version is captured in rule for future version-bound matching", async () => {
|
|
1012
|
-
checkResultOverride = {
|
|
1013
|
-
decision: "prompt",
|
|
1014
|
-
reason: "High risk: always requires approval",
|
|
1015
|
-
};
|
|
1016
|
-
const spy = setupAddRuleSpy();
|
|
1017
|
-
|
|
1018
|
-
getToolOverride = (name: string) => {
|
|
1019
|
-
if (name === "unknown_tool") return undefined;
|
|
1020
|
-
return {
|
|
1021
|
-
name,
|
|
1022
|
-
description: "versioned skill tool",
|
|
1023
|
-
category: "skill",
|
|
1024
|
-
defaultRiskLevel: RiskLevel.High,
|
|
1025
|
-
origin: "skill" as const,
|
|
1026
|
-
ownerSkillId: "versioned-editor",
|
|
1027
|
-
ownerSkillVersionHash: "v3:content-hash-xyz789",
|
|
1028
|
-
executionTarget: "sandbox" as const,
|
|
1029
|
-
getDefinition: () => ({
|
|
1030
|
-
name,
|
|
1031
|
-
description: "versioned skill editor",
|
|
1032
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1033
|
-
}),
|
|
1034
|
-
execute: async () => fakeToolResult,
|
|
1035
|
-
};
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
const prompter = makePrompterWithDecision(
|
|
1039
|
-
"always_allow",
|
|
1040
|
-
"file_edit:*/skills/**",
|
|
1041
|
-
"everywhere",
|
|
1042
|
-
);
|
|
1043
|
-
const executor = new ToolExecutor(prompter);
|
|
1044
|
-
const result = await executor.execute(
|
|
1045
|
-
"file_edit",
|
|
1046
|
-
{ path: "/tmp/skills/my-skill/SKILL.md" },
|
|
1047
|
-
makeContext(),
|
|
1048
|
-
);
|
|
1049
|
-
|
|
1050
|
-
expect(result.isError).toBe(false);
|
|
1051
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1052
|
-
const [tool, , , , , options] = spy.mock.calls[0];
|
|
1053
|
-
expect(tool).toBe("file_edit");
|
|
1054
|
-
expect(options.executionTarget).toBeDefined();
|
|
1055
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
test("executor forwards policyContext with version for skill source mutation", async () => {
|
|
1059
|
-
getToolOverride = (name: string) => {
|
|
1060
|
-
if (name === "unknown_tool") return undefined;
|
|
1061
|
-
return {
|
|
1062
|
-
name,
|
|
1063
|
-
description: "skill source editor",
|
|
1064
|
-
category: "skill",
|
|
1065
|
-
defaultRiskLevel: RiskLevel.High,
|
|
1066
|
-
origin: "skill" as const,
|
|
1067
|
-
ownerSkillId: "editor-skill",
|
|
1068
|
-
ownerSkillVersionHash: "sha256-v2-updated",
|
|
1069
|
-
executionTarget: "sandbox" as const,
|
|
1070
|
-
getDefinition: () => ({
|
|
1071
|
-
name,
|
|
1072
|
-
description: "skill editor",
|
|
1073
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1074
|
-
}),
|
|
1075
|
-
execute: async () => fakeToolResult,
|
|
1076
|
-
};
|
|
1077
|
-
};
|
|
1078
|
-
|
|
1079
|
-
const executor = new ToolExecutor(makePrompter());
|
|
1080
|
-
await executor.execute(
|
|
1081
|
-
"file_write",
|
|
1082
|
-
{ path: "/tmp/skills/my-skill/index.ts" },
|
|
1083
|
-
makeContext(),
|
|
1084
|
-
);
|
|
1085
|
-
|
|
1086
|
-
expect(lastCheckArgs).toBeDefined();
|
|
1087
|
-
expect(lastCheckArgs!.policyContext).toEqual({
|
|
1088
|
-
executionContext: "conversation",
|
|
1089
|
-
ephemeralRules: undefined,
|
|
1090
|
-
executionTarget: "sandbox",
|
|
1091
|
-
});
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
test("executor creates rule on always_allow with full context", async () => {
|
|
1095
|
-
checkResultOverride = {
|
|
1096
|
-
decision: "prompt",
|
|
1097
|
-
reason: "High risk: always requires approval",
|
|
1098
|
-
};
|
|
1099
|
-
const spy = setupAddRuleSpy();
|
|
1100
|
-
|
|
1101
|
-
getToolOverride = (name: string) => {
|
|
1102
|
-
if (name === "unknown_tool") return undefined;
|
|
1103
|
-
return {
|
|
1104
|
-
name,
|
|
1105
|
-
description: "admin skill tool",
|
|
1106
|
-
category: "skill",
|
|
1107
|
-
defaultRiskLevel: RiskLevel.High,
|
|
1108
|
-
origin: "skill" as const,
|
|
1109
|
-
ownerSkillId: "admin-skill",
|
|
1110
|
-
ownerSkillVersionHash: "sha256-admin-v2",
|
|
1111
|
-
executionTarget: "host" as const,
|
|
1112
|
-
getDefinition: () => ({
|
|
1113
|
-
name,
|
|
1114
|
-
description: "admin skill tool",
|
|
1115
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1116
|
-
}),
|
|
1117
|
-
execute: async () => fakeToolResult,
|
|
1118
|
-
};
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
const prompter = makePrompterWithDecision(
|
|
1122
|
-
"always_allow",
|
|
1123
|
-
"admin_action:*",
|
|
1124
|
-
"everywhere",
|
|
1125
|
-
);
|
|
1126
|
-
const executor = new ToolExecutor(prompter);
|
|
1127
|
-
const result = await executor.execute(
|
|
1128
|
-
"admin_action",
|
|
1129
|
-
{ op: "restart" },
|
|
1130
|
-
makeContext(),
|
|
1131
|
-
);
|
|
1132
|
-
|
|
1133
|
-
expect(result.isError).toBe(false);
|
|
1134
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1135
|
-
const [tool, pattern, scope, decision, , options] = spy.mock.calls[0];
|
|
1136
|
-
|
|
1137
|
-
// Verify complete integration of all fields
|
|
1138
|
-
expect(tool).toBe("admin_action");
|
|
1139
|
-
expect(pattern).toBe("admin_action:*");
|
|
1140
|
-
expect(scope).toBe("everywhere");
|
|
1141
|
-
expect(decision).toBe("allow");
|
|
1142
|
-
expect(options.executionTarget).toBeDefined();
|
|
1143
|
-
expect(options.executionTarget).toBe("host");
|
|
1144
|
-
});
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
|
-
// ---------------------------------------------------------------------------
|
|
1148
|
-
// isSideEffectTool classifier
|
|
1149
|
-
// ---------------------------------------------------------------------------
|
|
1150
|
-
|
|
1151
|
-
describe("isSideEffectTool", () => {
|
|
1152
|
-
describe("returns true for side-effect tools", () => {
|
|
1153
|
-
const sideEffectTools = [
|
|
1154
|
-
"file_write",
|
|
1155
|
-
"file_edit",
|
|
1156
|
-
"host_file_write",
|
|
1157
|
-
"host_file_edit",
|
|
1158
|
-
"bash",
|
|
1159
|
-
"host_bash",
|
|
1160
|
-
"web_fetch",
|
|
1161
|
-
"document_create",
|
|
1162
|
-
"document_update",
|
|
1163
|
-
"schedule_create",
|
|
1164
|
-
"schedule_update",
|
|
1165
|
-
"schedule_delete",
|
|
1166
|
-
];
|
|
1167
|
-
|
|
1168
|
-
for (const toolName of sideEffectTools) {
|
|
1169
|
-
test(toolName, () => {
|
|
1170
|
-
expect(isSideEffectTool(toolName)).toBe(true);
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
|
|
1175
|
-
describe("returns false for non-side-effect tools", () => {
|
|
1176
|
-
const readOnlyTools = [
|
|
1177
|
-
"file_read",
|
|
1178
|
-
"memory_recall",
|
|
1179
|
-
"memory_manage",
|
|
1180
|
-
"web_search",
|
|
1181
|
-
"browser_navigate",
|
|
1182
|
-
"browser_click",
|
|
1183
|
-
"browser_type",
|
|
1184
|
-
"browser_press_key",
|
|
1185
|
-
"browser_close",
|
|
1186
|
-
"browser_attach",
|
|
1187
|
-
"browser_detach",
|
|
1188
|
-
"browser_fill_credential",
|
|
1189
|
-
"browser_snapshot",
|
|
1190
|
-
"browser_screenshot",
|
|
1191
|
-
"browser_wait_for",
|
|
1192
|
-
"browser_extract",
|
|
1193
|
-
"skill_load",
|
|
1194
|
-
"schedule_list",
|
|
1195
|
-
];
|
|
1196
|
-
|
|
1197
|
-
for (const toolName of readOnlyTools) {
|
|
1198
|
-
test(toolName, () => {
|
|
1199
|
-
expect(isSideEffectTool(toolName)).toBe(false);
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
test("returns false for unknown tool names", () => {
|
|
1205
|
-
expect(isSideEffectTool("nonexistent_tool")).toBe(false);
|
|
1206
|
-
expect(isSideEffectTool("")).toBe(false);
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
describe("action-aware classification for mixed-action tools", () => {
|
|
1210
|
-
test("credential_store store is a side-effect", () => {
|
|
1211
|
-
expect(isSideEffectTool("credential_store", { action: "store" })).toBe(
|
|
1212
|
-
true,
|
|
1213
|
-
);
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
test("credential_store delete is a side-effect", () => {
|
|
1217
|
-
expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
|
|
1218
|
-
true,
|
|
1219
|
-
);
|
|
1220
|
-
});
|
|
1221
|
-
|
|
1222
|
-
test("credential_store prompt is a side-effect", () => {
|
|
1223
|
-
expect(isSideEffectTool("credential_store", { action: "prompt" })).toBe(
|
|
1224
|
-
true,
|
|
1225
|
-
);
|
|
1226
|
-
});
|
|
1227
|
-
|
|
1228
|
-
test("credential_store list is NOT a side-effect", () => {
|
|
1229
|
-
expect(isSideEffectTool("credential_store", { action: "list" })).toBe(
|
|
1230
|
-
false,
|
|
1231
|
-
);
|
|
1232
|
-
});
|
|
1233
|
-
|
|
1234
|
-
test("credential_store without input is NOT a side-effect", () => {
|
|
1235
|
-
expect(isSideEffectTool("credential_store")).toBe(false);
|
|
1236
|
-
});
|
|
1237
|
-
});
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
// Baseline: allow rules can auto-allow file_edit for the guardian persona
|
|
1241
|
-
// today (no forced prompting). The mock check() delegates to
|
|
1242
|
-
// findHighestPriorityRule (via spy) so a regression in trust-rule matching
|
|
1243
|
-
// would cause this test to fail instead of being masked by a blanket
|
|
1244
|
-
// mock-allow.
|
|
1245
|
-
describe("ToolExecutor baseline: allow rule auto-allows file_edit guardian persona", () => {
|
|
1246
|
-
const guardianPersonaPath = "/Users/alice/.vellum/workspace/users/alice.md";
|
|
1247
|
-
let ruleSpy: ReturnType<typeof spyOn> | undefined;
|
|
1248
|
-
|
|
1249
|
-
beforeEach(() => {
|
|
1250
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
1251
|
-
lastCheckArgs = undefined;
|
|
1252
|
-
getToolOverride = undefined;
|
|
1253
|
-
checkResultOverride = undefined;
|
|
1254
|
-
checkFnOverride = undefined;
|
|
1255
|
-
if (addRuleSpy) {
|
|
1256
|
-
addRuleSpy.mockRestore();
|
|
1257
|
-
addRuleSpy = undefined;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// Simulate a trust rule that allows file_edit on the guardian's per-user
|
|
1261
|
-
// persona file by stubbing findHighestPriorityRule. This mirrors the
|
|
1262
|
-
// default allow rules that the trust-store creates for the guardian
|
|
1263
|
-
// persona file (see permissions/defaults.ts).
|
|
1264
|
-
ruleSpy = spyOn(trustStore, "findHighestPriorityRule").mockImplementation(
|
|
1265
|
-
(tool: string, commands: string[], _scope: string) => {
|
|
1266
|
-
if (tool !== "file_edit") return null;
|
|
1267
|
-
for (const cmd of commands) {
|
|
1268
|
-
if (cmd === `file_edit:${guardianPersonaPath}`) {
|
|
1269
|
-
return {
|
|
1270
|
-
id: "default:allow-file_edit-guardian-persona",
|
|
1271
|
-
tool: "file_edit",
|
|
1272
|
-
pattern: `file_edit:${guardianPersonaPath}`,
|
|
1273
|
-
scope: "everywhere",
|
|
1274
|
-
decision: "allow" as const,
|
|
1275
|
-
priority: 100,
|
|
1276
|
-
createdAt: Date.now(),
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
return null;
|
|
1281
|
-
},
|
|
1282
|
-
);
|
|
1283
|
-
|
|
1284
|
-
// Wire the mock check() to delegate to findHighestPriorityRule, replicating
|
|
1285
|
-
// the real check() logic for Medium-risk tools (file_edit).
|
|
1286
|
-
checkFnOverride = async (toolName, input, workingDir) => {
|
|
1287
|
-
const filePath =
|
|
1288
|
-
(input.path as string) ?? (input.file_path as string) ?? "";
|
|
1289
|
-
const resolved = filePath.startsWith("/")
|
|
1290
|
-
? filePath
|
|
1291
|
-
: `${workingDir}/${filePath}`;
|
|
1292
|
-
const candidates = [`${toolName}:${resolved}`];
|
|
1293
|
-
const matched = trustStore.findHighestPriorityRule(
|
|
1294
|
-
toolName,
|
|
1295
|
-
candidates,
|
|
1296
|
-
workingDir,
|
|
1297
|
-
);
|
|
1298
|
-
if (matched && matched.decision === "allow") {
|
|
1299
|
-
return {
|
|
1300
|
-
decision: "allow",
|
|
1301
|
-
reason: `Matched trust rule: ${matched.pattern}`,
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
return { decision: "prompt", reason: "Medium risk: requires approval" };
|
|
1305
|
-
};
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
afterEach(() => {
|
|
1309
|
-
checkFnOverride = undefined;
|
|
1310
|
-
if (ruleSpy) {
|
|
1311
|
-
ruleSpy.mockRestore();
|
|
1312
|
-
ruleSpy = undefined;
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
|
|
1316
|
-
test("file_edit to guardian persona is auto-allowed via trust rule", async () => {
|
|
1317
|
-
const executor = new ToolExecutor(makePrompter());
|
|
1318
|
-
const result = await executor.execute(
|
|
1319
|
-
"file_edit",
|
|
1320
|
-
{ path: guardianPersonaPath, content: "hello" },
|
|
1321
|
-
makeContext(),
|
|
1322
|
-
);
|
|
1323
|
-
expect(result.isError).toBe(false);
|
|
1324
|
-
expect(result.content).toBe("ok");
|
|
1325
|
-
// Confirm checker was called with the correct tool name
|
|
1326
|
-
expect(lastCheckArgs).toBeDefined();
|
|
1327
|
-
expect(lastCheckArgs!.toolName).toBe("file_edit");
|
|
1328
|
-
// Confirm findHighestPriorityRule was consulted
|
|
1329
|
-
expect(ruleSpy).toHaveBeenCalled();
|
|
1330
|
-
});
|
|
1331
|
-
|
|
1332
|
-
test("file_edit to a non-guardian-persona path is NOT auto-allowed without a matching rule", async () => {
|
|
1333
|
-
let promptCalled = false;
|
|
1334
|
-
const trackingPrompter = {
|
|
1335
|
-
prompt: async () => {
|
|
1336
|
-
promptCalled = true;
|
|
1337
|
-
return { decision: "allow" as const };
|
|
1338
|
-
},
|
|
1339
|
-
resolveConfirmation: () => {},
|
|
1340
|
-
updateSender: () => {},
|
|
1341
|
-
dispose: () => {},
|
|
1342
|
-
} as unknown as PermissionPrompter;
|
|
1343
|
-
|
|
1344
|
-
const executor = new ToolExecutor(trackingPrompter);
|
|
1345
|
-
const result = await executor.execute(
|
|
1346
|
-
"file_edit",
|
|
1347
|
-
{ path: "/tmp/project/other.md", content: "hello" },
|
|
1348
|
-
makeContext(),
|
|
1349
|
-
);
|
|
1350
|
-
// check() returned 'prompt' (no matching trust rule for other.md),
|
|
1351
|
-
// so the executor must have called the prompter.
|
|
1352
|
-
expect(promptCalled).toBe(true);
|
|
1353
|
-
expect(result.isError).toBe(false);
|
|
1354
|
-
expect(lastCheckArgs).toBeDefined();
|
|
1355
|
-
expect(lastCheckArgs!.toolName).toBe("file_edit");
|
|
1356
|
-
});
|
|
1357
|
-
});
|
|
1358
|
-
|
|
1359
|
-
// ---------------------------------------------------------------------------
|
|
1360
|
-
// forcePromptSideEffects enforcement (PR 30)
|
|
1361
|
-
// ---------------------------------------------------------------------------
|
|
1362
|
-
|
|
1363
|
-
describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
1364
|
-
let promptCalled: boolean;
|
|
1365
|
-
|
|
1366
|
-
beforeEach(() => {
|
|
1367
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
1368
|
-
lastCheckArgs = undefined;
|
|
1369
|
-
getToolOverride = undefined;
|
|
1370
|
-
checkResultOverride = undefined;
|
|
1371
|
-
checkFnOverride = undefined;
|
|
1372
|
-
promptCalled = false;
|
|
1373
|
-
if (addRuleSpy) {
|
|
1374
|
-
addRuleSpy.mockRestore();
|
|
1375
|
-
addRuleSpy = undefined;
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
|
|
1379
|
-
/**
|
|
1380
|
-
* Prompter that tracks whether it was called and always allows.
|
|
1381
|
-
*/
|
|
1382
|
-
function makeTrackingPrompter(): PermissionPrompter {
|
|
1383
|
-
return {
|
|
1384
|
-
prompt: async () => {
|
|
1385
|
-
promptCalled = true;
|
|
1386
|
-
return { decision: "allow" as const };
|
|
1387
|
-
},
|
|
1388
|
-
resolveConfirmation: () => {},
|
|
1389
|
-
updateSender: () => {},
|
|
1390
|
-
dispose: () => {},
|
|
1391
|
-
} as unknown as PermissionPrompter;
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
test("side-effect tool with allow rule is forced to prompt when forcePromptSideEffects is true", async () => {
|
|
1395
|
-
// check() returns allow (simulating a matched trust rule)
|
|
1396
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1397
|
-
|
|
1398
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1399
|
-
const result = await executor.execute(
|
|
1400
|
-
"bash",
|
|
1401
|
-
{ command: "echo hello" },
|
|
1402
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1403
|
-
);
|
|
1404
|
-
|
|
1405
|
-
expect(result.isError).toBe(false);
|
|
1406
|
-
// The prompter must have been called despite the allow rule
|
|
1407
|
-
expect(promptCalled).toBe(true);
|
|
1408
|
-
});
|
|
1409
|
-
|
|
1410
|
-
test("deny decision is preserved (not converted to prompt) even with forcePromptSideEffects", async () => {
|
|
1411
|
-
checkResultOverride = {
|
|
1412
|
-
decision: "deny",
|
|
1413
|
-
reason: "Policy denies this tool",
|
|
1414
|
-
};
|
|
1415
|
-
|
|
1416
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1417
|
-
const result = await executor.execute(
|
|
1418
|
-
"bash",
|
|
1419
|
-
{ command: "rm -rf /" },
|
|
1420
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1421
|
-
);
|
|
1422
|
-
|
|
1423
|
-
// Should be denied, not prompted
|
|
1424
|
-
expect(result.isError).toBe(true);
|
|
1425
|
-
expect(result.content).toBe("Policy denies this tool");
|
|
1426
|
-
expect(promptCalled).toBe(false);
|
|
1427
|
-
});
|
|
1428
|
-
|
|
1429
|
-
test("non-side-effect tool is unchanged even with forcePromptSideEffects", async () => {
|
|
1430
|
-
// check() returns allow for a read-only tool
|
|
1431
|
-
checkResultOverride = { decision: "allow", reason: "Allowed by default" };
|
|
1432
|
-
|
|
1433
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1434
|
-
const result = await executor.execute(
|
|
1435
|
-
"file_read",
|
|
1436
|
-
{ path: "README.md" },
|
|
1437
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1438
|
-
);
|
|
1439
|
-
|
|
1440
|
-
expect(result.isError).toBe(false);
|
|
1441
|
-
// Prompter should NOT be called — file_read is not a side-effect tool
|
|
1442
|
-
expect(promptCalled).toBe(false);
|
|
1443
|
-
});
|
|
1444
|
-
|
|
1445
|
-
test("side-effect tool is auto-allowed when forcePromptSideEffects is false", async () => {
|
|
1446
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1447
|
-
|
|
1448
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1449
|
-
const result = await executor.execute(
|
|
1450
|
-
"file_write",
|
|
1451
|
-
{ path: "test.txt", content: "data" },
|
|
1452
|
-
makeContext({ forcePromptSideEffects: false }),
|
|
1453
|
-
);
|
|
1454
|
-
|
|
1455
|
-
expect(result.isError).toBe(false);
|
|
1456
|
-
// No prompt — standard behavior when forcePromptSideEffects is off
|
|
1457
|
-
expect(promptCalled).toBe(false);
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
test("side-effect tool is auto-allowed when forcePromptSideEffects is undefined", async () => {
|
|
1461
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1462
|
-
|
|
1463
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1464
|
-
const result = await executor.execute(
|
|
1465
|
-
"file_edit",
|
|
1466
|
-
{ path: "test.txt", old_string: "a", new_string: "b" },
|
|
1467
|
-
makeContext(), // forcePromptSideEffects not set
|
|
1468
|
-
);
|
|
1469
|
-
|
|
1470
|
-
expect(result.isError).toBe(false);
|
|
1471
|
-
expect(promptCalled).toBe(false);
|
|
1472
|
-
});
|
|
1473
|
-
|
|
1474
|
-
test("all side-effect tool types are forced to prompt", async () => {
|
|
1475
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
635
|
+
test("all side-effect tool types are forced to prompt", async () => {
|
|
636
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1476
637
|
|
|
1477
638
|
const sideEffectTools = [
|
|
1478
639
|
{ name: "file_write", input: { path: "x", content: "y" } },
|
|
@@ -1488,15 +649,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1488
649
|
{ name: "bash", input: { command: "echo hi" } },
|
|
1489
650
|
{ name: "host_bash", input: { command: "echo hi" } },
|
|
1490
651
|
{ name: "web_fetch", input: { url: "https://example.com" } },
|
|
1491
|
-
{ name: "browser_navigate", input: { url: "https://example.com" } },
|
|
1492
|
-
{ name: "browser_click", input: { selector: "#btn" } },
|
|
1493
|
-
{ name: "browser_type", input: { selector: "#input", text: "hello" } },
|
|
1494
|
-
{ name: "browser_press_key", input: { key: "Enter" } },
|
|
1495
|
-
{ name: "browser_close", input: {} },
|
|
1496
|
-
{
|
|
1497
|
-
name: "browser_fill_credential",
|
|
1498
|
-
input: { selector: "#pwd", credential: "test" },
|
|
1499
|
-
},
|
|
1500
652
|
{ name: "document_create", input: { title: "doc", content: "body" } },
|
|
1501
653
|
{ name: "document_update", input: { id: "doc-1", content: "updated" } },
|
|
1502
654
|
{
|
|
@@ -1549,113 +701,9 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1549
701
|
expect(promptCount).toBe(1);
|
|
1550
702
|
});
|
|
1551
703
|
|
|
1552
|
-
// ──
|
|
1553
|
-
|
|
1554
|
-
test("file_edit to guardian persona forces prompt in private conversation even with matching trust rule", async () => {
|
|
1555
|
-
// This is a key security invariant: the guardian persona file contains
|
|
1556
|
-
// the user's persistent memory. In a private conversation
|
|
1557
|
-
// (forcePromptSideEffects=true), edits to it must always require explicit
|
|
1558
|
-
// approval, even when a trust rule matches.
|
|
1559
|
-
checkResultOverride = {
|
|
1560
|
-
decision: "allow",
|
|
1561
|
-
reason: "Matched trust rule: file_edit:*/users/*.md",
|
|
1562
|
-
};
|
|
1563
|
-
|
|
1564
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1565
|
-
const result = await executor.execute(
|
|
1566
|
-
"file_edit",
|
|
1567
|
-
{
|
|
1568
|
-
path: "/Users/alice/.vellum/workspace/users/alice.md",
|
|
1569
|
-
old_string: "old pref",
|
|
1570
|
-
new_string: "new pref",
|
|
1571
|
-
},
|
|
1572
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1573
|
-
);
|
|
1574
|
-
|
|
1575
|
-
expect(result.isError).toBe(false);
|
|
1576
|
-
// file_edit is a side-effect tool, so forcePromptSideEffects must trigger prompting
|
|
1577
|
-
expect(promptCalled).toBe(true);
|
|
1578
|
-
});
|
|
1579
|
-
|
|
1580
|
-
test("host_file_edit to guardian persona forces prompt in private conversation", async () => {
|
|
1581
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1582
|
-
|
|
1583
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1584
|
-
const result = await executor.execute(
|
|
1585
|
-
"host_file_edit",
|
|
1586
|
-
{
|
|
1587
|
-
path: "/Users/alice/.vellum/workspace/users/alice.md",
|
|
1588
|
-
old_string: "x",
|
|
1589
|
-
new_string: "y",
|
|
1590
|
-
},
|
|
1591
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1592
|
-
);
|
|
1593
|
-
|
|
1594
|
-
expect(result.isError).toBe(false);
|
|
1595
|
-
expect(promptCalled).toBe(true);
|
|
1596
|
-
});
|
|
1597
|
-
|
|
1598
|
-
// ── Always-mutating document tools (PR fix5) ──────────
|
|
704
|
+
// ── Always-mutating schedule tools ──────────
|
|
1599
705
|
|
|
1600
|
-
test("
|
|
1601
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1602
|
-
|
|
1603
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1604
|
-
const result = await executor.execute(
|
|
1605
|
-
"document_create",
|
|
1606
|
-
{ title: "New Doc", content: "hello" },
|
|
1607
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1608
|
-
);
|
|
1609
|
-
|
|
1610
|
-
expect(result.isError).toBe(false);
|
|
1611
|
-
expect(promptCalled).toBe(true);
|
|
1612
|
-
});
|
|
1613
|
-
|
|
1614
|
-
test("document_update forces prompt in private conversation", async () => {
|
|
1615
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1616
|
-
|
|
1617
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1618
|
-
const result = await executor.execute(
|
|
1619
|
-
"document_update",
|
|
1620
|
-
{ id: "doc-1", content: "updated" },
|
|
1621
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1622
|
-
);
|
|
1623
|
-
|
|
1624
|
-
expect(result.isError).toBe(false);
|
|
1625
|
-
expect(promptCalled).toBe(true);
|
|
1626
|
-
});
|
|
1627
|
-
|
|
1628
|
-
// ── Always-mutating schedule tools (PR fix7) ──────────
|
|
1629
|
-
|
|
1630
|
-
test("schedule_create forces prompt in private conversation", async () => {
|
|
1631
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1632
|
-
|
|
1633
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1634
|
-
const result = await executor.execute(
|
|
1635
|
-
"schedule_create",
|
|
1636
|
-
{ name: "Morning standup", cron: "0 9 * * 1-5" },
|
|
1637
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1638
|
-
);
|
|
1639
|
-
|
|
1640
|
-
expect(result.isError).toBe(false);
|
|
1641
|
-
expect(promptCalled).toBe(true);
|
|
1642
|
-
});
|
|
1643
|
-
|
|
1644
|
-
test("schedule_update forces prompt in private conversation", async () => {
|
|
1645
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1646
|
-
|
|
1647
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1648
|
-
const result = await executor.execute(
|
|
1649
|
-
"schedule_update",
|
|
1650
|
-
{ id: "sched-1", cron: "0 10 * * 1-5" },
|
|
1651
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1652
|
-
);
|
|
1653
|
-
|
|
1654
|
-
expect(result.isError).toBe(false);
|
|
1655
|
-
expect(promptCalled).toBe(true);
|
|
1656
|
-
});
|
|
1657
|
-
|
|
1658
|
-
test("schedule_delete forces prompt in private conversation", async () => {
|
|
706
|
+
test("schedule_delete forces prompt under forcePromptSideEffects", async () => {
|
|
1659
707
|
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1660
708
|
|
|
1661
709
|
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
@@ -1671,373 +719,102 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1671
719
|
|
|
1672
720
|
// ── Credential store action-aware (PR fix9) ──────────
|
|
1673
721
|
|
|
1674
|
-
test("credential_store store forces prompt
|
|
1675
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1676
|
-
|
|
1677
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1678
|
-
const result = await executor.execute(
|
|
1679
|
-
"credential_store",
|
|
1680
|
-
{ action: "store", name: "api-key", value: "sk-secret-123" },
|
|
1681
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1682
|
-
);
|
|
1683
|
-
|
|
1684
|
-
expect(result.isError).toBe(false);
|
|
1685
|
-
expect(promptCalled).toBe(true);
|
|
1686
|
-
});
|
|
1687
|
-
|
|
1688
|
-
test("credential_store delete forces prompt in private conversation", async () => {
|
|
1689
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1690
|
-
|
|
1691
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1692
|
-
const result = await executor.execute(
|
|
1693
|
-
"credential_store",
|
|
1694
|
-
{ action: "delete", name: "api-key" },
|
|
1695
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1696
|
-
);
|
|
1697
|
-
|
|
1698
|
-
expect(result.isError).toBe(false);
|
|
1699
|
-
expect(promptCalled).toBe(true);
|
|
1700
|
-
});
|
|
1701
|
-
|
|
1702
|
-
test("credential_store list does NOT force prompt in private conversation", async () => {
|
|
1703
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1704
|
-
|
|
1705
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1706
|
-
const result = await executor.execute(
|
|
1707
|
-
"credential_store",
|
|
1708
|
-
{ action: "list" },
|
|
1709
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1710
|
-
);
|
|
1711
|
-
|
|
1712
|
-
expect(result.isError).toBe(false);
|
|
1713
|
-
// list is read-only — must NOT trigger forced prompting
|
|
1714
|
-
expect(promptCalled).toBe(false);
|
|
1715
|
-
});
|
|
1716
|
-
|
|
1717
|
-
// ── Workspace mode + forcePromptSideEffects interaction ──────────
|
|
1718
|
-
|
|
1719
|
-
test("workspace mode allow → prompt promotion still works for side-effect tools in private conversations", async () => {
|
|
1720
|
-
// Simulate workspace mode returning 'allow' for a workspace-scoped file_write
|
|
1721
|
-
checkResultOverride = {
|
|
1722
|
-
decision: "allow",
|
|
1723
|
-
reason: "Workspace mode: workspace-scoped operation auto-allowed",
|
|
1724
|
-
};
|
|
1725
|
-
|
|
1726
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1727
|
-
const result = await executor.execute(
|
|
1728
|
-
"file_write",
|
|
1729
|
-
{ path: "/tmp/project/test.txt", content: "data" },
|
|
1730
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1731
|
-
);
|
|
1732
|
-
|
|
1733
|
-
expect(result.isError).toBe(false);
|
|
1734
|
-
// file_write is a side-effect tool, so forcePromptSideEffects must promote
|
|
1735
|
-
// the workspace mode allow → prompt, requiring explicit user approval
|
|
1736
|
-
expect(promptCalled).toBe(true);
|
|
1737
|
-
});
|
|
1738
|
-
|
|
1739
|
-
test("schedule_create forces prompt in private conversation", async () => {
|
|
1740
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1741
|
-
|
|
1742
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1743
|
-
const result = await executor.execute(
|
|
1744
|
-
"schedule_create",
|
|
1745
|
-
{
|
|
1746
|
-
name: "test schedule",
|
|
1747
|
-
expression: "0 9 * * *",
|
|
1748
|
-
message: "test",
|
|
1749
|
-
},
|
|
1750
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1751
|
-
);
|
|
1752
|
-
|
|
1753
|
-
expect(result.isError).toBe(false);
|
|
1754
|
-
expect(promptCalled).toBe(true);
|
|
1755
|
-
});
|
|
1756
|
-
|
|
1757
|
-
test("schedule_list does NOT force prompt in private conversation", async () => {
|
|
722
|
+
test("credential_store store forces prompt under forcePromptSideEffects", async () => {
|
|
1758
723
|
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1759
724
|
|
|
1760
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1761
|
-
const result = await executor.execute(
|
|
1762
|
-
"schedule_list",
|
|
1763
|
-
{},
|
|
1764
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1765
|
-
);
|
|
1766
|
-
|
|
1767
|
-
expect(result.isError).toBe(false);
|
|
1768
|
-
// list is read-only — must NOT trigger forced prompting
|
|
1769
|
-
expect(promptCalled).toBe(false);
|
|
1770
|
-
});
|
|
1771
|
-
});
|
|
1772
|
-
|
|
1773
|
-
// ---------------------------------------------------------------------------
|
|
1774
|
-
// persistentDecisionsAllowed contract
|
|
1775
|
-
// ---------------------------------------------------------------------------
|
|
1776
|
-
|
|
1777
|
-
describe("ToolExecutor persistentDecisionsAllowed contract", () => {
|
|
1778
|
-
beforeEach(() => {
|
|
1779
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
1780
|
-
lastCheckArgs = undefined;
|
|
1781
|
-
getToolOverride = undefined;
|
|
1782
|
-
checkResultOverride = {
|
|
1783
|
-
decision: "prompt",
|
|
1784
|
-
reason: "Requires explicit approval",
|
|
1785
|
-
};
|
|
1786
|
-
checkFnOverride = undefined;
|
|
1787
|
-
if (addRuleSpy) {
|
|
1788
|
-
addRuleSpy.mockRestore();
|
|
1789
|
-
addRuleSpy = undefined;
|
|
1790
|
-
}
|
|
1791
|
-
});
|
|
1792
|
-
|
|
1793
|
-
function setupAddRuleSpy() {
|
|
1794
|
-
addRuleSpy = spyOn(trustStore, "addRule").mockImplementation(
|
|
1795
|
-
(
|
|
1796
|
-
tool: string,
|
|
1797
|
-
pattern: string,
|
|
1798
|
-
scope: string,
|
|
1799
|
-
decision = "allow",
|
|
1800
|
-
priority = 100,
|
|
1801
|
-
options?: { executionTarget?: string },
|
|
1802
|
-
) => {
|
|
1803
|
-
return {
|
|
1804
|
-
id: "spy-rule-id",
|
|
1805
|
-
tool,
|
|
1806
|
-
pattern,
|
|
1807
|
-
scope,
|
|
1808
|
-
decision,
|
|
1809
|
-
priority,
|
|
1810
|
-
createdAt: Date.now(),
|
|
1811
|
-
...options,
|
|
1812
|
-
} as TrustRule;
|
|
1813
|
-
},
|
|
1814
|
-
);
|
|
1815
|
-
return addRuleSpy;
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
test("proxied bash always_allow saves a trust rule (no special-casing)", async () => {
|
|
1819
|
-
const spy = setupAddRuleSpy();
|
|
1820
|
-
|
|
1821
|
-
const prompter = makePrompterWithDecision(
|
|
1822
|
-
"always_allow",
|
|
1823
|
-
"bash:*",
|
|
1824
|
-
"/tmp/project",
|
|
1825
|
-
);
|
|
1826
|
-
const executor = new ToolExecutor(prompter);
|
|
1827
|
-
const result = await executor.execute(
|
|
1828
|
-
"bash",
|
|
1829
|
-
{ command: "curl https://example.com", network_mode: "proxied" },
|
|
1830
|
-
makeContext(),
|
|
1831
|
-
);
|
|
1832
|
-
|
|
1833
|
-
expect(result.isError).toBe(false);
|
|
1834
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1835
|
-
});
|
|
1836
|
-
|
|
1837
|
-
test("non-proxied bash always_allow saves a trust rule", async () => {
|
|
1838
|
-
const spy = setupAddRuleSpy();
|
|
1839
|
-
|
|
1840
|
-
const prompter = makePrompterWithDecision(
|
|
1841
|
-
"always_allow",
|
|
1842
|
-
"bash:*",
|
|
1843
|
-
"/tmp/project",
|
|
1844
|
-
);
|
|
1845
|
-
const executor = new ToolExecutor(prompter);
|
|
1846
|
-
const result = await executor.execute(
|
|
1847
|
-
"bash",
|
|
1848
|
-
{ command: "git status" },
|
|
1849
|
-
makeContext(),
|
|
1850
|
-
);
|
|
1851
|
-
|
|
1852
|
-
expect(result.isError).toBe(false);
|
|
1853
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
|
-
test("proxied bash always_deny saves a deny rule (no special-casing)", async () => {
|
|
1857
|
-
const spy = setupAddRuleSpy();
|
|
1858
|
-
|
|
1859
|
-
const prompter = makePrompterWithDecision(
|
|
1860
|
-
"always_deny",
|
|
1861
|
-
"bash:*",
|
|
1862
|
-
"/tmp/project",
|
|
1863
|
-
);
|
|
1864
|
-
const executor = new ToolExecutor(prompter);
|
|
1865
|
-
const result = await executor.execute(
|
|
1866
|
-
"bash",
|
|
1867
|
-
{ command: "curl https://evil.com", network_mode: "proxied" },
|
|
1868
|
-
makeContext(),
|
|
1869
|
-
);
|
|
1870
|
-
|
|
1871
|
-
expect(result.isError).toBe(true);
|
|
1872
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1873
|
-
});
|
|
1874
|
-
|
|
1875
|
-
test("persistentDecisionsAllowed: true is emitted in lifecycle event for proxied bash", async () => {
|
|
1876
|
-
let capturedEvent: ToolPermissionPromptEvent | undefined;
|
|
1877
|
-
const prompter = makePrompterWithDecision("allow");
|
|
1878
|
-
const executor = new ToolExecutor(prompter);
|
|
725
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1879
726
|
const result = await executor.execute(
|
|
1880
|
-
"
|
|
1881
|
-
{
|
|
1882
|
-
makeContext({
|
|
1883
|
-
onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
|
|
1884
|
-
if (event.type === "permission_prompt") {
|
|
1885
|
-
capturedEvent = event;
|
|
1886
|
-
}
|
|
1887
|
-
},
|
|
1888
|
-
}),
|
|
727
|
+
"credential_store",
|
|
728
|
+
{ action: "store", name: "api-key", value: "sk-secret-123" },
|
|
729
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1889
730
|
);
|
|
1890
731
|
|
|
1891
732
|
expect(result.isError).toBe(false);
|
|
1892
|
-
expect(
|
|
1893
|
-
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
733
|
+
expect(promptCalled).toBe(true);
|
|
1894
734
|
});
|
|
1895
735
|
|
|
1896
|
-
test("
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const executor = new ToolExecutor(
|
|
736
|
+
test("credential_store delete forces prompt under forcePromptSideEffects", async () => {
|
|
737
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
738
|
+
|
|
739
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1900
740
|
const result = await executor.execute(
|
|
1901
|
-
"
|
|
1902
|
-
{
|
|
1903
|
-
makeContext({
|
|
1904
|
-
onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
|
|
1905
|
-
if (event.type === "permission_prompt") {
|
|
1906
|
-
capturedEvent = event;
|
|
1907
|
-
}
|
|
1908
|
-
},
|
|
1909
|
-
}),
|
|
741
|
+
"credential_store",
|
|
742
|
+
{ action: "delete", name: "api-key" },
|
|
743
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1910
744
|
);
|
|
1911
745
|
|
|
1912
746
|
expect(result.isError).toBe(false);
|
|
1913
|
-
expect(
|
|
1914
|
-
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
747
|
+
expect(promptCalled).toBe(true);
|
|
1915
748
|
});
|
|
1916
749
|
|
|
1917
|
-
test("
|
|
1918
|
-
|
|
1919
|
-
const prompter = {
|
|
1920
|
-
prompt: async (
|
|
1921
|
-
_toolName: string,
|
|
1922
|
-
_input: Record<string, unknown>,
|
|
1923
|
-
_riskLevel: string,
|
|
1924
|
-
_allowlistOptions: AllowlistOption[],
|
|
1925
|
-
_scopeOptions: ScopeOption[],
|
|
1926
|
-
_diff: unknown,
|
|
1927
|
-
_conversationId: unknown,
|
|
1928
|
-
_executionTarget: unknown,
|
|
1929
|
-
persistentDecisionsAllowed: boolean | undefined,
|
|
1930
|
-
) => {
|
|
1931
|
-
capturedPersistent = persistentDecisionsAllowed;
|
|
1932
|
-
return { decision: "allow" as const };
|
|
1933
|
-
},
|
|
1934
|
-
resolveConfirmation: () => {},
|
|
1935
|
-
updateSender: () => {},
|
|
1936
|
-
dispose: () => {},
|
|
1937
|
-
} as unknown as PermissionPrompter;
|
|
750
|
+
test("credential_store list does NOT force prompt under forcePromptSideEffects", async () => {
|
|
751
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1938
752
|
|
|
1939
|
-
const executor = new ToolExecutor(
|
|
753
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1940
754
|
const result = await executor.execute(
|
|
1941
|
-
"
|
|
1942
|
-
{
|
|
1943
|
-
makeContext(),
|
|
755
|
+
"credential_store",
|
|
756
|
+
{ action: "list" },
|
|
757
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1944
758
|
);
|
|
1945
759
|
|
|
1946
760
|
expect(result.isError).toBe(false);
|
|
1947
|
-
|
|
761
|
+
// list is read-only — must NOT trigger forced prompting
|
|
762
|
+
expect(promptCalled).toBe(false);
|
|
1948
763
|
});
|
|
1949
764
|
|
|
1950
|
-
|
|
1951
|
-
const spy = setupAddRuleSpy();
|
|
1952
|
-
|
|
1953
|
-
// 1. Proxied bash always_allow -> rule IS saved
|
|
1954
|
-
const p1 = makePrompterWithDecision(
|
|
1955
|
-
"always_allow",
|
|
1956
|
-
"bash:curl*",
|
|
1957
|
-
"/tmp/project",
|
|
1958
|
-
);
|
|
1959
|
-
const e1 = new ToolExecutor(p1);
|
|
1960
|
-
const r1 = await e1.execute(
|
|
1961
|
-
"bash",
|
|
1962
|
-
{ command: "curl https://api.example.com", network_mode: "proxied" },
|
|
1963
|
-
makeContext(),
|
|
1964
|
-
);
|
|
1965
|
-
expect(r1.isError).toBe(false);
|
|
1966
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1967
|
-
spy.mockClear();
|
|
1968
|
-
|
|
1969
|
-
// 2. Non-proxied bash always_allow -> rule IS saved
|
|
1970
|
-
const p2 = makePrompterWithDecision(
|
|
1971
|
-
"always_allow",
|
|
1972
|
-
"bash:git*",
|
|
1973
|
-
"/tmp/project",
|
|
1974
|
-
);
|
|
1975
|
-
const e2 = new ToolExecutor(p2);
|
|
1976
|
-
const r2 = await e2.execute("bash", { command: "git push" }, makeContext());
|
|
1977
|
-
expect(r2.isError).toBe(false);
|
|
1978
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1979
|
-
});
|
|
765
|
+
// ── Workspace mode + forcePromptSideEffects interaction ──────────
|
|
1980
766
|
|
|
1981
|
-
test("
|
|
1982
|
-
|
|
767
|
+
test("workspace mode allow → prompt promotion still works for side-effect tools under forcePromptSideEffects", async () => {
|
|
768
|
+
// Simulate workspace mode returning 'allow' for a workspace-scoped file_write
|
|
769
|
+
checkResultOverride = {
|
|
770
|
+
decision: "allow",
|
|
771
|
+
reason: "Workspace-scoped low-risk operation auto-allowed",
|
|
772
|
+
};
|
|
1983
773
|
|
|
1984
|
-
const
|
|
1985
|
-
"always_deny",
|
|
1986
|
-
"bash:rm*",
|
|
1987
|
-
"/tmp/project",
|
|
1988
|
-
);
|
|
1989
|
-
const executor = new ToolExecutor(prompter);
|
|
774
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1990
775
|
const result = await executor.execute(
|
|
1991
|
-
"
|
|
1992
|
-
{
|
|
1993
|
-
makeContext(),
|
|
776
|
+
"file_write",
|
|
777
|
+
{ path: "/tmp/project/test.txt", content: "data" },
|
|
778
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1994
779
|
);
|
|
1995
780
|
|
|
1996
|
-
expect(result.isError).toBe(
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
expect(
|
|
2000
|
-
expect(spy.mock.calls[0][3]).toBe("deny");
|
|
781
|
+
expect(result.isError).toBe(false);
|
|
782
|
+
// file_write is a side-effect tool, so forcePromptSideEffects must promote
|
|
783
|
+
// the workspace mode allow → prompt, requiring explicit user approval
|
|
784
|
+
expect(promptCalled).toBe(true);
|
|
2001
785
|
});
|
|
2002
786
|
|
|
2003
|
-
test(
|
|
2004
|
-
|
|
787
|
+
test("schedule_create forces prompt under forcePromptSideEffects", async () => {
|
|
788
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
2005
789
|
|
|
2006
|
-
const
|
|
2007
|
-
"always_deny",
|
|
2008
|
-
"bash:*",
|
|
2009
|
-
"/tmp/project",
|
|
2010
|
-
);
|
|
2011
|
-
const executor = new ToolExecutor(prompter);
|
|
790
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2012
791
|
const result = await executor.execute(
|
|
2013
|
-
"
|
|
2014
|
-
{
|
|
2015
|
-
|
|
792
|
+
"schedule_create",
|
|
793
|
+
{
|
|
794
|
+
name: "test schedule",
|
|
795
|
+
expression: "0 9 * * *",
|
|
796
|
+
message: "test",
|
|
797
|
+
},
|
|
798
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2016
799
|
);
|
|
2017
800
|
|
|
2018
|
-
expect(result.isError).toBe(
|
|
2019
|
-
expect(
|
|
2020
|
-
expect(result.content).toContain("rule was saved");
|
|
801
|
+
expect(result.isError).toBe(false);
|
|
802
|
+
expect(promptCalled).toBe(true);
|
|
2021
803
|
});
|
|
2022
804
|
|
|
2023
|
-
test(
|
|
2024
|
-
|
|
805
|
+
test("schedule_list does NOT force prompt under forcePromptSideEffects", async () => {
|
|
806
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
2025
807
|
|
|
2026
|
-
const
|
|
2027
|
-
"always_deny",
|
|
2028
|
-
"bash:rm*",
|
|
2029
|
-
"/tmp/project",
|
|
2030
|
-
);
|
|
2031
|
-
const executor = new ToolExecutor(prompter);
|
|
808
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2032
809
|
const result = await executor.execute(
|
|
2033
|
-
"
|
|
2034
|
-
{
|
|
2035
|
-
makeContext(),
|
|
810
|
+
"schedule_list",
|
|
811
|
+
{},
|
|
812
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2036
813
|
);
|
|
2037
814
|
|
|
2038
|
-
expect(result.isError).toBe(
|
|
2039
|
-
|
|
2040
|
-
expect(
|
|
815
|
+
expect(result.isError).toBe(false);
|
|
816
|
+
// list is read-only — must NOT trigger forced prompting
|
|
817
|
+
expect(promptCalled).toBe(false);
|
|
2041
818
|
});
|
|
2042
819
|
});
|
|
2043
820
|
|
|
@@ -2113,145 +890,6 @@ describe("buildSanitizedEnv — baseline: credential exclusion", () => {
|
|
|
2113
890
|
});
|
|
2114
891
|
});
|
|
2115
892
|
|
|
2116
|
-
// ---------------------------------------------------------------------------
|
|
2117
|
-
// Persistent-allow lifecycle: roundtrip and auto-allow on subsequent invocation
|
|
2118
|
-
// ---------------------------------------------------------------------------
|
|
2119
|
-
|
|
2120
|
-
describe("ToolExecutor persistent-allow lifecycle", () => {
|
|
2121
|
-
beforeEach(() => {
|
|
2122
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
2123
|
-
lastCheckArgs = undefined;
|
|
2124
|
-
getToolOverride = undefined;
|
|
2125
|
-
checkResultOverride = undefined;
|
|
2126
|
-
checkFnOverride = undefined;
|
|
2127
|
-
if (addRuleSpy) {
|
|
2128
|
-
addRuleSpy.mockRestore();
|
|
2129
|
-
addRuleSpy = undefined;
|
|
2130
|
-
}
|
|
2131
|
-
});
|
|
2132
|
-
|
|
2133
|
-
function setupAddRuleSpy() {
|
|
2134
|
-
addRuleSpy = spyOn(trustStore, "addRule").mockImplementation(
|
|
2135
|
-
(
|
|
2136
|
-
tool: string,
|
|
2137
|
-
pattern: string,
|
|
2138
|
-
scope: string,
|
|
2139
|
-
decision = "allow",
|
|
2140
|
-
priority = 100,
|
|
2141
|
-
options?: { executionTarget?: string },
|
|
2142
|
-
) => {
|
|
2143
|
-
return {
|
|
2144
|
-
id: "spy-rule-id",
|
|
2145
|
-
tool,
|
|
2146
|
-
pattern,
|
|
2147
|
-
scope,
|
|
2148
|
-
decision,
|
|
2149
|
-
priority,
|
|
2150
|
-
createdAt: Date.now(),
|
|
2151
|
-
...options,
|
|
2152
|
-
} as TrustRule;
|
|
2153
|
-
},
|
|
2154
|
-
);
|
|
2155
|
-
return addRuleSpy;
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
test("persistent-allow roundtrip: always_allow saves rule and allows tool", async () => {
|
|
2159
|
-
// Simulate check() returning 'prompt' so the executor asks the user
|
|
2160
|
-
checkResultOverride = {
|
|
2161
|
-
decision: "prompt",
|
|
2162
|
-
reason: "Medium risk: requires approval",
|
|
2163
|
-
};
|
|
2164
|
-
const spy = setupAddRuleSpy();
|
|
2165
|
-
|
|
2166
|
-
// User responds with always_allow, selecting a pattern and scope
|
|
2167
|
-
const prompter = makePrompterWithDecision(
|
|
2168
|
-
"always_allow",
|
|
2169
|
-
"git *",
|
|
2170
|
-
"/tmp/project",
|
|
2171
|
-
);
|
|
2172
|
-
const executor = new ToolExecutor(prompter);
|
|
2173
|
-
const result = await executor.execute(
|
|
2174
|
-
"bash",
|
|
2175
|
-
{ command: "git status" },
|
|
2176
|
-
makeContext(),
|
|
2177
|
-
);
|
|
2178
|
-
|
|
2179
|
-
// The tool should have been allowed to proceed
|
|
2180
|
-
expect(result.isError).toBe(false);
|
|
2181
|
-
expect(result.content).toBe("ok");
|
|
2182
|
-
|
|
2183
|
-
// addRule should have been called with the correct arguments
|
|
2184
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
2185
|
-
const [tool, pattern, scope, decision] = spy.mock.calls[0];
|
|
2186
|
-
expect(tool).toBe("bash");
|
|
2187
|
-
expect(pattern).toBe("git *");
|
|
2188
|
-
expect(scope).toBe("/tmp/project");
|
|
2189
|
-
expect(decision).toBe("allow");
|
|
2190
|
-
});
|
|
2191
|
-
|
|
2192
|
-
test("auto-allow on subsequent invocation: matching rule skips prompt", async () => {
|
|
2193
|
-
// Simulate a previously saved rule by making check() return 'allow'
|
|
2194
|
-
// with a matched rule (as findHighestPriorityRule would).
|
|
2195
|
-
checkResultOverride = {
|
|
2196
|
-
decision: "allow",
|
|
2197
|
-
reason: "Matched trust rule: git *",
|
|
2198
|
-
};
|
|
2199
|
-
|
|
2200
|
-
let promptCalled = false;
|
|
2201
|
-
const trackingPrompter = {
|
|
2202
|
-
prompt: async () => {
|
|
2203
|
-
promptCalled = true;
|
|
2204
|
-
return { decision: "allow" as const };
|
|
2205
|
-
},
|
|
2206
|
-
resolveConfirmation: () => {},
|
|
2207
|
-
updateSender: () => {},
|
|
2208
|
-
dispose: () => {},
|
|
2209
|
-
} as unknown as PermissionPrompter;
|
|
2210
|
-
|
|
2211
|
-
const executor = new ToolExecutor(trackingPrompter);
|
|
2212
|
-
const result = await executor.execute(
|
|
2213
|
-
"bash",
|
|
2214
|
-
{ command: "git status" },
|
|
2215
|
-
makeContext(),
|
|
2216
|
-
);
|
|
2217
|
-
|
|
2218
|
-
// The tool should be auto-allowed
|
|
2219
|
-
expect(result.isError).toBe(false);
|
|
2220
|
-
expect(result.content).toBe("ok");
|
|
2221
|
-
|
|
2222
|
-
// The prompter should NOT have been called — the rule auto-allowed
|
|
2223
|
-
expect(promptCalled).toBe(false);
|
|
2224
|
-
});
|
|
2225
|
-
|
|
2226
|
-
test("always_allow with everywhere scope saves rule and allows tool", async () => {
|
|
2227
|
-
checkResultOverride = {
|
|
2228
|
-
decision: "prompt",
|
|
2229
|
-
reason: "Medium risk: requires approval",
|
|
2230
|
-
};
|
|
2231
|
-
const spy = setupAddRuleSpy();
|
|
2232
|
-
|
|
2233
|
-
const prompter = makePrompterWithDecision(
|
|
2234
|
-
"always_allow",
|
|
2235
|
-
"file_write:*",
|
|
2236
|
-
"everywhere",
|
|
2237
|
-
);
|
|
2238
|
-
const executor = new ToolExecutor(prompter);
|
|
2239
|
-
const result = await executor.execute(
|
|
2240
|
-
"file_write",
|
|
2241
|
-
{ path: "/tmp/test.txt", content: "hello" },
|
|
2242
|
-
makeContext(),
|
|
2243
|
-
);
|
|
2244
|
-
|
|
2245
|
-
expect(result.isError).toBe(false);
|
|
2246
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
2247
|
-
const [tool, pattern, scope, decision] = spy.mock.calls[0];
|
|
2248
|
-
expect(tool).toBe("file_write");
|
|
2249
|
-
expect(pattern).toBe("file_write:*");
|
|
2250
|
-
expect(scope).toBe("everywhere");
|
|
2251
|
-
expect(decision).toBe("allow");
|
|
2252
|
-
});
|
|
2253
|
-
});
|
|
2254
|
-
|
|
2255
893
|
describe("integration regressions — prompt payload (PR 11)", () => {
|
|
2256
894
|
beforeEach(() => {
|
|
2257
895
|
fakeToolResult = { content: "ok", isError: false };
|
|
@@ -2286,7 +924,7 @@ describe("integration regressions — prompt payload (PR 11)", () => {
|
|
|
2286
924
|
} as unknown as PermissionPrompter;
|
|
2287
925
|
|
|
2288
926
|
const executor = new ToolExecutor(prompter);
|
|
2289
|
-
await executor.execute("bash", { command: "npm install" }, makeContext());
|
|
927
|
+
await executor.execute("bash", { command: "npm install" }, makeContext({ forcePromptSideEffects: true }));
|
|
2290
928
|
|
|
2291
929
|
// Verify that the prompter received allowlist options
|
|
2292
930
|
expect(capturedAllowlist).toBeDefined();
|
|
@@ -2302,3 +940,195 @@ describe("integration regressions — prompt payload (PR 11)", () => {
|
|
|
2302
940
|
expect(capturedScopes![0]).toHaveProperty("scope");
|
|
2303
941
|
});
|
|
2304
942
|
});
|
|
943
|
+
|
|
944
|
+
// ---------------------------------------------------------------------------
|
|
945
|
+
// Risk metadata on ToolExecutionResult (PR 5 — scope-ladder-v1)
|
|
946
|
+
// ---------------------------------------------------------------------------
|
|
947
|
+
|
|
948
|
+
describe("ToolExecutionResult includes risk metadata from classifier assessment", () => {
|
|
949
|
+
beforeEach(() => {
|
|
950
|
+
fakeToolResult = { content: "ok", isError: false };
|
|
951
|
+
lastCheckArgs = undefined;
|
|
952
|
+
getToolOverride = undefined;
|
|
953
|
+
checkResultOverride = undefined;
|
|
954
|
+
checkFnOverride = undefined;
|
|
955
|
+
cachedAssessmentOverride = undefined;
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
test("auto-approved tool result includes risk metadata when classifier assessment exists", async () => {
|
|
959
|
+
cachedAssessmentOverride = {
|
|
960
|
+
riskLevel: "medium",
|
|
961
|
+
reason: "Writes to a file outside the workspace",
|
|
962
|
+
scopeOptions: [
|
|
963
|
+
{ pattern: "file_write:/tmp/test.txt", label: "This file only" },
|
|
964
|
+
{ pattern: "file_write:/tmp/**", label: "Anything in tmp/" },
|
|
965
|
+
],
|
|
966
|
+
matchType: "registry",
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const executor = new ToolExecutor(makePrompter());
|
|
970
|
+
const result = await executor.execute(
|
|
971
|
+
"file_read",
|
|
972
|
+
{ path: "README.md" },
|
|
973
|
+
makeContext({ requireFreshApproval: true }),
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
expect(result.isError).toBe(false);
|
|
977
|
+
expect(result.riskLevel).toBe("medium");
|
|
978
|
+
expect(result.riskReason).toBe("Writes to a file outside the workspace");
|
|
979
|
+
expect(result.riskScopeOptions).toEqual([
|
|
980
|
+
{ pattern: "file_write:/tmp/test.txt", label: "This file only" },
|
|
981
|
+
{ pattern: "file_write:/tmp/**", label: "Anything in tmp/" },
|
|
982
|
+
]);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test("tool result omits risk metadata when no classifier assessment exists (e.g. MCP tools)", async () => {
|
|
986
|
+
// cachedAssessmentOverride is undefined (no classifier ran)
|
|
987
|
+
const executor = new ToolExecutor(makePrompter());
|
|
988
|
+
const result = await executor.execute(
|
|
989
|
+
"file_read",
|
|
990
|
+
{ path: "README.md" },
|
|
991
|
+
makeContext(),
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
expect(result.isError).toBe(false);
|
|
995
|
+
expect(result.riskLevel).toBeUndefined();
|
|
996
|
+
expect(result.riskReason).toBeUndefined();
|
|
997
|
+
expect(result.riskScopeOptions).toBeUndefined();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
test("denied tool result includes risk metadata", async () => {
|
|
1001
|
+
checkResultOverride = {
|
|
1002
|
+
decision: "deny",
|
|
1003
|
+
reason: "Blocked by deny rule",
|
|
1004
|
+
};
|
|
1005
|
+
cachedAssessmentOverride = {
|
|
1006
|
+
riskLevel: "high",
|
|
1007
|
+
reason: "Recursive force delete",
|
|
1008
|
+
scopeOptions: [{ pattern: "bash:rm -rf*", label: "rm -rf commands" }],
|
|
1009
|
+
matchType: "registry",
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1013
|
+
const result = await executor.execute(
|
|
1014
|
+
"bash",
|
|
1015
|
+
{ command: "rm -rf /" },
|
|
1016
|
+
makeContext({ requireFreshApproval: true }),
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
expect(result.isError).toBe(true);
|
|
1020
|
+
expect(result.riskLevel).toBe("high");
|
|
1021
|
+
expect(result.riskReason).toBe("Recursive force delete");
|
|
1022
|
+
expect(result.riskScopeOptions).toEqual([
|
|
1023
|
+
{ pattern: "bash:rm -rf*", label: "rm -rf commands" },
|
|
1024
|
+
]);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
test("prompted-then-approved tool result includes risk metadata", async () => {
|
|
1028
|
+
checkResultOverride = {
|
|
1029
|
+
decision: "prompt",
|
|
1030
|
+
reason: "Medium risk: requires approval",
|
|
1031
|
+
};
|
|
1032
|
+
cachedAssessmentOverride = {
|
|
1033
|
+
riskLevel: "medium",
|
|
1034
|
+
reason: "Package manager installation",
|
|
1035
|
+
scopeOptions: [
|
|
1036
|
+
{ pattern: "bash:npm install*", label: "npm install commands" },
|
|
1037
|
+
{ pattern: "bash:npm*", label: "All npm commands" },
|
|
1038
|
+
],
|
|
1039
|
+
matchType: "registry",
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1043
|
+
const result = await executor.execute(
|
|
1044
|
+
"bash",
|
|
1045
|
+
{ command: "npm install lodash" },
|
|
1046
|
+
makeContext({ requireFreshApproval: true }),
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
expect(result.isError).toBe(false);
|
|
1050
|
+
expect(result.content).toBe("ok");
|
|
1051
|
+
expect(result.riskLevel).toBe("medium");
|
|
1052
|
+
expect(result.riskReason).toBe("Package manager installation");
|
|
1053
|
+
expect(result.riskScopeOptions).toHaveLength(2);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test("tool result includes riskDirectoryScopeOptions when classifier emits directoryScopeOptions", async () => {
|
|
1057
|
+
cachedAssessmentOverride = {
|
|
1058
|
+
riskLevel: "medium",
|
|
1059
|
+
reason: "Writes to file in workspace",
|
|
1060
|
+
scopeOptions: [
|
|
1061
|
+
{
|
|
1062
|
+
pattern: "file_write:/workspace/scratch/out.txt",
|
|
1063
|
+
label: "This file only",
|
|
1064
|
+
},
|
|
1065
|
+
],
|
|
1066
|
+
directoryScopeOptions: [
|
|
1067
|
+
{ scope: "/workspace/scratch", label: "In scratch/" },
|
|
1068
|
+
{ scope: "/workspace", label: "Anywhere in workspace/" },
|
|
1069
|
+
{ scope: "everywhere", label: "Everywhere" },
|
|
1070
|
+
],
|
|
1071
|
+
matchType: "registry",
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1075
|
+
const result = await executor.execute(
|
|
1076
|
+
"file_read",
|
|
1077
|
+
{ path: "/workspace/scratch/out.txt" },
|
|
1078
|
+
makeContext({ requireFreshApproval: true }),
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
expect(result.isError).toBe(false);
|
|
1082
|
+
expect(result.riskDirectoryScopeOptions).toEqual([
|
|
1083
|
+
{ scope: "/workspace/scratch", label: "In scratch/" },
|
|
1084
|
+
{ scope: "/workspace", label: "Anywhere in workspace/" },
|
|
1085
|
+
{ scope: "everywhere", label: "Everywhere" },
|
|
1086
|
+
]);
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
test("tool result omits riskDirectoryScopeOptions when classifier does not emit directoryScopeOptions", async () => {
|
|
1090
|
+
cachedAssessmentOverride = {
|
|
1091
|
+
riskLevel: "low",
|
|
1092
|
+
reason: "Read-only operation",
|
|
1093
|
+
scopeOptions: [],
|
|
1094
|
+
// directoryScopeOptions intentionally omitted
|
|
1095
|
+
matchType: "registry",
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1099
|
+
const result = await executor.execute(
|
|
1100
|
+
"file_read",
|
|
1101
|
+
{ path: "README.md" },
|
|
1102
|
+
makeContext(),
|
|
1103
|
+
);
|
|
1104
|
+
|
|
1105
|
+
expect(result.isError).toBe(false);
|
|
1106
|
+
expect(result.riskDirectoryScopeOptions).toBeUndefined();
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
test("riskScopeOptions and riskDirectoryScopeOptions are independent — one does not clobber the other", async () => {
|
|
1110
|
+
cachedAssessmentOverride = {
|
|
1111
|
+
riskLevel: "medium",
|
|
1112
|
+
reason: "Filesystem write",
|
|
1113
|
+
scopeOptions: [
|
|
1114
|
+
{ pattern: "file_write:/tmp/foo.txt", label: "This file" },
|
|
1115
|
+
],
|
|
1116
|
+
directoryScopeOptions: [{ scope: "/tmp", label: "Anywhere in tmp/" }],
|
|
1117
|
+
matchType: "registry",
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1121
|
+
const result = await executor.execute(
|
|
1122
|
+
"file_read",
|
|
1123
|
+
{ path: "/tmp/foo.txt" },
|
|
1124
|
+
makeContext({ requireFreshApproval: true }),
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
expect(result.riskScopeOptions).toEqual([
|
|
1128
|
+
{ pattern: "file_write:/tmp/foo.txt", label: "This file" },
|
|
1129
|
+
]);
|
|
1130
|
+
expect(result.riskDirectoryScopeOptions).toEqual([
|
|
1131
|
+
{ scope: "/tmp", label: "Anywhere in tmp/" },
|
|
1132
|
+
]);
|
|
1133
|
+
});
|
|
1134
|
+
});
|