@vellumai/assistant 0.6.6 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +20 -0
- package/ARCHITECTURE.md +46 -38
- package/Dockerfile +27 -6
- package/README.md +9 -11
- package/__tests__/permissions/gateway-threshold-reader.test.ts +83 -149
- package/bun.lock +309 -119
- package/docs/architecture/memory.md +1 -90
- package/docs/architecture/security.md +28 -41
- 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 +9 -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 +887 -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 +86 -0
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +1342 -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 +325 -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/node_modules/@vellumai/slack-text/bun.lock +24 -0
- package/node_modules/@vellumai/slack-text/package.json +18 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
- package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
- package/openapi.yaml +3136 -650
- package/package.json +15 -7
- package/scripts/check-circular-deps.ts +80 -0
- package/scripts/generate-openapi.ts +29 -107
- 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 +283 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -1
- package/src/__tests__/anthropic-provider.test.ts +183 -28
- package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
- package/src/__tests__/app-conversation-ids.test.ts +151 -0
- package/src/__tests__/app-routes-csp.test.ts +106 -55
- package/src/__tests__/approval-cascade.test.ts +3 -370
- 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 +36 -464
- package/src/__tests__/assistant-event-hub.test.ts +126 -77
- package/src/__tests__/assistant-event.test.ts +0 -5
- package/src/__tests__/assistant-events-sse-hardening.test.ts +107 -92
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
- 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 +465 -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-site-routing-provider.test.ts +193 -0
- 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 +88 -344
- package/src/__tests__/channel-approval.test.ts +9 -7
- package/src/__tests__/channel-approvals.test.ts +34 -197
- package/src/__tests__/channel-delivery-store.test.ts +11 -10
- package/src/__tests__/channel-guardian.test.ts +114 -171
- 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 +272 -3933
- 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__/compact-event-conversation-id-guard.test.ts +50 -0
- package/src/__tests__/compaction-events.test.ts +2 -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 +36 -269
- package/src/__tests__/config-watcher.test.ts +12 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -25
- package/src/__tests__/connection-policy.test.ts +1 -52
- 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 +8 -125
- package/src/__tests__/context-image-dimensions.test.ts +1 -1
- 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 +430 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
- package/src/__tests__/context-search-pkb-source.test.ts +493 -0
- package/src/__tests__/context-search-types.test.ts +95 -0
- package/src/__tests__/context-search-workspace-source.test.ts +532 -0
- package/src/__tests__/context-window-manager.test.ts +71 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +10 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +633 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +117 -31
- package/src/__tests__/conversation-agent-loop.test.ts +1004 -15
- package/src/__tests__/conversation-analysis-routes.test.ts +68 -88
- 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 +15 -5
- package/src/__tests__/conversation-clear-safety.test.ts +53 -95
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -330
- package/src/__tests__/conversation-crud-inference-profile.test.ts +54 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +63 -157
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-disk-view.test.ts +5 -4
- package/src/__tests__/conversation-fork-crud.test.ts +26 -55
- package/src/__tests__/conversation-fork-route.test.ts +5 -74
- package/src/__tests__/conversation-history-web-search.test.ts +4 -3
- package/src/__tests__/conversation-inference-profile-list.test.ts +128 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +205 -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 +4 -5
- 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 +79 -3
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -1
- package/src/__tests__/conversation-queue.test.ts +4 -41
- package/src/__tests__/conversation-routes-disk-view.test.ts +55 -188
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +64 -71
- package/src/__tests__/conversation-routes-slash-commands.test.ts +144 -64
- package/src/__tests__/conversation-runtime-assembly.test.ts +295 -84
- package/src/__tests__/conversation-slash-commands.test.ts +30 -47
- package/src/__tests__/conversation-slash-queue.test.ts +2 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-speed-override.test.ts +0 -4
- package/src/__tests__/conversation-starter-routes.test.ts +254 -55
- package/src/__tests__/conversation-starters-cadence.test.ts +2 -2
- package/src/__tests__/conversation-store.test.ts +2 -375
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
- package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -47
- 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 +255 -4
- 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-execution-shell-lockdown.test.ts +0 -39
- package/src/__tests__/credential-health-service.test.ts +68 -0
- package/src/__tests__/credential-security-e2e.test.ts +4 -3
- package/src/__tests__/credential-security-invariants.test.ts +15 -5
- package/src/__tests__/credential-token-resolver.test.ts +180 -0
- package/src/__tests__/credentials-cli.test.ts +45 -21
- package/src/__tests__/cu-unified-flow.test.ts +33 -16
- package/src/__tests__/daemon-assistant-events.test.ts +34 -21
- package/src/__tests__/daemon-credential-client.test.ts +26 -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-connection-isolation.test.ts +125 -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-migration-rollback.test.ts +101 -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__/db-slack-compaction-watermark-migration.test.ts +169 -0
- package/src/__tests__/delete-propagation.test.ts +3 -2
- package/src/__tests__/deterministic-verification-control-plane.test.ts +38 -104
- package/src/__tests__/dm-backfill.test.ts +3 -2
- package/src/__tests__/document-conversations.test.ts +332 -0
- package/src/__tests__/edit-propagation.test.ts +5 -7
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +3 -3
- package/src/__tests__/emit-event-signal.test.ts +4 -6
- package/src/__tests__/empty-response-pipeline.test.ts +1 -1
- package/src/__tests__/events-client-registration.test.ts +441 -0
- package/src/__tests__/file-write-tool.test.ts +2 -4
- package/src/__tests__/filing-service.test.ts +197 -19
- package/src/__tests__/first-greeting.test.ts +156 -150
- package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
- 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__/get-skill-detail-audit.test.ts +3 -8
- 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 +3 -2
- 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 +16 -17
- 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 +41 -92
- package/src/__tests__/guardian-routing-state.test.ts +15 -23
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +274 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +10 -87
- package/src/__tests__/headless-browser-mode.test.ts +4 -9
- package/src/__tests__/headless-browser-navigate.test.ts +21 -20
- package/src/__tests__/heartbeat-service.test.ts +325 -25
- package/src/__tests__/helpers/call-route-handler.ts +72 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +161 -0
- package/src/__tests__/helpers/create-guardian-binding.ts +91 -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-bash-proxy.test.ts +46 -122
- package/src/__tests__/host-browser-e2e-cloud.test.ts +38 -498
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +35 -95
- package/src/__tests__/host-browser-proxy.test.ts +111 -185
- package/src/__tests__/host-browser-routes.test.ts +68 -153
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +35 -31
- package/src/__tests__/host-cu-proxy.test.ts +56 -111
- package/src/__tests__/host-file-proxy.test.ts +44 -98
- package/src/__tests__/host-file-read-tool.test.ts +42 -21
- package/src/__tests__/host-proxy-interface.test.ts +3 -3
- package/src/__tests__/host-shell-tool.test.ts +35 -72
- package/src/__tests__/host-transfer-pending-interactions.test.ts +144 -0
- package/src/__tests__/host-transfer-proxy.test.ts +723 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +18 -15
- package/src/__tests__/inbound-invite-redemption.test.ts +3 -2
- package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
- package/src/__tests__/injector-chain.test.ts +25 -21
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
- package/src/__tests__/inline-command-runner.test.ts +0 -66
- package/src/__tests__/inline-skill-load-permissions.test.ts +41 -208
- package/src/__tests__/install-skill-routing.test.ts +2 -14
- 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-callsite-catalog.test.ts +34 -0
- package/src/__tests__/llm-catalog-parity.test.ts +90 -0
- package/src/__tests__/llm-context-normalization.test.ts +69 -4
- package/src/__tests__/llm-context-resolution.test.ts +180 -0
- 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 +279 -0
- package/src/__tests__/llm-schema.test.ts +57 -1
- package/src/__tests__/llm-usage-store.test.ts +271 -5
- package/src/__tests__/log-export-routes.test.ts +89 -0
- package/src/__tests__/log-export-workspace.test.ts +28 -17
- package/src/__tests__/managed-profile-guard.test.ts +225 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
- package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
- 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__/memory-v2-static-injector.test.ts +95 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +209 -302
- package/src/__tests__/migration-export-http.test.ts +50 -43
- package/src/__tests__/migration-export-streaming.test.ts +18 -10
- package/src/__tests__/migration-export-to-gcs.test.ts +531 -0
- package/src/__tests__/migration-import-commit-http.test.ts +82 -37
- package/src/__tests__/migration-import-from-gcs.test.ts +574 -0
- package/src/__tests__/migration-import-from-url.test.ts +34 -27
- package/src/__tests__/migration-import-preflight-http.test.ts +108 -108
- package/src/__tests__/migration-jobs-status.test.ts +164 -0
- package/src/__tests__/migration-parity-persistence.test.ts +62 -25
- package/src/__tests__/migration-transport.test.ts +115 -23
- package/src/__tests__/migration-validate-http.test.ts +149 -159
- package/src/__tests__/migration-wizard.test.ts +133 -27
- 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 +14 -6
- 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 +22 -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 +44 -28
- 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__/prechat-onboarding-contract.test.ts +31 -7
- package/src/__tests__/pricing.test.ts +218 -5
- package/src/__tests__/process-message-background-slack.test.ts +331 -0
- package/src/__tests__/profiler-routes.test.ts +112 -177
- package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
- package/src/__tests__/provider-send-message-override-profile.test.ts +273 -0
- package/src/__tests__/provider-usage-tracking.test.ts +208 -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 +12 -8
- package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +64 -83
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
- package/src/__tests__/registry.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +37 -17
- package/src/__tests__/require-fresh-approval.test.ts +24 -182
- 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 +15 -17
- package/src/__tests__/runtime-events-sse.test.ts +16 -33
- 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__/search-skills-unified.test.ts +9 -15
- package/src/__tests__/secret-ingress-cli.test.ts +2 -5
- package/src/__tests__/secret-ingress-http.test.ts +36 -23
- package/src/__tests__/secret-onetime-send.test.ts +4 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
- package/src/__tests__/secret-response-routing.test.ts +29 -15
- package/src/__tests__/secret-routes-managed-proxy.test.ts +51 -103
- package/src/__tests__/secret-scanner.test.ts +2 -545
- package/src/__tests__/send-endpoint-busy.test.ts +36 -38
- 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 +36 -69
- package/src/__tests__/shell-credential-ref.test.ts +0 -8
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
- 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__/skill-script-runner-sandbox.test.ts +0 -11
- package/src/__tests__/skill-tool-factory.test.ts +97 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -32
- package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
- package/src/__tests__/slack-inbound-verification.test.ts +12 -64
- 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-notifications.test.ts +57 -47
- package/src/__tests__/subagent-fork-spawn.test.ts +20 -28
- package/src/__tests__/subagent-manager-notify.test.ts +70 -70
- package/src/__tests__/subagent-notify-parent.test.ts +83 -109
- 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__/system-prompt.test.ts +115 -13
- 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 +3 -401
- package/src/__tests__/test-preload.ts +0 -11
- package/src/__tests__/thread-backfill.test.ts +947 -32
- 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 -39
- package/src/__tests__/tool-error-pipeline.test.ts +6 -6
- package/src/__tests__/tool-execute-pipeline.test.ts +6 -14
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +69 -16
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +31 -62
- package/src/__tests__/tool-executor.test.ts +336 -1654
- package/src/__tests__/tool-grant-request-escalation.test.ts +90 -311
- package/src/__tests__/tool-metrics-listener.test.ts +0 -35
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +1 -1
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/tool-trace-listener.test.ts +0 -17
- package/src/__tests__/transfer-progress-screen.test.ts +63 -26
- 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 +5 -151
- package/src/__tests__/trusted-contact-multichannel.test.ts +5 -6
- package/src/__tests__/trusted-contact-verification.test.ts +3 -2
- package/src/__tests__/tts-catalog-parity.test.ts +16 -5
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -1
- package/src/__tests__/twilio-routes.test.ts +25 -66
- package/src/__tests__/usage-attribution.test.ts +247 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -7
- package/src/__tests__/usage-cli.test.ts +143 -0
- package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
- package/src/__tests__/usage-routes.test.ts +223 -90
- package/src/__tests__/user-plugin-loader.test.ts +54 -12
- package/src/__tests__/validation-results-screen.test.ts +39 -16
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +51 -139
- package/src/__tests__/verification-control-plane-policy.test.ts +97 -19
- 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-062-drop-memory-v2-edges-json.test.ts +103 -0
- package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
- package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -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 +252 -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/index.ts +0 -15
- package/src/acp/resolve-agent.test.ts +291 -0
- package/src/acp/resolve-agent.ts +176 -0
- package/src/acp/session-manager.ts +193 -31
- package/src/acp/types.ts +2 -50
- package/src/agent/loop.ts +53 -15
- package/src/agent/message-types.ts +0 -2
- package/src/approvals/AGENTS.md +5 -1
- package/src/approvals/__tests__/guardian-feed-event.test.ts +11 -12
- package/src/approvals/approval-primitive.ts +3 -20
- package/src/approvals/guardian-decision-primitive.ts +37 -68
- package/src/approvals/guardian-request-resolvers.ts +38 -104
- 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 +36 -10
- package/src/backup/__tests__/paths.test.ts +5 -4
- package/src/backup/__tests__/restore.test.ts +45 -28
- package/src/backup/backup-worker.ts +37 -12
- package/src/backup/paths.ts +11 -24
- 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/browser-session/events.ts +0 -9
- 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 -34
- package/src/calls/guardian-action-sweep.ts +9 -25
- package/src/calls/guardian-dispatch.ts +1 -20
- package/src/calls/guardian-question-copy.ts +0 -108
- 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 -42
- package/src/calls/relay-setup-router.ts +2 -2
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/twilio-rest.ts +1 -39
- 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 +141 -77
- package/src/channels/__tests__/types.test.ts +25 -3
- package/src/channels/permission-profiles.ts +2 -72
- package/src/channels/types.ts +25 -44
- 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 +175 -23
- package/src/cli/commands/__tests__/memory-v2.test.ts +382 -0
- package/src/cli/commands/__tests__/task.test.ts +36 -35
- package/src/cli/commands/__tests__/trust.test.ts +236 -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 +18 -48
- package/src/cli/commands/browser.ts +52 -4
- package/src/cli/commands/cache-fs.ts +8 -0
- package/src/cli/commands/cache.ts +157 -84
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/clients.ts +74 -17
- package/src/cli/commands/completions.ts +3 -3
- package/src/cli/commands/contacts.ts +241 -86
- 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 +5 -2
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory-v2.ts +315 -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/pending.ts +102 -0
- 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 +264 -116
- package/src/cli/commands/task.ts +12 -10
- package/src/cli/commands/trust.ts +105 -167
- package/src/cli/commands/ui.ts +3 -3
- package/src/cli/commands/usage.ts +29 -15
- 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 +41 -189
- package/src/cli/lib/ipc-params.ts +22 -0
- package/src/cli/program.ts +6 -0
- package/src/cli.ts +1 -82
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +57 -0
- package/src/config/acp-defaults.ts +40 -0
- package/src/config/acp-schema.ts +1 -1
- package/src/config/assistant-feature-flags.ts +18 -142
- package/src/config/bundled-skills/acp/SKILL.md +44 -16
- package/src/config/bundled-skills/acp/TOOLS.json +45 -1
- package/src/config/bundled-skills/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.json +14 -4
- 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-registry.ts +12 -2
- package/src/config/env.ts +10 -22
- package/src/config/feature-flag-registry.json +38 -46
- package/src/config/llm-callsite-catalog.ts +12 -0
- package/src/config/llm-context-resolution.ts +80 -0
- package/src/config/llm-resolver.ts +90 -36
- package/src/config/loader.ts +9 -12
- package/src/config/schema.ts +5 -228
- package/src/config/schemas/__tests__/filing.test.ts +58 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +187 -0
- package/src/config/schemas/call-site-catalog.ts +271 -0
- package/src/config/schemas/calls.ts +5 -5
- package/src/config/schemas/filing.ts +12 -0
- package/src/config/schemas/host-browser.ts +2 -2
- package/src/config/schemas/inference.ts +1 -3
- package/src/config/schemas/ingress.ts +2 -2
- package/src/config/schemas/llm.ts +82 -12
- package/src/config/schemas/memory-retrieval.ts +2 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory-v2.ts +185 -0
- package/src/config/schemas/memory.ts +2 -0
- package/src/config/schemas/security.ts +1 -102
- package/src/config/schemas/services.ts +52 -13
- package/src/config/schemas/skills.ts +5 -5
- package/src/config/schemas/tts.ts +1 -1
- package/src/config/seed-inference-profiles.ts +117 -0
- package/src/config/skills.ts +1 -91
- package/src/config/types.ts +3 -47
- package/src/contacts/contact-store.ts +2 -19
- package/src/contacts/contacts-write.ts +1 -143
- 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 +45 -6
- 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-execution/process-manager.ts +34 -10
- package/src/credential-health/credential-health-service.ts +22 -17
- package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -13
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +76 -83
- package/src/daemon/__tests__/daemon-skill-host.test.ts +265 -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 +1 -40
- package/src/daemon/conversation-agent-loop-handlers.ts +89 -9
- package/src/daemon/conversation-agent-loop.ts +440 -88
- package/src/daemon/conversation-attachments.ts +5 -81
- package/src/daemon/conversation-error.ts +9 -5
- package/src/daemon/conversation-history.ts +9 -9
- package/src/daemon/conversation-launch.ts +21 -136
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-messaging.ts +2 -1
- package/src/daemon/conversation-notifiers.ts +1 -1
- package/src/daemon/conversation-process.ts +90 -174
- package/src/daemon/conversation-runtime-assembly.ts +245 -164
- package/src/daemon/conversation-slash.ts +50 -164
- package/src/daemon/conversation-store.ts +344 -0
- package/src/daemon/conversation-surfaces.ts +27 -32
- package/src/daemon/conversation-tool-setup.ts +23 -202
- package/src/daemon/conversation-usage.ts +36 -0
- package/src/daemon/conversation.ts +129 -381
- package/src/daemon/daemon-control.ts +4 -72
- package/src/daemon/daemon-skill-host.ts +259 -0
- package/src/daemon/dictation-profile-store.ts +2 -26
- package/src/daemon/external-plugins-bootstrap.ts +67 -13
- package/src/daemon/first-greeting.ts +44 -156
- package/src/daemon/handlers/config-channels.ts +14 -14
- package/src/daemon/handlers/config-embeddings.ts +1 -1
- package/src/daemon/handlers/config-ingress.ts +27 -166
- package/src/daemon/handlers/config-model.test.ts +17 -0
- package/src/daemon/handlers/config-model.ts +8 -53
- package/src/daemon/handlers/config-telegram.ts +6 -53
- package/src/daemon/handlers/config-voice.ts +0 -42
- package/src/daemon/handlers/conversations.ts +32 -345
- package/src/daemon/handlers/recording.ts +27 -159
- package/src/daemon/handlers/shared.ts +50 -99
- package/src/daemon/handlers/skills.ts +55 -114
- package/src/daemon/host-bash-proxy.ts +67 -45
- package/src/daemon/host-browser-proxy.ts +65 -27
- package/src/daemon/host-cu-proxy.ts +40 -39
- package/src/daemon/host-file-proxy.ts +58 -37
- package/src/daemon/host-transfer-proxy.ts +538 -0
- package/src/daemon/lifecycle.ts +71 -272
- 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 +23 -2
- package/src/daemon/message-types/host-bash.ts +1 -0
- package/src/daemon/message-types/host-cu.ts +1 -0
- package/src/daemon/message-types/host-file.ts +1 -0
- package/src/daemon/message-types/host-transfer.ts +42 -0
- package/src/daemon/message-types/integrations.ts +6 -0
- package/src/daemon/message-types/messages.ts +24 -23
- 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 +1 -3
- 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 +479 -0
- package/src/daemon/providers-setup.ts +14 -6
- package/src/daemon/server.ts +58 -1702
- package/src/daemon/shutdown-handlers.ts +3 -3
- package/src/daemon/startup-error.ts +1 -1
- package/src/daemon/tool-side-effects.ts +125 -107
- package/src/daemon/trust-context.ts +45 -0
- package/src/daemon/wake-target-adapter.ts +218 -0
- package/src/email/feature-gate.ts +1 -1
- package/src/events/domain-events.ts +1 -16
- package/src/events/tool-audit-listener.ts +5 -9
- package/src/events/tool-domain-event-publisher.ts +0 -10
- package/src/events/tool-metrics-listener.ts +1 -21
- package/src/events/tool-trace-listener.ts +0 -14
- package/src/filing/filing-service.ts +207 -55
- package/src/followups/followup-store.ts +3 -71
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +93 -21
- package/src/heartbeat/heartbeat-service.ts +55 -16
- package/src/home/__tests__/feed-writer.test.ts +0 -4
- package/src/home/__tests__/phase5-exit-criteria.test.ts +18 -1
- package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
- 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/feed-writer.ts +1 -2
- package/src/home/relationship-state-writer.ts +17 -4
- 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 +72 -58
- 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 +253 -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 +443 -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 +171 -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 +123 -0
- package/src/ipc/skill-ipc-types.ts +54 -0
- package/src/ipc/skill-routes/__tests__/config.test.ts +146 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +388 -0
- package/src/ipc/skill-routes/__tests__/identity.test.ts +62 -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 +120 -0
- package/src/ipc/skill-routes/identity.ts +21 -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 +738 -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 +413 -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 +515 -0
- package/src/mcp/client.ts +2 -2
- package/src/mcp/manager.ts +0 -5
- 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__/fixtures/memory-v2-activation-fixtures.ts +55 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +235 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
- package/src/memory/admin.ts +65 -7
- package/src/memory/app-git-service.ts +0 -46
- package/src/memory/app-store.ts +154 -0
- package/src/memory/attachments-store.ts +20 -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-v2.ts +578 -0
- package/src/memory/context-search/sources/memory.ts +95 -0
- package/src/memory/context-search/sources/pkb.ts +477 -0
- package/src/memory/context-search/sources/workspace.ts +1256 -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 +86 -119
- package/src/memory/conversation-directories.ts +1 -11
- package/src/memory/conversation-disk-view.ts +1 -5
- 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-checkpoints.ts +63 -0
- 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-connection.ts +62 -0
- package/src/memory/db-init.ts +28 -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-backend.ts +3 -21
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-gemini.ts +0 -2
- package/src/memory/embedding-local.ts +6 -6
- package/src/memory/embedding-ollama.ts +6 -6
- package/src/memory/embedding-openai.ts +6 -6
- package/src/memory/embedding-types.ts +21 -0
- package/src/memory/external-conversation-store.ts +1 -1
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +408 -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 +184 -12
- 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/injection.test.ts +2 -2
- package/src/memory/graph/injection.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 -84
- package/src/memory/guardian-approvals.ts +1 -49
- package/src/memory/guardian-rate-limits.ts +1 -1
- package/src/memory/indexer.ts +44 -32
- 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 +54 -63
- 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 -9
- 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 +9 -2
- package/src/memory/jobs-worker.ts +56 -17
- package/src/memory/lifecycle-events-store.ts +1 -1
- package/src/memory/llm-request-log-store.ts +1 -42
- package/src/memory/llm-usage-store.ts +130 -44
- package/src/memory/media-store.ts +1 -1
- package/src/memory/memory-recall-log-store.ts +1 -1
- package/src/memory/memory-v2-activation-log-store.ts +115 -0
- 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/233-document-conversations.ts +54 -0
- package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
- package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
- package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
- package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
- package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +31 -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/raw-query.ts +2 -68
- package/src/memory/schema/acp.ts +30 -0
- package/src/memory/schema/conversations.ts +8 -1
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/infrastructure.ts +26 -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 +7 -18
- package/src/memory/shared-app-links-store.ts +2 -1
- package/src/memory/tool-usage-store.ts +3 -1
- package/src/memory/trace-event-store.ts +2 -1
- package/src/memory/turn-events-store.ts +1 -1
- package/src/memory/usage-buckets.ts +40 -1
- package/src/memory/usage-grouped-buckets.ts +127 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +202 -0
- package/src/memory/v2/__tests__/activation.test.ts +1155 -0
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +483 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +412 -0
- package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
- package/src/memory/v2/__tests__/injection.test.ts +1161 -0
- package/src/memory/v2/__tests__/migration.test.ts +840 -0
- package/src/memory/v2/__tests__/page-store.test.ts +517 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -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 +463 -0
- package/src/memory/v2/__tests__/static-context.test.ts +153 -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 +561 -0
- package/src/memory/v2/backfill-jobs.ts +357 -0
- package/src/memory/v2/consolidation-job.ts +306 -0
- package/src/memory/v2/edge-index.ts +191 -0
- package/src/memory/v2/injection.ts +431 -0
- package/src/memory/v2/migration.ts +647 -0
- package/src/memory/v2/now-text.ts +37 -0
- package/src/memory/v2/page-store.ts +382 -0
- package/src/memory/v2/prompts/consolidation.ts +261 -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 +176 -0
- package/src/memory/v2/static-context.ts +62 -0
- package/src/memory/v2/sweep-job.ts +298 -0
- package/src/memory/v2/types.ts +106 -0
- package/src/memory/validation.ts +0 -11
- package/src/messaging/draft-store.ts +0 -6
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/client.ts +1 -121
- package/src/messaging/providers/index.ts +262 -0
- package/src/messaging/providers/outlook/client.ts +0 -73
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
- package/src/messaging/providers/slack/adapter.ts +122 -21
- package/src/messaging/providers/slack/api.ts +242 -0
- package/src/messaging/providers/slack/backfill.test.ts +95 -6
- package/src/messaging/providers/slack/backfill.ts +89 -11
- package/src/messaging/providers/slack/client.ts +10 -124
- package/src/messaging/providers/slack/message-metadata.ts +13 -3
- package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
- package/src/messaging/providers/slack/render-transcript.ts +126 -25
- package/src/messaging/providers/slack/send.ts +383 -0
- package/src/messaging/providers/slack/types.ts +1 -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.test.ts +8 -0
- package/src/oauth/connection-resolver.ts +6 -5
- package/src/oauth/credential-token-resolver.ts +97 -0
- package/src/oauth/manual-token-connection.ts +30 -34
- package/src/oauth/oauth-store.ts +8 -5
- package/src/outbound-proxy/certs.ts +0 -7
- package/src/outbound-proxy/config.ts +0 -74
- package/src/outbound-proxy/health.ts +0 -44
- package/src/outbound-proxy/index.ts +0 -23
- package/src/permissions/approval-policy.test.ts +149 -132
- package/src/permissions/approval-policy.ts +65 -91
- package/src/permissions/approval-provenance.test.ts +184 -0
- package/src/permissions/approval-provenance.ts +70 -0
- package/src/permissions/checker.test.ts +632 -0
- package/src/permissions/checker.ts +270 -460
- package/src/permissions/gateway-threshold-reader.ts +31 -47
- package/src/permissions/ipc-risk-types.ts +95 -0
- package/src/permissions/prompter.ts +13 -11
- package/src/permissions/risk-types.ts +24 -210
- package/src/permissions/secret-prompter.ts +21 -48
- package/src/permissions/types.ts +49 -46
- package/src/permissions/workspace-policy.ts +1 -8
- package/src/platform/sync-identity.ts +0 -8
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/injectors.ts +87 -23
- 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 +12 -7
- package/src/plugins/defaults/token-estimate.ts +2 -3
- package/src/plugins/registry.ts +61 -1
- package/src/plugins/types.ts +14 -7
- package/src/plugins/user-loader.ts +36 -10
- package/src/prompts/persona-resolver.ts +2 -4
- package/src/prompts/system-prompt.ts +34 -31
- package/src/prompts/templates/BOOTSTRAP.md +52 -6
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/update-bulletin-job.ts +2 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +0 -21
- package/src/providers/__tests__/retry-callsite.test.ts +141 -7
- package/src/providers/anthropic/client.ts +143 -52
- package/src/providers/call-site-routing.ts +49 -6
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/gemini/client.ts +113 -23
- package/src/providers/managed-proxy/context.ts +0 -17
- package/src/providers/model-catalog.ts +188 -27
- package/src/providers/model-intents.ts +7 -8
- package/src/providers/openai/chat-completions-provider.ts +43 -7
- package/src/providers/openai/responses-provider.ts +46 -5
- package/src/providers/openrouter/client.ts +4 -5
- package/src/providers/provider-env-vars.ts +4 -12
- package/src/providers/provider-send-message.ts +61 -13
- package/src/providers/ratelimit.ts +7 -2
- package/src/providers/registry.ts +15 -10
- package/src/providers/retry.ts +148 -31
- 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 +35 -10
- package/src/providers/usage-tracking.ts +96 -0
- package/src/runtime/AGENTS.md +16 -11
- package/src/runtime/__tests__/agent-wake.test.ts +122 -9
- 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 +211 -68
- package/src/runtime/approval-conversation-turn.ts +2 -15
- package/src/runtime/approval-message-composer.ts +11 -60
- package/src/runtime/assistant-event-hub.ts +541 -45
- package/src/runtime/assistant-event.ts +16 -69
- 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/middleware.ts +5 -5
- package/src/runtime/auth/route-policy.ts +205 -12
- package/src/runtime/auth/token-service.ts +1 -111
- package/src/runtime/capability-tokens.ts +89 -313
- package/src/runtime/channel-approval-types.ts +1 -6
- package/src/runtime/channel-approvals.ts +13 -81
- 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/channel-verification-service.ts +3 -5
- 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 +1 -32
- package/src/runtime/http-router.ts +54 -8
- package/src/runtime/http-server.ts +362 -1187
- package/src/runtime/http-types.ts +20 -98
- package/src/runtime/interactive-ui-types.ts +145 -0
- package/src/runtime/interactive-ui.ts +37 -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/auth.ts +0 -20
- 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__/v1-test-helpers.ts +112 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +18 -2
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
- package/src/runtime/migrations/job-registry.ts +281 -0
- package/src/runtime/migrations/migration-transport.ts +46 -13
- package/src/runtime/migrations/migration-wizard.ts +2 -2
- package/src/runtime/migrations/origin-mode.ts +40 -0
- package/src/runtime/migrations/vbundle-builder.ts +133 -80
- package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
- package/src/runtime/migrations/vbundle-importer.ts +8 -8
- package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -16
- package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
- package/src/runtime/migrations/vbundle-tar-stream.ts +11 -3
- package/src/runtime/migrations/vbundle-validator.ts +214 -41
- package/src/runtime/nl-approval-parser.ts +16 -21
- package/src/runtime/pending-interactions.ts +42 -16
- package/src/runtime/routes/__tests__/acp-routes.test.ts +394 -0
- package/src/runtime/routes/__tests__/backup-routes.test.ts +232 -339
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +72 -4
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
- 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/__tests__/user-route-dispatcher.test.ts +7 -7
- package/src/runtime/routes/access-request-decision.ts +25 -50
- package/src/runtime/routes/acp-routes.test.ts +368 -0
- package/src/runtime/routes/acp-routes.ts +392 -170
- package/src/runtime/routes/app-management-routes.ts +475 -662
- package/src/runtime/routes/app-routes.ts +192 -177
- package/src/runtime/routes/approval-routes.ts +163 -440
- 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 +81 -76
- 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 +127 -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-guardian-routes.ts +1 -5
- package/src/runtime/routes/channel-readiness-routes.ts +79 -120
- package/src/runtime/routes/channel-route-definitions.ts +62 -0
- package/src/runtime/routes/channel-route-shared.ts +15 -45
- package/src/runtime/routes/channel-verification-routes.ts +207 -187
- package/src/runtime/routes/client-routes.ts +81 -0
- package/src/runtime/routes/consolidation-routes.ts +115 -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 +248 -0
- package/src/runtime/routes/conversation-management-routes.ts +591 -717
- package/src/runtime/routes/conversation-query-routes.ts +621 -459
- package/src/runtime/routes/conversation-routes.ts +396 -792
- package/src/runtime/routes/conversation-starter-routes.ts +137 -108
- 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 +163 -117
- package/src/runtime/routes/errors.ts +132 -0
- package/src/runtime/routes/events-routes.ts +126 -119
- package/src/runtime/routes/filing-routes.ts +80 -76
- 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 +100 -171
- 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 +45 -54
- package/src/runtime/routes/host-browser-routes.ts +44 -99
- package/src/runtime/routes/host-cu-routes.ts +80 -71
- package/src/runtime/routes/host-file-routes.ts +53 -62
- package/src/runtime/routes/host-transfer-routes.ts +216 -0
- package/src/runtime/routes/http-adapter.ts +172 -0
- package/src/runtime/routes/identity-routes.ts +161 -85
- package/src/runtime/routes/inbound-conversation.ts +11 -18
- package/src/runtime/routes/inbound-message-handler.ts +639 -232
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +81 -226
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -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/index.ts +201 -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 +50 -71
- 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-call-sites-routes.ts +22 -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 +188 -0
- package/src/runtime/routes/migration-rollback-routes.ts +167 -212
- package/src/runtime/routes/migration-routes.ts +1037 -377
- package/src/runtime/routes/notification-routes.ts +199 -70
- package/src/runtime/routes/oauth-apps.ts +254 -251
- package/src/runtime/routes/oauth-providers.ts +66 -57
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +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 -27
- 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 +189 -270
- package/src/runtime/routes/rename-conversation-routes.ts +85 -0
- package/src/runtime/routes/schedule-routes.ts +239 -246
- package/src/runtime/routes/secret-routes.ts +305 -282
- package/src/runtime/routes/secrets-deps.ts +24 -0
- package/src/runtime/routes/settings-routes.ts +370 -449
- package/src/runtime/routes/skills-routes.ts +417 -471
- 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 +275 -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 +61 -244
- 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 +150 -198
- package/src/runtime/routes/usage-routes.ts +222 -171
- package/src/runtime/routes/user-routes.ts +88 -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 +419 -437
- 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 +82 -120
- package/src/runtime/services/analyze-conversation.ts +18 -55
- package/src/runtime/services/conversation-serializer.ts +179 -0
- package/src/runtime/trust-context-resolver.ts +3 -2
- package/src/runtime/verification-outbound-actions.ts +14 -50
- package/src/runtime/verification-rate-limiter.ts +1 -1
- package/src/schedule/schedule-store.ts +64 -18
- 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/security/secret-scanner.ts +14 -547
- package/src/security/secure-keys.ts +31 -11
- package/src/security/token-manager.ts +7 -3
- 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/cancel.ts +16 -25
- package/src/signals/conversation-undo.ts +2 -27
- package/src/signals/emit-event.ts +1 -2
- package/src/signals/event-stream.ts +1 -1
- package/src/signals/user-message.ts +108 -22
- package/src/skills/catalog-cache.ts +7 -0
- package/src/skills/catalog-files.ts +0 -5
- package/src/skills/catalog-install.ts +29 -18
- package/src/skills/category-inference.ts +0 -11
- package/src/skills/clawhub.ts +4 -4
- package/src/skills/inline-command-runner.ts +1 -7
- 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 +94 -107
- 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 +2 -29
- package/src/telemetry/types.ts +6 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
- package/src/telemetry/usage-telemetry-reporter.ts +3 -5
- 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 +378 -0
- package/src/tools/acp/spawn.ts +142 -62
- package/src/tools/acp/steer.test.ts +100 -0
- package/src/tools/acp/steer.ts +38 -0
- package/src/tools/background-tool-registry.ts +98 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
- package/src/tools/browser/browser-execution.ts +38 -127
- package/src/tools/browser/browser-manager.ts +1 -8
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
- 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/factory.ts +48 -76
- package/src/tools/browser/cdp-client/index.ts +1 -14
- 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 +53 -45
- package/src/tools/host-filesystem/edit.ts +3 -2
- package/src/tools/host-filesystem/read.ts +3 -2
- package/src/tools/host-filesystem/transfer.test.ts +271 -0
- package/src/tools/host-filesystem/transfer.ts +235 -0
- package/src/tools/host-filesystem/write.ts +3 -2
- package/src/tools/host-terminal/host-shell.ts +192 -13
- package/src/tools/mcp/mcp-tool-factory.ts +1 -1
- package/src/tools/memory/register.test.ts +161 -1
- package/src/tools/memory/register.ts +19 -34
- package/src/tools/network/script-proxy/index.ts +1 -10
- package/src/tools/permission-checker.ts +84 -220
- package/src/tools/policy-context.ts +1 -8
- package/src/tools/registry.ts +16 -1
- 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/skills/sandbox-runner.ts +1 -6
- package/src/tools/skills/skill-tool-factory.ts +32 -0
- package/src/tools/subagent/spawn.ts +35 -11
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/terminal/shell.ts +142 -88
- package/src/tools/tool-approval-handler.ts +4 -70
- package/src/tools/tool-input-summary.ts +10 -0
- package/src/tools/types.ts +136 -183
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/tts/__tests__/provider-catalog.test.ts +2 -2
- package/src/tts/provider-catalog.ts +1 -1
- package/src/usage/actors.ts +2 -1
- package/src/usage/attribution.ts +185 -0
- package/src/usage/pricing.ts +166 -0
- package/src/usage/types.ts +14 -0
- package/src/util/debounce.ts +0 -21
- package/src/util/errors.ts +0 -8
- package/src/util/json.ts +13 -0
- package/src/util/log-redact.ts +0 -1
- package/src/util/logger.ts +3 -3
- package/src/util/platform.ts +85 -124
- package/src/util/pricing.ts +158 -8
- package/src/watcher/engine.ts +42 -20
- package/src/watcher/watcher-store.ts +2 -1
- package/src/work-items/work-item-runner.ts +15 -42
- 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/050-seed-main-agent-opus-callsite.ts +4 -3
- 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 +37 -0
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
- package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
- package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
- package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +1 -1
- package/src/workspace/migrations/registry.ts +26 -0
- package/src/workspace/migrations/runner.ts +2 -2
- package/src/workspace/provider-commit-message-generator.ts +4 -4
- 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__/sandbox-diagnostics.test.ts +0 -138
- package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
- package/src/__tests__/secret-detection-handler.test.ts +0 -74
- package/src/__tests__/secret-scanner-executor.test.ts +0 -451
- 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__/tcc-sandbox-deny.test.ts +0 -198
- package/src/__tests__/terminal-sandbox.test.ts +0 -374
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -354
- package/src/__tests__/tool-notification-listener.test.ts +0 -65
- 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/context/__tests__/microcompact.test.ts +0 -805
- package/src/context/microcompact.ts +0 -443
- 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/handlers/slack-channel-oauth-install.ts +0 -197
- package/src/daemon/message-types/trust.ts +0 -71
- package/src/daemon/pairing-store.ts +0 -229
- package/src/events/tool-notification-listener.ts +0 -17
- 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/__tests__/chrome-extension-registry.test.ts +0 -518
- package/src/runtime/__tests__/client-registry.test.ts +0 -293
- 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/chrome-extension-registry.ts +0 -368
- package/src/runtime/client-registry.ts +0 -261
- 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/inbound-stages/verification-intercept.ts +0 -336
- 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/secret-detection-handler.ts +0 -359
- package/src/tools/terminal/backends/native.ts +0 -327
- package/src/tools/terminal/backends/types.ts +0 -37
- package/src/tools/terminal/parser.ts +0 -623
- package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
- package/src/tools/terminal/sandbox.ts +0 -40
- 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,27 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
afterAll,
|
|
3
|
-
afterEach,
|
|
4
|
-
beforeEach,
|
|
5
|
-
describe,
|
|
6
|
-
expect,
|
|
7
|
-
mock,
|
|
8
|
-
spyOn,
|
|
9
|
-
test,
|
|
10
|
-
} from "bun:test";
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
2
|
|
|
12
3
|
import type {
|
|
13
4
|
AllowlistOption,
|
|
14
5
|
PolicyContext,
|
|
15
6
|
ScopeOption,
|
|
16
|
-
TrustRule,
|
|
17
7
|
} from "../permissions/types.js";
|
|
18
8
|
import { RiskLevel } from "../permissions/types.js";
|
|
19
|
-
import type {
|
|
20
|
-
Tool,
|
|
21
|
-
ToolExecutionResult,
|
|
22
|
-
ToolLifecycleEvent,
|
|
23
|
-
ToolPermissionPromptEvent,
|
|
24
|
-
} from "../tools/types.js";
|
|
9
|
+
import type { Tool, ToolExecutionResult } from "../tools/types.js";
|
|
25
10
|
|
|
26
11
|
const mockConfig = {
|
|
27
12
|
provider: "anthropic",
|
|
@@ -47,12 +32,8 @@ const mockConfig = {
|
|
|
47
32
|
rateLimit: { maxRequestsPerMinute: 0 },
|
|
48
33
|
secretDetection: {
|
|
49
34
|
enabled: false,
|
|
50
|
-
action: "warn" as const,
|
|
51
|
-
entropyThreshold: 4.0,
|
|
52
|
-
},
|
|
53
|
-
permissions: {
|
|
54
|
-
mode: "workspace" as const,
|
|
55
35
|
},
|
|
36
|
+
permissions: {},
|
|
56
37
|
};
|
|
57
38
|
|
|
58
39
|
let fakeToolResult: ToolExecutionResult = { content: "ok", isError: false };
|
|
@@ -92,13 +73,11 @@ let cachedAssessmentOverride:
|
|
|
92
73
|
riskLevel: string;
|
|
93
74
|
reason: string;
|
|
94
75
|
scopeOptions: Array<{ pattern: string; label: string }>;
|
|
76
|
+
directoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
95
77
|
matchType: string;
|
|
96
78
|
}
|
|
97
79
|
| undefined;
|
|
98
80
|
|
|
99
|
-
/** Spy on addRule to capture calls without replacing the real implementation. */
|
|
100
|
-
let addRuleSpy: ReturnType<typeof spyOn> | undefined;
|
|
101
|
-
|
|
102
81
|
mock.module("../config/loader.js", () => ({
|
|
103
82
|
getConfig: () => mockConfig,
|
|
104
83
|
loadConfig: () => mockConfig,
|
|
@@ -169,12 +148,7 @@ mock.module("../tools/shared/filesystem/path-policy.js", () => ({
|
|
|
169
148
|
hostPolicy: () => ({ ok: false }),
|
|
170
149
|
}));
|
|
171
150
|
|
|
172
|
-
mock.module("../tools/terminal/sandbox.js", () => ({
|
|
173
|
-
wrapCommand: () => ({ command: "", sandboxed: false }),
|
|
174
|
-
}));
|
|
175
|
-
|
|
176
151
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
177
|
-
import * as trustStore from "../permissions/trust-store.js";
|
|
178
152
|
import { isSideEffectTool, ToolExecutor } from "../tools/executor.js";
|
|
179
153
|
import type { ToolContext } from "../tools/types.js";
|
|
180
154
|
|
|
@@ -196,10 +170,6 @@ function makePrompter(): PermissionPrompter {
|
|
|
196
170
|
} as unknown as PermissionPrompter;
|
|
197
171
|
}
|
|
198
172
|
|
|
199
|
-
afterAll(() => {
|
|
200
|
-
mock.restore();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
173
|
describe("ToolExecutor allowedToolNames gating", () => {
|
|
204
174
|
beforeEach(() => {
|
|
205
175
|
fakeToolResult = { content: "ok", isError: false };
|
|
@@ -208,10 +178,6 @@ describe("ToolExecutor allowedToolNames gating", () => {
|
|
|
208
178
|
checkResultOverride = undefined;
|
|
209
179
|
checkFnOverride = undefined;
|
|
210
180
|
cachedAssessmentOverride = undefined;
|
|
211
|
-
if (addRuleSpy) {
|
|
212
|
-
addRuleSpy.mockRestore();
|
|
213
|
-
addRuleSpy = undefined;
|
|
214
|
-
}
|
|
215
181
|
});
|
|
216
182
|
|
|
217
183
|
test("executes normally when allowedToolNames is not set", async () => {
|
|
@@ -285,10 +251,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
285
251
|
checkResultOverride = undefined;
|
|
286
252
|
checkFnOverride = undefined;
|
|
287
253
|
cachedAssessmentOverride = undefined;
|
|
288
|
-
if (addRuleSpy) {
|
|
289
|
-
addRuleSpy.mockRestore();
|
|
290
|
-
addRuleSpy = undefined;
|
|
291
|
-
}
|
|
292
254
|
});
|
|
293
255
|
|
|
294
256
|
test("passes PolicyContext with executionTarget for skill-origin tools", async () => {
|
|
@@ -316,7 +278,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
316
278
|
const result = await executor.execute(
|
|
317
279
|
"skill_tool",
|
|
318
280
|
{ action: "run" },
|
|
319
|
-
makeContext(),
|
|
281
|
+
makeContext({ requireFreshApproval: true }),
|
|
320
282
|
);
|
|
321
283
|
|
|
322
284
|
expect(result.isError).toBe(false);
|
|
@@ -324,7 +286,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
324
286
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
325
287
|
conversationId: "conversation-1",
|
|
326
288
|
executionContext: "conversation",
|
|
327
|
-
ephemeralRules: undefined,
|
|
328
289
|
executionTarget: "sandbox",
|
|
329
290
|
});
|
|
330
291
|
});
|
|
@@ -337,7 +298,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
337
298
|
const result = await executor.execute(
|
|
338
299
|
"file_read",
|
|
339
300
|
{ path: "test.txt" },
|
|
340
|
-
makeContext(),
|
|
301
|
+
makeContext({ requireFreshApproval: true }),
|
|
341
302
|
);
|
|
342
303
|
|
|
343
304
|
expect(result.isError).toBe(false);
|
|
@@ -345,7 +306,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
345
306
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
346
307
|
conversationId: "conversation-1",
|
|
347
308
|
executionContext: "conversation",
|
|
348
|
-
ephemeralRules: undefined,
|
|
349
309
|
});
|
|
350
310
|
});
|
|
351
311
|
|
|
@@ -371,7 +331,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
371
331
|
const result = await executor.execute(
|
|
372
332
|
"file_read",
|
|
373
333
|
{ path: "test.txt" },
|
|
374
|
-
makeContext(),
|
|
334
|
+
makeContext({ requireFreshApproval: true }),
|
|
375
335
|
);
|
|
376
336
|
|
|
377
337
|
expect(result.isError).toBe(false);
|
|
@@ -379,7 +339,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
379
339
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
380
340
|
conversationId: "conversation-1",
|
|
381
341
|
executionContext: "conversation",
|
|
382
|
-
ephemeralRules: undefined,
|
|
383
342
|
});
|
|
384
343
|
});
|
|
385
344
|
|
|
@@ -408,7 +367,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
408
367
|
const result = await executor.execute(
|
|
409
368
|
"host_skill_tool",
|
|
410
369
|
{ action: "run" },
|
|
411
|
-
makeContext(),
|
|
370
|
+
makeContext({ requireFreshApproval: true }),
|
|
412
371
|
);
|
|
413
372
|
|
|
414
373
|
expect(result.isError).toBe(false);
|
|
@@ -416,7 +375,6 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
416
375
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
417
376
|
conversationId: "conversation-1",
|
|
418
377
|
executionContext: "conversation",
|
|
419
|
-
ephemeralRules: undefined,
|
|
420
378
|
executionTarget: "host",
|
|
421
379
|
});
|
|
422
380
|
});
|
|
@@ -442,1060 +400,229 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
442
400
|
};
|
|
443
401
|
|
|
444
402
|
const executor = new ToolExecutor(makePrompter());
|
|
445
|
-
const result = await executor.execute(
|
|
403
|
+
const result = await executor.execute(
|
|
404
|
+
"no_target_tool",
|
|
405
|
+
{},
|
|
406
|
+
makeContext({ requireFreshApproval: true }),
|
|
407
|
+
);
|
|
446
408
|
|
|
447
409
|
expect(result.isError).toBe(false);
|
|
448
410
|
expect(lastCheckArgs).toBeDefined();
|
|
449
411
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
450
412
|
conversationId: "conversation-1",
|
|
451
413
|
executionContext: "conversation",
|
|
452
|
-
ephemeralRules: undefined,
|
|
453
414
|
executionTarget: undefined,
|
|
454
415
|
});
|
|
455
416
|
});
|
|
456
417
|
});
|
|
457
418
|
|
|
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
|
-
}
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// isSideEffectTool classifier
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
473
422
|
|
|
474
|
-
describe("
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
423
|
+
describe("isSideEffectTool", () => {
|
|
424
|
+
describe("returns true for side-effect tools", () => {
|
|
425
|
+
const sideEffectTools = [
|
|
426
|
+
"file_write",
|
|
427
|
+
"file_edit",
|
|
428
|
+
"host_file_write",
|
|
429
|
+
"host_file_edit",
|
|
430
|
+
"bash",
|
|
431
|
+
"host_bash",
|
|
432
|
+
"web_fetch",
|
|
433
|
+
"document_create",
|
|
434
|
+
"document_update",
|
|
435
|
+
"schedule_create",
|
|
436
|
+
"schedule_update",
|
|
437
|
+
"schedule_delete",
|
|
438
|
+
];
|
|
439
|
+
|
|
440
|
+
for (const toolName of sideEffectTools) {
|
|
441
|
+
test(toolName, () => {
|
|
442
|
+
expect(isSideEffectTool(toolName)).toBe(true);
|
|
443
|
+
});
|
|
485
444
|
}
|
|
486
445
|
});
|
|
487
446
|
|
|
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
|
-
};
|
|
447
|
+
describe("returns false for non-side-effect tools", () => {
|
|
448
|
+
const readOnlyTools = [
|
|
449
|
+
"file_read",
|
|
450
|
+
"memory_recall",
|
|
451
|
+
"memory_manage",
|
|
452
|
+
"web_search",
|
|
453
|
+
"browser_navigate",
|
|
454
|
+
"browser_click",
|
|
455
|
+
"browser_type",
|
|
456
|
+
"browser_press_key",
|
|
457
|
+
"browser_close",
|
|
458
|
+
"browser_attach",
|
|
459
|
+
"browser_detach",
|
|
460
|
+
"browser_fill_credential",
|
|
461
|
+
"browser_snapshot",
|
|
462
|
+
"browser_screenshot",
|
|
463
|
+
"browser_wait_for",
|
|
464
|
+
"browser_extract",
|
|
465
|
+
"skill_load",
|
|
466
|
+
"schedule_list",
|
|
467
|
+
];
|
|
536
468
|
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const result = await executor.execute(
|
|
544
|
-
"skill_tool",
|
|
545
|
-
{ action: "run" },
|
|
546
|
-
makeContext(),
|
|
547
|
-
);
|
|
469
|
+
for (const toolName of readOnlyTools) {
|
|
470
|
+
test(toolName, () => {
|
|
471
|
+
expect(isSideEffectTool(toolName)).toBe(false);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
});
|
|
548
475
|
|
|
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");
|
|
476
|
+
test("returns false for unknown tool names", () => {
|
|
477
|
+
expect(isSideEffectTool("nonexistent_tool")).toBe(false);
|
|
478
|
+
expect(isSideEffectTool("")).toBe(false);
|
|
559
479
|
});
|
|
560
480
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
481
|
+
describe("action-aware classification for mixed-action tools", () => {
|
|
482
|
+
test("credential_store store is a side-effect", () => {
|
|
483
|
+
expect(isSideEffectTool("credential_store", { action: "store" })).toBe(
|
|
484
|
+
true,
|
|
485
|
+
);
|
|
486
|
+
});
|
|
564
487
|
|
|
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
|
-
};
|
|
488
|
+
test("credential_store delete is a side-effect", () => {
|
|
489
|
+
expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
|
|
490
|
+
true,
|
|
491
|
+
);
|
|
492
|
+
});
|
|
584
493
|
|
|
585
|
-
|
|
586
|
-
"
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
);
|
|
590
|
-
const executor = new ToolExecutor(prompter);
|
|
591
|
-
const result = await executor.execute("risky_tool", {}, makeContext());
|
|
494
|
+
test("credential_store prompt is a side-effect", () => {
|
|
495
|
+
expect(isSideEffectTool("credential_store", { action: "prompt" })).toBe(
|
|
496
|
+
true,
|
|
497
|
+
);
|
|
498
|
+
});
|
|
592
499
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
expect(options.executionTarget).toBe("host");
|
|
500
|
+
test("credential_store list is NOT a side-effect", () => {
|
|
501
|
+
expect(isSideEffectTool("credential_store", { action: "list" })).toBe(
|
|
502
|
+
false,
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("credential_store without input is NOT a side-effect", () => {
|
|
507
|
+
expect(isSideEffectTool("credential_store")).toBe(false);
|
|
508
|
+
});
|
|
603
509
|
});
|
|
510
|
+
});
|
|
604
511
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// forcePromptSideEffects enforcement (PR 30)
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
608
515
|
|
|
609
|
-
|
|
516
|
+
describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
517
|
+
let promptCalled: boolean;
|
|
518
|
+
|
|
519
|
+
beforeEach(() => {
|
|
520
|
+
fakeToolResult = { content: "ok", isError: false };
|
|
521
|
+
lastCheckArgs = undefined;
|
|
610
522
|
getToolOverride = undefined;
|
|
523
|
+
checkResultOverride = undefined;
|
|
524
|
+
checkFnOverride = undefined;
|
|
525
|
+
cachedAssessmentOverride = undefined;
|
|
526
|
+
promptCalled = false;
|
|
527
|
+
});
|
|
611
528
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Prompter that tracks whether it was called and always allows.
|
|
531
|
+
*/
|
|
532
|
+
function makeTrackingPrompter(): PermissionPrompter {
|
|
533
|
+
return {
|
|
534
|
+
prompt: async () => {
|
|
535
|
+
promptCalled = true;
|
|
536
|
+
return { decision: "allow" as const };
|
|
537
|
+
},
|
|
538
|
+
resolveConfirmation: () => {},
|
|
539
|
+
updateSender: () => {},
|
|
540
|
+
dispose: () => {},
|
|
541
|
+
} as unknown as PermissionPrompter;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
test("side-effect tool with allow rule is forced to prompt when forcePromptSideEffects is true", async () => {
|
|
545
|
+
// check() returns allow (simulating a matched trust rule)
|
|
546
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
547
|
+
|
|
548
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
618
549
|
const result = await executor.execute(
|
|
619
550
|
"bash",
|
|
620
|
-
{ command: "
|
|
621
|
-
makeContext(),
|
|
551
|
+
{ command: "echo hello" },
|
|
552
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
622
553
|
);
|
|
623
554
|
|
|
624
555
|
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();
|
|
556
|
+
// The prompter must have been called despite the allow rule
|
|
557
|
+
expect(promptCalled).toBe(true);
|
|
634
558
|
});
|
|
635
559
|
|
|
636
|
-
test("
|
|
637
|
-
checkResultOverride = {
|
|
638
|
-
|
|
560
|
+
test("deny decision is preserved (not converted to prompt) even with forcePromptSideEffects", async () => {
|
|
561
|
+
checkResultOverride = {
|
|
562
|
+
decision: "deny",
|
|
563
|
+
reason: "Policy denies this tool",
|
|
564
|
+
};
|
|
639
565
|
|
|
640
|
-
const
|
|
641
|
-
"always_allow",
|
|
642
|
-
undefined,
|
|
643
|
-
"/tmp/project",
|
|
644
|
-
);
|
|
645
|
-
const executor = new ToolExecutor(prompter);
|
|
566
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
646
567
|
const result = await executor.execute(
|
|
647
|
-
"
|
|
648
|
-
{
|
|
649
|
-
makeContext(),
|
|
568
|
+
"bash",
|
|
569
|
+
{ command: "rm -rf /" },
|
|
570
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
650
571
|
);
|
|
651
572
|
|
|
652
|
-
|
|
653
|
-
expect(
|
|
573
|
+
// Should be denied, not prompted
|
|
574
|
+
expect(result.isError).toBe(true);
|
|
575
|
+
expect(result.content).toBe("Policy denies this tool");
|
|
576
|
+
expect(promptCalled).toBe(false);
|
|
654
577
|
});
|
|
655
578
|
|
|
656
|
-
test("
|
|
657
|
-
|
|
658
|
-
|
|
579
|
+
test("non-side-effect tool is unchanged even with forcePromptSideEffects", async () => {
|
|
580
|
+
// check() returns allow for a read-only tool
|
|
581
|
+
checkResultOverride = { decision: "allow", reason: "Allowed by default" };
|
|
659
582
|
|
|
660
|
-
const
|
|
661
|
-
"always_allow",
|
|
662
|
-
"file_read:*",
|
|
663
|
-
undefined,
|
|
664
|
-
);
|
|
665
|
-
const executor = new ToolExecutor(prompter);
|
|
583
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
666
584
|
const result = await executor.execute(
|
|
667
585
|
"file_read",
|
|
668
|
-
{ path: "
|
|
669
|
-
makeContext(),
|
|
586
|
+
{ path: "README.md" },
|
|
587
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
670
588
|
);
|
|
671
589
|
|
|
672
590
|
expect(result.isError).toBe(false);
|
|
673
|
-
|
|
591
|
+
// Prompter should NOT be called — file_read is not a side-effect tool
|
|
592
|
+
expect(promptCalled).toBe(false);
|
|
674
593
|
});
|
|
675
594
|
|
|
676
|
-
test("
|
|
677
|
-
checkResultOverride = { decision: "
|
|
678
|
-
scopeOptionsOverride = [];
|
|
679
|
-
const spy = setupAddRuleSpy();
|
|
595
|
+
test("side-effect tool is auto-allowed when forcePromptSideEffects is false", async () => {
|
|
596
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
680
597
|
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
"
|
|
684
|
-
|
|
598
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
599
|
+
const result = await executor.execute(
|
|
600
|
+
"file_write",
|
|
601
|
+
{ path: "test.txt", content: "data" },
|
|
602
|
+
makeContext({ forcePromptSideEffects: false }),
|
|
685
603
|
);
|
|
686
|
-
const executor = new ToolExecutor(prompter);
|
|
687
|
-
const result = await executor.execute("some_tool", {}, makeContext());
|
|
688
604
|
|
|
689
605
|
expect(result.isError).toBe(false);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
expect(scope).toBe("everywhere");
|
|
606
|
+
// No prompt — standard behavior when forcePromptSideEffects is off
|
|
607
|
+
expect(promptCalled).toBe(false);
|
|
693
608
|
});
|
|
694
609
|
|
|
695
|
-
test("
|
|
696
|
-
checkResultOverride = { decision: "
|
|
697
|
-
const spy = setupAddRuleSpy();
|
|
698
|
-
getToolOverride = undefined;
|
|
610
|
+
test("side-effect tool is auto-allowed when forcePromptSideEffects is undefined", async () => {
|
|
611
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
699
612
|
|
|
700
|
-
const
|
|
701
|
-
"always_allow",
|
|
702
|
-
"sudo *",
|
|
703
|
-
"everywhere",
|
|
704
|
-
);
|
|
705
|
-
const executor = new ToolExecutor(prompter);
|
|
613
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
706
614
|
const result = await executor.execute(
|
|
707
|
-
"
|
|
708
|
-
{
|
|
709
|
-
makeContext(),
|
|
615
|
+
"file_edit",
|
|
616
|
+
{ path: "test.txt", old_string: "a", new_string: "b" },
|
|
617
|
+
makeContext(), // forcePromptSideEffects not set
|
|
710
618
|
);
|
|
711
619
|
|
|
712
620
|
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();
|
|
621
|
+
expect(promptCalled).toBe(false);
|
|
717
622
|
});
|
|
718
623
|
|
|
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" };
|
|
624
|
+
test("all side-effect tool types are forced to prompt", async () => {
|
|
625
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1499
626
|
|
|
1500
627
|
const sideEffectTools = [
|
|
1501
628
|
{ name: "file_write", input: { path: "x", content: "y" } },
|
|
@@ -1511,15 +638,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1511
638
|
{ name: "bash", input: { command: "echo hi" } },
|
|
1512
639
|
{ name: "host_bash", input: { command: "echo hi" } },
|
|
1513
640
|
{ 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
641
|
{ name: "document_create", input: { title: "doc", content: "body" } },
|
|
1524
642
|
{ name: "document_update", input: { id: "doc-1", content: "updated" } },
|
|
1525
643
|
{
|
|
@@ -1557,510 +675,135 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1557
675
|
resolveConfirmation: () => {},
|
|
1558
676
|
updateSender: () => {},
|
|
1559
677
|
dispose: () => {},
|
|
1560
|
-
} as unknown as PermissionPrompter;
|
|
1561
|
-
|
|
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);
|
|
1850
|
-
const result = await executor.execute(
|
|
1851
|
-
"bash",
|
|
1852
|
-
{ command: "curl https://example.com", network_mode: "proxied" },
|
|
1853
|
-
makeContext(),
|
|
1854
|
-
);
|
|
1855
|
-
|
|
1856
|
-
expect(result.isError).toBe(false);
|
|
1857
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
1858
|
-
});
|
|
1859
|
-
|
|
1860
|
-
test("non-proxied bash always_allow saves a trust rule", async () => {
|
|
1861
|
-
const spy = setupAddRuleSpy();
|
|
678
|
+
} as unknown as PermissionPrompter;
|
|
1862
679
|
|
|
1863
|
-
const
|
|
1864
|
-
"always_allow",
|
|
1865
|
-
"bash:*",
|
|
1866
|
-
"/tmp/project",
|
|
1867
|
-
);
|
|
1868
|
-
const executor = new ToolExecutor(prompter);
|
|
680
|
+
const executor = new ToolExecutor(countingPrompter);
|
|
1869
681
|
const result = await executor.execute(
|
|
1870
682
|
"bash",
|
|
1871
|
-
{ command: "
|
|
1872
|
-
makeContext(),
|
|
683
|
+
{ command: "ls" },
|
|
684
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1873
685
|
);
|
|
1874
686
|
|
|
1875
687
|
expect(result.isError).toBe(false);
|
|
1876
|
-
|
|
688
|
+
// Should only prompt once — forcePromptSideEffects doesn't add a second prompt
|
|
689
|
+
// when check() already returned 'prompt'
|
|
690
|
+
expect(promptCount).toBe(1);
|
|
1877
691
|
});
|
|
1878
692
|
|
|
1879
|
-
|
|
1880
|
-
const spy = setupAddRuleSpy();
|
|
693
|
+
// ── Always-mutating schedule tools ──────────
|
|
1881
694
|
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
);
|
|
1887
|
-
const executor = new ToolExecutor(prompter);
|
|
695
|
+
test("schedule_delete forces prompt under forcePromptSideEffects", async () => {
|
|
696
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
697
|
+
|
|
698
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1888
699
|
const result = await executor.execute(
|
|
1889
|
-
"
|
|
1890
|
-
{
|
|
1891
|
-
makeContext(),
|
|
700
|
+
"schedule_delete",
|
|
701
|
+
{ id: "sched-1" },
|
|
702
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1892
703
|
);
|
|
1893
704
|
|
|
1894
|
-
expect(result.isError).toBe(
|
|
1895
|
-
expect(
|
|
705
|
+
expect(result.isError).toBe(false);
|
|
706
|
+
expect(promptCalled).toBe(true);
|
|
1896
707
|
});
|
|
1897
708
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
709
|
+
// ── Credential store action-aware (PR fix9) ──────────
|
|
710
|
+
|
|
711
|
+
test("credential_store store forces prompt under forcePromptSideEffects", async () => {
|
|
712
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
713
|
+
|
|
714
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1902
715
|
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
|
-
}),
|
|
716
|
+
"credential_store",
|
|
717
|
+
{ action: "store", name: "api-key", value: "sk-secret-123" },
|
|
718
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1912
719
|
);
|
|
1913
720
|
|
|
1914
721
|
expect(result.isError).toBe(false);
|
|
1915
|
-
expect(
|
|
1916
|
-
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
722
|
+
expect(promptCalled).toBe(true);
|
|
1917
723
|
});
|
|
1918
724
|
|
|
1919
|
-
test("
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
const executor = new ToolExecutor(
|
|
725
|
+
test("credential_store delete forces prompt under forcePromptSideEffects", async () => {
|
|
726
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
727
|
+
|
|
728
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1923
729
|
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
|
-
}),
|
|
730
|
+
"credential_store",
|
|
731
|
+
{ action: "delete", name: "api-key" },
|
|
732
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1933
733
|
);
|
|
1934
734
|
|
|
1935
735
|
expect(result.isError).toBe(false);
|
|
1936
|
-
expect(
|
|
1937
|
-
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
736
|
+
expect(promptCalled).toBe(true);
|
|
1938
737
|
});
|
|
1939
738
|
|
|
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;
|
|
739
|
+
test("credential_store list does NOT force prompt under forcePromptSideEffects", async () => {
|
|
740
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1961
741
|
|
|
1962
|
-
const executor = new ToolExecutor(
|
|
742
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1963
743
|
const result = await executor.execute(
|
|
1964
|
-
"
|
|
1965
|
-
{
|
|
1966
|
-
makeContext(),
|
|
744
|
+
"credential_store",
|
|
745
|
+
{ action: "list" },
|
|
746
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
1967
747
|
);
|
|
1968
748
|
|
|
1969
749
|
expect(result.isError).toBe(false);
|
|
1970
|
-
|
|
750
|
+
// list is read-only — must NOT trigger forced prompting
|
|
751
|
+
expect(promptCalled).toBe(false);
|
|
1971
752
|
});
|
|
1972
753
|
|
|
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
|
-
});
|
|
754
|
+
// ── Workspace mode + forcePromptSideEffects interaction ──────────
|
|
2003
755
|
|
|
2004
|
-
test("
|
|
2005
|
-
|
|
756
|
+
test("workspace mode allow → prompt promotion still works for side-effect tools under forcePromptSideEffects", async () => {
|
|
757
|
+
// Simulate workspace mode returning 'allow' for a workspace-scoped file_write
|
|
758
|
+
checkResultOverride = {
|
|
759
|
+
decision: "allow",
|
|
760
|
+
reason: "Workspace-scoped low-risk operation auto-allowed",
|
|
761
|
+
};
|
|
2006
762
|
|
|
2007
|
-
const
|
|
2008
|
-
"always_deny",
|
|
2009
|
-
"bash:rm*",
|
|
2010
|
-
"/tmp/project",
|
|
2011
|
-
);
|
|
2012
|
-
const executor = new ToolExecutor(prompter);
|
|
763
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2013
764
|
const result = await executor.execute(
|
|
2014
|
-
"
|
|
2015
|
-
{
|
|
2016
|
-
makeContext(),
|
|
765
|
+
"file_write",
|
|
766
|
+
{ path: "/tmp/project/test.txt", content: "data" },
|
|
767
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2017
768
|
);
|
|
2018
769
|
|
|
2019
|
-
expect(result.isError).toBe(
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
expect(
|
|
2023
|
-
expect(spy.mock.calls[0][3]).toBe("deny");
|
|
770
|
+
expect(result.isError).toBe(false);
|
|
771
|
+
// file_write is a side-effect tool, so forcePromptSideEffects must promote
|
|
772
|
+
// the workspace mode allow → prompt, requiring explicit user approval
|
|
773
|
+
expect(promptCalled).toBe(true);
|
|
2024
774
|
});
|
|
2025
775
|
|
|
2026
|
-
test(
|
|
2027
|
-
|
|
776
|
+
test("schedule_create forces prompt under forcePromptSideEffects", async () => {
|
|
777
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
2028
778
|
|
|
2029
|
-
const
|
|
2030
|
-
"always_deny",
|
|
2031
|
-
"bash:*",
|
|
2032
|
-
"/tmp/project",
|
|
2033
|
-
);
|
|
2034
|
-
const executor = new ToolExecutor(prompter);
|
|
779
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2035
780
|
const result = await executor.execute(
|
|
2036
|
-
"
|
|
2037
|
-
{
|
|
2038
|
-
|
|
781
|
+
"schedule_create",
|
|
782
|
+
{
|
|
783
|
+
name: "test schedule",
|
|
784
|
+
expression: "0 9 * * *",
|
|
785
|
+
message: "test",
|
|
786
|
+
},
|
|
787
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2039
788
|
);
|
|
2040
789
|
|
|
2041
|
-
expect(result.isError).toBe(
|
|
2042
|
-
expect(
|
|
2043
|
-
expect(result.content).toContain("rule was saved");
|
|
790
|
+
expect(result.isError).toBe(false);
|
|
791
|
+
expect(promptCalled).toBe(true);
|
|
2044
792
|
});
|
|
2045
793
|
|
|
2046
|
-
test(
|
|
2047
|
-
|
|
794
|
+
test("schedule_list does NOT force prompt under forcePromptSideEffects", async () => {
|
|
795
|
+
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
2048
796
|
|
|
2049
|
-
const
|
|
2050
|
-
"always_deny",
|
|
2051
|
-
"bash:rm*",
|
|
2052
|
-
"/tmp/project",
|
|
2053
|
-
);
|
|
2054
|
-
const executor = new ToolExecutor(prompter);
|
|
797
|
+
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
2055
798
|
const result = await executor.execute(
|
|
2056
|
-
"
|
|
2057
|
-
{
|
|
2058
|
-
makeContext(),
|
|
799
|
+
"schedule_list",
|
|
800
|
+
{},
|
|
801
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
2059
802
|
);
|
|
2060
803
|
|
|
2061
|
-
expect(result.isError).toBe(
|
|
2062
|
-
|
|
2063
|
-
expect(
|
|
804
|
+
expect(result.isError).toBe(false);
|
|
805
|
+
// list is read-only — must NOT trigger forced prompting
|
|
806
|
+
expect(promptCalled).toBe(false);
|
|
2064
807
|
});
|
|
2065
808
|
});
|
|
2066
809
|
|
|
@@ -2136,146 +879,6 @@ describe("buildSanitizedEnv — baseline: credential exclusion", () => {
|
|
|
2136
879
|
});
|
|
2137
880
|
});
|
|
2138
881
|
|
|
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
882
|
describe("integration regressions — prompt payload (PR 11)", () => {
|
|
2280
883
|
beforeEach(() => {
|
|
2281
884
|
fakeToolResult = { content: "ok", isError: false };
|
|
@@ -2310,7 +913,11 @@ describe("integration regressions — prompt payload (PR 11)", () => {
|
|
|
2310
913
|
} as unknown as PermissionPrompter;
|
|
2311
914
|
|
|
2312
915
|
const executor = new ToolExecutor(prompter);
|
|
2313
|
-
await executor.execute(
|
|
916
|
+
await executor.execute(
|
|
917
|
+
"bash",
|
|
918
|
+
{ command: "npm install" },
|
|
919
|
+
makeContext({ forcePromptSideEffects: true }),
|
|
920
|
+
);
|
|
2314
921
|
|
|
2315
922
|
// Verify that the prompter received allowlist options
|
|
2316
923
|
expect(capturedAllowlist).toBeDefined();
|
|
@@ -2339,10 +946,6 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2339
946
|
checkResultOverride = undefined;
|
|
2340
947
|
checkFnOverride = undefined;
|
|
2341
948
|
cachedAssessmentOverride = undefined;
|
|
2342
|
-
if (addRuleSpy) {
|
|
2343
|
-
addRuleSpy.mockRestore();
|
|
2344
|
-
addRuleSpy = undefined;
|
|
2345
|
-
}
|
|
2346
949
|
});
|
|
2347
950
|
|
|
2348
951
|
test("auto-approved tool result includes risk metadata when classifier assessment exists", async () => {
|
|
@@ -2360,7 +963,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2360
963
|
const result = await executor.execute(
|
|
2361
964
|
"file_read",
|
|
2362
965
|
{ path: "README.md" },
|
|
2363
|
-
makeContext(),
|
|
966
|
+
makeContext({ requireFreshApproval: true }),
|
|
2364
967
|
);
|
|
2365
968
|
|
|
2366
969
|
expect(result.isError).toBe(false);
|
|
@@ -2403,7 +1006,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2403
1006
|
const result = await executor.execute(
|
|
2404
1007
|
"bash",
|
|
2405
1008
|
{ command: "rm -rf /" },
|
|
2406
|
-
makeContext(),
|
|
1009
|
+
makeContext({ requireFreshApproval: true }),
|
|
2407
1010
|
);
|
|
2408
1011
|
|
|
2409
1012
|
expect(result.isError).toBe(true);
|
|
@@ -2433,7 +1036,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2433
1036
|
const result = await executor.execute(
|
|
2434
1037
|
"bash",
|
|
2435
1038
|
{ command: "npm install lodash" },
|
|
2436
|
-
makeContext(),
|
|
1039
|
+
makeContext({ requireFreshApproval: true }),
|
|
2437
1040
|
);
|
|
2438
1041
|
|
|
2439
1042
|
expect(result.isError).toBe(false);
|
|
@@ -2442,4 +1045,83 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
2442
1045
|
expect(result.riskReason).toBe("Package manager installation");
|
|
2443
1046
|
expect(result.riskScopeOptions).toHaveLength(2);
|
|
2444
1047
|
});
|
|
1048
|
+
|
|
1049
|
+
test("tool result includes riskDirectoryScopeOptions when classifier emits directoryScopeOptions", async () => {
|
|
1050
|
+
cachedAssessmentOverride = {
|
|
1051
|
+
riskLevel: "medium",
|
|
1052
|
+
reason: "Writes to file in workspace",
|
|
1053
|
+
scopeOptions: [
|
|
1054
|
+
{
|
|
1055
|
+
pattern: "file_write:/workspace/scratch/out.txt",
|
|
1056
|
+
label: "This file only",
|
|
1057
|
+
},
|
|
1058
|
+
],
|
|
1059
|
+
directoryScopeOptions: [
|
|
1060
|
+
{ scope: "/workspace/scratch", label: "In scratch/" },
|
|
1061
|
+
{ scope: "/workspace", label: "Anywhere in workspace/" },
|
|
1062
|
+
{ scope: "everywhere", label: "Everywhere" },
|
|
1063
|
+
],
|
|
1064
|
+
matchType: "registry",
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1068
|
+
const result = await executor.execute(
|
|
1069
|
+
"file_read",
|
|
1070
|
+
{ path: "/workspace/scratch/out.txt" },
|
|
1071
|
+
makeContext({ requireFreshApproval: true }),
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1074
|
+
expect(result.isError).toBe(false);
|
|
1075
|
+
expect(result.riskDirectoryScopeOptions).toEqual([
|
|
1076
|
+
{ scope: "/workspace/scratch", label: "In scratch/" },
|
|
1077
|
+
{ scope: "/workspace", label: "Anywhere in workspace/" },
|
|
1078
|
+
{ scope: "everywhere", label: "Everywhere" },
|
|
1079
|
+
]);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
test("tool result omits riskDirectoryScopeOptions when classifier does not emit directoryScopeOptions", async () => {
|
|
1083
|
+
cachedAssessmentOverride = {
|
|
1084
|
+
riskLevel: "low",
|
|
1085
|
+
reason: "Read-only operation",
|
|
1086
|
+
scopeOptions: [],
|
|
1087
|
+
// directoryScopeOptions intentionally omitted
|
|
1088
|
+
matchType: "registry",
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1092
|
+
const result = await executor.execute(
|
|
1093
|
+
"file_read",
|
|
1094
|
+
{ path: "README.md" },
|
|
1095
|
+
makeContext(),
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
expect(result.isError).toBe(false);
|
|
1099
|
+
expect(result.riskDirectoryScopeOptions).toBeUndefined();
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
test("riskScopeOptions and riskDirectoryScopeOptions are independent — one does not clobber the other", async () => {
|
|
1103
|
+
cachedAssessmentOverride = {
|
|
1104
|
+
riskLevel: "medium",
|
|
1105
|
+
reason: "Filesystem write",
|
|
1106
|
+
scopeOptions: [
|
|
1107
|
+
{ pattern: "file_write:/tmp/foo.txt", label: "This file" },
|
|
1108
|
+
],
|
|
1109
|
+
directoryScopeOptions: [{ scope: "/tmp", label: "Anywhere in tmp/" }],
|
|
1110
|
+
matchType: "registry",
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1114
|
+
const result = await executor.execute(
|
|
1115
|
+
"file_read",
|
|
1116
|
+
{ path: "/tmp/foo.txt" },
|
|
1117
|
+
makeContext({ requireFreshApproval: true }),
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
expect(result.riskScopeOptions).toEqual([
|
|
1121
|
+
{ pattern: "file_write:/tmp/foo.txt", label: "This file" },
|
|
1122
|
+
]);
|
|
1123
|
+
expect(result.riskDirectoryScopeOptions).toEqual([
|
|
1124
|
+
{ scope: "/tmp", label: "Anywhere in tmp/" },
|
|
1125
|
+
]);
|
|
1126
|
+
});
|
|
2445
1127
|
});
|