@vellumai/assistant 0.6.5 → 0.7.0

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