vellum 0.2.13 → 0.3.2

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.
Files changed (1019) hide show
  1. package/bin/vellum.js +2 -0
  2. package/package.json +6 -65
  3. package/.dockerignore +0 -27
  4. package/.env.example +0 -22
  5. package/Dockerfile +0 -99
  6. package/Dockerfile.sandbox +0 -5
  7. package/README.md +0 -169
  8. package/bun.lock +0 -1743
  9. package/bunfig.toml +0 -2
  10. package/docs/skills.md +0 -158
  11. package/drizzle/0000_dizzy_maggott.sql +0 -301
  12. package/drizzle/meta/0000_snapshot.json +0 -1999
  13. package/drizzle/meta/_journal.json +0 -13
  14. package/drizzle.config.ts +0 -7
  15. package/eslint.config.mjs +0 -17
  16. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  17. package/hook-templates/debug-prompt-logger/run.sh +0 -68
  18. package/knip.json +0 -9
  19. package/scripts/capture-x-graphql.ts +0 -545
  20. package/scripts/ipc/check-contract-inventory.ts +0 -104
  21. package/scripts/ipc/check-swift-decoder-drift.ts +0 -164
  22. package/scripts/ipc/generate-swift.ts +0 -492
  23. package/scripts/test-filesystem-tools.sh +0 -48
  24. package/scripts/test.sh +0 -127
  25. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -2316
  26. package/src/__tests__/account-registry.test.ts +0 -245
  27. package/src/__tests__/active-skill-tools.test.ts +0 -378
  28. package/src/__tests__/agent-heartbeat-service.test.ts +0 -250
  29. package/src/__tests__/agent-loop-thinking.test.ts +0 -81
  30. package/src/__tests__/agent-loop.test.ts +0 -1135
  31. package/src/__tests__/anthropic-provider.test.ts +0 -778
  32. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -290
  33. package/src/__tests__/app-bundler.test.ts +0 -292
  34. package/src/__tests__/app-executors.test.ts +0 -613
  35. package/src/__tests__/app-open-proxy.test.ts +0 -62
  36. package/src/__tests__/asset-materialize-tool.test.ts +0 -452
  37. package/src/__tests__/asset-search-tool.test.ts +0 -477
  38. package/src/__tests__/assistant-attachment-directive.test.ts +0 -401
  39. package/src/__tests__/assistant-attachments.test.ts +0 -437
  40. package/src/__tests__/assistant-event-hub.test.ts +0 -226
  41. package/src/__tests__/assistant-event.test.ts +0 -123
  42. package/src/__tests__/attachments-store.test.ts +0 -476
  43. package/src/__tests__/attachments.test.ts +0 -134
  44. package/src/__tests__/audit-log-rotation.test.ts +0 -154
  45. package/src/__tests__/browser-fill-credential.test.ts +0 -309
  46. package/src/__tests__/browser-manager.test.ts +0 -203
  47. package/src/__tests__/browser-runtime-check.test.ts +0 -55
  48. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +0 -68
  49. package/src/__tests__/browser-skill-endstate.test.ts +0 -195
  50. package/src/__tests__/bundle-scanner.test.ts +0 -313
  51. package/src/__tests__/call-bridge.test.ts +0 -425
  52. package/src/__tests__/call-constants.test.ts +0 -40
  53. package/src/__tests__/call-orchestrator.test.ts +0 -512
  54. package/src/__tests__/call-recovery.test.ts +0 -518
  55. package/src/__tests__/call-routes-http.test.ts +0 -459
  56. package/src/__tests__/call-state-machine.test.ts +0 -143
  57. package/src/__tests__/call-state.test.ts +0 -174
  58. package/src/__tests__/call-store.test.ts +0 -691
  59. package/src/__tests__/checker.test.ts +0 -3960
  60. package/src/__tests__/clarification-resolver.test.ts +0 -159
  61. package/src/__tests__/classifier.test.ts +0 -67
  62. package/src/__tests__/claude-code-skill-regression.test.ts +0 -127
  63. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -88
  64. package/src/__tests__/cli-discover.test.ts +0 -85
  65. package/src/__tests__/cli.test.ts +0 -81
  66. package/src/__tests__/clipboard.test.ts +0 -80
  67. package/src/__tests__/commit-guarantee.test.ts +0 -335
  68. package/src/__tests__/commit-message-enrichment-service.test.ts +0 -550
  69. package/src/__tests__/compaction.benchmark.test.ts +0 -176
  70. package/src/__tests__/computer-use-session-compaction.test.ts +0 -132
  71. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -293
  72. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -117
  73. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -74
  74. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -89
  75. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -217
  76. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -107
  77. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +0 -54
  78. package/src/__tests__/computer-use-tools.test.ts +0 -250
  79. package/src/__tests__/config-schema.test.ts +0 -1343
  80. package/src/__tests__/conflict-store.test.ts +0 -330
  81. package/src/__tests__/connection-policy.test.ts +0 -102
  82. package/src/__tests__/contacts-tools.test.ts +0 -331
  83. package/src/__tests__/context-memory-e2e.test.ts +0 -434
  84. package/src/__tests__/context-token-estimator.test.ts +0 -135
  85. package/src/__tests__/context-window-manager.test.ts +0 -376
  86. package/src/__tests__/contradiction-checker.test.ts +0 -216
  87. package/src/__tests__/conversation-store.test.ts +0 -612
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +0 -517
  89. package/src/__tests__/credential-broker-server-use.test.ts +0 -554
  90. package/src/__tests__/credential-broker.test.ts +0 -167
  91. package/src/__tests__/credential-host-pattern-match.test.ts +0 -104
  92. package/src/__tests__/credential-metadata-store.test.ts +0 -779
  93. package/src/__tests__/credential-policy-validate.test.ts +0 -121
  94. package/src/__tests__/credential-resolve.test.ts +0 -328
  95. package/src/__tests__/credential-security-e2e.test.ts +0 -352
  96. package/src/__tests__/credential-security-invariants.test.ts +0 -567
  97. package/src/__tests__/credential-selection.test.ts +0 -354
  98. package/src/__tests__/credential-vault.test.ts +0 -852
  99. package/src/__tests__/daemon-assistant-events.test.ts +0 -164
  100. package/src/__tests__/daemon-server-session-init.test.ts +0 -522
  101. package/src/__tests__/date-context.test.ts +0 -373
  102. package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -129
  103. package/src/__tests__/delete-managed-skill-tool.test.ts +0 -97
  104. package/src/__tests__/diff.test.ts +0 -121
  105. package/src/__tests__/domain-normalize.test.ts +0 -112
  106. package/src/__tests__/domain-policy.test.ts +0 -124
  107. package/src/__tests__/doordash-client.test.ts +0 -186
  108. package/src/__tests__/doordash-session.test.ts +0 -152
  109. package/src/__tests__/dynamic-page-surface.test.ts +0 -91
  110. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -132
  111. package/src/__tests__/edit-engine.test.ts +0 -180
  112. package/src/__tests__/elevenlabs-client.test.ts +0 -209
  113. package/src/__tests__/email-cli.test.ts +0 -283
  114. package/src/__tests__/encrypted-store.test.ts +0 -332
  115. package/src/__tests__/entity-extractor.test.ts +0 -190
  116. package/src/__tests__/ephemeral-permissions.test.ts +0 -312
  117. package/src/__tests__/evaluate-typescript-tool.test.ts +0 -286
  118. package/src/__tests__/event-bus.test.ts +0 -222
  119. package/src/__tests__/file-edit-tool.test.ts +0 -122
  120. package/src/__tests__/file-ops-service.test.ts +0 -330
  121. package/src/__tests__/file-read-tool.test.ts +0 -75
  122. package/src/__tests__/file-write-tool.test.ts +0 -113
  123. package/src/__tests__/fixtures/credential-security-fixtures.ts +0 -181
  124. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -126
  125. package/src/__tests__/fixtures/mock-signup-server.ts +0 -387
  126. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  127. package/src/__tests__/followup-tools.test.ts +0 -303
  128. package/src/__tests__/forbidden-legacy-symbols.test.ts +0 -71
  129. package/src/__tests__/fuzzy-match-property.test.ts +0 -216
  130. package/src/__tests__/fuzzy-match.test.ts +0 -138
  131. package/src/__tests__/gateway-only-enforcement.test.ts +0 -436
  132. package/src/__tests__/gemini-image-service.test.ts +0 -261
  133. package/src/__tests__/gemini-provider.test.ts +0 -651
  134. package/src/__tests__/get-weather.test.ts +0 -318
  135. package/src/__tests__/gmail-integration.test.ts +0 -73
  136. package/src/__tests__/handlers-cu-observation-blob.test.ts +0 -351
  137. package/src/__tests__/handlers-ipc-blob-probe.test.ts +0 -190
  138. package/src/__tests__/handlers-slack-config.test.ts +0 -199
  139. package/src/__tests__/handlers-task-submit-slash.test.ts +0 -38
  140. package/src/__tests__/handlers-twitter-config.test.ts +0 -718
  141. package/src/__tests__/headless-browser-interactions.test.ts +0 -536
  142. package/src/__tests__/headless-browser-navigate.test.ts +0 -211
  143. package/src/__tests__/headless-browser-read-tools.test.ts +0 -261
  144. package/src/__tests__/headless-browser-snapshot.test.ts +0 -185
  145. package/src/__tests__/history-repair-observability.test.ts +0 -56
  146. package/src/__tests__/history-repair.test.ts +0 -510
  147. package/src/__tests__/home-base-bootstrap.test.ts +0 -82
  148. package/src/__tests__/hooks-blocking.test.ts +0 -128
  149. package/src/__tests__/hooks-cli.test.ts +0 -144
  150. package/src/__tests__/hooks-config.test.ts +0 -93
  151. package/src/__tests__/hooks-discovery.test.ts +0 -199
  152. package/src/__tests__/hooks-integration.test.ts +0 -189
  153. package/src/__tests__/hooks-manager.test.ts +0 -187
  154. package/src/__tests__/hooks-runner.test.ts +0 -178
  155. package/src/__tests__/hooks-settings.test.ts +0 -154
  156. package/src/__tests__/hooks-templates.test.ts +0 -137
  157. package/src/__tests__/hooks-ts-runner.test.ts +0 -125
  158. package/src/__tests__/hooks-watch.test.ts +0 -100
  159. package/src/__tests__/host-file-edit-tool.test.ts +0 -104
  160. package/src/__tests__/host-file-read-tool.test.ts +0 -61
  161. package/src/__tests__/host-file-write-tool.test.ts +0 -77
  162. package/src/__tests__/host-shell-tool.test.ts +0 -311
  163. package/src/__tests__/ingress-url-consistency.test.ts +0 -214
  164. package/src/__tests__/intent-routing.test.ts +0 -259
  165. package/src/__tests__/ipc-blob-store.test.ts +0 -315
  166. package/src/__tests__/ipc-contract-inventory.test.ts +0 -54
  167. package/src/__tests__/ipc-contract.test.ts +0 -74
  168. package/src/__tests__/ipc-protocol.test.ts +0 -113
  169. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +0 -237
  170. package/src/__tests__/ipc-snapshot.test.ts +0 -1698
  171. package/src/__tests__/ipc-validate.test.ts +0 -357
  172. package/src/__tests__/key-migration.test.ts +0 -183
  173. package/src/__tests__/keychain.test.ts +0 -258
  174. package/src/__tests__/llm-usage-store.test.ts +0 -221
  175. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -257
  176. package/src/__tests__/managed-store.test.ts +0 -608
  177. package/src/__tests__/media-generate-image.test.ts +0 -238
  178. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -676
  179. package/src/__tests__/media-visibility-policy.test.ts +0 -141
  180. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -235
  181. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -481
  182. package/src/__tests__/memory-query-builder.test.ts +0 -59
  183. package/src/__tests__/memory-recall-quality.test.ts +0 -846
  184. package/src/__tests__/memory-regressions.experimental.test.ts +0 -538
  185. package/src/__tests__/memory-regressions.test.ts +0 -4336
  186. package/src/__tests__/memory-retrieval-budget.test.ts +0 -49
  187. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -430
  188. package/src/__tests__/migration-cli-flows.test.ts +0 -169
  189. package/src/__tests__/migration-ordering.test.ts +0 -249
  190. package/src/__tests__/mock-signup-server.test.ts +0 -528
  191. package/src/__tests__/oauth-callback-registry.test.ts +0 -85
  192. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -285
  193. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -176
  194. package/src/__tests__/onboarding-template-contract.test.ts +0 -58
  195. package/src/__tests__/openai-provider.test.ts +0 -753
  196. package/src/__tests__/parallel-tool.benchmark.test.ts +0 -294
  197. package/src/__tests__/parser.test.ts +0 -472
  198. package/src/__tests__/path-classifier.test.ts +0 -73
  199. package/src/__tests__/path-policy.test.ts +0 -435
  200. package/src/__tests__/platform-move-helper.test.ts +0 -99
  201. package/src/__tests__/platform-socket-path.test.ts +0 -52
  202. package/src/__tests__/platform-workspace-migration.test.ts +0 -1000
  203. package/src/__tests__/platform.test.ts +0 -131
  204. package/src/__tests__/playbook-tools.test.ts +0 -342
  205. package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -75
  206. package/src/__tests__/pricing.test.ts +0 -256
  207. package/src/__tests__/profile-compiler.test.ts +0 -374
  208. package/src/__tests__/provider-commit-message-generator.test.ts +0 -342
  209. package/src/__tests__/provider-registry-ollama.test.ts +0 -16
  210. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -773
  211. package/src/__tests__/proxy-approval-callback.test.ts +0 -601
  212. package/src/__tests__/public-ingress-urls.test.ts +0 -222
  213. package/src/__tests__/ratelimit.test.ts +0 -297
  214. package/src/__tests__/recurrence-engine-rruleset.test.ts +0 -78
  215. package/src/__tests__/recurrence-engine.test.ts +0 -69
  216. package/src/__tests__/recurrence-types.test.ts +0 -71
  217. package/src/__tests__/registry.test.ts +0 -494
  218. package/src/__tests__/relay-server.test.ts +0 -688
  219. package/src/__tests__/reminder-store.test.ts +0 -223
  220. package/src/__tests__/reminder.test.ts +0 -229
  221. package/src/__tests__/request-file-tool.test.ts +0 -158
  222. package/src/__tests__/run-orchestrator-assistant-events.test.ts +0 -222
  223. package/src/__tests__/run-orchestrator.test.ts +0 -200
  224. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -189
  225. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -343
  226. package/src/__tests__/runtime-events-sse.test.ts +0 -162
  227. package/src/__tests__/runtime-runs-http.test.ts +0 -433
  228. package/src/__tests__/runtime-runs.test.ts +0 -273
  229. package/src/__tests__/sandbox-diagnostics.test.ts +0 -408
  230. package/src/__tests__/sandbox-host-parity.test.ts +0 -950
  231. package/src/__tests__/scaffold-managed-skill-tool.test.ts +0 -253
  232. package/src/__tests__/schedule-store.test.ts +0 -482
  233. package/src/__tests__/schedule-tools.test.ts +0 -700
  234. package/src/__tests__/scheduler-recurrence.test.ts +0 -329
  235. package/src/__tests__/script-proxy-certs.test.ts +0 -90
  236. package/src/__tests__/script-proxy-connect-tunnel.test.ts +0 -177
  237. package/src/__tests__/script-proxy-decision-trace.test.ts +0 -156
  238. package/src/__tests__/script-proxy-http-forwarder.test.ts +0 -281
  239. package/src/__tests__/script-proxy-injection-runtime.test.ts +0 -401
  240. package/src/__tests__/script-proxy-mitm-handler.test.ts +0 -407
  241. package/src/__tests__/script-proxy-policy-runtime.test.ts +0 -287
  242. package/src/__tests__/script-proxy-policy.test.ts +0 -310
  243. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +0 -135
  244. package/src/__tests__/script-proxy-router.test.ts +0 -180
  245. package/src/__tests__/script-proxy-session-manager.test.ts +0 -382
  246. package/src/__tests__/script-proxy-session-runtime.test.ts +0 -113
  247. package/src/__tests__/secret-allowlist.test.ts +0 -229
  248. package/src/__tests__/secret-ingress-handler.test.ts +0 -99
  249. package/src/__tests__/secret-onetime-send.test.ts +0 -130
  250. package/src/__tests__/secret-prompt-log-hygiene.test.ts +0 -106
  251. package/src/__tests__/secret-response-routing.test.ts +0 -93
  252. package/src/__tests__/secret-scanner-executor.test.ts +0 -348
  253. package/src/__tests__/secret-scanner.test.ts +0 -857
  254. package/src/__tests__/secure-keys.test.ts +0 -323
  255. package/src/__tests__/server-history-render.test.ts +0 -431
  256. package/src/__tests__/session-abort-tool-results.test.ts +0 -240
  257. package/src/__tests__/session-conflict-gate.test.ts +0 -700
  258. package/src/__tests__/session-error.test.ts +0 -369
  259. package/src/__tests__/session-evictor.test.ts +0 -188
  260. package/src/__tests__/session-init.benchmark.test.ts +0 -462
  261. package/src/__tests__/session-load-history-repair.test.ts +0 -222
  262. package/src/__tests__/session-pre-run-repair.test.ts +0 -213
  263. package/src/__tests__/session-profile-injection.test.ts +0 -444
  264. package/src/__tests__/session-provider-retry-repair.test.ts +0 -306
  265. package/src/__tests__/session-queue.test.ts +0 -1535
  266. package/src/__tests__/session-runtime-assembly.test.ts +0 -476
  267. package/src/__tests__/session-runtime-workspace.test.ts +0 -183
  268. package/src/__tests__/session-skill-tools.test.ts +0 -2431
  269. package/src/__tests__/session-slash-known.test.ts +0 -368
  270. package/src/__tests__/session-slash-queue.test.ts +0 -288
  271. package/src/__tests__/session-slash-unknown.test.ts +0 -271
  272. package/src/__tests__/session-surfaces-task-progress.test.ts +0 -104
  273. package/src/__tests__/session-tool-setup-app-refresh.test.ts +0 -473
  274. package/src/__tests__/session-tool-setup-memory-scope.test.ts +0 -140
  275. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +0 -140
  276. package/src/__tests__/session-undo.test.ts +0 -75
  277. package/src/__tests__/session-workspace-cache-state.test.ts +0 -246
  278. package/src/__tests__/session-workspace-injection.test.ts +0 -327
  279. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -240
  280. package/src/__tests__/shared-filesystem-errors.test.ts +0 -78
  281. package/src/__tests__/shell-credential-ref.test.ts +0 -187
  282. package/src/__tests__/shell-parser-fuzz.test.ts +0 -544
  283. package/src/__tests__/shell-parser-property.test.ts +0 -433
  284. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -272
  285. package/src/__tests__/signup-e2e.test.ts +0 -353
  286. package/src/__tests__/size-guard.test.ts +0 -117
  287. package/src/__tests__/skill-include-graph.test.ts +0 -303
  288. package/src/__tests__/skill-load-tool.test.ts +0 -409
  289. package/src/__tests__/skill-projection.benchmark.test.ts +0 -328
  290. package/src/__tests__/skill-script-runner-host.test.ts +0 -489
  291. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -349
  292. package/src/__tests__/skill-script-runner.test.ts +0 -159
  293. package/src/__tests__/skill-tool-factory.test.ts +0 -252
  294. package/src/__tests__/skill-tool-manifest.test.ts +0 -658
  295. package/src/__tests__/skill-version-hash.test.ts +0 -182
  296. package/src/__tests__/skills.test.ts +0 -680
  297. package/src/__tests__/slash-commands-catalog.test.ts +0 -86
  298. package/src/__tests__/slash-commands-parser.test.ts +0 -119
  299. package/src/__tests__/slash-commands-resolver.test.ts +0 -193
  300. package/src/__tests__/slash-commands-rewrite.test.ts +0 -39
  301. package/src/__tests__/speaker-identification.test.ts +0 -52
  302. package/src/__tests__/starter-bundle.test.ts +0 -136
  303. package/src/__tests__/starter-task-flow.test.ts +0 -143
  304. package/src/__tests__/subagent-manager-notify.test.ts +0 -404
  305. package/src/__tests__/subagent-tools.test.ts +0 -218
  306. package/src/__tests__/subagent-types.test.ts +0 -78
  307. package/src/__tests__/swarm-orchestrator.test.ts +0 -428
  308. package/src/__tests__/swarm-plan-validator.test.ts +0 -330
  309. package/src/__tests__/swarm-recursion.test.ts +0 -165
  310. package/src/__tests__/swarm-router-planner.test.ts +0 -208
  311. package/src/__tests__/swarm-session-integration.test.ts +0 -274
  312. package/src/__tests__/swarm-tool.test.ts +0 -145
  313. package/src/__tests__/swarm-worker-backend.test.ts +0 -129
  314. package/src/__tests__/swarm-worker-runner.test.ts +0 -272
  315. package/src/__tests__/system-prompt.test.ts +0 -439
  316. package/src/__tests__/task-compiler.test.ts +0 -284
  317. package/src/__tests__/task-runner.test.ts +0 -216
  318. package/src/__tests__/task-scheduler.test.ts +0 -217
  319. package/src/__tests__/task-tools.test.ts +0 -595
  320. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1064
  321. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -178
  322. package/src/__tests__/terminal-sandbox.test.ts +0 -202
  323. package/src/__tests__/test-support/browser-skill-harness.ts +0 -90
  324. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -45
  325. package/src/__tests__/tool-audit-listener.test.ts +0 -113
  326. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -253
  327. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -500
  328. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -516
  329. package/src/__tests__/tool-executor-redaction.test.ts +0 -289
  330. package/src/__tests__/tool-executor.test.ts +0 -2055
  331. package/src/__tests__/tool-metrics-listener.test.ts +0 -225
  332. package/src/__tests__/tool-notification-listener.test.ts +0 -49
  333. package/src/__tests__/tool-policy.test.ts +0 -54
  334. package/src/__tests__/tool-profiling-listener.test.ts +0 -268
  335. package/src/__tests__/tool-result-truncation.test.ts +0 -217
  336. package/src/__tests__/tool-trace-listener.test.ts +0 -226
  337. package/src/__tests__/top-level-renderer.test.ts +0 -121
  338. package/src/__tests__/top-level-scanner.test.ts +0 -141
  339. package/src/__tests__/trace-emitter.test.ts +0 -173
  340. package/src/__tests__/trust-store.test.ts +0 -2031
  341. package/src/__tests__/turn-commit.test.ts +0 -554
  342. package/src/__tests__/twilio-provider.test.ts +0 -179
  343. package/src/__tests__/twilio-routes-twiml.test.ts +0 -127
  344. package/src/__tests__/twilio-routes.test.ts +0 -822
  345. package/src/__tests__/twitter-auth-handler.test.ts +0 -666
  346. package/src/__tests__/url-safety.test.ts +0 -418
  347. package/src/__tests__/view-image-tool.test.ts +0 -217
  348. package/src/__tests__/weather-skill-regression.test.ts +0 -225
  349. package/src/__tests__/web-fetch.test.ts +0 -869
  350. package/src/__tests__/web-search.test.ts +0 -584
  351. package/src/__tests__/workspace-git-service.test.ts +0 -1153
  352. package/src/__tests__/workspace-heartbeat-service.test.ts +0 -486
  353. package/src/__tests__/workspace-lifecycle.test.ts +0 -292
  354. package/src/agent/attachments.ts +0 -35
  355. package/src/agent/loop.ts +0 -500
  356. package/src/agent/message-types.ts +0 -17
  357. package/src/agent-heartbeat/agent-heartbeat-service.ts +0 -155
  358. package/src/autonomy/autonomy-resolver.ts +0 -60
  359. package/src/autonomy/autonomy-store.ts +0 -122
  360. package/src/autonomy/disposition-mapper.ts +0 -31
  361. package/src/autonomy/index.ts +0 -11
  362. package/src/autonomy/types.ts +0 -39
  363. package/src/bundler/app-bundler.ts +0 -295
  364. package/src/bundler/bundle-scanner.ts +0 -535
  365. package/src/bundler/bundle-signer.ts +0 -124
  366. package/src/bundler/manifest.ts +0 -21
  367. package/src/bundler/signature-verifier.ts +0 -184
  368. package/src/calls/call-bridge.ts +0 -95
  369. package/src/calls/call-constants.ts +0 -48
  370. package/src/calls/call-domain.ts +0 -278
  371. package/src/calls/call-orchestrator.ts +0 -412
  372. package/src/calls/call-recovery.ts +0 -207
  373. package/src/calls/call-state-machine.ts +0 -68
  374. package/src/calls/call-state.ts +0 -87
  375. package/src/calls/call-store.ts +0 -416
  376. package/src/calls/elevenlabs-client.ts +0 -89
  377. package/src/calls/elevenlabs-config.ts +0 -29
  378. package/src/calls/relay-server.ts +0 -390
  379. package/src/calls/speaker-identification.ts +0 -213
  380. package/src/calls/twilio-config.ts +0 -45
  381. package/src/calls/twilio-provider.ts +0 -178
  382. package/src/calls/twilio-routes.ts +0 -316
  383. package/src/calls/types.ts +0 -37
  384. package/src/calls/voice-provider.ts +0 -14
  385. package/src/calls/voice-quality.ts +0 -92
  386. package/src/cli/autonomy.ts +0 -188
  387. package/src/cli/config-commands.ts +0 -334
  388. package/src/cli/contacts.ts +0 -149
  389. package/src/cli/core-commands.ts +0 -784
  390. package/src/cli/doordash.ts +0 -1055
  391. package/src/cli/email-guardrails.ts +0 -200
  392. package/src/cli/email.ts +0 -405
  393. package/src/cli/ipc-client.ts +0 -82
  394. package/src/cli/main-screen.tsx +0 -53
  395. package/src/cli/map.ts +0 -270
  396. package/src/cli/twitter.ts +0 -575
  397. package/src/cli.ts +0 -937
  398. package/src/commands/__tests__/cc-command-registry.test.ts +0 -319
  399. package/src/commands/cc-command-registry.ts +0 -209
  400. package/src/config/bundled-skills/.gitkeep +0 -0
  401. package/src/config/bundled-skills/agentmail/SKILL.md +0 -128
  402. package/src/config/bundled-skills/agentmail/icon.svg +0 -21
  403. package/src/config/bundled-skills/app-builder/SKILL.md +0 -1404
  404. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -279
  405. package/src/config/bundled-skills/app-builder/icon.svg +0 -9
  406. package/src/config/bundled-skills/app-builder/tools/app-create.ts +0 -15
  407. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +0 -10
  408. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -11
  409. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -10
  410. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -18
  411. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -11
  412. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -10
  413. package/src/config/bundled-skills/app-builder/tools/app-query.ts +0 -10
  414. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -20
  415. package/src/config/bundled-skills/browser/SKILL.md +0 -28
  416. package/src/config/bundled-skills/browser/TOOLS.json +0 -234
  417. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -9
  418. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -9
  419. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -9
  420. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -9
  421. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -9
  422. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -9
  423. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -9
  424. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -9
  425. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -9
  426. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -9
  427. package/src/config/bundled-skills/claude-code/SKILL.md +0 -50
  428. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -40
  429. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -9
  430. package/src/config/bundled-skills/computer-use/SKILL.md +0 -17
  431. package/src/config/bundled-skills/computer-use/TOOLS.json +0 -326
  432. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +0 -9
  433. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +0 -9
  434. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +0 -9
  435. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +0 -9
  436. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +0 -9
  437. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +0 -9
  438. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -9
  439. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +0 -9
  440. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +0 -9
  441. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +0 -9
  442. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +0 -9
  443. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +0 -9
  444. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +0 -9
  445. package/src/config/bundled-skills/contacts/SKILL.md +0 -39
  446. package/src/config/bundled-skills/contacts/TOOLS.json +0 -122
  447. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +0 -9
  448. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -9
  449. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -9
  450. package/src/config/bundled-skills/document/SKILL.md +0 -26
  451. package/src/config/bundled-skills/document/TOOLS.json +0 -53
  452. package/src/config/bundled-skills/document/tools/document-create.ts +0 -9
  453. package/src/config/bundled-skills/document/tools/document-update.ts +0 -9
  454. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  455. package/src/config/bundled-skills/followups/SKILL.md +0 -32
  456. package/src/config/bundled-skills/followups/TOOLS.json +0 -100
  457. package/src/config/bundled-skills/followups/icon.svg +0 -24
  458. package/src/config/bundled-skills/followups/tools/followup-create.ts +0 -9
  459. package/src/config/bundled-skills/followups/tools/followup-list.ts +0 -9
  460. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +0 -9
  461. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  462. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -108
  463. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -165
  464. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -21
  465. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -42
  466. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -13
  467. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -30
  468. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -41
  469. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -18
  470. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  471. package/src/config/bundled-skills/image-studio/SKILL.md +0 -32
  472. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -42
  473. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +0 -115
  474. package/src/config/bundled-skills/macos-automation/SKILL.md +0 -66
  475. package/src/config/bundled-skills/messaging/SKILL.md +0 -130
  476. package/src/config/bundled-skills/messaging/TOOLS.json +0 -357
  477. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +0 -23
  478. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +0 -23
  479. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +0 -25
  480. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +0 -26
  481. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +0 -25
  482. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +0 -23
  483. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +0 -84
  484. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +0 -18
  485. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +0 -125
  486. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +0 -16
  487. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +0 -49
  488. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +0 -21
  489. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +0 -25
  490. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +0 -28
  491. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +0 -29
  492. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +0 -22
  493. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -27
  494. package/src/config/bundled-skills/messaging/tools/shared.ts +0 -71
  495. package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +0 -25
  496. package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +0 -23
  497. package/src/config/bundled-skills/phone-calls/SKILL.md +0 -414
  498. package/src/config/bundled-skills/playbooks/SKILL.md +0 -31
  499. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -126
  500. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +0 -9
  501. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +0 -9
  502. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +0 -9
  503. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +0 -9
  504. package/src/config/bundled-skills/public-ingress/SKILL.md +0 -183
  505. package/src/config/bundled-skills/reminder/SKILL.md +0 -20
  506. package/src/config/bundled-skills/reminder/TOOLS.json +0 -67
  507. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +0 -9
  508. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +0 -9
  509. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +0 -9
  510. package/src/config/bundled-skills/schedule/SKILL.md +0 -74
  511. package/src/config/bundled-skills/schedule/TOOLS.json +0 -135
  512. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +0 -9
  513. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +0 -9
  514. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +0 -9
  515. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +0 -9
  516. package/src/config/bundled-skills/self-upgrade/SKILL.md +0 -68
  517. package/src/config/bundled-skills/start-the-day/SKILL.md +0 -70
  518. package/src/config/bundled-skills/start-the-day/icon.svg +0 -13
  519. package/src/config/bundled-skills/subagent/SKILL.md +0 -25
  520. package/src/config/bundled-skills/subagent/TOOLS.json +0 -107
  521. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +0 -9
  522. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +0 -9
  523. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +0 -9
  524. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +0 -9
  525. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +0 -9
  526. package/src/config/bundled-skills/tasks/SKILL.md +0 -28
  527. package/src/config/bundled-skills/tasks/TOOLS.json +0 -281
  528. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -9
  529. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -9
  530. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -9
  531. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -9
  532. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -9
  533. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -9
  534. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -9
  535. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -9
  536. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -9
  537. package/src/config/bundled-skills/transcribe/SKILL.md +0 -25
  538. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -32
  539. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +0 -370
  540. package/src/config/bundled-skills/twitter/SKILL.md +0 -134
  541. package/src/config/bundled-skills/watcher/SKILL.md +0 -27
  542. package/src/config/bundled-skills/watcher/TOOLS.json +0 -147
  543. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -9
  544. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -9
  545. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -9
  546. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -9
  547. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -9
  548. package/src/config/bundled-skills/weather/SKILL.md +0 -37
  549. package/src/config/bundled-skills/weather/TOOLS.json +0 -32
  550. package/src/config/bundled-skills/weather/icon.svg +0 -24
  551. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -9
  552. package/src/config/computer-use-prompt.ts +0 -97
  553. package/src/config/defaults.ts +0 -252
  554. package/src/config/loader.ts +0 -339
  555. package/src/config/schema.ts +0 -1356
  556. package/src/config/skill-state.ts +0 -95
  557. package/src/config/skills.ts +0 -972
  558. package/src/config/system-prompt.ts +0 -675
  559. package/src/config/templates/BOOTSTRAP.md +0 -70
  560. package/src/config/templates/IDENTITY.md +0 -25
  561. package/src/config/templates/LOOKS.md +0 -25
  562. package/src/config/templates/SOUL.md +0 -37
  563. package/src/config/templates/USER.md +0 -19
  564. package/src/config/types.ts +0 -40
  565. package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +0 -179
  566. package/src/config/vellum-skills/document-writer/SKILL.md +0 -195
  567. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
  568. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +0 -153
  569. package/src/config/vellum-skills/telegram-setup/SKILL.md +0 -102
  570. package/src/contacts/contact-store.ts +0 -410
  571. package/src/contacts/index.ts +0 -11
  572. package/src/contacts/types.ts +0 -28
  573. package/src/context/token-estimator.ts +0 -108
  574. package/src/context/tool-result-truncation.ts +0 -128
  575. package/src/context/window-manager.ts +0 -531
  576. package/src/daemon/assistant-attachments.ts +0 -689
  577. package/src/daemon/classifier.ts +0 -110
  578. package/src/daemon/computer-use-session.ts +0 -903
  579. package/src/daemon/connection-policy.ts +0 -41
  580. package/src/daemon/date-context.ts +0 -136
  581. package/src/daemon/handlers/apps.ts +0 -461
  582. package/src/daemon/handlers/browser.ts +0 -54
  583. package/src/daemon/handlers/computer-use.ts +0 -187
  584. package/src/daemon/handlers/config.ts +0 -707
  585. package/src/daemon/handlers/diagnostics.ts +0 -338
  586. package/src/daemon/handlers/documents.ts +0 -173
  587. package/src/daemon/handlers/home-base.ts +0 -78
  588. package/src/daemon/handlers/identity.ts +0 -127
  589. package/src/daemon/handlers/index.ts +0 -128
  590. package/src/daemon/handlers/misc.ts +0 -331
  591. package/src/daemon/handlers/open-bundle-handler.ts +0 -80
  592. package/src/daemon/handlers/publish.ts +0 -187
  593. package/src/daemon/handlers/sessions.ts +0 -539
  594. package/src/daemon/handlers/shared.ts +0 -569
  595. package/src/daemon/handlers/signing.ts +0 -37
  596. package/src/daemon/handlers/skills.ts +0 -501
  597. package/src/daemon/handlers/subagents.ts +0 -210
  598. package/src/daemon/handlers/twitter-auth.ts +0 -198
  599. package/src/daemon/handlers/work-items.ts +0 -632
  600. package/src/daemon/handlers/workspace-files.ts +0 -75
  601. package/src/daemon/handlers.ts +0 -17
  602. package/src/daemon/history-repair.ts +0 -214
  603. package/src/daemon/ipc-blob-store.ts +0 -231
  604. package/src/daemon/ipc-contract-inventory.json +0 -463
  605. package/src/daemon/ipc-contract-inventory.ts +0 -126
  606. package/src/daemon/ipc-contract.ts +0 -2352
  607. package/src/daemon/ipc-protocol.ts +0 -75
  608. package/src/daemon/ipc-validate.ts +0 -171
  609. package/src/daemon/lifecycle.ts +0 -580
  610. package/src/daemon/main.ts +0 -21
  611. package/src/daemon/media-visibility-policy.ts +0 -57
  612. package/src/daemon/ride-shotgun-handler.ts +0 -309
  613. package/src/daemon/server.ts +0 -1207
  614. package/src/daemon/session-agent-loop.ts +0 -922
  615. package/src/daemon/session-attachments.ts +0 -196
  616. package/src/daemon/session-conflict-gate.ts +0 -128
  617. package/src/daemon/session-dynamic-profile.ts +0 -63
  618. package/src/daemon/session-error.ts +0 -290
  619. package/src/daemon/session-evictor.ts +0 -196
  620. package/src/daemon/session-history.ts +0 -437
  621. package/src/daemon/session-lifecycle.ts +0 -147
  622. package/src/daemon/session-media-retry.ts +0 -147
  623. package/src/daemon/session-memory.ts +0 -212
  624. package/src/daemon/session-messaging.ts +0 -145
  625. package/src/daemon/session-notifiers.ts +0 -193
  626. package/src/daemon/session-process.ts +0 -264
  627. package/src/daemon/session-queue-manager.ts +0 -82
  628. package/src/daemon/session-runtime-assembly.ts +0 -447
  629. package/src/daemon/session-skill-tools.ts +0 -356
  630. package/src/daemon/session-slash.ts +0 -305
  631. package/src/daemon/session-surfaces.ts +0 -702
  632. package/src/daemon/session-tool-setup.ts +0 -524
  633. package/src/daemon/session-usage.ts +0 -72
  634. package/src/daemon/session-workspace.ts +0 -19
  635. package/src/daemon/session.ts +0 -400
  636. package/src/daemon/trace-emitter.ts +0 -82
  637. package/src/daemon/video-thumbnail.ts +0 -60
  638. package/src/daemon/watch-handler.ts +0 -274
  639. package/src/doordash/client.ts +0 -999
  640. package/src/doordash/queries.ts +0 -1311
  641. package/src/doordash/query-extractor.ts +0 -93
  642. package/src/doordash/session.ts +0 -82
  643. package/src/email/provider.ts +0 -117
  644. package/src/email/providers/agentmail.ts +0 -317
  645. package/src/email/providers/index.ts +0 -58
  646. package/src/email/service.ts +0 -303
  647. package/src/email/types.ts +0 -126
  648. package/src/events/bus.ts +0 -157
  649. package/src/events/domain-events.ts +0 -83
  650. package/src/events/index.ts +0 -18
  651. package/src/events/tool-audit-listener.ts +0 -80
  652. package/src/events/tool-domain-event-publisher.ts +0 -111
  653. package/src/events/tool-metrics-listener.ts +0 -159
  654. package/src/events/tool-notification-listener.ts +0 -17
  655. package/src/events/tool-profiling-listener.ts +0 -158
  656. package/src/events/tool-trace-listener.ts +0 -75
  657. package/src/export/formatter.ts +0 -98
  658. package/src/followups/followup-store.ts +0 -168
  659. package/src/followups/index.ts +0 -10
  660. package/src/followups/types.ts +0 -29
  661. package/src/gallery/default-gallery.ts +0 -795
  662. package/src/gallery/gallery-manifest.ts +0 -24
  663. package/src/home-base/app-link-store.ts +0 -82
  664. package/src/home-base/bootstrap.ts +0 -68
  665. package/src/home-base/prebuilt/index.html +0 -662
  666. package/src/home-base/prebuilt/seed-metadata.json +0 -21
  667. package/src/home-base/prebuilt/seed.ts +0 -112
  668. package/src/home-base/prebuilt-home-base-updater.ts +0 -30
  669. package/src/hooks/cli.ts +0 -163
  670. package/src/hooks/config.ts +0 -88
  671. package/src/hooks/discovery.ts +0 -110
  672. package/src/hooks/manager.ts +0 -128
  673. package/src/hooks/runner.ts +0 -123
  674. package/src/hooks/templates.ts +0 -52
  675. package/src/hooks/types.ts +0 -72
  676. package/src/inbound/public-ingress-urls.ts +0 -123
  677. package/src/index.ts +0 -75
  678. package/src/instrument.ts +0 -60
  679. package/src/logfire.ts +0 -99
  680. package/src/media/gemini-image-service.ts +0 -136
  681. package/src/memory/account-store.ts +0 -108
  682. package/src/memory/admin.ts +0 -211
  683. package/src/memory/app-store.ts +0 -556
  684. package/src/memory/attachments-store.ts +0 -397
  685. package/src/memory/channel-delivery-store.ts +0 -353
  686. package/src/memory/checkpoints.ts +0 -52
  687. package/src/memory/clarification-resolver.ts +0 -298
  688. package/src/memory/conflict-intent.ts +0 -114
  689. package/src/memory/conflict-store.ts +0 -342
  690. package/src/memory/contradiction-checker.ts +0 -330
  691. package/src/memory/conversation-key-store.ts +0 -107
  692. package/src/memory/conversation-store.ts +0 -470
  693. package/src/memory/db.ts +0 -1825
  694. package/src/memory/embedding-backend.ts +0 -229
  695. package/src/memory/embedding-gemini.ts +0 -52
  696. package/src/memory/embedding-local.ts +0 -75
  697. package/src/memory/embedding-ollama.ts +0 -55
  698. package/src/memory/embedding-openai.ts +0 -25
  699. package/src/memory/entity-extractor.ts +0 -474
  700. package/src/memory/fingerprint.ts +0 -20
  701. package/src/memory/indexer.ts +0 -156
  702. package/src/memory/items-extractor.ts +0 -461
  703. package/src/memory/job-handlers/backfill.ts +0 -139
  704. package/src/memory/job-handlers/cleanup.ts +0 -58
  705. package/src/memory/job-handlers/conflict.ts +0 -121
  706. package/src/memory/job-handlers/embedding.ts +0 -61
  707. package/src/memory/job-handlers/extraction.ts +0 -123
  708. package/src/memory/job-handlers/index-maintenance.ts +0 -54
  709. package/src/memory/job-handlers/summarization.ts +0 -286
  710. package/src/memory/job-utils.ts +0 -170
  711. package/src/memory/jobs-store.ts +0 -401
  712. package/src/memory/jobs-worker.ts +0 -274
  713. package/src/memory/llm-request-log-store.ts +0 -45
  714. package/src/memory/llm-usage-store.ts +0 -60
  715. package/src/memory/message-content.ts +0 -54
  716. package/src/memory/profile-compiler.ts +0 -160
  717. package/src/memory/published-pages-store.ts +0 -137
  718. package/src/memory/qdrant-client.ts +0 -366
  719. package/src/memory/qdrant-manager.ts +0 -242
  720. package/src/memory/query-builder.ts +0 -45
  721. package/src/memory/retrieval-budget.ts +0 -30
  722. package/src/memory/retriever.ts +0 -653
  723. package/src/memory/runs-store.ts +0 -255
  724. package/src/memory/schema.ts +0 -588
  725. package/src/memory/search/entity.ts +0 -298
  726. package/src/memory/search/formatting.ts +0 -207
  727. package/src/memory/search/lexical.ts +0 -227
  728. package/src/memory/search/ranking.ts +0 -401
  729. package/src/memory/search/semantic.ts +0 -121
  730. package/src/memory/search/types.ts +0 -137
  731. package/src/memory/segmenter.ts +0 -68
  732. package/src/memory/shared-app-links-store.ts +0 -138
  733. package/src/memory/tool-usage-store.ts +0 -62
  734. package/src/messaging/activity-analyzer.ts +0 -76
  735. package/src/messaging/draft-store.ts +0 -88
  736. package/src/messaging/index.ts +0 -3
  737. package/src/messaging/provider-types.ts +0 -80
  738. package/src/messaging/provider.ts +0 -43
  739. package/src/messaging/providers/gmail/adapter.ts +0 -193
  740. package/src/messaging/providers/gmail/client.ts +0 -204
  741. package/src/messaging/providers/gmail/types.ts +0 -90
  742. package/src/messaging/providers/slack/adapter.ts +0 -202
  743. package/src/messaging/providers/slack/client.ts +0 -198
  744. package/src/messaging/providers/slack/types.ts +0 -119
  745. package/src/messaging/registry.ts +0 -34
  746. package/src/messaging/style-analyzer.ts +0 -159
  747. package/src/messaging/thread-summarizer.ts +0 -306
  748. package/src/messaging/triage-engine.ts +0 -323
  749. package/src/messaging/types.ts +0 -55
  750. package/src/permissions/checker.ts +0 -636
  751. package/src/permissions/defaults.ts +0 -254
  752. package/src/permissions/prompter.ts +0 -102
  753. package/src/permissions/secret-prompter.ts +0 -114
  754. package/src/permissions/trust-store.ts +0 -584
  755. package/src/permissions/types.ts +0 -62
  756. package/src/playbooks/index.ts +0 -2
  757. package/src/playbooks/playbook-compiler.ts +0 -90
  758. package/src/playbooks/types.ts +0 -55
  759. package/src/providers/anthropic/client.ts +0 -751
  760. package/src/providers/failover.ts +0 -129
  761. package/src/providers/fireworks/client.ts +0 -20
  762. package/src/providers/gemini/client.ts +0 -285
  763. package/src/providers/ollama/client.ts +0 -30
  764. package/src/providers/openai/client.ts +0 -337
  765. package/src/providers/openrouter/client.ts +0 -20
  766. package/src/providers/ratelimit.ts +0 -93
  767. package/src/providers/registry.ts +0 -146
  768. package/src/providers/retry.ts +0 -106
  769. package/src/providers/stream-timeout.ts +0 -38
  770. package/src/providers/types.ts +0 -109
  771. package/src/runtime/assistant-event-hub.ts +0 -120
  772. package/src/runtime/assistant-event.ts +0 -82
  773. package/src/runtime/gateway-client.ts +0 -42
  774. package/src/runtime/http-server.ts +0 -1056
  775. package/src/runtime/http-types.ts +0 -66
  776. package/src/runtime/routes/app-routes.ts +0 -174
  777. package/src/runtime/routes/attachment-routes.ts +0 -133
  778. package/src/runtime/routes/call-routes.ts +0 -140
  779. package/src/runtime/routes/channel-routes.ts +0 -382
  780. package/src/runtime/routes/conversation-routes.ts +0 -352
  781. package/src/runtime/routes/events-routes.ts +0 -79
  782. package/src/runtime/routes/run-routes.ts +0 -262
  783. package/src/runtime/routes/secret-routes.ts +0 -76
  784. package/src/runtime/run-orchestrator.ts +0 -296
  785. package/src/schedule/recurrence-engine.ts +0 -138
  786. package/src/schedule/recurrence-types.ts +0 -67
  787. package/src/schedule/schedule-store.ts +0 -497
  788. package/src/schedule/scheduler.ts +0 -171
  789. package/src/security/encrypted-store.ts +0 -238
  790. package/src/security/keychain.ts +0 -252
  791. package/src/security/oauth-callback-registry.ts +0 -66
  792. package/src/security/oauth2.ts +0 -274
  793. package/src/security/redaction.ts +0 -89
  794. package/src/security/secret-allowlist.ts +0 -164
  795. package/src/security/secret-ingress.ts +0 -57
  796. package/src/security/secret-scanner.ts +0 -543
  797. package/src/security/secure-keys.ts +0 -180
  798. package/src/security/token-manager.ts +0 -141
  799. package/src/services/published-app-updater.ts +0 -69
  800. package/src/services/vercel-deploy.ts +0 -73
  801. package/src/skills/active-skill-tools.ts +0 -81
  802. package/src/skills/clawhub.ts +0 -414
  803. package/src/skills/include-graph.ts +0 -146
  804. package/src/skills/managed-store.ts +0 -233
  805. package/src/skills/path-classifier.ts +0 -128
  806. package/src/skills/slash-commands.ts +0 -174
  807. package/src/skills/tool-manifest.ts +0 -165
  808. package/src/skills/version-hash.ts +0 -110
  809. package/src/slack/slack-webhook.ts +0 -61
  810. package/src/subagent/index.ts +0 -19
  811. package/src/subagent/manager.ts +0 -511
  812. package/src/subagent/types.ts +0 -69
  813. package/src/swarm/backend-claude-code.ts +0 -145
  814. package/src/swarm/index.ts +0 -44
  815. package/src/swarm/limits.ts +0 -37
  816. package/src/swarm/orchestrator.ts +0 -279
  817. package/src/swarm/plan-validator.ts +0 -151
  818. package/src/swarm/router-planner.ts +0 -100
  819. package/src/swarm/router-prompts.ts +0 -36
  820. package/src/swarm/synthesizer.ts +0 -62
  821. package/src/swarm/types.ts +0 -62
  822. package/src/swarm/worker-backend.ts +0 -121
  823. package/src/swarm/worker-prompts.ts +0 -79
  824. package/src/swarm/worker-runner.ts +0 -164
  825. package/src/tasks/SPEC.md +0 -139
  826. package/src/tasks/candidate-store.ts +0 -86
  827. package/src/tasks/ephemeral-permissions.ts +0 -50
  828. package/src/tasks/task-compiler.ts +0 -199
  829. package/src/tasks/task-runner.ts +0 -90
  830. package/src/tasks/task-scheduler.ts +0 -20
  831. package/src/tasks/task-store.ts +0 -127
  832. package/src/tasks/tool-sanitizer.ts +0 -36
  833. package/src/tools/apps/definitions.ts +0 -59
  834. package/src/tools/apps/executors.ts +0 -313
  835. package/src/tools/apps/open-proxy.ts +0 -43
  836. package/src/tools/apps/registry.ts +0 -16
  837. package/src/tools/assets/materialize.ts +0 -218
  838. package/src/tools/assets/search.ts +0 -361
  839. package/src/tools/browser/__tests__/auth-cache.test.ts +0 -219
  840. package/src/tools/browser/__tests__/auth-detector.test.ts +0 -362
  841. package/src/tools/browser/__tests__/jit-auth.test.ts +0 -189
  842. package/src/tools/browser/api-map.ts +0 -293
  843. package/src/tools/browser/auth-cache.ts +0 -149
  844. package/src/tools/browser/auth-detector.ts +0 -347
  845. package/src/tools/browser/auto-navigate.ts +0 -270
  846. package/src/tools/browser/browser-execution.ts +0 -980
  847. package/src/tools/browser/browser-handoff.ts +0 -79
  848. package/src/tools/browser/browser-manager.ts +0 -715
  849. package/src/tools/browser/browser-screencast.ts +0 -217
  850. package/src/tools/browser/headless-browser.ts +0 -450
  851. package/src/tools/browser/jit-auth.ts +0 -51
  852. package/src/tools/browser/network-recorder.ts +0 -349
  853. package/src/tools/browser/network-recording-types.ts +0 -49
  854. package/src/tools/browser/recording-store.ts +0 -49
  855. package/src/tools/browser/runtime-check.ts +0 -43
  856. package/src/tools/browser/x-auto-navigate.ts +0 -207
  857. package/src/tools/calls/call-end.ts +0 -67
  858. package/src/tools/calls/call-start.ts +0 -73
  859. package/src/tools/calls/call-status.ts +0 -81
  860. package/src/tools/claude-code/claude-code.ts +0 -428
  861. package/src/tools/computer-use/definitions.ts +0 -443
  862. package/src/tools/computer-use/registry.ts +0 -22
  863. package/src/tools/computer-use/request-computer-control.ts +0 -53
  864. package/src/tools/computer-use/skill-proxy-bridge.ts +0 -28
  865. package/src/tools/contacts/contact-merge.ts +0 -55
  866. package/src/tools/contacts/contact-search.ts +0 -58
  867. package/src/tools/contacts/contact-upsert.ts +0 -64
  868. package/src/tools/credentials/account-registry.ts +0 -127
  869. package/src/tools/credentials/broker-types.ts +0 -107
  870. package/src/tools/credentials/broker.ts +0 -372
  871. package/src/tools/credentials/domain-policy.ts +0 -51
  872. package/src/tools/credentials/host-pattern-match.ts +0 -60
  873. package/src/tools/credentials/metadata-store.ts +0 -335
  874. package/src/tools/credentials/policy-types.ts +0 -52
  875. package/src/tools/credentials/policy-validate.ts +0 -80
  876. package/src/tools/credentials/resolve.ts +0 -122
  877. package/src/tools/credentials/selection.ts +0 -159
  878. package/src/tools/credentials/tool-policy.ts +0 -25
  879. package/src/tools/credentials/vault.ts +0 -657
  880. package/src/tools/document/document-tool.ts +0 -92
  881. package/src/tools/document/editor-template.ts +0 -237
  882. package/src/tools/executor.ts +0 -944
  883. package/src/tools/filesystem/edit.ts +0 -127
  884. package/src/tools/filesystem/fuzzy-match.ts +0 -202
  885. package/src/tools/filesystem/read.ts +0 -71
  886. package/src/tools/filesystem/view-image.ts +0 -199
  887. package/src/tools/filesystem/write.ts +0 -79
  888. package/src/tools/followups/followup_create.ts +0 -76
  889. package/src/tools/followups/followup_list.ts +0 -60
  890. package/src/tools/followups/followup_resolve.ts +0 -56
  891. package/src/tools/host-filesystem/edit.ts +0 -125
  892. package/src/tools/host-filesystem/read.ts +0 -80
  893. package/src/tools/host-filesystem/write.ts +0 -76
  894. package/src/tools/host-terminal/cli-discover.ts +0 -180
  895. package/src/tools/host-terminal/host-shell.ts +0 -191
  896. package/src/tools/memory/definitions.ts +0 -69
  897. package/src/tools/memory/handlers.ts +0 -246
  898. package/src/tools/memory/register.ts +0 -66
  899. package/src/tools/network/__tests__/web-search.test.ts +0 -427
  900. package/src/tools/network/domain-normalize.ts +0 -85
  901. package/src/tools/network/script-proxy/__tests__/logging.test.ts +0 -248
  902. package/src/tools/network/script-proxy/__tests__/policy.test.ts +0 -234
  903. package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -76
  904. package/src/tools/network/script-proxy/certs.ts +0 -237
  905. package/src/tools/network/script-proxy/connect-tunnel.ts +0 -82
  906. package/src/tools/network/script-proxy/http-forwarder.ts +0 -151
  907. package/src/tools/network/script-proxy/index.ts +0 -28
  908. package/src/tools/network/script-proxy/logging.ts +0 -196
  909. package/src/tools/network/script-proxy/mitm-handler.ts +0 -269
  910. package/src/tools/network/script-proxy/policy.ts +0 -152
  911. package/src/tools/network/script-proxy/router.ts +0 -60
  912. package/src/tools/network/script-proxy/server.ts +0 -136
  913. package/src/tools/network/script-proxy/session-manager.ts +0 -534
  914. package/src/tools/network/script-proxy/types.ts +0 -125
  915. package/src/tools/network/url-safety.ts +0 -227
  916. package/src/tools/network/web-fetch.ts +0 -713
  917. package/src/tools/network/web-search.ts +0 -319
  918. package/src/tools/playbooks/index.ts +0 -4
  919. package/src/tools/playbooks/playbook-create.ts +0 -96
  920. package/src/tools/playbooks/playbook-delete.ts +0 -52
  921. package/src/tools/playbooks/playbook-list.ts +0 -74
  922. package/src/tools/playbooks/playbook-update.ts +0 -111
  923. package/src/tools/registry.ts +0 -295
  924. package/src/tools/reminder/reminder-store.ts +0 -148
  925. package/src/tools/reminder/reminder.ts +0 -80
  926. package/src/tools/schedule/create.ts +0 -81
  927. package/src/tools/schedule/delete.ts +0 -28
  928. package/src/tools/schedule/list.ts +0 -69
  929. package/src/tools/schedule/update.ts +0 -90
  930. package/src/tools/shared/filesystem/edit-engine.ts +0 -56
  931. package/src/tools/shared/filesystem/errors.ts +0 -85
  932. package/src/tools/shared/filesystem/file-ops-service.ts +0 -215
  933. package/src/tools/shared/filesystem/format-diff.ts +0 -35
  934. package/src/tools/shared/filesystem/path-policy.ts +0 -125
  935. package/src/tools/shared/filesystem/size-guard.ts +0 -41
  936. package/src/tools/shared/filesystem/types.ts +0 -80
  937. package/src/tools/shared/shell-output.ts +0 -52
  938. package/src/tools/skills/delete-managed.ts +0 -60
  939. package/src/tools/skills/load.ts +0 -139
  940. package/src/tools/skills/sandbox-runner.ts +0 -279
  941. package/src/tools/skills/scaffold-managed.ts +0 -150
  942. package/src/tools/skills/script-contract.ts +0 -6
  943. package/src/tools/skills/skill-script-runner.ts +0 -86
  944. package/src/tools/skills/skill-tool-factory.ts +0 -64
  945. package/src/tools/skills/vellum-catalog.ts +0 -217
  946. package/src/tools/subagent/abort.ts +0 -33
  947. package/src/tools/subagent/message.ts +0 -39
  948. package/src/tools/subagent/read.ts +0 -67
  949. package/src/tools/subagent/spawn.ts +0 -46
  950. package/src/tools/subagent/status.ts +0 -45
  951. package/src/tools/swarm/delegate.ts +0 -183
  952. package/src/tools/system/request-permission.ts +0 -98
  953. package/src/tools/system/version.ts +0 -43
  954. package/src/tools/tasks/index.ts +0 -27
  955. package/src/tools/tasks/task-delete.ts +0 -82
  956. package/src/tools/tasks/task-list.ts +0 -44
  957. package/src/tools/tasks/task-run.ts +0 -97
  958. package/src/tools/tasks/task-save.ts +0 -47
  959. package/src/tools/tasks/work-item-enqueue.ts +0 -234
  960. package/src/tools/tasks/work-item-list.ts +0 -55
  961. package/src/tools/tasks/work-item-remove.ts +0 -60
  962. package/src/tools/tasks/work-item-run.ts +0 -78
  963. package/src/tools/tasks/work-item-update.ts +0 -114
  964. package/src/tools/terminal/backends/docker.ts +0 -372
  965. package/src/tools/terminal/backends/native.ts +0 -190
  966. package/src/tools/terminal/backends/types.ts +0 -26
  967. package/src/tools/terminal/evaluate-typescript.ts +0 -275
  968. package/src/tools/terminal/parser.ts +0 -415
  969. package/src/tools/terminal/safe-env.ts +0 -37
  970. package/src/tools/terminal/sandbox-diagnostics.ts +0 -149
  971. package/src/tools/terminal/sandbox.ts +0 -44
  972. package/src/tools/terminal/shell.ts +0 -257
  973. package/src/tools/tool-manifest.ts +0 -198
  974. package/src/tools/types.ts +0 -183
  975. package/src/tools/ui-surface/definitions.ts +0 -244
  976. package/src/tools/ui-surface/registry.ts +0 -14
  977. package/src/tools/watch/screen-watch.ts +0 -130
  978. package/src/tools/watch/watch-state.ts +0 -119
  979. package/src/tools/watcher/create.ts +0 -64
  980. package/src/tools/watcher/delete.ts +0 -27
  981. package/src/tools/watcher/digest.ts +0 -50
  982. package/src/tools/watcher/list.ts +0 -60
  983. package/src/tools/watcher/update.ts +0 -56
  984. package/src/tools/weather/service.ts +0 -551
  985. package/src/twitter/client.ts +0 -690
  986. package/src/twitter/session.ts +0 -91
  987. package/src/usage/actors.ts +0 -24
  988. package/src/usage/types.ts +0 -37
  989. package/src/util/clipboard.ts +0 -33
  990. package/src/util/content-id.ts +0 -16
  991. package/src/util/diff.ts +0 -181
  992. package/src/util/errors.ts +0 -129
  993. package/src/util/logger.ts +0 -243
  994. package/src/util/platform.ts +0 -607
  995. package/src/util/pricing.ts +0 -150
  996. package/src/util/spinner.ts +0 -51
  997. package/src/util/time.ts +0 -16
  998. package/src/util/truncate.ts +0 -6
  999. package/src/util/xml.ts +0 -4
  1000. package/src/version.ts +0 -3
  1001. package/src/watcher/constants.ts +0 -11
  1002. package/src/watcher/engine.ts +0 -199
  1003. package/src/watcher/provider-registry.ts +0 -15
  1004. package/src/watcher/provider-types.ts +0 -48
  1005. package/src/watcher/providers/gmail.ts +0 -198
  1006. package/src/watcher/providers/google-calendar.ts +0 -228
  1007. package/src/watcher/providers/slack.ts +0 -129
  1008. package/src/watcher/watcher-store.ts +0 -419
  1009. package/src/work-items/work-item-runner.ts +0 -171
  1010. package/src/work-items/work-item-store.ts +0 -325
  1011. package/src/workspace/commit-message-enrichment-service.ts +0 -284
  1012. package/src/workspace/commit-message-provider.ts +0 -95
  1013. package/src/workspace/git-service.ts +0 -840
  1014. package/src/workspace/heartbeat-service.ts +0 -345
  1015. package/src/workspace/provider-commit-message-generator.ts +0 -285
  1016. package/src/workspace/top-level-renderer.ts +0 -19
  1017. package/src/workspace/top-level-scanner.ts +0 -41
  1018. package/src/workspace/turn-commit.ts +0 -175
  1019. package/tsconfig.json +0 -21
@@ -1,2055 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { describe, test, expect, beforeEach, afterEach, afterAll, mock, spyOn } from 'bun:test';
3
- import type { ToolExecutionResult, Tool } from '../tools/types.js';
4
- import { RiskLevel } from '../permissions/types.js';
5
- import type { PolicyContext } from '../permissions/types.js';
6
-
7
- const mockConfig = {
8
- provider: 'anthropic',
9
- model: 'test',
10
- apiKeys: {},
11
- maxTokens: 4096,
12
- dataDir: '/tmp',
13
- timeouts: { shellDefaultTimeoutSec: 120, shellMaxTimeoutSec: 600, permissionTimeoutSec: 300 },
14
- sandbox: { enabled: false, backend: 'native' as const, docker: { image: 'vellum-sandbox:latest', cpus: 1, memoryMb: 512, pidsLimit: 256, network: 'none' as const } },
15
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
16
- secretDetection: { enabled: false, action: 'warn' as const, entropyThreshold: 4.0 },
17
- };
18
-
19
- let fakeToolResult: ToolExecutionResult = { content: 'ok', isError: false };
20
-
21
- /** Captured arguments from the last check() call, for assertion in tests. */
22
- let lastCheckArgs: { toolName: string; input: Record<string, unknown>; workingDir: string; policyContext?: PolicyContext } | undefined;
23
-
24
- /** Optional override for getTool — lets tests supply skill-origin tools. */
25
- let getToolOverride: ((name: string) => Tool | undefined) | undefined;
26
-
27
- /** Override the check() result for tests that need to trigger prompting. */
28
- let checkResultOverride: { decision: string; reason: string } | undefined;
29
-
30
- /** Function override for check() — when set, takes precedence over the static override. */
31
- let checkFnOverride: ((toolName: string, input: Record<string, unknown>, workingDir: string, policyContext?: PolicyContext) => Promise<{ decision: string; reason: string }>) | undefined;
32
-
33
- /** Spy on addRule to capture calls without replacing the real implementation. */
34
- let addRuleSpy: ReturnType<typeof spyOn> | undefined;
35
-
36
- mock.module('../config/loader.js', () => ({
37
- getConfig: () => mockConfig,
38
- loadConfig: () => mockConfig,
39
- invalidateConfigCache: () => {},
40
- saveConfig: () => {},
41
- loadRawConfig: () => ({}),
42
- saveRawConfig: () => {},
43
- getNestedValue: () => undefined,
44
- setNestedValue: () => {},
45
- }));
46
-
47
- mock.module('../util/logger.js', () => ({
48
- getLogger: () => new Proxy({} as Record<string, unknown>, {
49
- get: () => () => {},
50
- }),
51
- isDebug: () => false,
52
- truncateForLog: (value: string) => value,
53
- }));
54
-
55
- mock.module('../permissions/checker.js', () => ({
56
- classifyRisk: async () => 'low',
57
- check: async (toolName: string, input: Record<string, unknown>, workingDir: string, policyContext?: PolicyContext) => {
58
- lastCheckArgs = { toolName, input, workingDir, policyContext };
59
- if (checkFnOverride) return checkFnOverride(toolName, input, workingDir, policyContext);
60
- if (checkResultOverride) return checkResultOverride;
61
- return { decision: 'allow', reason: 'allowed' };
62
- },
63
- generateAllowlistOptions: () => [{ label: 'exact', description: 'exact', pattern: 'exact' }],
64
- generateScopeOptions: () => [{ label: '/tmp', scope: '/tmp' }],
65
- }));
66
-
67
- mock.module('../memory/tool-usage-store.js', () => ({
68
- recordToolInvocation: () => {},
69
- }));
70
-
71
- mock.module('../tools/registry.js', () => ({
72
- getTool: (name: string) => {
73
- if (getToolOverride) return getToolOverride(name);
74
- if (name === 'unknown_tool') return undefined;
75
- return {
76
- name,
77
- description: 'test tool',
78
- category: 'test',
79
- defaultRiskLevel: 'low',
80
- getDefinition: () => ({}),
81
- execute: async () => fakeToolResult,
82
- };
83
- },
84
- getAllTools: () => [],
85
- }));
86
-
87
- mock.module('../tools/shared/filesystem/path-policy.js', () => ({
88
- sandboxPolicy: () => ({ ok: false }),
89
- hostPolicy: () => ({ ok: false }),
90
- }));
91
-
92
- mock.module('../tools/terminal/sandbox.js', () => ({
93
- wrapCommand: () => ({ command: '', sandboxed: false }),
94
- }));
95
-
96
- import { ToolExecutor, isSideEffectTool } from '../tools/executor.js';
97
- import type { ToolContext } from '../tools/types.js';
98
- import { PermissionPrompter } from '../permissions/prompter.js';
99
- import * as trustStore from '../permissions/trust-store.js';
100
-
101
- function makeContext(overrides?: Partial<ToolContext>): ToolContext {
102
- return {
103
- workingDir: '/tmp/project',
104
- sessionId: 'session-1',
105
- conversationId: 'conversation-1',
106
- ...overrides,
107
- };
108
- }
109
-
110
- function makePrompter(): PermissionPrompter {
111
- return {
112
- prompt: async () => ({ decision: 'allow' as const }),
113
- resolveConfirmation: () => {},
114
- updateSender: () => {},
115
- dispose: () => {},
116
- } as unknown as PermissionPrompter;
117
- }
118
-
119
- afterAll(() => { mock.restore(); });
120
-
121
- describe('ToolExecutor allowedToolNames gating', () => {
122
- beforeEach(() => {
123
- fakeToolResult = { content: 'ok', isError: false };
124
- lastCheckArgs = undefined;
125
- getToolOverride = undefined;
126
- checkResultOverride = undefined;
127
- checkFnOverride = undefined;
128
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
129
- });
130
-
131
- test('executes normally when allowedToolNames is not set (backward compat)', async () => {
132
- const executor = new ToolExecutor(makePrompter());
133
- const result = await executor.execute('file_read', { path: 'README.md' }, makeContext());
134
- expect(result.isError).toBe(false);
135
- expect(result.content).toBe('ok');
136
- });
137
-
138
- test('executes normally when tool is in the allowed set', async () => {
139
- const executor = new ToolExecutor(makePrompter());
140
- const allowed = new Set(['file_read', 'file_write', 'bash']);
141
- const result = await executor.execute('file_read', { path: 'README.md' }, makeContext({ allowedToolNames: allowed }));
142
- expect(result.isError).toBe(false);
143
- expect(result.content).toBe('ok');
144
- });
145
-
146
- test('blocks execution when tool is NOT in the allowed set', async () => {
147
- const executor = new ToolExecutor(makePrompter());
148
- const allowed = new Set(['file_read', 'bash']);
149
- const result = await executor.execute('file_write', { path: 'test.txt', content: 'hello' }, makeContext({ allowedToolNames: allowed }));
150
- expect(result.isError).toBe(true);
151
- expect(result.content).toContain('not currently active');
152
- });
153
-
154
- test('error message includes the blocked tool name', async () => {
155
- const executor = new ToolExecutor(makePrompter());
156
- const allowed = new Set(['bash']);
157
- const result = await executor.execute('file_edit', { path: 'x' }, makeContext({ allowedToolNames: allowed }));
158
- expect(result.isError).toBe(true);
159
- expect(result.content).toBe('Tool "file_edit" is not currently active. Load the skill that provides this tool first.');
160
- });
161
-
162
- test('empty allowed set blocks all tools', async () => {
163
- const executor = new ToolExecutor(makePrompter());
164
- const allowed = new Set<string>();
165
- const result = await executor.execute('file_read', { path: 'README.md' }, makeContext({ allowedToolNames: allowed }));
166
- expect(result.isError).toBe(true);
167
- expect(result.content).toContain('file_read');
168
- expect(result.content).toContain('not currently active');
169
- });
170
- });
171
-
172
- describe('ToolExecutor principal context plumbing', () => {
173
- beforeEach(() => {
174
- fakeToolResult = { content: 'ok', isError: false };
175
- lastCheckArgs = undefined;
176
- getToolOverride = undefined;
177
- checkResultOverride = undefined;
178
- checkFnOverride = undefined;
179
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
180
- });
181
-
182
- test('passes PolicyContext with skill principal for skill-origin tools', async () => {
183
- getToolOverride = (name: string) => {
184
- if (name === 'unknown_tool') return undefined;
185
- return {
186
- name,
187
- description: 'skill tool',
188
- category: 'skill',
189
- defaultRiskLevel: RiskLevel.Low,
190
- origin: 'skill' as const,
191
- ownerSkillId: 'my-skill-123',
192
- ownerSkillVersionHash: 'abc123hash',
193
- executionTarget: 'sandbox' as const,
194
- getDefinition: () => ({ name, description: 'skill tool', input_schema: { type: 'object' as const, properties: {} } }),
195
- execute: async () => fakeToolResult,
196
- };
197
- };
198
-
199
- const executor = new ToolExecutor(makePrompter());
200
- const result = await executor.execute('skill_tool', { action: 'run' }, makeContext());
201
-
202
- expect(result.isError).toBe(false);
203
- expect(lastCheckArgs).toBeDefined();
204
- expect(lastCheckArgs!.policyContext).toEqual({
205
- principal: {
206
- kind: 'skill',
207
- id: 'my-skill-123',
208
- version: 'abc123hash',
209
- },
210
- executionTarget: 'sandbox',
211
- });
212
- });
213
-
214
- test('passes undefined policyContext for core tools (no origin)', async () => {
215
- // Default getTool returns core tools with no origin field
216
- getToolOverride = undefined;
217
-
218
- const executor = new ToolExecutor(makePrompter());
219
- const result = await executor.execute('file_read', { path: 'test.txt' }, makeContext());
220
-
221
- expect(result.isError).toBe(false);
222
- expect(lastCheckArgs).toBeDefined();
223
- expect(lastCheckArgs!.policyContext).toBeUndefined();
224
- });
225
-
226
- test('passes undefined policyContext for tools with origin "core"', async () => {
227
- getToolOverride = (name: string) => {
228
- if (name === 'unknown_tool') return undefined;
229
- return {
230
- name,
231
- description: 'core tool',
232
- category: 'core',
233
- defaultRiskLevel: RiskLevel.Low,
234
- origin: 'core' as const,
235
- getDefinition: () => ({ name, description: 'core tool', input_schema: { type: 'object' as const, properties: {} } }),
236
- execute: async () => fakeToolResult,
237
- };
238
- };
239
-
240
- const executor = new ToolExecutor(makePrompter());
241
- const result = await executor.execute('file_read', { path: 'test.txt' }, makeContext());
242
-
243
- expect(result.isError).toBe(false);
244
- expect(lastCheckArgs).toBeDefined();
245
- expect(lastCheckArgs!.policyContext).toBeUndefined();
246
- });
247
-
248
- test('includes executionTarget "host" from skill tool metadata', async () => {
249
- getToolOverride = (name: string) => {
250
- if (name === 'unknown_tool') return undefined;
251
- return {
252
- name,
253
- description: 'host skill tool',
254
- category: 'skill',
255
- defaultRiskLevel: RiskLevel.Low,
256
- origin: 'skill' as const,
257
- ownerSkillId: 'host-skill',
258
- ownerSkillVersionHash: 'host-hash',
259
- executionTarget: 'host' as const,
260
- getDefinition: () => ({ name, description: 'host skill tool', input_schema: { type: 'object' as const, properties: {} } }),
261
- execute: async () => fakeToolResult,
262
- };
263
- };
264
-
265
- const executor = new ToolExecutor(makePrompter());
266
- const result = await executor.execute('host_skill_tool', { action: 'run' }, makeContext());
267
-
268
- expect(result.isError).toBe(false);
269
- expect(lastCheckArgs).toBeDefined();
270
- expect(lastCheckArgs!.policyContext).toEqual({
271
- principal: {
272
- kind: 'skill',
273
- id: 'host-skill',
274
- version: 'host-hash',
275
- },
276
- executionTarget: 'host',
277
- });
278
- });
279
-
280
- test('skill tool without version hash passes undefined version in principal', async () => {
281
- getToolOverride = (name: string) => {
282
- if (name === 'unknown_tool') return undefined;
283
- return {
284
- name,
285
- description: 'skill without hash',
286
- category: 'skill',
287
- defaultRiskLevel: RiskLevel.Low,
288
- origin: 'skill' as const,
289
- ownerSkillId: 'no-hash-skill',
290
- // ownerSkillVersionHash intentionally omitted
291
- getDefinition: () => ({ name, description: 'skill tool', input_schema: { type: 'object' as const, properties: {} } }),
292
- execute: async () => fakeToolResult,
293
- };
294
- };
295
-
296
- const executor = new ToolExecutor(makePrompter());
297
- const result = await executor.execute('no_hash_tool', {}, makeContext());
298
-
299
- expect(result.isError).toBe(false);
300
- expect(lastCheckArgs).toBeDefined();
301
- expect(lastCheckArgs!.policyContext).toEqual({
302
- principal: {
303
- kind: 'skill',
304
- id: 'no-hash-skill',
305
- version: undefined,
306
- },
307
- executionTarget: undefined,
308
- });
309
- });
310
- });
311
-
312
- /**
313
- * Helper: create a prompter that returns a specific decision with pattern/scope.
314
- */
315
- function makePrompterWithDecision(
316
- decision: string,
317
- selectedPattern?: string,
318
- selectedScope?: string,
319
- ): PermissionPrompter {
320
- return {
321
- prompt: async () => ({ decision, selectedPattern, selectedScope }),
322
- resolveConfirmation: () => {},
323
- updateSender: () => {},
324
- dispose: () => {},
325
- } as unknown as PermissionPrompter;
326
- }
327
-
328
- describe('ToolExecutor contextual rule creation', () => {
329
- beforeEach(() => {
330
- fakeToolResult = { content: 'ok', isError: false };
331
- lastCheckArgs = undefined;
332
- getToolOverride = undefined;
333
- checkResultOverride = undefined;
334
- checkFnOverride = undefined;
335
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
336
- });
337
-
338
- function setupAddRuleSpy() {
339
- addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
340
- (tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: any) => {
341
- return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as any;
342
- },
343
- );
344
- return addRuleSpy;
345
- }
346
-
347
- test('always_allow for a skill tool captures principal context in the rule', async () => {
348
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
349
- const spy = setupAddRuleSpy();
350
-
351
- getToolOverride = (name: string) => {
352
- if (name === 'unknown_tool') return undefined;
353
- return {
354
- name,
355
- description: 'skill tool',
356
- category: 'skill',
357
- defaultRiskLevel: RiskLevel.Low,
358
- origin: 'skill' as const,
359
- ownerSkillId: 'my-skill-42',
360
- ownerSkillVersionHash: 'sha256-deadbeef',
361
- executionTarget: 'sandbox' as const,
362
- getDefinition: () => ({ name, description: 'skill tool', input_schema: { type: 'object' as const, properties: {} } }),
363
- execute: async () => fakeToolResult,
364
- };
365
- };
366
-
367
- const prompter = makePrompterWithDecision('always_allow', 'skill_tool:*', '/tmp/project');
368
- const executor = new ToolExecutor(prompter);
369
- const result = await executor.execute('skill_tool', { action: 'run' }, makeContext());
370
-
371
- expect(result.isError).toBe(false);
372
- expect(spy).toHaveBeenCalledTimes(1);
373
- const [tool, pattern, scope, decision, _priority, options] = spy.mock.calls[0];
374
- expect(tool).toBe('skill_tool');
375
- expect(pattern).toBe('skill_tool:*');
376
- expect(scope).toBe('/tmp/project');
377
- expect(decision).toBe('allow');
378
- expect(options).toBeDefined();
379
- expect(options.principalKind).toBe('skill');
380
- expect(options.principalId).toBe('my-skill-42');
381
- expect(options.principalVersion).toBe('sha256-deadbeef');
382
- expect(options.executionTarget).toBe('sandbox');
383
- });
384
-
385
- test('always_allow_high_risk sets allowHighRisk and captures principal context', async () => {
386
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
387
- const spy = setupAddRuleSpy();
388
-
389
- getToolOverride = (name: string) => {
390
- if (name === 'unknown_tool') return undefined;
391
- return {
392
- name,
393
- description: 'high-risk skill tool',
394
- category: 'skill',
395
- defaultRiskLevel: RiskLevel.High,
396
- origin: 'skill' as const,
397
- ownerSkillId: 'dangerous-skill',
398
- ownerSkillVersionHash: 'sha256-abc',
399
- executionTarget: 'host' as const,
400
- getDefinition: () => ({ name, description: 'high-risk skill tool', input_schema: { type: 'object' as const, properties: {} } }),
401
- execute: async () => fakeToolResult,
402
- };
403
- };
404
-
405
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'risky_tool:*', 'everywhere');
406
- const executor = new ToolExecutor(prompter);
407
- const result = await executor.execute('risky_tool', {}, makeContext());
408
-
409
- expect(result.isError).toBe(false);
410
- expect(spy).toHaveBeenCalledTimes(1);
411
- const [tool, pattern, scope, decision, _priority, options] = spy.mock.calls[0];
412
- expect(tool).toBe('risky_tool');
413
- expect(pattern).toBe('risky_tool:*');
414
- expect(scope).toBe('everywhere');
415
- expect(decision).toBe('allow');
416
- expect(options).toBeDefined();
417
- expect(options.allowHighRisk).toBe(true);
418
- expect(options.principalKind).toBe('skill');
419
- expect(options.principalId).toBe('dangerous-skill');
420
- expect(options.principalVersion).toBe('sha256-abc');
421
- expect(options.executionTarget).toBe('host');
422
- });
423
-
424
- test('always_allow for a core tool creates rule without principal context', async () => {
425
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
426
- const spy = setupAddRuleSpy();
427
-
428
- // Default getTool returns core tools with no origin field
429
- getToolOverride = undefined;
430
-
431
- const prompter = makePrompterWithDecision('always_allow', 'git *', '/tmp/project');
432
- const executor = new ToolExecutor(prompter);
433
- const result = await executor.execute('bash', { command: 'git status' }, makeContext());
434
-
435
- expect(result.isError).toBe(false);
436
- expect(spy).toHaveBeenCalledTimes(1);
437
- const [tool, pattern, scope, decision, _priority, options] = spy.mock.calls[0];
438
- expect(tool).toBe('bash');
439
- expect(pattern).toBe('git *');
440
- expect(scope).toBe('/tmp/project');
441
- expect(decision).toBe('allow');
442
- // No options since there's no principal context for core tools
443
- expect(options).toBeUndefined();
444
- });
445
-
446
- test('always_allow without selectedPattern does not create a rule', async () => {
447
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
448
- const spy = setupAddRuleSpy();
449
-
450
- const prompter = makePrompterWithDecision('always_allow', undefined, '/tmp/project');
451
- const executor = new ToolExecutor(prompter);
452
- const result = await executor.execute('file_read', { path: 'test.txt' }, makeContext());
453
-
454
- expect(result.isError).toBe(false);
455
- expect(spy).not.toHaveBeenCalled();
456
- });
457
-
458
- test('always_allow without selectedScope does not create a rule', async () => {
459
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
460
- const spy = setupAddRuleSpy();
461
-
462
- const prompter = makePrompterWithDecision('always_allow', 'file_read:*', undefined);
463
- const executor = new ToolExecutor(prompter);
464
- const result = await executor.execute('file_read', { path: 'test.txt' }, makeContext());
465
-
466
- expect(result.isError).toBe(false);
467
- expect(spy).not.toHaveBeenCalled();
468
- });
469
-
470
- test('always_allow_high_risk for core tool sets allowHighRisk without principal fields', async () => {
471
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
472
- const spy = setupAddRuleSpy();
473
- getToolOverride = undefined;
474
-
475
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'sudo *', 'everywhere');
476
- const executor = new ToolExecutor(prompter);
477
- const result = await executor.execute('bash', { command: 'sudo apt update' }, makeContext());
478
-
479
- expect(result.isError).toBe(false);
480
- expect(spy).toHaveBeenCalledTimes(1);
481
- const [,,,, , options] = spy.mock.calls[0];
482
- expect(options).toBeDefined();
483
- expect(options.allowHighRisk).toBe(true);
484
- // No principal fields for core tools
485
- expect(options.principalKind).toBeUndefined();
486
- expect(options.principalId).toBeUndefined();
487
- expect(options.principalVersion).toBeUndefined();
488
- expect(options.executionTarget).toBeUndefined();
489
- });
490
-
491
- test('skill tool with host execution target records executionTarget in rule', async () => {
492
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
493
- const spy = setupAddRuleSpy();
494
-
495
- getToolOverride = (name: string) => {
496
- if (name === 'unknown_tool') return undefined;
497
- return {
498
- name,
499
- description: 'host skill tool',
500
- category: 'skill',
501
- defaultRiskLevel: RiskLevel.Low,
502
- origin: 'skill' as const,
503
- ownerSkillId: 'host-skill',
504
- ownerSkillVersionHash: 'host-hash-v1',
505
- executionTarget: 'host' as const,
506
- getDefinition: () => ({ name, description: 'host skill tool', input_schema: { type: 'object' as const, properties: {} } }),
507
- execute: async () => fakeToolResult,
508
- };
509
- };
510
-
511
- const prompter = makePrompterWithDecision('always_allow', 'host_action:*', '/tmp/project');
512
- const executor = new ToolExecutor(prompter);
513
- const result = await executor.execute('host_action', { action: 'click' }, makeContext());
514
-
515
- expect(result.isError).toBe(false);
516
- expect(spy).toHaveBeenCalledTimes(1);
517
- const [,,,, , options] = spy.mock.calls[0];
518
- expect(options).toBeDefined();
519
- expect(options.executionTarget).toBe('host');
520
- expect(options.principalKind).toBe('skill');
521
- expect(options.principalId).toBe('host-skill');
522
- expect(options.principalVersion).toBe('host-hash-v1');
523
- });
524
- });
525
-
526
- describe('ToolExecutor prompter principal arg (PR fix3)', () => {
527
- beforeEach(() => {
528
- fakeToolResult = { content: 'ok', isError: false };
529
- lastCheckArgs = undefined;
530
- getToolOverride = undefined;
531
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
532
- checkFnOverride = undefined;
533
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
534
- });
535
-
536
- test('passes principal context (kind, id, version) to prompter for skill-origin tools', async () => {
537
- getToolOverride = (name: string) => {
538
- if (name === 'unknown_tool') return undefined;
539
- return {
540
- name,
541
- description: 'skill tool',
542
- category: 'skill',
543
- defaultRiskLevel: RiskLevel.Low,
544
- origin: 'skill' as const,
545
- ownerSkillId: 'prompt-skill-42',
546
- ownerSkillVersionHash: 'sha256-prompt-v1',
547
- executionTarget: 'sandbox' as const,
548
- getDefinition: () => ({ name, description: 'skill tool', input_schema: { type: 'object' as const, properties: {} } }),
549
- execute: async () => fakeToolResult,
550
- };
551
- };
552
-
553
- let capturedPrincipal: unknown;
554
- const prompter = {
555
- prompt: async (
556
- _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
557
- _allowlistOptions: any[], _scopeOptions: any[], _diff: any, _sandboxed: any,
558
- _sessionId: any, _executionTarget: any, principal: any,
559
- ) => {
560
- capturedPrincipal = principal;
561
- return { decision: 'allow' as const };
562
- },
563
- resolveConfirmation: () => {},
564
- updateSender: () => {},
565
- dispose: () => {},
566
- } as unknown as PermissionPrompter;
567
-
568
- const executor = new ToolExecutor(prompter);
569
- const result = await executor.execute('skill_tool', { action: 'run' }, makeContext());
570
-
571
- expect(result.isError).toBe(false);
572
- expect(capturedPrincipal).toEqual({
573
- kind: 'skill',
574
- id: 'prompt-skill-42',
575
- version: 'sha256-prompt-v1',
576
- });
577
- });
578
-
579
- test('passes undefined principal to prompter for core tools', async () => {
580
- // Default getTool returns core tools with no origin field
581
- getToolOverride = undefined;
582
-
583
- let capturedPrincipal: unknown = 'NOT_CALLED';
584
- const prompter = {
585
- prompt: async (
586
- _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
587
- _allowlistOptions: any[], _scopeOptions: any[], _diff: any, _sandboxed: any,
588
- _sessionId: any, _executionTarget: any, principal: any,
589
- ) => {
590
- capturedPrincipal = principal;
591
- return { decision: 'allow' as const };
592
- },
593
- resolveConfirmation: () => {},
594
- updateSender: () => {},
595
- dispose: () => {},
596
- } as unknown as PermissionPrompter;
597
-
598
- const executor = new ToolExecutor(prompter);
599
- const result = await executor.execute('file_read', { path: 'test.txt' }, makeContext());
600
-
601
- expect(result.isError).toBe(false);
602
- expect(capturedPrincipal).toBeUndefined();
603
- });
604
- });
605
-
606
- describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
607
- beforeEach(() => {
608
- fakeToolResult = { content: 'ok', isError: false };
609
- lastCheckArgs = undefined;
610
- getToolOverride = undefined;
611
- checkResultOverride = undefined;
612
- checkFnOverride = undefined;
613
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
614
- });
615
-
616
- function setupAddRuleSpy() {
617
- addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
618
- (tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: any) => {
619
- return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as any;
620
- },
621
- );
622
- return addRuleSpy;
623
- }
624
-
625
- test('always_allow_high_risk creates rule with allowHighRisk: true for high-risk skill tool', async () => {
626
- checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
627
- const spy = setupAddRuleSpy();
628
-
629
- getToolOverride = (name: string) => {
630
- if (name === 'unknown_tool') return undefined;
631
- return {
632
- name,
633
- description: 'high-risk skill tool',
634
- category: 'skill',
635
- defaultRiskLevel: RiskLevel.High,
636
- origin: 'skill' as const,
637
- ownerSkillId: 'deploy-skill',
638
- ownerSkillVersionHash: 'sha256-deploy-v1',
639
- executionTarget: 'host' as const,
640
- getDefinition: () => ({ name, description: 'high-risk skill tool', input_schema: { type: 'object' as const, properties: {} } }),
641
- execute: async () => fakeToolResult,
642
- };
643
- };
644
-
645
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'deploy_tool:*', 'everywhere');
646
- const executor = new ToolExecutor(prompter);
647
- const result = await executor.execute('deploy_tool', { target: 'prod' }, makeContext());
648
-
649
- expect(result.isError).toBe(false);
650
- expect(spy).toHaveBeenCalledTimes(1);
651
- const [tool, pattern, scope, decision, _priority, options] = spy.mock.calls[0];
652
- expect(tool).toBe('deploy_tool');
653
- expect(pattern).toBe('deploy_tool:*');
654
- expect(scope).toBe('everywhere');
655
- expect(decision).toBe('allow');
656
- // The key integration assertion: allowHighRisk + principal context together
657
- expect(options.allowHighRisk).toBe(true);
658
- expect(options.principalKind).toBe('skill');
659
- expect(options.principalId).toBe('deploy-skill');
660
- expect(options.principalVersion).toBe('sha256-deploy-v1');
661
- expect(options.executionTarget).toBe('host');
662
- });
663
-
664
- test('always_allow creates rule without allowHighRisk even for high-risk skill tool', async () => {
665
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
666
- const spy = setupAddRuleSpy();
667
-
668
- getToolOverride = (name: string) => {
669
- if (name === 'unknown_tool') return undefined;
670
- return {
671
- name,
672
- description: 'high-risk skill tool',
673
- category: 'skill',
674
- defaultRiskLevel: RiskLevel.High,
675
- origin: 'skill' as const,
676
- ownerSkillId: 'risky-skill',
677
- ownerSkillVersionHash: 'sha256-risky',
678
- executionTarget: 'sandbox' as const,
679
- getDefinition: () => ({ name, description: 'high-risk skill tool', input_schema: { type: 'object' as const, properties: {} } }),
680
- execute: async () => fakeToolResult,
681
- };
682
- };
683
-
684
- // User chooses always_allow (NOT always_allow_high_risk) — the rule
685
- // should NOT have allowHighRisk set, meaning future high-risk checks
686
- // will still prompt.
687
- const prompter = makePrompterWithDecision('always_allow', 'risky_op:*', '/tmp/project');
688
- const executor = new ToolExecutor(prompter);
689
- const result = await executor.execute('risky_op', {}, makeContext());
690
-
691
- expect(result.isError).toBe(false);
692
- expect(spy).toHaveBeenCalledTimes(1);
693
- const [,,,, , options] = spy.mock.calls[0];
694
- expect(options).toBeDefined();
695
- // Principal context should be present
696
- expect(options.principalKind).toBe('skill');
697
- expect(options.principalId).toBe('risky-skill');
698
- // But allowHighRisk should NOT be set
699
- expect(options.allowHighRisk).toBeUndefined();
700
- });
701
-
702
- test('executor forwards policyContext to check() for version-bound skill tool', async () => {
703
- getToolOverride = (name: string) => {
704
- if (name === 'unknown_tool') return undefined;
705
- return {
706
- name,
707
- description: 'versioned skill tool',
708
- category: 'skill',
709
- defaultRiskLevel: RiskLevel.Low,
710
- origin: 'skill' as const,
711
- ownerSkillId: 'versioned-skill',
712
- ownerSkillVersionHash: 'v3:content-hash-xyz',
713
- executionTarget: 'sandbox' as const,
714
- getDefinition: () => ({ name, description: 'versioned skill tool', input_schema: { type: 'object' as const, properties: {} } }),
715
- execute: async () => fakeToolResult,
716
- };
717
- };
718
-
719
- const executor = new ToolExecutor(makePrompter());
720
- await executor.execute('versioned_tool', { action: 'test' }, makeContext());
721
-
722
- expect(lastCheckArgs).toBeDefined();
723
- expect(lastCheckArgs!.policyContext).toEqual({
724
- principal: {
725
- kind: 'skill',
726
- id: 'versioned-skill',
727
- version: 'v3:content-hash-xyz',
728
- },
729
- executionTarget: 'sandbox',
730
- });
731
- });
732
-
733
- // ── Skill mutation approval regression tests (PR 30) ──────────
734
-
735
- test('always_allow_high_risk for skill source write creates rule with allowHighRisk and principal context', async () => {
736
- checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
737
- const spy = setupAddRuleSpy();
738
-
739
- getToolOverride = (name: string) => {
740
- if (name === 'unknown_tool') return undefined;
741
- return {
742
- name,
743
- description: 'skill tool that writes to skill source',
744
- category: 'skill',
745
- defaultRiskLevel: RiskLevel.High,
746
- origin: 'skill' as const,
747
- ownerSkillId: 'code-editor-skill',
748
- ownerSkillVersionHash: 'sha256-v1-original',
749
- executionTarget: 'sandbox' as const,
750
- getDefinition: () => ({ name, description: 'skill source writer', input_schema: { type: 'object' as const, properties: {} } }),
751
- execute: async () => fakeToolResult,
752
- };
753
- };
754
-
755
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'file_write:*/skills/**', 'everywhere');
756
- const executor = new ToolExecutor(prompter);
757
- const result = await executor.execute('file_write', { path: '/tmp/skills/my-skill/executor.ts' }, makeContext());
758
-
759
- expect(result.isError).toBe(false);
760
- expect(spy).toHaveBeenCalledTimes(1);
761
- const [tool, pattern, scope, decision, , options] = spy.mock.calls[0];
762
- expect(tool).toBe('file_write');
763
- expect(pattern).toBe('file_write:*/skills/**');
764
- expect(scope).toBe('everywhere');
765
- expect(decision).toBe('allow');
766
- expect(options.allowHighRisk).toBe(true);
767
- expect(options.principalKind).toBe('skill');
768
- expect(options.principalId).toBe('code-editor-skill');
769
- expect(options.principalVersion).toBe('sha256-v1-original');
770
- expect(options.executionTarget).toBe('sandbox');
771
- });
772
-
773
- test('always_allow (not high risk) for skill source write creates rule WITHOUT allowHighRisk', async () => {
774
- checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
775
- const spy = setupAddRuleSpy();
776
-
777
- getToolOverride = (name: string) => {
778
- if (name === 'unknown_tool') return undefined;
779
- return {
780
- name,
781
- description: 'skill tool that writes to skill source',
782
- category: 'skill',
783
- defaultRiskLevel: RiskLevel.High,
784
- origin: 'skill' as const,
785
- ownerSkillId: 'editor-skill',
786
- ownerSkillVersionHash: 'sha256-editor-v1',
787
- executionTarget: 'sandbox' as const,
788
- getDefinition: () => ({ name, description: 'skill source writer', input_schema: { type: 'object' as const, properties: {} } }),
789
- execute: async () => fakeToolResult,
790
- };
791
- };
792
-
793
- // User chooses always_allow instead of always_allow_high_risk
794
- const prompter = makePrompterWithDecision('always_allow', 'file_write:*/skills/**', '/tmp/project');
795
- const executor = new ToolExecutor(prompter);
796
- const result = await executor.execute('file_write', { path: '/tmp/skills/my-skill/executor.ts' }, makeContext());
797
-
798
- expect(result.isError).toBe(false);
799
- expect(spy).toHaveBeenCalledTimes(1);
800
- const [,,,, , options] = spy.mock.calls[0];
801
- expect(options).toBeDefined();
802
- expect(options.principalKind).toBe('skill');
803
- expect(options.principalId).toBe('editor-skill');
804
- // Without always_allow_high_risk, the allowHighRisk flag should NOT be set
805
- expect(options.allowHighRisk).toBeUndefined();
806
- });
807
-
808
- test('skill version is captured in rule for future version-bound matching', async () => {
809
- checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
810
- const spy = setupAddRuleSpy();
811
-
812
- getToolOverride = (name: string) => {
813
- if (name === 'unknown_tool') return undefined;
814
- return {
815
- name,
816
- description: 'versioned skill tool',
817
- category: 'skill',
818
- defaultRiskLevel: RiskLevel.High,
819
- origin: 'skill' as const,
820
- ownerSkillId: 'versioned-editor',
821
- ownerSkillVersionHash: 'v3:content-hash-xyz789',
822
- executionTarget: 'sandbox' as const,
823
- getDefinition: () => ({ name, description: 'versioned skill editor', input_schema: { type: 'object' as const, properties: {} } }),
824
- execute: async () => fakeToolResult,
825
- };
826
- };
827
-
828
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'file_edit:*/skills/**', 'everywhere');
829
- const executor = new ToolExecutor(prompter);
830
- const result = await executor.execute('file_edit', { path: '/tmp/skills/my-skill/SKILL.md' }, makeContext());
831
-
832
- expect(result.isError).toBe(false);
833
- expect(spy).toHaveBeenCalledTimes(1);
834
- const [tool, , , , , options] = spy.mock.calls[0];
835
- expect(tool).toBe('file_edit');
836
- // Verify the version hash is persisted — a changed skill will have a
837
- // different hash, so the rule won't match (version mismatch rejection).
838
- expect(options.principalVersion).toBe('v3:content-hash-xyz789');
839
- expect(options.allowHighRisk).toBe(true);
840
- });
841
-
842
- test('executor forwards policyContext with version for skill source mutation', async () => {
843
- getToolOverride = (name: string) => {
844
- if (name === 'unknown_tool') return undefined;
845
- return {
846
- name,
847
- description: 'skill source editor',
848
- category: 'skill',
849
- defaultRiskLevel: RiskLevel.High,
850
- origin: 'skill' as const,
851
- ownerSkillId: 'editor-skill',
852
- ownerSkillVersionHash: 'sha256-v2-updated',
853
- executionTarget: 'sandbox' as const,
854
- getDefinition: () => ({ name, description: 'skill editor', input_schema: { type: 'object' as const, properties: {} } }),
855
- execute: async () => fakeToolResult,
856
- };
857
- };
858
-
859
- const executor = new ToolExecutor(makePrompter());
860
- await executor.execute('file_write', { path: '/tmp/skills/my-skill/index.ts' }, makeContext());
861
-
862
- expect(lastCheckArgs).toBeDefined();
863
- expect(lastCheckArgs!.policyContext).toEqual({
864
- principal: {
865
- kind: 'skill',
866
- id: 'editor-skill',
867
- version: 'sha256-v2-updated',
868
- },
869
- executionTarget: 'sandbox',
870
- });
871
- });
872
-
873
- test('executor creates principal-scoped rule on always_allow_high_risk with full context', async () => {
874
- checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
875
- const spy = setupAddRuleSpy();
876
-
877
- getToolOverride = (name: string) => {
878
- if (name === 'unknown_tool') return undefined;
879
- return {
880
- name,
881
- description: 'admin skill tool',
882
- category: 'skill',
883
- defaultRiskLevel: RiskLevel.High,
884
- origin: 'skill' as const,
885
- ownerSkillId: 'admin-skill',
886
- ownerSkillVersionHash: 'sha256-admin-v2',
887
- executionTarget: 'host' as const,
888
- getDefinition: () => ({ name, description: 'admin skill tool', input_schema: { type: 'object' as const, properties: {} } }),
889
- execute: async () => fakeToolResult,
890
- };
891
- };
892
-
893
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'admin_action:*', 'everywhere');
894
- const executor = new ToolExecutor(prompter);
895
- const result = await executor.execute('admin_action', { op: 'restart' }, makeContext());
896
-
897
- expect(result.isError).toBe(false);
898
- expect(spy).toHaveBeenCalledTimes(1);
899
- const [tool, pattern, scope, decision, , options] = spy.mock.calls[0];
900
-
901
- // Verify complete integration of all fields
902
- expect(tool).toBe('admin_action');
903
- expect(pattern).toBe('admin_action:*');
904
- expect(scope).toBe('everywhere');
905
- expect(decision).toBe('allow');
906
- expect(options.allowHighRisk).toBe(true);
907
- expect(options.principalKind).toBe('skill');
908
- expect(options.principalId).toBe('admin-skill');
909
- expect(options.principalVersion).toBe('sha256-admin-v2');
910
- expect(options.executionTarget).toBe('host');
911
- });
912
- });
913
-
914
- // ---------------------------------------------------------------------------
915
- // isSideEffectTool classifier
916
- // ---------------------------------------------------------------------------
917
-
918
- describe('isSideEffectTool', () => {
919
- describe('returns true for side-effect tools', () => {
920
- const sideEffectTools = [
921
- 'file_write',
922
- 'file_edit',
923
- 'host_file_write',
924
- 'host_file_edit',
925
- 'bash',
926
- 'host_bash',
927
- 'web_fetch',
928
- 'browser_navigate',
929
- 'browser_click',
930
- 'browser_type',
931
- 'browser_press_key',
932
- 'browser_close',
933
- 'browser_fill_credential',
934
- 'document_create',
935
- 'document_update',
936
- 'schedule_create',
937
- 'schedule_update',
938
- 'schedule_delete',
939
- ];
940
-
941
- for (const toolName of sideEffectTools) {
942
- test(toolName, () => {
943
- expect(isSideEffectTool(toolName)).toBe(true);
944
- });
945
- }
946
- });
947
-
948
- describe('returns false for non-side-effect tools', () => {
949
- const readOnlyTools = [
950
- 'file_read',
951
- 'memory_search',
952
- 'memory_save',
953
- 'web_search',
954
- 'browser_snapshot',
955
- 'browser_screenshot',
956
- 'browser_wait_for',
957
- 'browser_extract',
958
- 'skill_load',
959
- 'schedule_list',
960
- 'evaluate_typescript_code',
961
- ];
962
-
963
- for (const toolName of readOnlyTools) {
964
- test(toolName, () => {
965
- expect(isSideEffectTool(toolName)).toBe(false);
966
- });
967
- }
968
- });
969
-
970
- test('returns false for unknown tool names', () => {
971
- expect(isSideEffectTool('nonexistent_tool')).toBe(false);
972
- expect(isSideEffectTool('')).toBe(false);
973
- });
974
-
975
- describe('action-aware classification for mixed-action tools', () => {
976
- test('account_manage create is a side-effect', () => {
977
- expect(isSideEffectTool('account_manage', { action: 'create' })).toBe(true);
978
- });
979
-
980
- test('account_manage update is a side-effect', () => {
981
- expect(isSideEffectTool('account_manage', { action: 'update' })).toBe(true);
982
- });
983
-
984
- test('account_manage list is NOT a side-effect', () => {
985
- expect(isSideEffectTool('account_manage', { action: 'list' })).toBe(false);
986
- });
987
-
988
- test('account_manage get is NOT a side-effect', () => {
989
- expect(isSideEffectTool('account_manage', { action: 'get' })).toBe(false);
990
- });
991
-
992
- test('account_manage without input is NOT a side-effect', () => {
993
- expect(isSideEffectTool('account_manage')).toBe(false);
994
- });
995
-
996
- test('reminder_create is a side-effect', () => {
997
- expect(isSideEffectTool('reminder_create')).toBe(true);
998
- });
999
-
1000
- test('reminder_cancel is a side-effect', () => {
1001
- expect(isSideEffectTool('reminder_cancel')).toBe(true);
1002
- });
1003
-
1004
- test('reminder_list is NOT a side-effect', () => {
1005
- expect(isSideEffectTool('reminder_list')).toBe(false);
1006
- });
1007
-
1008
- test('credential_store store is a side-effect', () => {
1009
- expect(isSideEffectTool('credential_store', { action: 'store' })).toBe(true);
1010
- });
1011
-
1012
- test('credential_store delete is a side-effect', () => {
1013
- expect(isSideEffectTool('credential_store', { action: 'delete' })).toBe(true);
1014
- });
1015
-
1016
- test('credential_store prompt is a side-effect', () => {
1017
- expect(isSideEffectTool('credential_store', { action: 'prompt' })).toBe(true);
1018
- });
1019
-
1020
- test('credential_store oauth2_connect is a side-effect', () => {
1021
- expect(isSideEffectTool('credential_store', { action: 'oauth2_connect' })).toBe(true);
1022
- });
1023
-
1024
- test('credential_store list is NOT a side-effect', () => {
1025
- expect(isSideEffectTool('credential_store', { action: 'list' })).toBe(false);
1026
- });
1027
-
1028
- test('credential_store without input is NOT a side-effect', () => {
1029
- expect(isSideEffectTool('credential_store')).toBe(false);
1030
- });
1031
- });
1032
- });
1033
-
1034
- // Baseline: allow rules can auto-allow file_edit for USER.md today (no forced prompting).
1035
- // The mock check() delegates to findHighestPriorityRule (via spy) so a regression
1036
- // in trust-rule matching would cause this test to fail instead of being masked by
1037
- // a blanket mock-allow.
1038
- describe('ToolExecutor baseline: allow rule auto-allows file_edit USER.md', () => {
1039
- const userMdPath = '/Users/sidd/.vellum/workspace/USER.md';
1040
- let ruleSpy: ReturnType<typeof spyOn> | undefined;
1041
-
1042
- beforeEach(() => {
1043
- fakeToolResult = { content: 'ok', isError: false };
1044
- lastCheckArgs = undefined;
1045
- getToolOverride = undefined;
1046
- checkResultOverride = undefined;
1047
- checkFnOverride = undefined;
1048
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
1049
-
1050
- // Simulate a trust rule that allows file_edit on USER.md by stubbing
1051
- // findHighestPriorityRule. This mirrors the default allow rules that
1052
- // the trust-store creates for workspace prompt files.
1053
- ruleSpy = spyOn(trustStore, 'findHighestPriorityRule').mockImplementation(
1054
- (tool: string, commands: string[], _scope: string) => {
1055
- if (tool !== 'file_edit') return null;
1056
- for (const cmd of commands) {
1057
- if (cmd === `file_edit:${userMdPath}`) {
1058
- return {
1059
- id: 'default:allow-file_edit-user',
1060
- tool: 'file_edit',
1061
- pattern: `file_edit:${userMdPath}`,
1062
- scope: 'everywhere',
1063
- decision: 'allow' as const,
1064
- priority: 100,
1065
- createdAt: Date.now(),
1066
- };
1067
- }
1068
- }
1069
- return null;
1070
- },
1071
- );
1072
-
1073
- // Wire the mock check() to delegate to findHighestPriorityRule, replicating
1074
- // the real check() logic for Medium-risk tools (file_edit).
1075
- checkFnOverride = async (toolName, input, workingDir) => {
1076
- const filePath = (input.path as string) ?? (input.file_path as string) ?? '';
1077
- const resolved = filePath.startsWith('/') ? filePath : `${workingDir}/${filePath}`;
1078
- const candidates = [`${toolName}:${resolved}`];
1079
- const matched = trustStore.findHighestPriorityRule(toolName, candidates, workingDir);
1080
- if (matched && matched.decision === 'allow') {
1081
- return { decision: 'allow', reason: `Matched trust rule: ${matched.pattern}` };
1082
- }
1083
- return { decision: 'prompt', reason: 'Medium risk: requires approval' };
1084
- };
1085
- });
1086
-
1087
- afterEach(() => {
1088
- checkFnOverride = undefined;
1089
- if (ruleSpy) { ruleSpy.mockRestore(); ruleSpy = undefined; }
1090
- });
1091
-
1092
- test('file_edit to USER.md is auto-allowed via trust rule', async () => {
1093
- const executor = new ToolExecutor(makePrompter());
1094
- const result = await executor.execute(
1095
- 'file_edit',
1096
- { path: userMdPath, content: 'hello' },
1097
- makeContext(),
1098
- );
1099
- expect(result.isError).toBe(false);
1100
- expect(result.content).toBe('ok');
1101
- // Confirm checker was called with the correct tool name
1102
- expect(lastCheckArgs).toBeDefined();
1103
- expect(lastCheckArgs!.toolName).toBe('file_edit');
1104
- // Confirm findHighestPriorityRule was consulted
1105
- expect(ruleSpy).toHaveBeenCalled();
1106
- });
1107
-
1108
- test('file_edit to a non-USER.md path is NOT auto-allowed without a matching rule', async () => {
1109
- let promptCalled = false;
1110
- const trackingPrompter = {
1111
- prompt: async () => { promptCalled = true; return { decision: 'allow' as const }; },
1112
- resolveConfirmation: () => {},
1113
- updateSender: () => {},
1114
- dispose: () => {},
1115
- } as unknown as PermissionPrompter;
1116
-
1117
- const executor = new ToolExecutor(trackingPrompter);
1118
- const result = await executor.execute(
1119
- 'file_edit',
1120
- { path: '/tmp/project/other.md', content: 'hello' },
1121
- makeContext(),
1122
- );
1123
- // check() returned 'prompt' (no matching trust rule for other.md),
1124
- // so the executor must have called the prompter.
1125
- expect(promptCalled).toBe(true);
1126
- expect(result.isError).toBe(false);
1127
- expect(lastCheckArgs).toBeDefined();
1128
- expect(lastCheckArgs!.toolName).toBe('file_edit');
1129
- });
1130
- });
1131
-
1132
- // ---------------------------------------------------------------------------
1133
- // forcePromptSideEffects enforcement (PR 30)
1134
- // ---------------------------------------------------------------------------
1135
-
1136
- describe('ToolExecutor forcePromptSideEffects enforcement', () => {
1137
- let promptCalled: boolean;
1138
-
1139
- beforeEach(() => {
1140
- fakeToolResult = { content: 'ok', isError: false };
1141
- lastCheckArgs = undefined;
1142
- getToolOverride = undefined;
1143
- checkResultOverride = undefined;
1144
- checkFnOverride = undefined;
1145
- promptCalled = false;
1146
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
1147
- });
1148
-
1149
- /**
1150
- * Prompter that tracks whether it was called and always allows.
1151
- */
1152
- function makeTrackingPrompter(): PermissionPrompter {
1153
- return {
1154
- prompt: async () => {
1155
- promptCalled = true;
1156
- return { decision: 'allow' as const };
1157
- },
1158
- resolveConfirmation: () => {},
1159
- updateSender: () => {},
1160
- dispose: () => {},
1161
- } as unknown as PermissionPrompter;
1162
- }
1163
-
1164
- test('side-effect tool with allow rule is forced to prompt when forcePromptSideEffects is true', async () => {
1165
- // check() returns allow (simulating a matched trust rule)
1166
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1167
-
1168
- const executor = new ToolExecutor(makeTrackingPrompter());
1169
- const result = await executor.execute(
1170
- 'bash',
1171
- { command: 'echo hello' },
1172
- makeContext({ forcePromptSideEffects: true }),
1173
- );
1174
-
1175
- expect(result.isError).toBe(false);
1176
- // The prompter must have been called despite the allow rule
1177
- expect(promptCalled).toBe(true);
1178
- });
1179
-
1180
- test('deny decision is preserved (not converted to prompt) even with forcePromptSideEffects', async () => {
1181
- checkResultOverride = { decision: 'deny', reason: 'Policy denies this tool' };
1182
-
1183
- const executor = new ToolExecutor(makeTrackingPrompter());
1184
- const result = await executor.execute(
1185
- 'bash',
1186
- { command: 'rm -rf /' },
1187
- makeContext({ forcePromptSideEffects: true }),
1188
- );
1189
-
1190
- // Should be denied, not prompted
1191
- expect(result.isError).toBe(true);
1192
- expect(result.content).toBe('Policy denies this tool');
1193
- expect(promptCalled).toBe(false);
1194
- });
1195
-
1196
- test('non-side-effect tool is unchanged even with forcePromptSideEffects', async () => {
1197
- // check() returns allow for a read-only tool
1198
- checkResultOverride = { decision: 'allow', reason: 'Allowed by default' };
1199
-
1200
- const executor = new ToolExecutor(makeTrackingPrompter());
1201
- const result = await executor.execute(
1202
- 'file_read',
1203
- { path: 'README.md' },
1204
- makeContext({ forcePromptSideEffects: true }),
1205
- );
1206
-
1207
- expect(result.isError).toBe(false);
1208
- // Prompter should NOT be called — file_read is not a side-effect tool
1209
- expect(promptCalled).toBe(false);
1210
- });
1211
-
1212
- test('side-effect tool is auto-allowed when forcePromptSideEffects is false', async () => {
1213
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1214
-
1215
- const executor = new ToolExecutor(makeTrackingPrompter());
1216
- const result = await executor.execute(
1217
- 'file_write',
1218
- { path: 'test.txt', content: 'data' },
1219
- makeContext({ forcePromptSideEffects: false }),
1220
- );
1221
-
1222
- expect(result.isError).toBe(false);
1223
- // No prompt — standard behavior when forcePromptSideEffects is off
1224
- expect(promptCalled).toBe(false);
1225
- });
1226
-
1227
- test('side-effect tool is auto-allowed when forcePromptSideEffects is undefined', async () => {
1228
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1229
-
1230
- const executor = new ToolExecutor(makeTrackingPrompter());
1231
- const result = await executor.execute(
1232
- 'file_edit',
1233
- { path: 'test.txt', old_string: 'a', new_string: 'b' },
1234
- makeContext(), // forcePromptSideEffects not set
1235
- );
1236
-
1237
- expect(result.isError).toBe(false);
1238
- expect(promptCalled).toBe(false);
1239
- });
1240
-
1241
- test('all side-effect tool types are forced to prompt', async () => {
1242
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1243
-
1244
- const sideEffectTools = [
1245
- { name: 'file_write', input: { path: 'x', content: 'y' } },
1246
- { name: 'file_edit', input: { path: 'x', old_string: 'a', new_string: 'b' } },
1247
- { name: 'host_file_write', input: { path: 'x', content: 'y' } },
1248
- { name: 'host_file_edit', input: { path: 'x', old_string: 'a', new_string: 'b' } },
1249
- { name: 'bash', input: { command: 'echo hi' } },
1250
- { name: 'host_bash', input: { command: 'echo hi' } },
1251
- { name: 'web_fetch', input: { url: 'https://example.com' } },
1252
- { name: 'browser_navigate', input: { url: 'https://example.com' } },
1253
- { name: 'browser_click', input: { selector: '#btn' } },
1254
- { name: 'browser_type', input: { selector: '#input', text: 'hello' } },
1255
- { name: 'browser_press_key', input: { key: 'Enter' } },
1256
- { name: 'browser_close', input: {} },
1257
- { name: 'browser_fill_credential', input: { selector: '#pwd', credential: 'test' } },
1258
- { name: 'document_create', input: { title: 'doc', content: 'body' } },
1259
- { name: 'document_update', input: { id: 'doc-1', content: 'updated' } },
1260
- { name: 'account_manage', input: { action: 'create', name: 'acct' } },
1261
- { name: 'reminder_create', input: { fire_at: '2030-01-01T00:00:00Z', label: 'test', message: 'remind me' } },
1262
- { name: 'credential_store', input: { action: 'store', name: 'api-key', value: 'secret' } },
1263
- ];
1264
-
1265
- for (const { name, input } of sideEffectTools) {
1266
- promptCalled = false;
1267
- const executor = new ToolExecutor(makeTrackingPrompter());
1268
- const result = await executor.execute(
1269
- name,
1270
- input,
1271
- makeContext({ forcePromptSideEffects: true }),
1272
- );
1273
- expect(result.isError).toBe(false);
1274
- expect(promptCalled).toBe(true);
1275
- }
1276
- });
1277
-
1278
- test('tool that is already prompted is not double-prompted', async () => {
1279
- // check() returns prompt (tool already needs prompting)
1280
- checkResultOverride = { decision: 'prompt', reason: 'Medium risk: requires approval' };
1281
-
1282
- let promptCount = 0;
1283
- const countingPrompter = {
1284
- prompt: async () => {
1285
- promptCount++;
1286
- return { decision: 'allow' as const };
1287
- },
1288
- resolveConfirmation: () => {},
1289
- updateSender: () => {},
1290
- dispose: () => {},
1291
- } as unknown as PermissionPrompter;
1292
-
1293
- const executor = new ToolExecutor(countingPrompter);
1294
- const result = await executor.execute(
1295
- 'bash',
1296
- { command: 'ls' },
1297
- makeContext({ forcePromptSideEffects: true }),
1298
- );
1299
-
1300
- expect(result.isError).toBe(false);
1301
- // Should only prompt once — forcePromptSideEffects doesn't add a second prompt
1302
- // when check() already returned 'prompt'
1303
- expect(promptCount).toBe(1);
1304
- });
1305
-
1306
- // ── USER.md security invariant (PR 31) ──────────
1307
-
1308
- test('file_edit to USER.md forces prompt in private thread even with matching trust rule', async () => {
1309
- // This is a key security invariant: USER.md contains the user's persistent
1310
- // memory. In a private thread (forcePromptSideEffects=true), edits to it
1311
- // must always require explicit approval, even when a trust rule matches.
1312
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule: file_edit:*/USER.md' };
1313
-
1314
- const executor = new ToolExecutor(makeTrackingPrompter());
1315
- const result = await executor.execute(
1316
- 'file_edit',
1317
- { path: '/Users/sidd/.vellum/workspace/USER.md', old_string: 'old pref', new_string: 'new pref' },
1318
- makeContext({ forcePromptSideEffects: true }),
1319
- );
1320
-
1321
- expect(result.isError).toBe(false);
1322
- // file_edit is a side-effect tool, so forcePromptSideEffects must trigger prompting
1323
- expect(promptCalled).toBe(true);
1324
- });
1325
-
1326
- test('host_file_edit to USER.md forces prompt in private thread', async () => {
1327
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1328
-
1329
- const executor = new ToolExecutor(makeTrackingPrompter());
1330
- const result = await executor.execute(
1331
- 'host_file_edit',
1332
- { path: '/Users/sidd/.vellum/workspace/USER.md', old_string: 'x', new_string: 'y' },
1333
- makeContext({ forcePromptSideEffects: true }),
1334
- );
1335
-
1336
- expect(result.isError).toBe(false);
1337
- expect(promptCalled).toBe(true);
1338
- });
1339
-
1340
- // ── Browser action tools as side-effect tools (PR fix2) ──────────
1341
-
1342
- test('browser_click forces prompt in private thread', async () => {
1343
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1344
-
1345
- const executor = new ToolExecutor(makeTrackingPrompter());
1346
- const result = await executor.execute(
1347
- 'browser_click',
1348
- { selector: '#submit-btn' },
1349
- makeContext({ forcePromptSideEffects: true }),
1350
- );
1351
-
1352
- expect(result.isError).toBe(false);
1353
- expect(promptCalled).toBe(true);
1354
- });
1355
-
1356
- test('browser_type forces prompt in private thread', async () => {
1357
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1358
-
1359
- const executor = new ToolExecutor(makeTrackingPrompter());
1360
- const result = await executor.execute(
1361
- 'browser_type',
1362
- { selector: '#search-input', text: 'query' },
1363
- makeContext({ forcePromptSideEffects: true }),
1364
- );
1365
-
1366
- expect(result.isError).toBe(false);
1367
- expect(promptCalled).toBe(true);
1368
- });
1369
-
1370
- test('browser_snapshot does NOT force prompt in private thread', async () => {
1371
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1372
-
1373
- const executor = new ToolExecutor(makeTrackingPrompter());
1374
- const result = await executor.execute(
1375
- 'browser_snapshot',
1376
- {},
1377
- makeContext({ forcePromptSideEffects: true }),
1378
- );
1379
-
1380
- expect(result.isError).toBe(false);
1381
- // browser_snapshot is read-only — must NOT trigger forced prompting
1382
- expect(promptCalled).toBe(false);
1383
- });
1384
-
1385
- // ── Always-mutating document tools (PR fix5) ──────────
1386
-
1387
- test('document_create forces prompt in private thread', async () => {
1388
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1389
-
1390
- const executor = new ToolExecutor(makeTrackingPrompter());
1391
- const result = await executor.execute(
1392
- 'document_create',
1393
- { title: 'New Doc', content: 'hello' },
1394
- makeContext({ forcePromptSideEffects: true }),
1395
- );
1396
-
1397
- expect(result.isError).toBe(false);
1398
- expect(promptCalled).toBe(true);
1399
- });
1400
-
1401
- test('document_update forces prompt in private thread', async () => {
1402
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1403
-
1404
- const executor = new ToolExecutor(makeTrackingPrompter());
1405
- const result = await executor.execute(
1406
- 'document_update',
1407
- { id: 'doc-1', content: 'updated' },
1408
- makeContext({ forcePromptSideEffects: true }),
1409
- );
1410
-
1411
- expect(result.isError).toBe(false);
1412
- expect(promptCalled).toBe(true);
1413
- });
1414
-
1415
- // ── Always-mutating schedule tools (PR fix7) ──────────
1416
-
1417
- test('schedule_create forces prompt in private thread', async () => {
1418
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1419
-
1420
- const executor = new ToolExecutor(makeTrackingPrompter());
1421
- const result = await executor.execute(
1422
- 'schedule_create',
1423
- { name: 'Morning standup', cron: '0 9 * * 1-5' },
1424
- makeContext({ forcePromptSideEffects: true }),
1425
- );
1426
-
1427
- expect(result.isError).toBe(false);
1428
- expect(promptCalled).toBe(true);
1429
- });
1430
-
1431
- test('schedule_update forces prompt in private thread', async () => {
1432
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1433
-
1434
- const executor = new ToolExecutor(makeTrackingPrompter());
1435
- const result = await executor.execute(
1436
- 'schedule_update',
1437
- { id: 'sched-1', cron: '0 10 * * 1-5' },
1438
- makeContext({ forcePromptSideEffects: true }),
1439
- );
1440
-
1441
- expect(result.isError).toBe(false);
1442
- expect(promptCalled).toBe(true);
1443
- });
1444
-
1445
- test('schedule_delete forces prompt in private thread', async () => {
1446
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1447
-
1448
- const executor = new ToolExecutor(makeTrackingPrompter());
1449
- const result = await executor.execute(
1450
- 'schedule_delete',
1451
- { id: 'sched-1' },
1452
- makeContext({ forcePromptSideEffects: true }),
1453
- );
1454
-
1455
- expect(result.isError).toBe(false);
1456
- expect(promptCalled).toBe(true);
1457
- });
1458
-
1459
- // ── Credential store action-aware (PR fix9) ──────────
1460
-
1461
- test('credential_store store forces prompt in private thread', async () => {
1462
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1463
-
1464
- const executor = new ToolExecutor(makeTrackingPrompter());
1465
- const result = await executor.execute(
1466
- 'credential_store',
1467
- { action: 'store', name: 'api-key', value: 'sk-secret-123' },
1468
- makeContext({ forcePromptSideEffects: true }),
1469
- );
1470
-
1471
- expect(result.isError).toBe(false);
1472
- expect(promptCalled).toBe(true);
1473
- });
1474
-
1475
- test('credential_store delete forces prompt in private thread', async () => {
1476
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1477
-
1478
- const executor = new ToolExecutor(makeTrackingPrompter());
1479
- const result = await executor.execute(
1480
- 'credential_store',
1481
- { action: 'delete', name: 'api-key' },
1482
- makeContext({ forcePromptSideEffects: true }),
1483
- );
1484
-
1485
- expect(result.isError).toBe(false);
1486
- expect(promptCalled).toBe(true);
1487
- });
1488
-
1489
- test('credential_store oauth2_connect forces prompt in private thread', async () => {
1490
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1491
-
1492
- const executor = new ToolExecutor(makeTrackingPrompter());
1493
- const result = await executor.execute(
1494
- 'credential_store',
1495
- { action: 'oauth2_connect', provider: 'google' },
1496
- makeContext({ forcePromptSideEffects: true }),
1497
- );
1498
-
1499
- expect(result.isError).toBe(false);
1500
- expect(promptCalled).toBe(true);
1501
- });
1502
-
1503
- test('credential_store list does NOT force prompt in private thread', async () => {
1504
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1505
-
1506
- const executor = new ToolExecutor(makeTrackingPrompter());
1507
- const result = await executor.execute(
1508
- 'credential_store',
1509
- { action: 'list' },
1510
- makeContext({ forcePromptSideEffects: true }),
1511
- );
1512
-
1513
- expect(result.isError).toBe(false);
1514
- // list is read-only — must NOT trigger forced prompting
1515
- expect(promptCalled).toBe(false);
1516
- });
1517
-
1518
- // ── Action-aware mixed-action tools (PR fix5) ──────────
1519
-
1520
- test('account_manage create forces prompt in private thread', async () => {
1521
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1522
-
1523
- const executor = new ToolExecutor(makeTrackingPrompter());
1524
- const result = await executor.execute(
1525
- 'account_manage',
1526
- { action: 'create', name: 'test-account' },
1527
- makeContext({ forcePromptSideEffects: true }),
1528
- );
1529
-
1530
- expect(result.isError).toBe(false);
1531
- expect(promptCalled).toBe(true);
1532
- });
1533
-
1534
- test('account_manage list does NOT force prompt in private thread', async () => {
1535
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1536
-
1537
- const executor = new ToolExecutor(makeTrackingPrompter());
1538
- const result = await executor.execute(
1539
- 'account_manage',
1540
- { action: 'list' },
1541
- makeContext({ forcePromptSideEffects: true }),
1542
- );
1543
-
1544
- expect(result.isError).toBe(false);
1545
- // list is read-only — must NOT trigger forced prompting
1546
- expect(promptCalled).toBe(false);
1547
- });
1548
-
1549
- test('reminder_create forces prompt in private thread', async () => {
1550
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1551
-
1552
- const executor = new ToolExecutor(makeTrackingPrompter());
1553
- const result = await executor.execute(
1554
- 'reminder_create',
1555
- { fire_at: '2030-01-01T00:00:00Z', label: 'test', message: 'test reminder' },
1556
- makeContext({ forcePromptSideEffects: true }),
1557
- );
1558
-
1559
- expect(result.isError).toBe(false);
1560
- expect(promptCalled).toBe(true);
1561
- });
1562
-
1563
- test('reminder_list does NOT force prompt in private thread', async () => {
1564
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule' };
1565
-
1566
- const executor = new ToolExecutor(makeTrackingPrompter());
1567
- const result = await executor.execute(
1568
- 'reminder_list',
1569
- {},
1570
- makeContext({ forcePromptSideEffects: true }),
1571
- );
1572
-
1573
- expect(result.isError).toBe(false);
1574
- // list is read-only — must NOT trigger forced prompting
1575
- expect(promptCalled).toBe(false);
1576
- });
1577
- });
1578
-
1579
- // ---------------------------------------------------------------------------
1580
- // persistentDecisionsAllowed contract (PR 15)
1581
- // ---------------------------------------------------------------------------
1582
-
1583
- describe('ToolExecutor persistentDecisionsAllowed contract', () => {
1584
- beforeEach(() => {
1585
- fakeToolResult = { content: 'ok', isError: false };
1586
- lastCheckArgs = undefined;
1587
- getToolOverride = undefined;
1588
- checkResultOverride = { decision: 'prompt', reason: 'Proxied network mode requires explicit approval for each invocation.' };
1589
- checkFnOverride = undefined;
1590
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
1591
- });
1592
-
1593
- function setupAddRuleSpy() {
1594
- addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
1595
- (tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: any) => {
1596
- return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as any;
1597
- },
1598
- );
1599
- return addRuleSpy;
1600
- }
1601
-
1602
- test('proxied bash always_allow does NOT save a trust rule', async () => {
1603
- const spy = setupAddRuleSpy();
1604
-
1605
- const prompter = makePrompterWithDecision('always_allow', 'bash:*', '/tmp/project');
1606
- const executor = new ToolExecutor(prompter);
1607
- const result = await executor.execute(
1608
- 'bash',
1609
- { command: 'curl https://example.com', network_mode: 'proxied' },
1610
- makeContext(),
1611
- );
1612
-
1613
- expect(result.isError).toBe(false);
1614
- expect(spy).not.toHaveBeenCalled();
1615
- });
1616
-
1617
- test('non-proxied bash always_allow DOES save a trust rule', async () => {
1618
- const spy = setupAddRuleSpy();
1619
-
1620
- const prompter = makePrompterWithDecision('always_allow', 'bash:*', '/tmp/project');
1621
- const executor = new ToolExecutor(prompter);
1622
- const result = await executor.execute(
1623
- 'bash',
1624
- { command: 'git status' },
1625
- makeContext(),
1626
- );
1627
-
1628
- expect(result.isError).toBe(false);
1629
- expect(spy).toHaveBeenCalledTimes(1);
1630
- });
1631
-
1632
- test('proxied bash always_deny does NOT save a trust rule', async () => {
1633
- const spy = setupAddRuleSpy();
1634
-
1635
- const prompter = makePrompterWithDecision('always_deny', 'bash:*', '/tmp/project');
1636
- const executor = new ToolExecutor(prompter);
1637
- const result = await executor.execute(
1638
- 'bash',
1639
- { command: 'curl https://evil.com', network_mode: 'proxied' },
1640
- makeContext(),
1641
- );
1642
-
1643
- expect(result.isError).toBe(true);
1644
- expect(spy).not.toHaveBeenCalled();
1645
- });
1646
-
1647
- test('persistentDecisionsAllowed: false is emitted in lifecycle event for proxied bash', async () => {
1648
- let capturedEvent: any;
1649
- const prompter = makePrompterWithDecision('allow');
1650
- const executor = new ToolExecutor(prompter);
1651
- const result = await executor.execute(
1652
- 'bash',
1653
- { command: 'curl https://example.com', network_mode: 'proxied' },
1654
- makeContext({
1655
- onToolLifecycleEvent: (event: any) => {
1656
- if (event.type === 'permission_prompt') {
1657
- capturedEvent = event;
1658
- }
1659
- },
1660
- }),
1661
- );
1662
-
1663
- expect(result.isError).toBe(false);
1664
- expect(capturedEvent).toBeDefined();
1665
- expect(capturedEvent.persistentDecisionsAllowed).toBe(false);
1666
- });
1667
-
1668
- test('persistentDecisionsAllowed: true is emitted in lifecycle event for non-proxied bash', async () => {
1669
- let capturedEvent: any;
1670
- const prompter = makePrompterWithDecision('allow');
1671
- const executor = new ToolExecutor(prompter);
1672
- const result = await executor.execute(
1673
- 'bash',
1674
- { command: 'echo hello' },
1675
- makeContext({
1676
- onToolLifecycleEvent: (event: any) => {
1677
- if (event.type === 'permission_prompt') {
1678
- capturedEvent = event;
1679
- }
1680
- },
1681
- }),
1682
- );
1683
-
1684
- expect(result.isError).toBe(false);
1685
- expect(capturedEvent).toBeDefined();
1686
- expect(capturedEvent.persistentDecisionsAllowed).toBe(true);
1687
- });
1688
-
1689
- test('persistentDecisionsAllowed is passed to prompter confirmation_request for proxied bash', async () => {
1690
- let capturedPersistent: unknown;
1691
- const prompter = {
1692
- prompt: async (
1693
- _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
1694
- _allowlistOptions: any[], _scopeOptions: any[], _diff: any, _sandboxed: any,
1695
- _sessionId: any, _executionTarget: any, _principal: any, persistentDecisionsAllowed: any,
1696
- ) => {
1697
- capturedPersistent = persistentDecisionsAllowed;
1698
- return { decision: 'allow' as const };
1699
- },
1700
- resolveConfirmation: () => {},
1701
- updateSender: () => {},
1702
- dispose: () => {},
1703
- } as unknown as PermissionPrompter;
1704
-
1705
- const executor = new ToolExecutor(prompter);
1706
- const result = await executor.execute(
1707
- 'bash',
1708
- { command: 'curl https://example.com', network_mode: 'proxied' },
1709
- makeContext(),
1710
- );
1711
-
1712
- expect(result.isError).toBe(false);
1713
- expect(capturedPersistent).toBe(false);
1714
- });
1715
-
1716
- test('host_bash with proxied network_mode still allows persistent decisions', async () => {
1717
- // host_bash does not support network_mode — proxied-mode persistence
1718
- // blocking applies only to sandboxed bash, not host_bash.
1719
- const spy = setupAddRuleSpy();
1720
-
1721
- const prompter = makePrompterWithDecision('always_allow', 'host_bash:*', '/tmp/project');
1722
- const executor = new ToolExecutor(prompter);
1723
- const result = await executor.execute(
1724
- 'host_bash',
1725
- { command: 'curl https://example.com', network_mode: 'proxied' },
1726
- makeContext(),
1727
- );
1728
-
1729
- expect(result.isError).toBe(false);
1730
- expect(spy).toHaveBeenCalledTimes(1);
1731
- });
1732
- });
1733
-
1734
- // ---------------------------------------------------------------------------
1735
- // E2E: Proxied bash vs. proxy approval persistence invariants (PR 32)
1736
- //
1737
- // Design invariant: the proxied-run activation prompt (when the agent wants
1738
- // to run `bash` with `network_mode=proxied`) must NOT allow saving persistent
1739
- // trust rules — each invocation must be explicitly approved. In contrast,
1740
- // the proxy-request approval path (when the proxy service asks the user
1741
- // about a specific outbound request) CAN save persistent trust rules so the
1742
- // user doesn't get re-prompted for the same host/pattern.
1743
- // ---------------------------------------------------------------------------
1744
-
1745
- describe('E2E: proxied bash activation vs proxy approval persistence', () => {
1746
- beforeEach(() => {
1747
- fakeToolResult = { content: 'ok', isError: false };
1748
- lastCheckArgs = undefined;
1749
- getToolOverride = undefined;
1750
- checkResultOverride = { decision: 'prompt', reason: 'Requires explicit approval' };
1751
- checkFnOverride = undefined;
1752
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
1753
- });
1754
-
1755
- function setupAddRuleSpy() {
1756
- addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
1757
- (tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: any) => {
1758
- return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as any;
1759
- },
1760
- );
1761
- return addRuleSpy;
1762
- }
1763
-
1764
- test('proxied bash: always_allow skips rule, always_deny skips rule, non-proxied bash saves both', async () => {
1765
- const spy = setupAddRuleSpy();
1766
-
1767
- // 1. Proxied bash always_allow -> NO rule saved
1768
- const p1 = makePrompterWithDecision('always_allow', 'bash:curl*', '/tmp/project');
1769
- const e1 = new ToolExecutor(p1);
1770
- const r1 = await e1.execute('bash', { command: 'curl https://api.example.com', network_mode: 'proxied' }, makeContext());
1771
- expect(r1.isError).toBe(false);
1772
- expect(spy).not.toHaveBeenCalled();
1773
-
1774
- // 2. Proxied bash always_deny -> NO rule saved
1775
- const p2 = makePrompterWithDecision('always_deny', 'bash:curl*', '/tmp/project');
1776
- const e2 = new ToolExecutor(p2);
1777
- const r2 = await e2.execute('bash', { command: 'curl https://evil.com', network_mode: 'proxied' }, makeContext());
1778
- expect(r2.isError).toBe(true);
1779
- expect(spy).not.toHaveBeenCalled();
1780
-
1781
- // 3. Non-proxied bash always_allow -> rule IS saved
1782
- const p3 = makePrompterWithDecision('always_allow', 'bash:git*', '/tmp/project');
1783
- const e3 = new ToolExecutor(p3);
1784
- const r3 = await e3.execute('bash', { command: 'git push' }, makeContext());
1785
- expect(r3.isError).toBe(false);
1786
- expect(spy).toHaveBeenCalledTimes(1);
1787
- expect(spy.mock.calls[0][0]).toBe('bash');
1788
- expect(spy.mock.calls[0][1]).toBe('bash:git*');
1789
- expect(spy.mock.calls[0][3]).toBe('allow');
1790
- });
1791
-
1792
- test('proxied bash always_allow_high_risk also skips rule saving', async () => {
1793
- const spy = setupAddRuleSpy();
1794
-
1795
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'bash:*', 'everywhere');
1796
- const executor = new ToolExecutor(prompter);
1797
- const result = await executor.execute(
1798
- 'bash',
1799
- { command: 'curl -X POST https://api.example.com/deploy', network_mode: 'proxied' },
1800
- makeContext(),
1801
- );
1802
-
1803
- expect(result.isError).toBe(false);
1804
- expect(spy).not.toHaveBeenCalled();
1805
- });
1806
-
1807
- test('non-proxied bash always_deny DOES save a deny rule', async () => {
1808
- const spy = setupAddRuleSpy();
1809
-
1810
- const prompter = makePrompterWithDecision('always_deny', 'bash:rm*', '/tmp/project');
1811
- const executor = new ToolExecutor(prompter);
1812
- const result = await executor.execute(
1813
- 'bash',
1814
- { command: 'rm -rf /' },
1815
- makeContext(),
1816
- );
1817
-
1818
- expect(result.isError).toBe(true);
1819
- expect(spy).toHaveBeenCalledTimes(1);
1820
- expect(spy.mock.calls[0][0]).toBe('bash');
1821
- expect(spy.mock.calls[0][1]).toBe('bash:rm*');
1822
- expect(spy.mock.calls[0][3]).toBe('deny');
1823
- });
1824
-
1825
- test('file_write with proxied network_mode is NOT affected (persistence still allowed)', async () => {
1826
- // Only bash with proxied mode disables persistence
1827
- const spy = setupAddRuleSpy();
1828
-
1829
- const prompter = makePrompterWithDecision('always_allow', 'file_write:*', '/tmp/project');
1830
- const executor = new ToolExecutor(prompter);
1831
- const result = await executor.execute(
1832
- 'file_write',
1833
- { path: '/tmp/test.txt', content: 'data', network_mode: 'proxied' },
1834
- makeContext(),
1835
- );
1836
-
1837
- expect(result.isError).toBe(false);
1838
- expect(spy).toHaveBeenCalledTimes(1);
1839
- });
1840
-
1841
- test('host_bash proxied always_allow_high_risk still saves rule (host_bash ignores network_mode)', async () => {
1842
- // host_bash does not support network_mode — persistence blocking
1843
- // applies only to sandboxed bash.
1844
- const spy = setupAddRuleSpy();
1845
-
1846
- const prompter = makePrompterWithDecision('always_allow_high_risk', 'host_bash:*', 'everywhere');
1847
- const executor = new ToolExecutor(prompter);
1848
- const result = await executor.execute(
1849
- 'host_bash',
1850
- { command: 'wget https://example.com/data.tar.gz', network_mode: 'proxied' },
1851
- makeContext(),
1852
- );
1853
-
1854
- expect(result.isError).toBe(false);
1855
- expect(spy).toHaveBeenCalledTimes(1);
1856
- });
1857
-
1858
- test('proxied bash denied result message omits "rule saved" suffix', async () => {
1859
- setupAddRuleSpy();
1860
-
1861
- const prompter = makePrompterWithDecision('always_deny', 'bash:*', '/tmp/project');
1862
- const executor = new ToolExecutor(prompter);
1863
- const result = await executor.execute(
1864
- 'bash',
1865
- { command: 'curl https://malicious.com', network_mode: 'proxied' },
1866
- makeContext(),
1867
- );
1868
-
1869
- expect(result.isError).toBe(true);
1870
- // Since no rule was saved, the message should NOT include "rule was saved"
1871
- expect(result.content).toContain('Permission denied by user');
1872
- expect(result.content).not.toContain('rule was saved');
1873
- });
1874
-
1875
- test('non-proxied bash denied result message includes "rule saved" suffix', async () => {
1876
- setupAddRuleSpy();
1877
-
1878
- const prompter = makePrompterWithDecision('always_deny', 'bash:rm*', '/tmp/project');
1879
- const executor = new ToolExecutor(prompter);
1880
- const result = await executor.execute(
1881
- 'bash',
1882
- { command: 'rm -rf /' },
1883
- makeContext(),
1884
- );
1885
-
1886
- expect(result.isError).toBe(true);
1887
- expect(result.content).toContain('Permission denied by user');
1888
- expect(result.content).toContain('rule was saved');
1889
- });
1890
- });
1891
-
1892
- // ---------------------------------------------------------------------------
1893
- // Baseline: sanitized env excludes credential-like variables
1894
- // ---------------------------------------------------------------------------
1895
-
1896
- // Import the real buildSanitizedEnv (not mocked) for baseline credential tests
1897
- const { buildSanitizedEnv } = await import('../tools/terminal/safe-env.js');
1898
-
1899
- describe('buildSanitizedEnv — baseline: credential exclusion', () => {
1900
- // Credential-like env vars that must never appear in the sanitized env.
1901
- // Names are constructed dynamically to avoid tripping pre-commit secret scanners.
1902
- const k = (...parts: string[]) => parts.join('_');
1903
- const CREDENTIAL_VARS = [
1904
- k('OPENAI', 'API', 'KEY'),
1905
- k('ANTHROPIC', 'API', 'KEY'),
1906
- k('AWS', 'SECRET', 'ACCESS', 'KEY'),
1907
- k('AWS', 'SESSION', 'TOKEN'),
1908
- k('GITHUB', 'TOKEN'),
1909
- k('GH', 'TOKEN'),
1910
- k('NPM', 'TOKEN'),
1911
- k('DOCKER', 'PASSWORD'),
1912
- k('DATABASE', 'URL'),
1913
- k('PGPASSWORD'),
1914
- k('REDIS', 'URL'),
1915
- k('API', 'SECRET'),
1916
- ];
1917
-
1918
- test('sanitized env does not include API key variables', () => {
1919
- // Temporarily set credential-like env vars
1920
- const originalValues: Record<string, string | undefined> = {};
1921
- for (const key of CREDENTIAL_VARS) {
1922
- originalValues[key] = process.env[key];
1923
- process.env[key] = `fake-${key}-value`;
1924
- }
1925
-
1926
- try {
1927
- const env = buildSanitizedEnv();
1928
- for (const key of CREDENTIAL_VARS) {
1929
- expect(env[key]).toBeUndefined();
1930
- }
1931
- } finally {
1932
- // Restore original env
1933
- for (const key of CREDENTIAL_VARS) {
1934
- if (originalValues[key] === undefined) {
1935
- delete process.env[key];
1936
- } else {
1937
- process.env[key] = originalValues[key];
1938
- }
1939
- }
1940
- }
1941
- });
1942
-
1943
- test('sanitized env includes expected safe variables when present', () => {
1944
- const env = buildSanitizedEnv();
1945
- // PATH and HOME should be present (they exist in the process env)
1946
- if (process.env.PATH) {
1947
- expect(env.PATH).toBe(process.env.PATH);
1948
- }
1949
- if (process.env.HOME) {
1950
- expect(env.HOME).toBe(process.env.HOME);
1951
- }
1952
- });
1953
-
1954
- test('sanitized env only contains keys from the allowlist', () => {
1955
- const SAFE_ENV_VARS = [
1956
- 'PATH', 'HOME', 'TERM', 'LANG', 'EDITOR', 'SHELL', 'USER',
1957
- 'TMPDIR', 'LC_ALL', 'LC_CTYPE', 'XDG_RUNTIME_DIR', 'DISPLAY',
1958
- 'COLORTERM', 'TERM_PROGRAM', 'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
1959
- 'GPG_TTY', 'GNUPGHOME',
1960
- ];
1961
-
1962
- const env = buildSanitizedEnv();
1963
- for (const key of Object.keys(env)) {
1964
- expect(SAFE_ENV_VARS).toContain(key);
1965
- }
1966
- });
1967
- });
1968
-
1969
- // ---------------------------------------------------------------------------
1970
- // Persistent-allow lifecycle: roundtrip and auto-allow on subsequent invocation
1971
- // ---------------------------------------------------------------------------
1972
-
1973
- describe('ToolExecutor persistent-allow lifecycle', () => {
1974
- beforeEach(() => {
1975
- fakeToolResult = { content: 'ok', isError: false };
1976
- lastCheckArgs = undefined;
1977
- getToolOverride = undefined;
1978
- checkResultOverride = undefined;
1979
- checkFnOverride = undefined;
1980
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
1981
- });
1982
-
1983
- function setupAddRuleSpy() {
1984
- addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
1985
- (tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: any) => {
1986
- return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as any;
1987
- },
1988
- );
1989
- return addRuleSpy;
1990
- }
1991
-
1992
- test('persistent-allow roundtrip: always_allow saves rule and allows tool', async () => {
1993
- // Simulate check() returning 'prompt' so the executor asks the user
1994
- checkResultOverride = { decision: 'prompt', reason: 'Medium risk: requires approval' };
1995
- const spy = setupAddRuleSpy();
1996
-
1997
- // User responds with always_allow, selecting a pattern and scope
1998
- const prompter = makePrompterWithDecision('always_allow', 'git *', '/tmp/project');
1999
- const executor = new ToolExecutor(prompter);
2000
- const result = await executor.execute('bash', { command: 'git status' }, makeContext());
2001
-
2002
- // The tool should have been allowed to proceed
2003
- expect(result.isError).toBe(false);
2004
- expect(result.content).toBe('ok');
2005
-
2006
- // addRule should have been called with the correct arguments
2007
- expect(spy).toHaveBeenCalledTimes(1);
2008
- const [tool, pattern, scope, decision] = spy.mock.calls[0];
2009
- expect(tool).toBe('bash');
2010
- expect(pattern).toBe('git *');
2011
- expect(scope).toBe('/tmp/project');
2012
- expect(decision).toBe('allow');
2013
- });
2014
-
2015
- test('auto-allow on subsequent invocation: matching rule skips prompt', async () => {
2016
- // Simulate a previously saved rule by making check() return 'allow'
2017
- // with a matched rule (as findHighestPriorityRule would).
2018
- checkResultOverride = { decision: 'allow', reason: 'Matched trust rule: git *' };
2019
-
2020
- let promptCalled = false;
2021
- const trackingPrompter = {
2022
- prompt: async () => { promptCalled = true; return { decision: 'allow' as const }; },
2023
- resolveConfirmation: () => {},
2024
- updateSender: () => {},
2025
- dispose: () => {},
2026
- } as unknown as PermissionPrompter;
2027
-
2028
- const executor = new ToolExecutor(trackingPrompter);
2029
- const result = await executor.execute('bash', { command: 'git status' }, makeContext());
2030
-
2031
- // The tool should be auto-allowed
2032
- expect(result.isError).toBe(false);
2033
- expect(result.content).toBe('ok');
2034
-
2035
- // The prompter should NOT have been called — the rule auto-allowed
2036
- expect(promptCalled).toBe(false);
2037
- });
2038
-
2039
- test('always_allow with everywhere scope saves rule and allows tool', async () => {
2040
- checkResultOverride = { decision: 'prompt', reason: 'Medium risk: requires approval' };
2041
- const spy = setupAddRuleSpy();
2042
-
2043
- const prompter = makePrompterWithDecision('always_allow', 'file_write:*', 'everywhere');
2044
- const executor = new ToolExecutor(prompter);
2045
- const result = await executor.execute('file_write', { path: '/tmp/test.txt', content: 'hello' }, makeContext());
2046
-
2047
- expect(result.isError).toBe(false);
2048
- expect(spy).toHaveBeenCalledTimes(1);
2049
- const [tool, pattern, scope, decision] = spy.mock.calls[0];
2050
- expect(tool).toBe('file_write');
2051
- expect(pattern).toBe('file_write:*');
2052
- expect(scope).toBe('everywhere');
2053
- expect(decision).toBe('allow');
2054
- });
2055
- });