@vellumai/assistant 0.6.6 → 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 +20 -0
- package/ARCHITECTURE.md +45 -36
- package/Dockerfile +26 -6
- package/README.md +8 -10
- package/__tests__/permissions/gateway-threshold-reader.test.ts +14 -20
- package/bun.lock +306 -119
- package/docs/architecture/memory.md +1 -90
- package/docs/architecture/security.md +15 -30
- package/docs/credential-execution-service.md +7 -5
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +17 -45
- package/examples/plugins/echo/bun.lock +25 -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/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 +2855 -556
- 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} +27 -27
- 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-routes-csp.test.ts +106 -55
- package/src/__tests__/approval-cascade.test.ts +3 -355
- 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 +8 -20
- 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__/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-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 +274 -3921
- package/src/__tests__/circuit-breaker-pipeline.test.ts +1 -1
- 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 +0 -1
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +2 -2
- package/src/__tests__/compaction-timeout-recovery.test.ts +1 -1
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -7
- package/src/__tests__/config-model-image-provider.test.ts +0 -1
- package/src/__tests__/config-schema-cmd.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +30 -221
- 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 +56 -29
- package/src/__tests__/contacts-write.test.ts +6 -61
- 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 +25 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +10 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +631 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +15 -2
- package/src/__tests__/conversation-agent-loop.test.ts +24 -2
- 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 +1 -39
- 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-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 -81
- 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 +0 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +137 -297
- package/src/__tests__/conversation-process-callsite.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -1
- package/src/__tests__/conversation-queue.test.ts +1 -33
- package/src/__tests__/conversation-routes-disk-view.test.ts +124 -97
- 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 +41 -84
- package/src/__tests__/conversation-slash-commands.test.ts +6 -43
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-speed-override.test.ts +0 -1
- 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-tool-setup-app-refresh.test.ts +1 -1
- 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 +2 -1
- package/src/__tests__/conversation-wipe.test.ts +2 -103
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- 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-security-invariants.test.ts +14 -0
- 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 +1 -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 +1 -1
- 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__/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__/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 +1 -1
- package/src/__tests__/home-state-routes.test.ts +10 -31
- package/src/__tests__/host-browser-e2e-cloud.test.ts +2 -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 +3 -3
- 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__/inbound-invite-redemption.test.ts +3 -2
- package/src/__tests__/injector-chain.test.ts +16 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +41 -206
- package/src/__tests__/install-skill-routing.test.ts +1 -1
- 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 +7 -8
- 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 +57 -1
- 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__/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 +6 -8
- package/src/__tests__/memory-upsert-concurrency.test.ts +2 -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 +16 -23
- 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 +15 -2
- 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-guardian-path.test.ts +15 -8
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +2 -1
- package/src/__tests__/notification-telegram-adapter.test.ts +57 -55
- package/src/__tests__/oauth-apps-routes.test.ts +76 -122
- package/src/__tests__/oauth-cli.test.ts +14 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -1
- package/src/__tests__/oauth-providers-routes.test.ts +78 -101
- package/src/__tests__/oauth-store.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +6 -3
- 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 +4 -9
- package/src/__tests__/permission-types.test.ts +3 -18
- package/src/__tests__/persistence-pipeline.test.ts +3 -2
- package/src/__tests__/pipeline-runner.test.ts +1 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +27 -20
- package/src/__tests__/platform.test.ts +11 -63
- 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 +51 -5
- package/src/__tests__/plugin-registry.test.ts +30 -0
- package/src/__tests__/plugin-route-contribution.test.ts +17 -11
- package/src/__tests__/plugin-skill-contribution.test.ts +3 -3
- package/src/__tests__/plugin-tool-contribution.test.ts +10 -4
- package/src/__tests__/plugin-types.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +151 -2
- 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 -554
- package/src/__tests__/qdrant-collection-migration.test.ts +7 -7
- package/src/__tests__/reaction-persistence.test.ts +3 -2
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +0 -2
- package/src/__tests__/registry.test.ts +1 -0
- 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 +226 -129
- 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 +2 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +2 -1
- 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 -9
- 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 +55 -62
- 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 +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/terminal-tools.test.ts +5 -314
- package/src/__tests__/test-preload.ts +0 -11
- package/src/__tests__/thread-backfill.test.ts +3 -2
- package/src/__tests__/token-estimate-pipeline.test.ts +68 -15
- 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 +6 -6
- package/src/__tests__/tool-execute-pipeline.test.ts +6 -8
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +64 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +28 -56
- package/src/__tests__/tool-executor.test.ts +322 -1633
- package/src/__tests__/tool-grant-request-escalation.test.ts +90 -311
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +1 -1
- 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 +54 -12
- 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-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/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 +37 -14
- package/src/agent/message-types.ts +0 -2
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/__tests__/guardian-feed-event.test.ts +1 -9
- package/src/approvals/approval-primitive.ts +3 -20
- package/src/approvals/guardian-decision-primitive.ts +37 -68
- package/src/approvals/guardian-request-resolvers.ts +29 -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 +0 -2
- package/src/backup/__tests__/paths.test.ts +3 -2
- package/src/backup/backup-worker.ts +1 -10
- 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/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-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 +25 -3
- package/src/channels/permission-profiles.ts +2 -72
- package/src/channels/types.ts +42 -26
- package/src/cli/AGENTS.md +1 -0
- package/src/cli/__tests__/notifications.test.ts +12 -10
- package/src/cli/commands/__tests__/attachment.test.ts +14 -8
- package/src/cli/commands/__tests__/backup.test.ts +3 -14
- 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__/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__/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 +14 -44
- 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 +11 -12
- package/src/cli/commands/completions.ts +1 -1
- 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 +63 -53
- 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 +1 -1
- 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 +21 -20
- 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__/status.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/token.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/shared.ts +29 -2
- 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 +188 -82
- 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 +4 -0
- package/src/cli.ts +1 -61
- 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/acp/tools/acp-list-agents.ts +12 -0
- package/src/config/bundled-skills/acp/tools/acp-steer.ts +12 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +14 -14
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +11 -6
- 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/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/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/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 -6
- package/src/config/env.ts +7 -8
- package/src/config/feature-flag-registry.json +16 -24
- package/src/config/llm-resolver.ts +51 -33
- package/src/config/loader.ts +12 -15
- package/src/config/schema.ts +5 -72
- 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/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 -9
- 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/skills.ts +1 -1
- 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/token-estimator.ts +1 -1
- package/src/context/tool-result-truncation.ts +1 -1
- package/src/context/window-manager.ts +1 -1
- 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 +1 -1
- package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -13
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- 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 -54
- package/src/daemon/connection-policy.ts +0 -14
- package/src/daemon/conversation-agent-loop-handlers.ts +37 -6
- package/src/daemon/conversation-agent-loop.ts +164 -53
- package/src/daemon/conversation-attachments.ts +5 -81
- package/src/daemon/conversation-error.ts +9 -5
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-launch.ts +1 -1
- package/src/daemon/conversation-messaging.ts +1 -1
- package/src/daemon/conversation-notifiers.ts +1 -1
- package/src/daemon/conversation-process.ts +9 -13
- package/src/daemon/conversation-runtime-assembly.ts +26 -88
- 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 +23 -172
- package/src/daemon/conversation.ts +46 -182
- package/src/daemon/daemon-control.ts +3 -3
- package/src/daemon/daemon-skill-host.ts +262 -0
- package/src/daemon/external-plugins-bootstrap.ts +67 -13
- 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 +7 -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 +15 -23
- package/src/daemon/host-transfer-proxy.ts +500 -0
- package/src/daemon/lifecycle.ts +23 -258
- 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/conversations.ts +16 -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 +14 -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 -1274
- package/src/daemon/shutdown-handlers.ts +1 -1
- package/src/daemon/startup-error.ts +1 -1
- 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 +89 -21
- package/src/heartbeat/heartbeat-service.ts +32 -11
- 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/feed-types.ts +1 -1
- package/src/home/relationship-state-writer.ts +1 -1
- 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 +69 -23
- 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 -123
- 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 +63 -32
- 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/types.ts +4 -4
- 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 +69 -127
- 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 +13 -26
- 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 +2 -1
- package/src/memory/db-init.ts +14 -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 +4 -4
- 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 +2 -7
- package/src/memory/graph/bootstrap.ts +2 -1
- 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 +53 -21
- package/src/memory/graph/graph-memory-state-store.ts +1 -1
- package/src/memory/graph/graph-search.test.ts +94 -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 +17 -5
- 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/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 +10 -0
- package/src/memory/migrations/registry.ts +7 -0
- package/src/memory/pkb/pkb-index.test.ts +4 -5
- package/src/memory/pkb/pkb-reconcile.test.ts +4 -5
- package/src/memory/pkb/pkb-search.test.ts +83 -3
- package/src/memory/pkb/pkb-search.ts +27 -14
- package/src/memory/published-pages-store.ts +1 -1
- 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 +1 -32
- package/src/memory/schema/memory-graph.ts +36 -14
- 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/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-seed-composer.ts +12 -6
- package/src/notifications/copy-composer.ts +1 -1
- 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/events-store.ts +1 -13
- package/src/notifications/preferences-store.ts +1 -1
- package/src/notifications/signal.ts +0 -9
- package/src/oauth/connection-resolver.ts +11 -2
- package/src/oauth/oauth-store.ts +2 -1
- package/src/outbound-proxy/index.ts +0 -1
- package/src/permissions/approval-policy.test.ts +149 -132
- package/src/permissions/approval-policy.ts +65 -91
- package/src/permissions/checker.test.ts +632 -0
- package/src/permissions/checker.ts +266 -459
- package/src/permissions/gateway-threshold-reader.ts +28 -47
- package/src/permissions/ipc-risk-types.ts +95 -0
- package/src/permissions/prompter.ts +4 -9
- package/src/permissions/risk-types.ts +24 -210
- package/src/permissions/types.ts +17 -47
- package/src/permissions/workspace-policy.ts +2 -4
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/injectors.ts +18 -21
- package/src/plugins/defaults/llm-call.ts +6 -9
- package/src/plugins/defaults/memory-retrieval.ts +1 -6
- package/src/plugins/defaults/overflow-reduce.ts +9 -5
- package/src/plugins/defaults/token-estimate.ts +2 -3
- package/src/plugins/registry.ts +61 -1
- package/src/plugins/types.ts +6 -7
- package/src/plugins/user-loader.ts +36 -10
- 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/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 +83 -8
- package/src/providers/model-intents.ts +7 -8
- 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 +4 -5
- 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/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.ts +5 -5
- package/src/providers/thinking-config.ts +34 -0
- package/src/providers/types.ts +22 -10
- package/src/runtime/AGENTS.md +10 -9
- package/src/runtime/__tests__/agent-wake.test.ts +33 -9
- package/src/runtime/__tests__/client-registry.test.ts +5 -27
- 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 +21 -28
- 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 -1110
- package/src/runtime/http-types.ts +15 -98
- 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 +3 -4
- 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-routes.ts +116 -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 +456 -368
- 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 +74 -110
- 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 +152 -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 -374
- 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 +60 -120
- package/src/runtime/routes/playground/__tests__/guard.test.ts +34 -54
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +107 -151
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +41 -117
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +95 -138
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +115 -217
- package/src/runtime/routes/playground/__tests__/state.test.ts +41 -90
- package/src/runtime/routes/playground/conversation-not-found.ts +9 -11
- package/src/runtime/routes/playground/force-compact.ts +41 -54
- package/src/runtime/routes/playground/guard.ts +18 -19
- package/src/runtime/routes/playground/helpers.ts +103 -0
- package/src/runtime/routes/playground/index.ts +15 -25
- package/src/runtime/routes/playground/inject-failures.ts +48 -64
- package/src/runtime/routes/playground/reset-circuit.ts +31 -57
- package/src/runtime/routes/playground/seed-conversation.ts +66 -92
- package/src/runtime/routes/playground/seeded-conversations.ts +60 -64
- package/src/runtime/routes/playground/state.ts +23 -24
- 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 +238 -242
- 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/trust-context-resolver.ts +3 -2
- package/src/runtime/verification-outbound-actions.ts +13 -49
- package/src/schedule/schedule-store.ts +64 -2
- package/src/schedule/scheduler.ts +101 -0
- 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 +7 -0
- 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/browser-execution.ts +34 -7
- package/src/tools/browser/browser-manager.ts +1 -8
- 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/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 +10 -15
- 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/permission-checker.ts +18 -219
- package/src/tools/policy-context.ts +1 -8
- package/src/tools/registry.ts +16 -1
- package/src/tools/secret-detection-handler.ts +13 -103
- 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/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 +143 -163
- 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 -124
- package/src/util/pricing.ts +109 -6
- 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 +10 -1
- 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/028-recover-conversations-from-disk-view.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -1
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +3 -4
- 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 +18 -0
- package/src/workspace/migrations/runner.ts +2 -2
- package/src/workspace/provider-commit-message-generator.ts +1 -1
- 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__/config-watcher-feature-flags.test.ts +0 -211
- 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__/host-browser-e2e-self-hosted.test.ts +0 -374
- package/src/__tests__/native-host-marker-sync-guard.test.ts +0 -157
- 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 -488
- 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__/shell-identity.test.ts +0 -236
- 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 -354
- 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/db.ts +0 -1
- 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/daemon/__tests__/lifecycle-startup-ordering.test.ts +0 -127
- package/src/daemon/approved-devices-store.ts +0 -110
- 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/ipc/cli-server.ts +0 -252
- package/src/ipc/routes/attachment.ts +0 -114
- package/src/ipc/routes/browser-context.ts +0 -63
- package/src/ipc/routes/browser.ts +0 -97
- package/src/ipc/routes/cache.ts +0 -96
- package/src/ipc/routes/get-contact.ts +0 -16
- package/src/ipc/routes/index.ts +0 -35
- package/src/ipc/routes/list-clients.ts +0 -31
- package/src/ipc/routes/merge-contacts.ts +0 -17
- package/src/ipc/routes/notification.ts +0 -133
- package/src/ipc/routes/rename-conversation.ts +0 -59
- package/src/ipc/routes/search-contacts.ts +0 -19
- package/src/ipc/routes/task-queue.ts +0 -226
- package/src/ipc/routes/task.ts +0 -173
- package/src/ipc/routes/upsert-contact.ts +0 -25
- package/src/ipc/routes/wake-conversation.ts +0 -19
- package/src/memory/db.ts +0 -23
- package/src/permissions/arg-parser.test.ts +0 -161
- package/src/permissions/arg-parser.ts +0 -141
- package/src/permissions/bash-risk-classifier.test.ts +0 -1620
- package/src/permissions/bash-risk-classifier.ts +0 -950
- package/src/permissions/command-registry.test.ts +0 -774
- package/src/permissions/command-registry.ts +0 -1005
- package/src/permissions/defaults.ts +0 -314
- 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/schedule-risk-classifier.test.ts +0 -129
- package/src/permissions/schedule-risk-classifier.ts +0 -85
- package/src/permissions/shell-identity.ts +0 -297
- 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/routes/browser-extension-pair-routes.ts +0 -575
- 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/playground/deps.ts +0 -56
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +0 -67
- package/src/runtime/services/analyze-deps-singleton.ts +0 -32
- package/src/tasks/ephemeral-permissions.ts +0 -55
- package/src/tools/terminal/parser.ts +0 -623
- 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 };
|
|
@@ -92,13 +84,11 @@ let cachedAssessmentOverride:
|
|
|
92
84
|
riskLevel: string;
|
|
93
85
|
reason: string;
|
|
94
86
|
scopeOptions: Array<{ pattern: string; label: string }>;
|
|
87
|
+
directoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
95
88
|
matchType: string;
|
|
96
89
|
}
|
|
97
90
|
| undefined;
|
|
98
91
|
|
|
99
|
-
/** Spy on addRule to capture calls without replacing the real implementation. */
|
|
100
|
-
let addRuleSpy: ReturnType<typeof spyOn> | undefined;
|
|
101
|
-
|
|
102
92
|
mock.module("../config/loader.js", () => ({
|
|
103
93
|
getConfig: () => mockConfig,
|
|
104
94
|
loadConfig: () => mockConfig,
|
|
@@ -174,7 +164,6 @@ mock.module("../tools/terminal/sandbox.js", () => ({
|
|
|
174
164
|
}));
|
|
175
165
|
|
|
176
166
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
177
|
-
import * as trustStore from "../permissions/trust-store.js";
|
|
178
167
|
import { isSideEffectTool, ToolExecutor } from "../tools/executor.js";
|
|
179
168
|
import type { ToolContext } from "../tools/types.js";
|
|
180
169
|
|
|
@@ -196,10 +185,6 @@ function makePrompter(): PermissionPrompter {
|
|
|
196
185
|
} as unknown as PermissionPrompter;
|
|
197
186
|
}
|
|
198
187
|
|
|
199
|
-
afterAll(() => {
|
|
200
|
-
mock.restore();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
188
|
describe("ToolExecutor allowedToolNames gating", () => {
|
|
204
189
|
beforeEach(() => {
|
|
205
190
|
fakeToolResult = { content: "ok", isError: false };
|
|
@@ -208,10 +193,6 @@ describe("ToolExecutor allowedToolNames gating", () => {
|
|
|
208
193
|
checkResultOverride = undefined;
|
|
209
194
|
checkFnOverride = undefined;
|
|
210
195
|
cachedAssessmentOverride = undefined;
|
|
211
|
-
if (addRuleSpy) {
|
|
212
|
-
addRuleSpy.mockRestore();
|
|
213
|
-
addRuleSpy = undefined;
|
|
214
|
-
}
|
|
215
196
|
});
|
|
216
197
|
|
|
217
198
|
test("executes normally when allowedToolNames is not set", async () => {
|
|
@@ -285,10 +266,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
285
266
|
checkResultOverride = undefined;
|
|
286
267
|
checkFnOverride = undefined;
|
|
287
268
|
cachedAssessmentOverride = undefined;
|
|
288
|
-
if (addRuleSpy) {
|
|
289
|
-
addRuleSpy.mockRestore();
|
|
290
|
-
addRuleSpy = undefined;
|
|
291
|
-
}
|
|
292
269
|
});
|
|
293
270
|
|
|
294
271
|
test("passes PolicyContext with executionTarget for skill-origin tools", async () => {
|
|
@@ -316,7 +293,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
316
293
|
const result = await executor.execute(
|
|
317
294
|
"skill_tool",
|
|
318
295
|
{ action: "run" },
|
|
319
|
-
makeContext(),
|
|
296
|
+
makeContext({ requireFreshApproval: true }),
|
|
320
297
|
);
|
|
321
298
|
|
|
322
299
|
expect(result.isError).toBe(false);
|
|
@@ -324,7 +301,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
324
301
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
325
302
|
conversationId: "conversation-1",
|
|
326
303
|
executionContext: "conversation",
|
|
327
|
-
ephemeralRules: undefined,
|
|
328
304
|
executionTarget: "sandbox",
|
|
329
305
|
});
|
|
330
306
|
});
|
|
@@ -337,7 +313,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
337
313
|
const result = await executor.execute(
|
|
338
314
|
"file_read",
|
|
339
315
|
{ path: "test.txt" },
|
|
340
|
-
makeContext(),
|
|
316
|
+
makeContext({ requireFreshApproval: true }),
|
|
341
317
|
);
|
|
342
318
|
|
|
343
319
|
expect(result.isError).toBe(false);
|
|
@@ -345,7 +321,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
345
321
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
346
322
|
conversationId: "conversation-1",
|
|
347
323
|
executionContext: "conversation",
|
|
348
|
-
ephemeralRules: undefined,
|
|
349
324
|
});
|
|
350
325
|
});
|
|
351
326
|
|
|
@@ -371,7 +346,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
371
346
|
const result = await executor.execute(
|
|
372
347
|
"file_read",
|
|
373
348
|
{ path: "test.txt" },
|
|
374
|
-
makeContext(),
|
|
349
|
+
makeContext({ requireFreshApproval: true }),
|
|
375
350
|
);
|
|
376
351
|
|
|
377
352
|
expect(result.isError).toBe(false);
|
|
@@ -379,7 +354,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
379
354
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
380
355
|
conversationId: "conversation-1",
|
|
381
356
|
executionContext: "conversation",
|
|
382
|
-
ephemeralRules: undefined,
|
|
383
357
|
});
|
|
384
358
|
});
|
|
385
359
|
|
|
@@ -408,7 +382,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
408
382
|
const result = await executor.execute(
|
|
409
383
|
"host_skill_tool",
|
|
410
384
|
{ action: "run" },
|
|
411
|
-
makeContext(),
|
|
385
|
+
makeContext({ requireFreshApproval: true }),
|
|
412
386
|
);
|
|
413
387
|
|
|
414
388
|
expect(result.isError).toBe(false);
|
|
@@ -416,7 +390,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
416
390
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
417
391
|
conversationId: "conversation-1",
|
|
418
392
|
executionContext: "conversation",
|
|
419
|
-
ephemeralRules: undefined,
|
|
420
393
|
executionTarget: "host",
|
|
421
394
|
});
|
|
422
395
|
});
|
|
@@ -442,1060 +415,225 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
442
415
|
};
|
|
443
416
|
|
|
444
417
|
const executor = new ToolExecutor(makePrompter());
|
|
445
|
-
const result = await executor.execute("no_target_tool", {}, makeContext());
|
|
418
|
+
const result = await executor.execute("no_target_tool", {}, makeContext({ requireFreshApproval: true }));
|
|
446
419
|
|
|
447
420
|
expect(result.isError).toBe(false);
|
|
448
421
|
expect(lastCheckArgs).toBeDefined();
|
|
449
422
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
450
423
|
conversationId: "conversation-1",
|
|
451
424
|
executionContext: "conversation",
|
|
452
|
-
ephemeralRules: undefined,
|
|
453
425
|
executionTarget: undefined,
|
|
454
426
|
});
|
|
455
427
|
});
|
|
456
428
|
});
|
|
457
429
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
function makePrompterWithDecision(
|
|
462
|
-
decision: string,
|
|
463
|
-
selectedPattern?: string,
|
|
464
|
-
selectedScope?: string,
|
|
465
|
-
): PermissionPrompter {
|
|
466
|
-
return {
|
|
467
|
-
prompt: async () => ({ decision, selectedPattern, selectedScope }),
|
|
468
|
-
resolveConfirmation: () => {},
|
|
469
|
-
updateSender: () => {},
|
|
470
|
-
dispose: () => {},
|
|
471
|
-
} as unknown as PermissionPrompter;
|
|
472
|
-
}
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// isSideEffectTool classifier
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
473
433
|
|
|
474
|
-
describe("
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
+
});
|
|
485
455
|
}
|
|
486
456
|
});
|
|
487
457
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
);
|
|
510
|
-
return addRuleSpy;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
test("always_allow for a skill tool captures execution target in the rule", async () => {
|
|
514
|
-
checkResultOverride = { decision: "prompt", reason: "test prompt" };
|
|
515
|
-
const spy = setupAddRuleSpy();
|
|
516
|
-
|
|
517
|
-
getToolOverride = (name: string) => {
|
|
518
|
-
if (name === "unknown_tool") return undefined;
|
|
519
|
-
return {
|
|
520
|
-
name,
|
|
521
|
-
description: "skill tool",
|
|
522
|
-
category: "skill",
|
|
523
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
524
|
-
origin: "skill" as const,
|
|
525
|
-
ownerSkillId: "my-skill-42",
|
|
526
|
-
ownerSkillVersionHash: "sha256-deadbeef",
|
|
527
|
-
executionTarget: "sandbox" as const,
|
|
528
|
-
getDefinition: () => ({
|
|
529
|
-
name,
|
|
530
|
-
description: "skill tool",
|
|
531
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
532
|
-
}),
|
|
533
|
-
execute: async () => fakeToolResult,
|
|
534
|
-
};
|
|
535
|
-
};
|
|
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
|
+
];
|
|
536
479
|
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const result = await executor.execute(
|
|
544
|
-
"skill_tool",
|
|
545
|
-
{ action: "run" },
|
|
546
|
-
makeContext(),
|
|
547
|
-
);
|
|
480
|
+
for (const toolName of readOnlyTools) {
|
|
481
|
+
test(toolName, () => {
|
|
482
|
+
expect(isSideEffectTool(toolName)).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
});
|
|
548
486
|
|
|
549
|
-
|
|
550
|
-
expect(
|
|
551
|
-
|
|
552
|
-
spy.mock.calls[0];
|
|
553
|
-
expect(tool).toBe("skill_tool");
|
|
554
|
-
expect(pattern).toBe("skill_tool:*");
|
|
555
|
-
expect(scope).toBe("/tmp/project");
|
|
556
|
-
expect(decision).toBe("allow");
|
|
557
|
-
expect(options).toBeDefined();
|
|
558
|
-
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);
|
|
559
490
|
});
|
|
560
491
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
+
});
|
|
564
498
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
category: "skill",
|
|
571
|
-
defaultRiskLevel: RiskLevel.High,
|
|
572
|
-
origin: "skill" as const,
|
|
573
|
-
ownerSkillId: "dangerous-skill",
|
|
574
|
-
ownerSkillVersionHash: "sha256-abc",
|
|
575
|
-
executionTarget: "host" as const,
|
|
576
|
-
getDefinition: () => ({
|
|
577
|
-
name,
|
|
578
|
-
description: "high-risk skill tool",
|
|
579
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
580
|
-
}),
|
|
581
|
-
execute: async () => fakeToolResult,
|
|
582
|
-
};
|
|
583
|
-
};
|
|
499
|
+
test("credential_store delete is a side-effect", () => {
|
|
500
|
+
expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
|
|
501
|
+
true,
|
|
502
|
+
);
|
|
503
|
+
});
|
|
584
504
|
|
|
585
|
-
|
|
586
|
-
"
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
);
|
|
590
|
-
const executor = new ToolExecutor(prompter);
|
|
591
|
-
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
|
+
});
|
|
592
510
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
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
|
+
});
|
|
603
520
|
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// forcePromptSideEffects enforcement (PR 30)
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
604
526
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const spy = setupAddRuleSpy();
|
|
527
|
+
describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
528
|
+
let promptCalled: boolean;
|
|
608
529
|
|
|
609
|
-
|
|
530
|
+
beforeEach(() => {
|
|
531
|
+
fakeToolResult = { content: "ok", isError: false };
|
|
532
|
+
lastCheckArgs = undefined;
|
|
610
533
|
getToolOverride = undefined;
|
|
534
|
+
checkResultOverride = undefined;
|
|
535
|
+
checkFnOverride = undefined;
|
|
536
|
+
cachedAssessmentOverride = undefined;
|
|
537
|
+
promptCalled = false;
|
|
538
|
+
});
|
|
611
539
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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());
|
|
618
560
|
const result = await executor.execute(
|
|
619
561
|
"bash",
|
|
620
|
-
{ command: "
|
|
621
|
-
makeContext(),
|
|
562
|
+
{ command: "echo hello" },
|
|
563
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
622
564
|
);
|
|
623
565
|
|
|
624
566
|
expect(result.isError).toBe(false);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
spy.mock.calls[0];
|
|
628
|
-
expect(tool).toBe("bash");
|
|
629
|
-
expect(pattern).toBe("git *");
|
|
630
|
-
expect(scope).toBe("/tmp/project");
|
|
631
|
-
expect(decision).toBe("allow");
|
|
632
|
-
// No options since there's no execution target for core tools
|
|
633
|
-
expect(options).toBeUndefined();
|
|
567
|
+
// The prompter must have been called despite the allow rule
|
|
568
|
+
expect(promptCalled).toBe(true);
|
|
634
569
|
});
|
|
635
570
|
|
|
636
|
-
test("
|
|
637
|
-
checkResultOverride = {
|
|
638
|
-
|
|
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
|
+
};
|
|
639
576
|
|
|
640
|
-
const
|
|
641
|
-
"always_allow",
|
|
642
|
-
undefined,
|
|
643
|
-
"/tmp/project",
|
|
644
|
-
);
|
|
645
|
-
const executor = new ToolExecutor(prompter);
|
|
577
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
646
578
|
const result = await executor.execute(
|
|
647
|
-
"
|
|
648
|
-
{
|
|
649
|
-
makeContext(),
|
|
579
|
+
"bash",
|
|
580
|
+
{ command: "rm -rf /" },
|
|
581
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
650
582
|
);
|
|
651
583
|
|
|
652
|
-
|
|
653
|
-
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);
|
|
654
588
|
});
|
|
655
589
|
|
|
656
|
-
test("
|
|
657
|
-
|
|
658
|
-
|
|
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" };
|
|
659
593
|
|
|
660
|
-
const
|
|
661
|
-
"always_allow",
|
|
662
|
-
"file_read:*",
|
|
663
|
-
undefined,
|
|
664
|
-
);
|
|
665
|
-
const executor = new ToolExecutor(prompter);
|
|
594
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
666
595
|
const result = await executor.execute(
|
|
667
596
|
"file_read",
|
|
668
|
-
{ path: "
|
|
669
|
-
makeContext(),
|
|
597
|
+
{ path: "README.md" },
|
|
598
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
670
599
|
);
|
|
671
600
|
|
|
672
601
|
expect(result.isError).toBe(false);
|
|
673
|
-
|
|
602
|
+
// Prompter should NOT be called — file_read is not a side-effect tool
|
|
603
|
+
expect(promptCalled).toBe(false);
|
|
674
604
|
});
|
|
675
605
|
|
|
676
|
-
test("
|
|
677
|
-
checkResultOverride = { decision: "
|
|
678
|
-
scopeOptionsOverride = [];
|
|
679
|
-
const spy = setupAddRuleSpy();
|
|
606
|
+
test("side-effect tool is auto-allowed when forcePromptSideEffects is false", async () => {
|
|
607
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
680
608
|
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
"
|
|
684
|
-
|
|
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 }),
|
|
685
614
|
);
|
|
686
|
-
const executor = new ToolExecutor(prompter);
|
|
687
|
-
const result = await executor.execute("some_tool", {}, makeContext());
|
|
688
615
|
|
|
689
616
|
expect(result.isError).toBe(false);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
expect(scope).toBe("everywhere");
|
|
617
|
+
// No prompt — standard behavior when forcePromptSideEffects is off
|
|
618
|
+
expect(promptCalled).toBe(false);
|
|
693
619
|
});
|
|
694
620
|
|
|
695
|
-
test("
|
|
696
|
-
checkResultOverride = { decision: "
|
|
697
|
-
const spy = setupAddRuleSpy();
|
|
698
|
-
getToolOverride = undefined;
|
|
621
|
+
test("side-effect tool is auto-allowed when forcePromptSideEffects is undefined", async () => {
|
|
622
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
699
623
|
|
|
700
|
-
const
|
|
701
|
-
"always_allow",
|
|
702
|
-
"sudo *",
|
|
703
|
-
"everywhere",
|
|
704
|
-
);
|
|
705
|
-
const executor = new ToolExecutor(prompter);
|
|
624
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
706
625
|
const result = await executor.execute(
|
|
707
|
-
"
|
|
708
|
-
{
|
|
709
|
-
makeContext(),
|
|
626
|
+
"file_edit",
|
|
627
|
+
{ path: "test.txt", old_string: "a", new_string: "b" },
|
|
628
|
+
makeContext(), // forcePromptSideEffects not set
|
|
710
629
|
);
|
|
711
630
|
|
|
712
631
|
expect(result.isError).toBe(false);
|
|
713
|
-
expect(
|
|
714
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
715
|
-
// Core tools have no executionTarget, so ruleOptions is empty → undefined
|
|
716
|
-
expect(options).toBeUndefined();
|
|
632
|
+
expect(promptCalled).toBe(false);
|
|
717
633
|
});
|
|
718
634
|
|
|
719
|
-
test("
|
|
720
|
-
checkResultOverride = { decision: "
|
|
721
|
-
const spy = setupAddRuleSpy();
|
|
722
|
-
|
|
723
|
-
getToolOverride = (name: string) => {
|
|
724
|
-
if (name === "unknown_tool") return undefined;
|
|
725
|
-
return {
|
|
726
|
-
name,
|
|
727
|
-
description: "host skill tool",
|
|
728
|
-
category: "skill",
|
|
729
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
730
|
-
origin: "skill" as const,
|
|
731
|
-
ownerSkillId: "host-skill",
|
|
732
|
-
ownerSkillVersionHash: "host-hash-v1",
|
|
733
|
-
executionTarget: "host" as const,
|
|
734
|
-
getDefinition: () => ({
|
|
735
|
-
name,
|
|
736
|
-
description: "host skill tool",
|
|
737
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
738
|
-
}),
|
|
739
|
-
execute: async () => fakeToolResult,
|
|
740
|
-
};
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
const prompter = makePrompterWithDecision(
|
|
744
|
-
"always_allow",
|
|
745
|
-
"host_action:*",
|
|
746
|
-
"/tmp/project",
|
|
747
|
-
);
|
|
748
|
-
const executor = new ToolExecutor(prompter);
|
|
749
|
-
const result = await executor.execute(
|
|
750
|
-
"host_action",
|
|
751
|
-
{ action: "click" },
|
|
752
|
-
makeContext(),
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
expect(result.isError).toBe(false);
|
|
756
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
757
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
758
|
-
expect(options).toBeDefined();
|
|
759
|
-
expect(options.executionTarget).toBe("host");
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
describe("ToolExecutor strict mode + high-risk integration (PR 25)", () => {
|
|
764
|
-
beforeEach(() => {
|
|
765
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
766
|
-
lastCheckArgs = undefined;
|
|
767
|
-
getToolOverride = undefined;
|
|
768
|
-
checkResultOverride = undefined;
|
|
769
|
-
checkFnOverride = undefined;
|
|
770
|
-
cachedAssessmentOverride = undefined;
|
|
771
|
-
if (addRuleSpy) {
|
|
772
|
-
addRuleSpy.mockRestore();
|
|
773
|
-
addRuleSpy = undefined;
|
|
774
|
-
}
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
function setupAddRuleSpy() {
|
|
778
|
-
addRuleSpy = spyOn(trustStore, "addRule").mockImplementation(
|
|
779
|
-
(
|
|
780
|
-
tool: string,
|
|
781
|
-
pattern: string,
|
|
782
|
-
scope: string,
|
|
783
|
-
decision = "allow",
|
|
784
|
-
priority = 100,
|
|
785
|
-
options?: { executionTarget?: string },
|
|
786
|
-
) => {
|
|
787
|
-
return {
|
|
788
|
-
id: "spy-rule-id",
|
|
789
|
-
tool,
|
|
790
|
-
pattern,
|
|
791
|
-
scope,
|
|
792
|
-
decision,
|
|
793
|
-
priority,
|
|
794
|
-
createdAt: Date.now(),
|
|
795
|
-
...options,
|
|
796
|
-
} as TrustRule;
|
|
797
|
-
},
|
|
798
|
-
);
|
|
799
|
-
return addRuleSpy;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
test("always_allow creates rule with execution target for high-risk skill tool", async () => {
|
|
803
|
-
checkResultOverride = {
|
|
804
|
-
decision: "prompt",
|
|
805
|
-
reason: "High risk: always requires approval",
|
|
806
|
-
};
|
|
807
|
-
const spy = setupAddRuleSpy();
|
|
808
|
-
|
|
809
|
-
getToolOverride = (name: string) => {
|
|
810
|
-
if (name === "unknown_tool") return undefined;
|
|
811
|
-
return {
|
|
812
|
-
name,
|
|
813
|
-
description: "high-risk skill tool",
|
|
814
|
-
category: "skill",
|
|
815
|
-
defaultRiskLevel: RiskLevel.High,
|
|
816
|
-
origin: "skill" as const,
|
|
817
|
-
ownerSkillId: "deploy-skill",
|
|
818
|
-
ownerSkillVersionHash: "sha256-deploy-v1",
|
|
819
|
-
executionTarget: "host" as const,
|
|
820
|
-
getDefinition: () => ({
|
|
821
|
-
name,
|
|
822
|
-
description: "high-risk skill tool",
|
|
823
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
824
|
-
}),
|
|
825
|
-
execute: async () => fakeToolResult,
|
|
826
|
-
};
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
const prompter = makePrompterWithDecision(
|
|
830
|
-
"always_allow",
|
|
831
|
-
"deploy_tool:*",
|
|
832
|
-
"everywhere",
|
|
833
|
-
);
|
|
834
|
-
const executor = new ToolExecutor(prompter);
|
|
835
|
-
const result = await executor.execute(
|
|
836
|
-
"deploy_tool",
|
|
837
|
-
{ target: "prod" },
|
|
838
|
-
makeContext(),
|
|
839
|
-
);
|
|
840
|
-
|
|
841
|
-
expect(result.isError).toBe(false);
|
|
842
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
843
|
-
const [tool, pattern, scope, decision, _priority, options] =
|
|
844
|
-
spy.mock.calls[0];
|
|
845
|
-
expect(tool).toBe("deploy_tool");
|
|
846
|
-
expect(pattern).toBe("deploy_tool:*");
|
|
847
|
-
expect(scope).toBe("everywhere");
|
|
848
|
-
expect(decision).toBe("allow");
|
|
849
|
-
// The key integration assertion: execution target is captured
|
|
850
|
-
expect(options.executionTarget).toBeDefined();
|
|
851
|
-
expect(options.executionTarget).toBe("host");
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
test("always_allow creates rule with execution target for skill tool", async () => {
|
|
855
|
-
checkResultOverride = { decision: "prompt", reason: "test prompt" };
|
|
856
|
-
const spy = setupAddRuleSpy();
|
|
857
|
-
|
|
858
|
-
getToolOverride = (name: string) => {
|
|
859
|
-
if (name === "unknown_tool") return undefined;
|
|
860
|
-
return {
|
|
861
|
-
name,
|
|
862
|
-
description: "high-risk skill tool",
|
|
863
|
-
category: "skill",
|
|
864
|
-
defaultRiskLevel: RiskLevel.High,
|
|
865
|
-
origin: "skill" as const,
|
|
866
|
-
ownerSkillId: "risky-skill",
|
|
867
|
-
ownerSkillVersionHash: "sha256-risky",
|
|
868
|
-
executionTarget: "sandbox" as const,
|
|
869
|
-
getDefinition: () => ({
|
|
870
|
-
name,
|
|
871
|
-
description: "high-risk skill tool",
|
|
872
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
873
|
-
}),
|
|
874
|
-
execute: async () => fakeToolResult,
|
|
875
|
-
};
|
|
876
|
-
};
|
|
877
|
-
|
|
878
|
-
// User chooses always_allow — rule is created with execution target.
|
|
879
|
-
const prompter = makePrompterWithDecision(
|
|
880
|
-
"always_allow",
|
|
881
|
-
"risky_op:*",
|
|
882
|
-
"/tmp/project",
|
|
883
|
-
);
|
|
884
|
-
const executor = new ToolExecutor(prompter);
|
|
885
|
-
const result = await executor.execute("risky_op", {}, makeContext());
|
|
886
|
-
|
|
887
|
-
expect(result.isError).toBe(false);
|
|
888
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
889
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
890
|
-
expect(options).toBeDefined();
|
|
891
|
-
// executionTarget should be present
|
|
892
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
893
|
-
// Rule should have execution target
|
|
894
|
-
// allowHighRisk is no longer persisted
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
test("executor forwards policyContext to check() for version-bound skill tool", async () => {
|
|
898
|
-
getToolOverride = (name: string) => {
|
|
899
|
-
if (name === "unknown_tool") return undefined;
|
|
900
|
-
return {
|
|
901
|
-
name,
|
|
902
|
-
description: "versioned skill tool",
|
|
903
|
-
category: "skill",
|
|
904
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
905
|
-
origin: "skill" as const,
|
|
906
|
-
ownerSkillId: "versioned-skill",
|
|
907
|
-
ownerSkillVersionHash: "v3:content-hash-xyz",
|
|
908
|
-
executionTarget: "sandbox" as const,
|
|
909
|
-
getDefinition: () => ({
|
|
910
|
-
name,
|
|
911
|
-
description: "versioned skill tool",
|
|
912
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
913
|
-
}),
|
|
914
|
-
execute: async () => fakeToolResult,
|
|
915
|
-
};
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
const executor = new ToolExecutor(makePrompter());
|
|
919
|
-
await executor.execute("versioned_tool", { action: "test" }, makeContext());
|
|
920
|
-
|
|
921
|
-
expect(lastCheckArgs).toBeDefined();
|
|
922
|
-
expect(lastCheckArgs!.policyContext).toEqual({
|
|
923
|
-
conversationId: "conversation-1",
|
|
924
|
-
executionContext: "conversation",
|
|
925
|
-
ephemeralRules: undefined,
|
|
926
|
-
executionTarget: "sandbox",
|
|
927
|
-
});
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
// ── Skill mutation approval regression tests (PR 30) ──────────
|
|
931
|
-
|
|
932
|
-
test("always_allow for skill source write creates rule with execution target", async () => {
|
|
933
|
-
checkResultOverride = {
|
|
934
|
-
decision: "prompt",
|
|
935
|
-
reason: "High risk: always requires approval",
|
|
936
|
-
};
|
|
937
|
-
const spy = setupAddRuleSpy();
|
|
938
|
-
|
|
939
|
-
getToolOverride = (name: string) => {
|
|
940
|
-
if (name === "unknown_tool") return undefined;
|
|
941
|
-
return {
|
|
942
|
-
name,
|
|
943
|
-
description: "skill tool that writes to skill source",
|
|
944
|
-
category: "skill",
|
|
945
|
-
defaultRiskLevel: RiskLevel.High,
|
|
946
|
-
origin: "skill" as const,
|
|
947
|
-
ownerSkillId: "code-editor-skill",
|
|
948
|
-
ownerSkillVersionHash: "sha256-v1-original",
|
|
949
|
-
executionTarget: "sandbox" as const,
|
|
950
|
-
getDefinition: () => ({
|
|
951
|
-
name,
|
|
952
|
-
description: "skill source writer",
|
|
953
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
954
|
-
}),
|
|
955
|
-
execute: async () => fakeToolResult,
|
|
956
|
-
};
|
|
957
|
-
};
|
|
958
|
-
|
|
959
|
-
const prompter = makePrompterWithDecision(
|
|
960
|
-
"always_allow",
|
|
961
|
-
"file_write:*/skills/**",
|
|
962
|
-
"everywhere",
|
|
963
|
-
);
|
|
964
|
-
const executor = new ToolExecutor(prompter);
|
|
965
|
-
const result = await executor.execute(
|
|
966
|
-
"file_write",
|
|
967
|
-
{ path: "/tmp/skills/my-skill/executor.ts" },
|
|
968
|
-
makeContext(),
|
|
969
|
-
);
|
|
970
|
-
|
|
971
|
-
expect(result.isError).toBe(false);
|
|
972
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
973
|
-
const [tool, pattern, scope, decision, , options] = spy.mock.calls[0];
|
|
974
|
-
expect(tool).toBe("file_write");
|
|
975
|
-
expect(pattern).toBe("file_write:*/skills/**");
|
|
976
|
-
expect(scope).toBe("everywhere");
|
|
977
|
-
expect(decision).toBe("allow");
|
|
978
|
-
expect(options.executionTarget).toBeDefined();
|
|
979
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
980
|
-
});
|
|
981
|
-
|
|
982
|
-
test("always_allow for skill source write creates rule with execution target (baseline)", async () => {
|
|
983
|
-
checkResultOverride = {
|
|
984
|
-
decision: "prompt",
|
|
985
|
-
reason: "High risk: always requires approval",
|
|
986
|
-
};
|
|
987
|
-
const spy = setupAddRuleSpy();
|
|
988
|
-
|
|
989
|
-
getToolOverride = (name: string) => {
|
|
990
|
-
if (name === "unknown_tool") return undefined;
|
|
991
|
-
return {
|
|
992
|
-
name,
|
|
993
|
-
description: "skill tool that writes to skill source",
|
|
994
|
-
category: "skill",
|
|
995
|
-
defaultRiskLevel: RiskLevel.High,
|
|
996
|
-
origin: "skill" as const,
|
|
997
|
-
ownerSkillId: "editor-skill",
|
|
998
|
-
ownerSkillVersionHash: "sha256-editor-v1",
|
|
999
|
-
executionTarget: "sandbox" as const,
|
|
1000
|
-
getDefinition: () => ({
|
|
1001
|
-
name,
|
|
1002
|
-
description: "skill source writer",
|
|
1003
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1004
|
-
}),
|
|
1005
|
-
execute: async () => fakeToolResult,
|
|
1006
|
-
};
|
|
1007
|
-
};
|
|
1008
|
-
|
|
1009
|
-
// User chooses always_allow
|
|
1010
|
-
const prompter = makePrompterWithDecision(
|
|
1011
|
-
"always_allow",
|
|
1012
|
-
"file_write:*/skills/**",
|
|
1013
|
-
"/tmp/project",
|
|
1014
|
-
);
|
|
1015
|
-
const executor = new ToolExecutor(prompter);
|
|
1016
|
-
const result = await executor.execute(
|
|
1017
|
-
"file_write",
|
|
1018
|
-
{ path: "/tmp/skills/my-skill/executor.ts" },
|
|
1019
|
-
makeContext(),
|
|
1020
|
-
);
|
|
1021
|
-
|
|
1022
|
-
expect(result.isError).toBe(false);
|
|
1023
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1024
|
-
const [, , , , , options] = spy.mock.calls[0];
|
|
1025
|
-
expect(options).toBeDefined();
|
|
1026
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
1027
|
-
// Execution target is captured from the tool context
|
|
1028
|
-
// allowHighRisk is no longer persisted
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
test("skill version is captured in rule for future version-bound matching", async () => {
|
|
1032
|
-
checkResultOverride = {
|
|
1033
|
-
decision: "prompt",
|
|
1034
|
-
reason: "High risk: always requires approval",
|
|
1035
|
-
};
|
|
1036
|
-
const spy = setupAddRuleSpy();
|
|
1037
|
-
|
|
1038
|
-
getToolOverride = (name: string) => {
|
|
1039
|
-
if (name === "unknown_tool") return undefined;
|
|
1040
|
-
return {
|
|
1041
|
-
name,
|
|
1042
|
-
description: "versioned skill tool",
|
|
1043
|
-
category: "skill",
|
|
1044
|
-
defaultRiskLevel: RiskLevel.High,
|
|
1045
|
-
origin: "skill" as const,
|
|
1046
|
-
ownerSkillId: "versioned-editor",
|
|
1047
|
-
ownerSkillVersionHash: "v3:content-hash-xyz789",
|
|
1048
|
-
executionTarget: "sandbox" as const,
|
|
1049
|
-
getDefinition: () => ({
|
|
1050
|
-
name,
|
|
1051
|
-
description: "versioned skill editor",
|
|
1052
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1053
|
-
}),
|
|
1054
|
-
execute: async () => fakeToolResult,
|
|
1055
|
-
};
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
|
-
const prompter = makePrompterWithDecision(
|
|
1059
|
-
"always_allow",
|
|
1060
|
-
"file_edit:*/skills/**",
|
|
1061
|
-
"everywhere",
|
|
1062
|
-
);
|
|
1063
|
-
const executor = new ToolExecutor(prompter);
|
|
1064
|
-
const result = await executor.execute(
|
|
1065
|
-
"file_edit",
|
|
1066
|
-
{ path: "/tmp/skills/my-skill/SKILL.md" },
|
|
1067
|
-
makeContext(),
|
|
1068
|
-
);
|
|
1069
|
-
|
|
1070
|
-
expect(result.isError).toBe(false);
|
|
1071
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1072
|
-
const [tool, , , , , options] = spy.mock.calls[0];
|
|
1073
|
-
expect(tool).toBe("file_edit");
|
|
1074
|
-
expect(options.executionTarget).toBeDefined();
|
|
1075
|
-
expect(options.executionTarget).toBe("sandbox");
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
test("executor forwards policyContext with version for skill source mutation", async () => {
|
|
1079
|
-
getToolOverride = (name: string) => {
|
|
1080
|
-
if (name === "unknown_tool") return undefined;
|
|
1081
|
-
return {
|
|
1082
|
-
name,
|
|
1083
|
-
description: "skill source editor",
|
|
1084
|
-
category: "skill",
|
|
1085
|
-
defaultRiskLevel: RiskLevel.High,
|
|
1086
|
-
origin: "skill" as const,
|
|
1087
|
-
ownerSkillId: "editor-skill",
|
|
1088
|
-
ownerSkillVersionHash: "sha256-v2-updated",
|
|
1089
|
-
executionTarget: "sandbox" as const,
|
|
1090
|
-
getDefinition: () => ({
|
|
1091
|
-
name,
|
|
1092
|
-
description: "skill editor",
|
|
1093
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1094
|
-
}),
|
|
1095
|
-
execute: async () => fakeToolResult,
|
|
1096
|
-
};
|
|
1097
|
-
};
|
|
1098
|
-
|
|
1099
|
-
const executor = new ToolExecutor(makePrompter());
|
|
1100
|
-
await executor.execute(
|
|
1101
|
-
"file_write",
|
|
1102
|
-
{ path: "/tmp/skills/my-skill/index.ts" },
|
|
1103
|
-
makeContext(),
|
|
1104
|
-
);
|
|
1105
|
-
|
|
1106
|
-
expect(lastCheckArgs).toBeDefined();
|
|
1107
|
-
expect(lastCheckArgs!.policyContext).toEqual({
|
|
1108
|
-
conversationId: "conversation-1",
|
|
1109
|
-
executionContext: "conversation",
|
|
1110
|
-
ephemeralRules: undefined,
|
|
1111
|
-
executionTarget: "sandbox",
|
|
1112
|
-
});
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
test("executor creates rule on always_allow with full context", async () => {
|
|
1116
|
-
checkResultOverride = {
|
|
1117
|
-
decision: "prompt",
|
|
1118
|
-
reason: "High risk: always requires approval",
|
|
1119
|
-
};
|
|
1120
|
-
const spy = setupAddRuleSpy();
|
|
1121
|
-
|
|
1122
|
-
getToolOverride = (name: string) => {
|
|
1123
|
-
if (name === "unknown_tool") return undefined;
|
|
1124
|
-
return {
|
|
1125
|
-
name,
|
|
1126
|
-
description: "admin skill tool",
|
|
1127
|
-
category: "skill",
|
|
1128
|
-
defaultRiskLevel: RiskLevel.High,
|
|
1129
|
-
origin: "skill" as const,
|
|
1130
|
-
ownerSkillId: "admin-skill",
|
|
1131
|
-
ownerSkillVersionHash: "sha256-admin-v2",
|
|
1132
|
-
executionTarget: "host" as const,
|
|
1133
|
-
getDefinition: () => ({
|
|
1134
|
-
name,
|
|
1135
|
-
description: "admin skill tool",
|
|
1136
|
-
input_schema: { type: "object" as const, properties: {} },
|
|
1137
|
-
}),
|
|
1138
|
-
execute: async () => fakeToolResult,
|
|
1139
|
-
};
|
|
1140
|
-
};
|
|
1141
|
-
|
|
1142
|
-
const prompter = makePrompterWithDecision(
|
|
1143
|
-
"always_allow",
|
|
1144
|
-
"admin_action:*",
|
|
1145
|
-
"everywhere",
|
|
1146
|
-
);
|
|
1147
|
-
const executor = new ToolExecutor(prompter);
|
|
1148
|
-
const result = await executor.execute(
|
|
1149
|
-
"admin_action",
|
|
1150
|
-
{ op: "restart" },
|
|
1151
|
-
makeContext(),
|
|
1152
|
-
);
|
|
1153
|
-
|
|
1154
|
-
expect(result.isError).toBe(false);
|
|
1155
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1156
|
-
const [tool, pattern, scope, decision, , options] = spy.mock.calls[0];
|
|
1157
|
-
|
|
1158
|
-
// Verify complete integration of all fields
|
|
1159
|
-
expect(tool).toBe("admin_action");
|
|
1160
|
-
expect(pattern).toBe("admin_action:*");
|
|
1161
|
-
expect(scope).toBe("everywhere");
|
|
1162
|
-
expect(decision).toBe("allow");
|
|
1163
|
-
expect(options.executionTarget).toBeDefined();
|
|
1164
|
-
expect(options.executionTarget).toBe("host");
|
|
1165
|
-
});
|
|
1166
|
-
});
|
|
1167
|
-
|
|
1168
|
-
// ---------------------------------------------------------------------------
|
|
1169
|
-
// isSideEffectTool classifier
|
|
1170
|
-
// ---------------------------------------------------------------------------
|
|
1171
|
-
|
|
1172
|
-
describe("isSideEffectTool", () => {
|
|
1173
|
-
describe("returns true for side-effect tools", () => {
|
|
1174
|
-
const sideEffectTools = [
|
|
1175
|
-
"file_write",
|
|
1176
|
-
"file_edit",
|
|
1177
|
-
"host_file_write",
|
|
1178
|
-
"host_file_edit",
|
|
1179
|
-
"bash",
|
|
1180
|
-
"host_bash",
|
|
1181
|
-
"web_fetch",
|
|
1182
|
-
"document_create",
|
|
1183
|
-
"document_update",
|
|
1184
|
-
"schedule_create",
|
|
1185
|
-
"schedule_update",
|
|
1186
|
-
"schedule_delete",
|
|
1187
|
-
];
|
|
1188
|
-
|
|
1189
|
-
for (const toolName of sideEffectTools) {
|
|
1190
|
-
test(toolName, () => {
|
|
1191
|
-
expect(isSideEffectTool(toolName)).toBe(true);
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
describe("returns false for non-side-effect tools", () => {
|
|
1197
|
-
const readOnlyTools = [
|
|
1198
|
-
"file_read",
|
|
1199
|
-
"memory_recall",
|
|
1200
|
-
"memory_manage",
|
|
1201
|
-
"web_search",
|
|
1202
|
-
"browser_navigate",
|
|
1203
|
-
"browser_click",
|
|
1204
|
-
"browser_type",
|
|
1205
|
-
"browser_press_key",
|
|
1206
|
-
"browser_close",
|
|
1207
|
-
"browser_attach",
|
|
1208
|
-
"browser_detach",
|
|
1209
|
-
"browser_fill_credential",
|
|
1210
|
-
"browser_snapshot",
|
|
1211
|
-
"browser_screenshot",
|
|
1212
|
-
"browser_wait_for",
|
|
1213
|
-
"browser_extract",
|
|
1214
|
-
"skill_load",
|
|
1215
|
-
"schedule_list",
|
|
1216
|
-
];
|
|
1217
|
-
|
|
1218
|
-
for (const toolName of readOnlyTools) {
|
|
1219
|
-
test(toolName, () => {
|
|
1220
|
-
expect(isSideEffectTool(toolName)).toBe(false);
|
|
1221
|
-
});
|
|
1222
|
-
}
|
|
1223
|
-
});
|
|
1224
|
-
|
|
1225
|
-
test("returns false for unknown tool names", () => {
|
|
1226
|
-
expect(isSideEffectTool("nonexistent_tool")).toBe(false);
|
|
1227
|
-
expect(isSideEffectTool("")).toBe(false);
|
|
1228
|
-
});
|
|
1229
|
-
|
|
1230
|
-
describe("action-aware classification for mixed-action tools", () => {
|
|
1231
|
-
test("credential_store store is a side-effect", () => {
|
|
1232
|
-
expect(isSideEffectTool("credential_store", { action: "store" })).toBe(
|
|
1233
|
-
true,
|
|
1234
|
-
);
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
test("credential_store delete is a side-effect", () => {
|
|
1238
|
-
expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
|
|
1239
|
-
true,
|
|
1240
|
-
);
|
|
1241
|
-
});
|
|
1242
|
-
|
|
1243
|
-
test("credential_store prompt is a side-effect", () => {
|
|
1244
|
-
expect(isSideEffectTool("credential_store", { action: "prompt" })).toBe(
|
|
1245
|
-
true,
|
|
1246
|
-
);
|
|
1247
|
-
});
|
|
1248
|
-
|
|
1249
|
-
test("credential_store list is NOT a side-effect", () => {
|
|
1250
|
-
expect(isSideEffectTool("credential_store", { action: "list" })).toBe(
|
|
1251
|
-
false,
|
|
1252
|
-
);
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
test("credential_store without input is NOT a side-effect", () => {
|
|
1256
|
-
expect(isSideEffectTool("credential_store")).toBe(false);
|
|
1257
|
-
});
|
|
1258
|
-
});
|
|
1259
|
-
});
|
|
1260
|
-
|
|
1261
|
-
// Baseline: allow rules can auto-allow file_edit for the guardian persona
|
|
1262
|
-
// today (no forced prompting). The mock check() delegates to
|
|
1263
|
-
// findHighestPriorityRule (via spy) so a regression in trust-rule matching
|
|
1264
|
-
// would cause this test to fail instead of being masked by a blanket
|
|
1265
|
-
// mock-allow.
|
|
1266
|
-
describe("ToolExecutor baseline: allow rule auto-allows file_edit guardian persona", () => {
|
|
1267
|
-
const guardianPersonaPath = "/Users/alice/.vellum/workspace/users/alice.md";
|
|
1268
|
-
let ruleSpy: ReturnType<typeof spyOn> | undefined;
|
|
1269
|
-
|
|
1270
|
-
beforeEach(() => {
|
|
1271
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
1272
|
-
lastCheckArgs = undefined;
|
|
1273
|
-
getToolOverride = undefined;
|
|
1274
|
-
checkResultOverride = undefined;
|
|
1275
|
-
checkFnOverride = undefined;
|
|
1276
|
-
cachedAssessmentOverride = undefined;
|
|
1277
|
-
if (addRuleSpy) {
|
|
1278
|
-
addRuleSpy.mockRestore();
|
|
1279
|
-
addRuleSpy = undefined;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
// Simulate a trust rule that allows file_edit on the guardian's per-user
|
|
1283
|
-
// persona file by stubbing findHighestPriorityRule. This mirrors the
|
|
1284
|
-
// default allow rules that the trust-store creates for the guardian
|
|
1285
|
-
// persona file (see permissions/defaults.ts).
|
|
1286
|
-
ruleSpy = spyOn(trustStore, "findHighestPriorityRule").mockImplementation(
|
|
1287
|
-
(tool: string, commands: string[], _scope: string) => {
|
|
1288
|
-
if (tool !== "file_edit") return null;
|
|
1289
|
-
for (const cmd of commands) {
|
|
1290
|
-
if (cmd === `file_edit:${guardianPersonaPath}`) {
|
|
1291
|
-
return {
|
|
1292
|
-
id: "default:allow-file_edit-guardian-persona",
|
|
1293
|
-
tool: "file_edit",
|
|
1294
|
-
pattern: `file_edit:${guardianPersonaPath}`,
|
|
1295
|
-
scope: "everywhere",
|
|
1296
|
-
decision: "allow" as const,
|
|
1297
|
-
priority: 100,
|
|
1298
|
-
createdAt: Date.now(),
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
return null;
|
|
1303
|
-
},
|
|
1304
|
-
);
|
|
1305
|
-
|
|
1306
|
-
// Wire the mock check() to delegate to findHighestPriorityRule, replicating
|
|
1307
|
-
// the real check() logic for Medium-risk tools (file_edit).
|
|
1308
|
-
checkFnOverride = async (toolName, input, workingDir) => {
|
|
1309
|
-
const filePath =
|
|
1310
|
-
(input.path as string) ?? (input.file_path as string) ?? "";
|
|
1311
|
-
const resolved = filePath.startsWith("/")
|
|
1312
|
-
? filePath
|
|
1313
|
-
: `${workingDir}/${filePath}`;
|
|
1314
|
-
const candidates = [`${toolName}:${resolved}`];
|
|
1315
|
-
const matched = trustStore.findHighestPriorityRule(
|
|
1316
|
-
toolName,
|
|
1317
|
-
candidates,
|
|
1318
|
-
workingDir,
|
|
1319
|
-
);
|
|
1320
|
-
if (matched && matched.decision === "allow") {
|
|
1321
|
-
return {
|
|
1322
|
-
decision: "allow",
|
|
1323
|
-
reason: `Matched trust rule: ${matched.pattern}`,
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
return { decision: "prompt", reason: "Medium risk: requires approval" };
|
|
1327
|
-
};
|
|
1328
|
-
});
|
|
1329
|
-
|
|
1330
|
-
afterEach(() => {
|
|
1331
|
-
checkFnOverride = undefined;
|
|
1332
|
-
if (ruleSpy) {
|
|
1333
|
-
ruleSpy.mockRestore();
|
|
1334
|
-
ruleSpy = undefined;
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
|
|
1338
|
-
test("file_edit to guardian persona is auto-allowed via trust rule", async () => {
|
|
1339
|
-
const executor = new ToolExecutor(makePrompter());
|
|
1340
|
-
const result = await executor.execute(
|
|
1341
|
-
"file_edit",
|
|
1342
|
-
{ path: guardianPersonaPath, content: "hello" },
|
|
1343
|
-
makeContext(),
|
|
1344
|
-
);
|
|
1345
|
-
expect(result.isError).toBe(false);
|
|
1346
|
-
expect(result.content).toBe("ok");
|
|
1347
|
-
// Confirm checker was called with the correct tool name
|
|
1348
|
-
expect(lastCheckArgs).toBeDefined();
|
|
1349
|
-
expect(lastCheckArgs!.toolName).toBe("file_edit");
|
|
1350
|
-
// Confirm findHighestPriorityRule was consulted
|
|
1351
|
-
expect(ruleSpy).toHaveBeenCalled();
|
|
1352
|
-
});
|
|
1353
|
-
|
|
1354
|
-
test("file_edit to a non-guardian-persona path is NOT auto-allowed without a matching rule", async () => {
|
|
1355
|
-
let promptCalled = false;
|
|
1356
|
-
const trackingPrompter = {
|
|
1357
|
-
prompt: async () => {
|
|
1358
|
-
promptCalled = true;
|
|
1359
|
-
return { decision: "allow" as const };
|
|
1360
|
-
},
|
|
1361
|
-
resolveConfirmation: () => {},
|
|
1362
|
-
updateSender: () => {},
|
|
1363
|
-
dispose: () => {},
|
|
1364
|
-
} as unknown as PermissionPrompter;
|
|
1365
|
-
|
|
1366
|
-
const executor = new ToolExecutor(trackingPrompter);
|
|
1367
|
-
const result = await executor.execute(
|
|
1368
|
-
"file_edit",
|
|
1369
|
-
{ path: "/tmp/project/other.md", content: "hello" },
|
|
1370
|
-
makeContext(),
|
|
1371
|
-
);
|
|
1372
|
-
// check() returned 'prompt' (no matching trust rule for other.md),
|
|
1373
|
-
// so the executor must have called the prompter.
|
|
1374
|
-
expect(promptCalled).toBe(true);
|
|
1375
|
-
expect(result.isError).toBe(false);
|
|
1376
|
-
expect(lastCheckArgs).toBeDefined();
|
|
1377
|
-
expect(lastCheckArgs!.toolName).toBe("file_edit");
|
|
1378
|
-
});
|
|
1379
|
-
});
|
|
1380
|
-
|
|
1381
|
-
// ---------------------------------------------------------------------------
|
|
1382
|
-
// forcePromptSideEffects enforcement (PR 30)
|
|
1383
|
-
// ---------------------------------------------------------------------------
|
|
1384
|
-
|
|
1385
|
-
describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
1386
|
-
let promptCalled: boolean;
|
|
1387
|
-
|
|
1388
|
-
beforeEach(() => {
|
|
1389
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
1390
|
-
lastCheckArgs = undefined;
|
|
1391
|
-
getToolOverride = undefined;
|
|
1392
|
-
checkResultOverride = undefined;
|
|
1393
|
-
checkFnOverride = undefined;
|
|
1394
|
-
cachedAssessmentOverride = undefined;
|
|
1395
|
-
promptCalled = false;
|
|
1396
|
-
if (addRuleSpy) {
|
|
1397
|
-
addRuleSpy.mockRestore();
|
|
1398
|
-
addRuleSpy = undefined;
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
/**
|
|
1403
|
-
* Prompter that tracks whether it was called and always allows.
|
|
1404
|
-
*/
|
|
1405
|
-
function makeTrackingPrompter(): PermissionPrompter {
|
|
1406
|
-
return {
|
|
1407
|
-
prompt: async () => {
|
|
1408
|
-
promptCalled = true;
|
|
1409
|
-
return { decision: "allow" as const };
|
|
1410
|
-
},
|
|
1411
|
-
resolveConfirmation: () => {},
|
|
1412
|
-
updateSender: () => {},
|
|
1413
|
-
dispose: () => {},
|
|
1414
|
-
} as unknown as PermissionPrompter;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
test("side-effect tool with allow rule is forced to prompt when forcePromptSideEffects is true", async () => {
|
|
1418
|
-
// check() returns allow (simulating a matched trust rule)
|
|
1419
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1420
|
-
|
|
1421
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1422
|
-
const result = await executor.execute(
|
|
1423
|
-
"bash",
|
|
1424
|
-
{ command: "echo hello" },
|
|
1425
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1426
|
-
);
|
|
1427
|
-
|
|
1428
|
-
expect(result.isError).toBe(false);
|
|
1429
|
-
// The prompter must have been called despite the allow rule
|
|
1430
|
-
expect(promptCalled).toBe(true);
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
test("deny decision is preserved (not converted to prompt) even with forcePromptSideEffects", async () => {
|
|
1434
|
-
checkResultOverride = {
|
|
1435
|
-
decision: "deny",
|
|
1436
|
-
reason: "Policy denies this tool",
|
|
1437
|
-
};
|
|
1438
|
-
|
|
1439
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1440
|
-
const result = await executor.execute(
|
|
1441
|
-
"bash",
|
|
1442
|
-
{ command: "rm -rf /" },
|
|
1443
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1444
|
-
);
|
|
1445
|
-
|
|
1446
|
-
// Should be denied, not prompted
|
|
1447
|
-
expect(result.isError).toBe(true);
|
|
1448
|
-
expect(result.content).toBe("Policy denies this tool");
|
|
1449
|
-
expect(promptCalled).toBe(false);
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
test("non-side-effect tool is unchanged even with forcePromptSideEffects", async () => {
|
|
1453
|
-
// check() returns allow for a read-only tool
|
|
1454
|
-
checkResultOverride = { decision: "allow", reason: "Allowed by default" };
|
|
1455
|
-
|
|
1456
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1457
|
-
const result = await executor.execute(
|
|
1458
|
-
"file_read",
|
|
1459
|
-
{ path: "README.md" },
|
|
1460
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1461
|
-
);
|
|
1462
|
-
|
|
1463
|
-
expect(result.isError).toBe(false);
|
|
1464
|
-
// Prompter should NOT be called — file_read is not a side-effect tool
|
|
1465
|
-
expect(promptCalled).toBe(false);
|
|
1466
|
-
});
|
|
1467
|
-
|
|
1468
|
-
test("side-effect tool is auto-allowed when forcePromptSideEffects is false", async () => {
|
|
1469
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1470
|
-
|
|
1471
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1472
|
-
const result = await executor.execute(
|
|
1473
|
-
"file_write",
|
|
1474
|
-
{ path: "test.txt", content: "data" },
|
|
1475
|
-
makeContext({ forcePromptSideEffects: false }),
|
|
1476
|
-
);
|
|
1477
|
-
|
|
1478
|
-
expect(result.isError).toBe(false);
|
|
1479
|
-
// No prompt — standard behavior when forcePromptSideEffects is off
|
|
1480
|
-
expect(promptCalled).toBe(false);
|
|
1481
|
-
});
|
|
1482
|
-
|
|
1483
|
-
test("side-effect tool is auto-allowed when forcePromptSideEffects is undefined", async () => {
|
|
1484
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1485
|
-
|
|
1486
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1487
|
-
const result = await executor.execute(
|
|
1488
|
-
"file_edit",
|
|
1489
|
-
{ path: "test.txt", old_string: "a", new_string: "b" },
|
|
1490
|
-
makeContext(), // forcePromptSideEffects not set
|
|
1491
|
-
);
|
|
1492
|
-
|
|
1493
|
-
expect(result.isError).toBe(false);
|
|
1494
|
-
expect(promptCalled).toBe(false);
|
|
1495
|
-
});
|
|
1496
|
-
|
|
1497
|
-
test("all side-effect tool types are forced to prompt", async () => {
|
|
1498
|
-
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" };
|
|
1499
637
|
|
|
1500
638
|
const sideEffectTools = [
|
|
1501
639
|
{ name: "file_write", input: { path: "x", content: "y" } },
|
|
@@ -1511,15 +649,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1511
649
|
{ name: "bash", input: { command: "echo hi" } },
|
|
1512
650
|
{ name: "host_bash", input: { command: "echo hi" } },
|
|
1513
651
|
{ name: "web_fetch", input: { url: "https://example.com" } },
|
|
1514
|
-
{ name: "browser_navigate", input: { url: "https://example.com" } },
|
|
1515
|
-
{ name: "browser_click", input: { selector: "#btn" } },
|
|
1516
|
-
{ name: "browser_type", input: { selector: "#input", text: "hello" } },
|
|
1517
|
-
{ name: "browser_press_key", input: { key: "Enter" } },
|
|
1518
|
-
{ name: "browser_close", input: {} },
|
|
1519
|
-
{
|
|
1520
|
-
name: "browser_fill_credential",
|
|
1521
|
-
input: { selector: "#pwd", credential: "test" },
|
|
1522
|
-
},
|
|
1523
652
|
{ name: "document_create", input: { title: "doc", content: "body" } },
|
|
1524
653
|
{ name: "document_update", input: { id: "doc-1", content: "updated" } },
|
|
1525
654
|
{
|
|
@@ -1559,508 +688,133 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1559
688
|
dispose: () => {},
|
|
1560
689
|
} as unknown as PermissionPrompter;
|
|
1561
690
|
|
|
1562
|
-
const executor = new ToolExecutor(countingPrompter);
|
|
1563
|
-
const result = await executor.execute(
|
|
1564
|
-
"bash",
|
|
1565
|
-
{ command: "ls" },
|
|
1566
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1567
|
-
);
|
|
1568
|
-
|
|
1569
|
-
expect(result.isError).toBe(false);
|
|
1570
|
-
// Should only prompt once — forcePromptSideEffects doesn't add a second prompt
|
|
1571
|
-
// when check() already returned 'prompt'
|
|
1572
|
-
expect(promptCount).toBe(1);
|
|
1573
|
-
});
|
|
1574
|
-
|
|
1575
|
-
// ── Guardian persona security invariant (PR 31) ──────────
|
|
1576
|
-
|
|
1577
|
-
test("file_edit to guardian persona forces prompt in private conversation even with matching trust rule", async () => {
|
|
1578
|
-
// This is a key security invariant: the guardian persona file contains
|
|
1579
|
-
// the user's persistent memory. In a private conversation
|
|
1580
|
-
// (forcePromptSideEffects=true), edits to it must always require explicit
|
|
1581
|
-
// approval, even when a trust rule matches.
|
|
1582
|
-
checkResultOverride = {
|
|
1583
|
-
decision: "allow",
|
|
1584
|
-
reason: "Matched trust rule: file_edit:*/users/*.md",
|
|
1585
|
-
};
|
|
1586
|
-
|
|
1587
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1588
|
-
const result = await executor.execute(
|
|
1589
|
-
"file_edit",
|
|
1590
|
-
{
|
|
1591
|
-
path: "/Users/alice/.vellum/workspace/users/alice.md",
|
|
1592
|
-
old_string: "old pref",
|
|
1593
|
-
new_string: "new pref",
|
|
1594
|
-
},
|
|
1595
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1596
|
-
);
|
|
1597
|
-
|
|
1598
|
-
expect(result.isError).toBe(false);
|
|
1599
|
-
// file_edit is a side-effect tool, so forcePromptSideEffects must trigger prompting
|
|
1600
|
-
expect(promptCalled).toBe(true);
|
|
1601
|
-
});
|
|
1602
|
-
|
|
1603
|
-
test("host_file_edit to guardian persona forces prompt in private conversation", async () => {
|
|
1604
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1605
|
-
|
|
1606
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1607
|
-
const result = await executor.execute(
|
|
1608
|
-
"host_file_edit",
|
|
1609
|
-
{
|
|
1610
|
-
path: "/Users/alice/.vellum/workspace/users/alice.md",
|
|
1611
|
-
old_string: "x",
|
|
1612
|
-
new_string: "y",
|
|
1613
|
-
},
|
|
1614
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1615
|
-
);
|
|
1616
|
-
|
|
1617
|
-
expect(result.isError).toBe(false);
|
|
1618
|
-
expect(promptCalled).toBe(true);
|
|
1619
|
-
});
|
|
1620
|
-
|
|
1621
|
-
// ── Always-mutating document tools (PR fix5) ──────────
|
|
1622
|
-
|
|
1623
|
-
test("document_create forces prompt in private conversation", async () => {
|
|
1624
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1625
|
-
|
|
1626
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1627
|
-
const result = await executor.execute(
|
|
1628
|
-
"document_create",
|
|
1629
|
-
{ title: "New Doc", content: "hello" },
|
|
1630
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1631
|
-
);
|
|
1632
|
-
|
|
1633
|
-
expect(result.isError).toBe(false);
|
|
1634
|
-
expect(promptCalled).toBe(true);
|
|
1635
|
-
});
|
|
1636
|
-
|
|
1637
|
-
test("document_update forces prompt in private conversation", async () => {
|
|
1638
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1639
|
-
|
|
1640
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1641
|
-
const result = await executor.execute(
|
|
1642
|
-
"document_update",
|
|
1643
|
-
{ id: "doc-1", content: "updated" },
|
|
1644
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1645
|
-
);
|
|
1646
|
-
|
|
1647
|
-
expect(result.isError).toBe(false);
|
|
1648
|
-
expect(promptCalled).toBe(true);
|
|
1649
|
-
});
|
|
1650
|
-
|
|
1651
|
-
// ── Always-mutating schedule tools (PR fix7) ──────────
|
|
1652
|
-
|
|
1653
|
-
test("schedule_create forces prompt in private conversation", async () => {
|
|
1654
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1655
|
-
|
|
1656
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1657
|
-
const result = await executor.execute(
|
|
1658
|
-
"schedule_create",
|
|
1659
|
-
{ name: "Morning standup", cron: "0 9 * * 1-5" },
|
|
1660
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1661
|
-
);
|
|
1662
|
-
|
|
1663
|
-
expect(result.isError).toBe(false);
|
|
1664
|
-
expect(promptCalled).toBe(true);
|
|
1665
|
-
});
|
|
1666
|
-
|
|
1667
|
-
test("schedule_update forces prompt in private conversation", async () => {
|
|
1668
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1669
|
-
|
|
1670
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1671
|
-
const result = await executor.execute(
|
|
1672
|
-
"schedule_update",
|
|
1673
|
-
{ id: "sched-1", cron: "0 10 * * 1-5" },
|
|
1674
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1675
|
-
);
|
|
1676
|
-
|
|
1677
|
-
expect(result.isError).toBe(false);
|
|
1678
|
-
expect(promptCalled).toBe(true);
|
|
1679
|
-
});
|
|
1680
|
-
|
|
1681
|
-
test("schedule_delete forces prompt in private conversation", async () => {
|
|
1682
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1683
|
-
|
|
1684
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1685
|
-
const result = await executor.execute(
|
|
1686
|
-
"schedule_delete",
|
|
1687
|
-
{ id: "sched-1" },
|
|
1688
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1689
|
-
);
|
|
1690
|
-
|
|
1691
|
-
expect(result.isError).toBe(false);
|
|
1692
|
-
expect(promptCalled).toBe(true);
|
|
1693
|
-
});
|
|
1694
|
-
|
|
1695
|
-
// ── Credential store action-aware (PR fix9) ──────────
|
|
1696
|
-
|
|
1697
|
-
test("credential_store store forces prompt in private conversation", async () => {
|
|
1698
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1699
|
-
|
|
1700
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1701
|
-
const result = await executor.execute(
|
|
1702
|
-
"credential_store",
|
|
1703
|
-
{ action: "store", name: "api-key", value: "sk-secret-123" },
|
|
1704
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1705
|
-
);
|
|
1706
|
-
|
|
1707
|
-
expect(result.isError).toBe(false);
|
|
1708
|
-
expect(promptCalled).toBe(true);
|
|
1709
|
-
});
|
|
1710
|
-
|
|
1711
|
-
test("credential_store delete forces prompt in private conversation", async () => {
|
|
1712
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1713
|
-
|
|
1714
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1715
|
-
const result = await executor.execute(
|
|
1716
|
-
"credential_store",
|
|
1717
|
-
{ action: "delete", name: "api-key" },
|
|
1718
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1719
|
-
);
|
|
1720
|
-
|
|
1721
|
-
expect(result.isError).toBe(false);
|
|
1722
|
-
expect(promptCalled).toBe(true);
|
|
1723
|
-
});
|
|
1724
|
-
|
|
1725
|
-
test("credential_store list does NOT force prompt in private conversation", async () => {
|
|
1726
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1727
|
-
|
|
1728
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1729
|
-
const result = await executor.execute(
|
|
1730
|
-
"credential_store",
|
|
1731
|
-
{ action: "list" },
|
|
1732
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1733
|
-
);
|
|
1734
|
-
|
|
1735
|
-
expect(result.isError).toBe(false);
|
|
1736
|
-
// list is read-only — must NOT trigger forced prompting
|
|
1737
|
-
expect(promptCalled).toBe(false);
|
|
1738
|
-
});
|
|
1739
|
-
|
|
1740
|
-
// ── Workspace mode + forcePromptSideEffects interaction ──────────
|
|
1741
|
-
|
|
1742
|
-
test("workspace mode allow → prompt promotion still works for side-effect tools in private conversations", async () => {
|
|
1743
|
-
// Simulate workspace mode returning 'allow' for a workspace-scoped file_write
|
|
1744
|
-
checkResultOverride = {
|
|
1745
|
-
decision: "allow",
|
|
1746
|
-
reason: "Workspace mode: workspace-scoped operation auto-allowed",
|
|
1747
|
-
};
|
|
1748
|
-
|
|
1749
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1750
|
-
const result = await executor.execute(
|
|
1751
|
-
"file_write",
|
|
1752
|
-
{ path: "/tmp/project/test.txt", content: "data" },
|
|
1753
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1754
|
-
);
|
|
1755
|
-
|
|
1756
|
-
expect(result.isError).toBe(false);
|
|
1757
|
-
// file_write is a side-effect tool, so forcePromptSideEffects must promote
|
|
1758
|
-
// the workspace mode allow → prompt, requiring explicit user approval
|
|
1759
|
-
expect(promptCalled).toBe(true);
|
|
1760
|
-
});
|
|
1761
|
-
|
|
1762
|
-
test("schedule_create forces prompt in private conversation", async () => {
|
|
1763
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1764
|
-
|
|
1765
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1766
|
-
const result = await executor.execute(
|
|
1767
|
-
"schedule_create",
|
|
1768
|
-
{
|
|
1769
|
-
name: "test schedule",
|
|
1770
|
-
expression: "0 9 * * *",
|
|
1771
|
-
message: "test",
|
|
1772
|
-
},
|
|
1773
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1774
|
-
);
|
|
1775
|
-
|
|
1776
|
-
expect(result.isError).toBe(false);
|
|
1777
|
-
expect(promptCalled).toBe(true);
|
|
1778
|
-
});
|
|
1779
|
-
|
|
1780
|
-
test("schedule_list does NOT force prompt in private conversation", async () => {
|
|
1781
|
-
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1782
|
-
|
|
1783
|
-
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1784
|
-
const result = await executor.execute(
|
|
1785
|
-
"schedule_list",
|
|
1786
|
-
{},
|
|
1787
|
-
makeContext({ forcePromptSideEffects: true }),
|
|
1788
|
-
);
|
|
1789
|
-
|
|
1790
|
-
expect(result.isError).toBe(false);
|
|
1791
|
-
// list is read-only — must NOT trigger forced prompting
|
|
1792
|
-
expect(promptCalled).toBe(false);
|
|
1793
|
-
});
|
|
1794
|
-
});
|
|
1795
|
-
|
|
1796
|
-
// ---------------------------------------------------------------------------
|
|
1797
|
-
// persistentDecisionsAllowed contract
|
|
1798
|
-
// ---------------------------------------------------------------------------
|
|
1799
|
-
|
|
1800
|
-
describe("ToolExecutor persistentDecisionsAllowed contract", () => {
|
|
1801
|
-
beforeEach(() => {
|
|
1802
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
1803
|
-
lastCheckArgs = undefined;
|
|
1804
|
-
getToolOverride = undefined;
|
|
1805
|
-
checkResultOverride = {
|
|
1806
|
-
decision: "prompt",
|
|
1807
|
-
reason: "Requires explicit approval",
|
|
1808
|
-
};
|
|
1809
|
-
checkFnOverride = undefined;
|
|
1810
|
-
if (addRuleSpy) {
|
|
1811
|
-
addRuleSpy.mockRestore();
|
|
1812
|
-
addRuleSpy = undefined;
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
|
|
1816
|
-
function setupAddRuleSpy() {
|
|
1817
|
-
addRuleSpy = spyOn(trustStore, "addRule").mockImplementation(
|
|
1818
|
-
(
|
|
1819
|
-
tool: string,
|
|
1820
|
-
pattern: string,
|
|
1821
|
-
scope: string,
|
|
1822
|
-
decision = "allow",
|
|
1823
|
-
priority = 100,
|
|
1824
|
-
options?: { executionTarget?: string },
|
|
1825
|
-
) => {
|
|
1826
|
-
return {
|
|
1827
|
-
id: "spy-rule-id",
|
|
1828
|
-
tool,
|
|
1829
|
-
pattern,
|
|
1830
|
-
scope,
|
|
1831
|
-
decision,
|
|
1832
|
-
priority,
|
|
1833
|
-
createdAt: Date.now(),
|
|
1834
|
-
...options,
|
|
1835
|
-
} as TrustRule;
|
|
1836
|
-
},
|
|
1837
|
-
);
|
|
1838
|
-
return addRuleSpy;
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
test("proxied bash always_allow saves a trust rule (no special-casing)", async () => {
|
|
1842
|
-
const spy = setupAddRuleSpy();
|
|
1843
|
-
|
|
1844
|
-
const prompter = makePrompterWithDecision(
|
|
1845
|
-
"always_allow",
|
|
1846
|
-
"bash:*",
|
|
1847
|
-
"/tmp/project",
|
|
1848
|
-
);
|
|
1849
|
-
const executor = new ToolExecutor(prompter);
|
|
691
|
+
const executor = new ToolExecutor(countingPrompter);
|
|
1850
692
|
const result = await executor.execute(
|
|
1851
693
|
"bash",
|
|
1852
|
-
{ command: "
|
|
1853
|
-
makeContext(),
|
|
694
|
+
{ command: "ls" },
|
|
695
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1854
696
|
);
|
|
1855
697
|
|
|
1856
698
|
expect(result.isError).toBe(false);
|
|
1857
|
-
|
|
699
|
+
// Should only prompt once — forcePromptSideEffects doesn't add a second prompt
|
|
700
|
+
// when check() already returned 'prompt'
|
|
701
|
+
expect(promptCount).toBe(1);
|
|
1858
702
|
});
|
|
1859
703
|
|
|
1860
|
-
|
|
1861
|
-
const spy = setupAddRuleSpy();
|
|
704
|
+
// ── Always-mutating schedule tools ──────────
|
|
1862
705
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
);
|
|
1868
|
-
const executor = new ToolExecutor(prompter);
|
|
706
|
+
test("schedule_delete forces prompt under forcePromptSideEffects", async () => {
|
|
707
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
708
|
+
|
|
709
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1869
710
|
const result = await executor.execute(
|
|
1870
|
-
"
|
|
1871
|
-
{
|
|
1872
|
-
makeContext(),
|
|
711
|
+
"schedule_delete",
|
|
712
|
+
{ id: "sched-1" },
|
|
713
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1873
714
|
);
|
|
1874
715
|
|
|
1875
716
|
expect(result.isError).toBe(false);
|
|
1876
|
-
expect(
|
|
717
|
+
expect(promptCalled).toBe(true);
|
|
1877
718
|
});
|
|
1878
719
|
|
|
1879
|
-
|
|
1880
|
-
const spy = setupAddRuleSpy();
|
|
1881
|
-
|
|
1882
|
-
const prompter = makePrompterWithDecision(
|
|
1883
|
-
"always_deny",
|
|
1884
|
-
"bash:*",
|
|
1885
|
-
"/tmp/project",
|
|
1886
|
-
);
|
|
1887
|
-
const executor = new ToolExecutor(prompter);
|
|
1888
|
-
const result = await executor.execute(
|
|
1889
|
-
"bash",
|
|
1890
|
-
{ command: "curl https://evil.com", network_mode: "proxied" },
|
|
1891
|
-
makeContext(),
|
|
1892
|
-
);
|
|
720
|
+
// ── Credential store action-aware (PR fix9) ──────────
|
|
1893
721
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
});
|
|
722
|
+
test("credential_store store forces prompt under forcePromptSideEffects", async () => {
|
|
723
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1897
724
|
|
|
1898
|
-
|
|
1899
|
-
let capturedEvent: ToolPermissionPromptEvent | undefined;
|
|
1900
|
-
const prompter = makePrompterWithDecision("allow");
|
|
1901
|
-
const executor = new ToolExecutor(prompter);
|
|
725
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1902
726
|
const result = await executor.execute(
|
|
1903
|
-
"
|
|
1904
|
-
{
|
|
1905
|
-
makeContext({
|
|
1906
|
-
onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
|
|
1907
|
-
if (event.type === "permission_prompt") {
|
|
1908
|
-
capturedEvent = event;
|
|
1909
|
-
}
|
|
1910
|
-
},
|
|
1911
|
-
}),
|
|
727
|
+
"credential_store",
|
|
728
|
+
{ action: "store", name: "api-key", value: "sk-secret-123" },
|
|
729
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1912
730
|
);
|
|
1913
731
|
|
|
1914
732
|
expect(result.isError).toBe(false);
|
|
1915
|
-
expect(
|
|
1916
|
-
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
733
|
+
expect(promptCalled).toBe(true);
|
|
1917
734
|
});
|
|
1918
735
|
|
|
1919
|
-
test("
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
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());
|
|
1923
740
|
const result = await executor.execute(
|
|
1924
|
-
"
|
|
1925
|
-
{
|
|
1926
|
-
makeContext({
|
|
1927
|
-
onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
|
|
1928
|
-
if (event.type === "permission_prompt") {
|
|
1929
|
-
capturedEvent = event;
|
|
1930
|
-
}
|
|
1931
|
-
},
|
|
1932
|
-
}),
|
|
741
|
+
"credential_store",
|
|
742
|
+
{ action: "delete", name: "api-key" },
|
|
743
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1933
744
|
);
|
|
1934
745
|
|
|
1935
746
|
expect(result.isError).toBe(false);
|
|
1936
|
-
expect(
|
|
1937
|
-
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
747
|
+
expect(promptCalled).toBe(true);
|
|
1938
748
|
});
|
|
1939
749
|
|
|
1940
|
-
test("
|
|
1941
|
-
|
|
1942
|
-
const prompter = {
|
|
1943
|
-
prompt: async (
|
|
1944
|
-
_toolName: string,
|
|
1945
|
-
_input: Record<string, unknown>,
|
|
1946
|
-
_riskLevel: string,
|
|
1947
|
-
_allowlistOptions: AllowlistOption[],
|
|
1948
|
-
_scopeOptions: ScopeOption[],
|
|
1949
|
-
_diff: unknown,
|
|
1950
|
-
_conversationId: unknown,
|
|
1951
|
-
_executionTarget: unknown,
|
|
1952
|
-
persistentDecisionsAllowed: boolean | undefined,
|
|
1953
|
-
) => {
|
|
1954
|
-
capturedPersistent = persistentDecisionsAllowed;
|
|
1955
|
-
return { decision: "allow" as const };
|
|
1956
|
-
},
|
|
1957
|
-
resolveConfirmation: () => {},
|
|
1958
|
-
updateSender: () => {},
|
|
1959
|
-
dispose: () => {},
|
|
1960
|
-
} as unknown as PermissionPrompter;
|
|
750
|
+
test("credential_store list does NOT force prompt under forcePromptSideEffects", async () => {
|
|
751
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1961
752
|
|
|
1962
|
-
const executor = new ToolExecutor(
|
|
753
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1963
754
|
const result = await executor.execute(
|
|
1964
|
-
"
|
|
1965
|
-
{
|
|
1966
|
-
makeContext(),
|
|
755
|
+
"credential_store",
|
|
756
|
+
{ action: "list" },
|
|
757
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1967
758
|
);
|
|
1968
759
|
|
|
1969
760
|
expect(result.isError).toBe(false);
|
|
1970
|
-
|
|
761
|
+
// list is read-only — must NOT trigger forced prompting
|
|
762
|
+
expect(promptCalled).toBe(false);
|
|
1971
763
|
});
|
|
1972
764
|
|
|
1973
|
-
|
|
1974
|
-
const spy = setupAddRuleSpy();
|
|
1975
|
-
|
|
1976
|
-
// 1. Proxied bash always_allow -> rule IS saved
|
|
1977
|
-
const p1 = makePrompterWithDecision(
|
|
1978
|
-
"always_allow",
|
|
1979
|
-
"bash:curl*",
|
|
1980
|
-
"/tmp/project",
|
|
1981
|
-
);
|
|
1982
|
-
const e1 = new ToolExecutor(p1);
|
|
1983
|
-
const r1 = await e1.execute(
|
|
1984
|
-
"bash",
|
|
1985
|
-
{ command: "curl https://api.example.com", network_mode: "proxied" },
|
|
1986
|
-
makeContext(),
|
|
1987
|
-
);
|
|
1988
|
-
expect(r1.isError).toBe(false);
|
|
1989
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1990
|
-
spy.mockClear();
|
|
1991
|
-
|
|
1992
|
-
// 2. Non-proxied bash always_allow -> rule IS saved
|
|
1993
|
-
const p2 = makePrompterWithDecision(
|
|
1994
|
-
"always_allow",
|
|
1995
|
-
"bash:git*",
|
|
1996
|
-
"/tmp/project",
|
|
1997
|
-
);
|
|
1998
|
-
const e2 = new ToolExecutor(p2);
|
|
1999
|
-
const r2 = await e2.execute("bash", { command: "git push" }, makeContext());
|
|
2000
|
-
expect(r2.isError).toBe(false);
|
|
2001
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
2002
|
-
});
|
|
765
|
+
// ── Workspace mode + forcePromptSideEffects interaction ──────────
|
|
2003
766
|
|
|
2004
|
-
test("
|
|
2005
|
-
|
|
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
|
+
};
|
|
2006
773
|
|
|
2007
|
-
const
|
|
2008
|
-
"always_deny",
|
|
2009
|
-
"bash:rm*",
|
|
2010
|
-
"/tmp/project",
|
|
2011
|
-
);
|
|
2012
|
-
const executor = new ToolExecutor(prompter);
|
|
774
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2013
775
|
const result = await executor.execute(
|
|
2014
|
-
"
|
|
2015
|
-
{
|
|
2016
|
-
makeContext(),
|
|
776
|
+
"file_write",
|
|
777
|
+
{ path: "/tmp/project/test.txt", content: "data" },
|
|
778
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2017
779
|
);
|
|
2018
780
|
|
|
2019
|
-
expect(result.isError).toBe(
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
expect(
|
|
2023
|
-
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);
|
|
2024
785
|
});
|
|
2025
786
|
|
|
2026
|
-
test(
|
|
2027
|
-
|
|
787
|
+
test("schedule_create forces prompt under forcePromptSideEffects", async () => {
|
|
788
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
2028
789
|
|
|
2029
|
-
const
|
|
2030
|
-
"always_deny",
|
|
2031
|
-
"bash:*",
|
|
2032
|
-
"/tmp/project",
|
|
2033
|
-
);
|
|
2034
|
-
const executor = new ToolExecutor(prompter);
|
|
790
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2035
791
|
const result = await executor.execute(
|
|
2036
|
-
"
|
|
2037
|
-
{
|
|
2038
|
-
|
|
792
|
+
"schedule_create",
|
|
793
|
+
{
|
|
794
|
+
name: "test schedule",
|
|
795
|
+
expression: "0 9 * * *",
|
|
796
|
+
message: "test",
|
|
797
|
+
},
|
|
798
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2039
799
|
);
|
|
2040
800
|
|
|
2041
|
-
expect(result.isError).toBe(
|
|
2042
|
-
expect(
|
|
2043
|
-
expect(result.content).toContain("rule was saved");
|
|
801
|
+
expect(result.isError).toBe(false);
|
|
802
|
+
expect(promptCalled).toBe(true);
|
|
2044
803
|
});
|
|
2045
804
|
|
|
2046
|
-
test(
|
|
2047
|
-
|
|
805
|
+
test("schedule_list does NOT force prompt under forcePromptSideEffects", async () => {
|
|
806
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
2048
807
|
|
|
2049
|
-
const
|
|
2050
|
-
"always_deny",
|
|
2051
|
-
"bash:rm*",
|
|
2052
|
-
"/tmp/project",
|
|
2053
|
-
);
|
|
2054
|
-
const executor = new ToolExecutor(prompter);
|
|
808
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2055
809
|
const result = await executor.execute(
|
|
2056
|
-
"
|
|
2057
|
-
{
|
|
2058
|
-
makeContext(),
|
|
810
|
+
"schedule_list",
|
|
811
|
+
{},
|
|
812
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2059
813
|
);
|
|
2060
814
|
|
|
2061
|
-
expect(result.isError).toBe(
|
|
2062
|
-
|
|
2063
|
-
expect(
|
|
815
|
+
expect(result.isError).toBe(false);
|
|
816
|
+
// list is read-only — must NOT trigger forced prompting
|
|
817
|
+
expect(promptCalled).toBe(false);
|
|
2064
818
|
});
|
|
2065
819
|
});
|
|
2066
820
|
|
|
@@ -2136,146 +890,6 @@ describe("buildSanitizedEnv — baseline: credential exclusion", () => {
|
|
|
2136
890
|
});
|
|
2137
891
|
});
|
|
2138
892
|
|
|
2139
|
-
// ---------------------------------------------------------------------------
|
|
2140
|
-
// Persistent-allow lifecycle: roundtrip and auto-allow on subsequent invocation
|
|
2141
|
-
// ---------------------------------------------------------------------------
|
|
2142
|
-
|
|
2143
|
-
describe("ToolExecutor persistent-allow lifecycle", () => {
|
|
2144
|
-
beforeEach(() => {
|
|
2145
|
-
fakeToolResult = { content: "ok", isError: false };
|
|
2146
|
-
lastCheckArgs = undefined;
|
|
2147
|
-
getToolOverride = undefined;
|
|
2148
|
-
checkResultOverride = undefined;
|
|
2149
|
-
checkFnOverride = undefined;
|
|
2150
|
-
cachedAssessmentOverride = undefined;
|
|
2151
|
-
if (addRuleSpy) {
|
|
2152
|
-
addRuleSpy.mockRestore();
|
|
2153
|
-
addRuleSpy = undefined;
|
|
2154
|
-
}
|
|
2155
|
-
});
|
|
2156
|
-
|
|
2157
|
-
function setupAddRuleSpy() {
|
|
2158
|
-
addRuleSpy = spyOn(trustStore, "addRule").mockImplementation(
|
|
2159
|
-
(
|
|
2160
|
-
tool: string,
|
|
2161
|
-
pattern: string,
|
|
2162
|
-
scope: string,
|
|
2163
|
-
decision = "allow",
|
|
2164
|
-
priority = 100,
|
|
2165
|
-
options?: { executionTarget?: string },
|
|
2166
|
-
) => {
|
|
2167
|
-
return {
|
|
2168
|
-
id: "spy-rule-id",
|
|
2169
|
-
tool,
|
|
2170
|
-
pattern,
|
|
2171
|
-
scope,
|
|
2172
|
-
decision,
|
|
2173
|
-
priority,
|
|
2174
|
-
createdAt: Date.now(),
|
|
2175
|
-
...options,
|
|
2176
|
-
} as TrustRule;
|
|
2177
|
-
},
|
|
2178
|
-
);
|
|
2179
|
-
return addRuleSpy;
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
test("persistent-allow roundtrip: always_allow saves rule and allows tool", async () => {
|
|
2183
|
-
// Simulate check() returning 'prompt' so the executor asks the user
|
|
2184
|
-
checkResultOverride = {
|
|
2185
|
-
decision: "prompt",
|
|
2186
|
-
reason: "Medium risk: requires approval",
|
|
2187
|
-
};
|
|
2188
|
-
const spy = setupAddRuleSpy();
|
|
2189
|
-
|
|
2190
|
-
// User responds with always_allow, selecting a pattern and scope
|
|
2191
|
-
const prompter = makePrompterWithDecision(
|
|
2192
|
-
"always_allow",
|
|
2193
|
-
"git *",
|
|
2194
|
-
"/tmp/project",
|
|
2195
|
-
);
|
|
2196
|
-
const executor = new ToolExecutor(prompter);
|
|
2197
|
-
const result = await executor.execute(
|
|
2198
|
-
"bash",
|
|
2199
|
-
{ command: "git status" },
|
|
2200
|
-
makeContext(),
|
|
2201
|
-
);
|
|
2202
|
-
|
|
2203
|
-
// The tool should have been allowed to proceed
|
|
2204
|
-
expect(result.isError).toBe(false);
|
|
2205
|
-
expect(result.content).toBe("ok");
|
|
2206
|
-
|
|
2207
|
-
// addRule should have been called with the correct arguments
|
|
2208
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
2209
|
-
const [tool, pattern, scope, decision] = spy.mock.calls[0];
|
|
2210
|
-
expect(tool).toBe("bash");
|
|
2211
|
-
expect(pattern).toBe("git *");
|
|
2212
|
-
expect(scope).toBe("/tmp/project");
|
|
2213
|
-
expect(decision).toBe("allow");
|
|
2214
|
-
});
|
|
2215
|
-
|
|
2216
|
-
test("auto-allow on subsequent invocation: matching rule skips prompt", async () => {
|
|
2217
|
-
// Simulate a previously saved rule by making check() return 'allow'
|
|
2218
|
-
// with a matched rule (as findHighestPriorityRule would).
|
|
2219
|
-
checkResultOverride = {
|
|
2220
|
-
decision: "allow",
|
|
2221
|
-
reason: "Matched trust rule: git *",
|
|
2222
|
-
};
|
|
2223
|
-
|
|
2224
|
-
let promptCalled = false;
|
|
2225
|
-
const trackingPrompter = {
|
|
2226
|
-
prompt: async () => {
|
|
2227
|
-
promptCalled = true;
|
|
2228
|
-
return { decision: "allow" as const };
|
|
2229
|
-
},
|
|
2230
|
-
resolveConfirmation: () => {},
|
|
2231
|
-
updateSender: () => {},
|
|
2232
|
-
dispose: () => {},
|
|
2233
|
-
} as unknown as PermissionPrompter;
|
|
2234
|
-
|
|
2235
|
-
const executor = new ToolExecutor(trackingPrompter);
|
|
2236
|
-
const result = await executor.execute(
|
|
2237
|
-
"bash",
|
|
2238
|
-
{ command: "git status" },
|
|
2239
|
-
makeContext(),
|
|
2240
|
-
);
|
|
2241
|
-
|
|
2242
|
-
// The tool should be auto-allowed
|
|
2243
|
-
expect(result.isError).toBe(false);
|
|
2244
|
-
expect(result.content).toBe("ok");
|
|
2245
|
-
|
|
2246
|
-
// The prompter should NOT have been called — the rule auto-allowed
|
|
2247
|
-
expect(promptCalled).toBe(false);
|
|
2248
|
-
});
|
|
2249
|
-
|
|
2250
|
-
test("always_allow with everywhere scope saves rule and allows tool", async () => {
|
|
2251
|
-
checkResultOverride = {
|
|
2252
|
-
decision: "prompt",
|
|
2253
|
-
reason: "Medium risk: requires approval",
|
|
2254
|
-
};
|
|
2255
|
-
const spy = setupAddRuleSpy();
|
|
2256
|
-
|
|
2257
|
-
const prompter = makePrompterWithDecision(
|
|
2258
|
-
"always_allow",
|
|
2259
|
-
"file_write:*",
|
|
2260
|
-
"everywhere",
|
|
2261
|
-
);
|
|
2262
|
-
const executor = new ToolExecutor(prompter);
|
|
2263
|
-
const result = await executor.execute(
|
|
2264
|
-
"file_write",
|
|
2265
|
-
{ path: "/tmp/test.txt", content: "hello" },
|
|
2266
|
-
makeContext(),
|
|
2267
|
-
);
|
|
2268
|
-
|
|
2269
|
-
expect(result.isError).toBe(false);
|
|
2270
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
2271
|
-
const [tool, pattern, scope, decision] = spy.mock.calls[0];
|
|
2272
|
-
expect(tool).toBe("file_write");
|
|
2273
|
-
expect(pattern).toBe("file_write:*");
|
|
2274
|
-
expect(scope).toBe("everywhere");
|
|
2275
|
-
expect(decision).toBe("allow");
|
|
2276
|
-
});
|
|
2277
|
-
});
|
|
2278
|
-
|
|
2279
893
|
describe("integration regressions — prompt payload (PR 11)", () => {
|
|
2280
894
|
beforeEach(() => {
|
|
2281
895
|
fakeToolResult = { content: "ok", isError: false };
|
|
@@ -2310,7 +924,7 @@ describe("integration regressions — prompt payload (PR 11)", () => {
|
|
|
2310
924
|
} as unknown as PermissionPrompter;
|
|
2311
925
|
|
|
2312
926
|
const executor = new ToolExecutor(prompter);
|
|
2313
|
-
await executor.execute("bash", { command: "npm install" }, makeContext());
|
|
927
|
+
await executor.execute("bash", { command: "npm install" }, makeContext({ forcePromptSideEffects: true }));
|
|
2314
928
|
|
|
2315
929
|
// Verify that the prompter received allowlist options
|
|
2316
930
|
expect(capturedAllowlist).toBeDefined();
|
|
@@ -2339,10 +953,6 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2339
953
|
checkResultOverride = undefined;
|
|
2340
954
|
checkFnOverride = undefined;
|
|
2341
955
|
cachedAssessmentOverride = undefined;
|
|
2342
|
-
if (addRuleSpy) {
|
|
2343
|
-
addRuleSpy.mockRestore();
|
|
2344
|
-
addRuleSpy = undefined;
|
|
2345
|
-
}
|
|
2346
956
|
});
|
|
2347
957
|
|
|
2348
958
|
test("auto-approved tool result includes risk metadata when classifier assessment exists", async () => {
|
|
@@ -2360,7 +970,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2360
970
|
const result = await executor.execute(
|
|
2361
971
|
"file_read",
|
|
2362
972
|
{ path: "README.md" },
|
|
2363
|
-
makeContext(),
|
|
973
|
+
makeContext({ requireFreshApproval: true }),
|
|
2364
974
|
);
|
|
2365
975
|
|
|
2366
976
|
expect(result.isError).toBe(false);
|
|
@@ -2403,7 +1013,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2403
1013
|
const result = await executor.execute(
|
|
2404
1014
|
"bash",
|
|
2405
1015
|
{ command: "rm -rf /" },
|
|
2406
|
-
makeContext(),
|
|
1016
|
+
makeContext({ requireFreshApproval: true }),
|
|
2407
1017
|
);
|
|
2408
1018
|
|
|
2409
1019
|
expect(result.isError).toBe(true);
|
|
@@ -2433,7 +1043,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2433
1043
|
const result = await executor.execute(
|
|
2434
1044
|
"bash",
|
|
2435
1045
|
{ command: "npm install lodash" },
|
|
2436
|
-
makeContext(),
|
|
1046
|
+
makeContext({ requireFreshApproval: true }),
|
|
2437
1047
|
);
|
|
2438
1048
|
|
|
2439
1049
|
expect(result.isError).toBe(false);
|
|
@@ -2442,4 +1052,83 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2442
1052
|
expect(result.riskReason).toBe("Package manager installation");
|
|
2443
1053
|
expect(result.riskScopeOptions).toHaveLength(2);
|
|
2444
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
|
+
});
|
|
2445
1134
|
});
|