@vellumai/assistant 0.6.6 → 0.7.1

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