@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -1,17 +1,13 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { homedir } from "node:os";
3
- import { dirname, join, resolve } from "node:path";
3
+ import { dirname, resolve } from "node:path";
4
4
 
5
5
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
6
6
  import { getIsContainerized } from "../config/env-registry.js";
7
7
  import { getConfig } from "../config/loader.js";
8
8
  import { loadSkillCatalog, resolveSkillSelector } from "../config/skills.js";
9
9
  import { indexCatalogById } from "../skills/include-graph.js";
10
- import {
11
- isSkillSourcePath,
12
- normalizeDirPath,
13
- normalizeFilePath,
14
- } from "../skills/path-classifier.js";
10
+ import { normalizeFilePath } from "../skills/path-classifier.js";
15
11
  import { computeTransitiveSkillVersionHash } from "../skills/transitive-version-hash.js";
16
12
  import { computeSkillVersionHash } from "../skills/version-hash.js";
17
13
  import type { ManifestOverride } from "../tools/execution-target.js";
@@ -21,16 +17,20 @@ import {
21
17
  } from "../tools/network/url-safety.js";
22
18
  import { getTool } from "../tools/registry.js";
23
19
  import {
24
- getDeprecatedDir,
25
- getProtectedDir,
26
- getWorkspaceHooksDir,
27
- } from "../util/platform.js";
20
+ type ApprovalContext,
21
+ DefaultApprovalPolicy,
22
+ resolveThreshold,
23
+ } from "./approval-policy.js";
24
+ import { bashRiskClassifier } from "./bash-risk-classifier.js";
25
+ import { fileRiskClassifier } from "./file-risk-classifier.js";
26
+ import { type RiskAssessment, riskToRiskLevel } from "./risk-types.js";
28
27
  import {
29
28
  buildShellAllowlistOptions,
30
29
  buildShellCommandCandidates,
31
30
  cachedParse,
32
31
  type ParsedCommand,
33
32
  } from "./shell-identity.js";
33
+ import { skillLoadRiskClassifier } from "./skill-risk-classifier.js";
34
34
  import { findHighestPriorityRule, onRulesChanged } from "./trust-store.js";
35
35
  import {
36
36
  type AllowlistOption,
@@ -39,6 +39,7 @@ import {
39
39
  RiskLevel,
40
40
  type ScopeOption,
41
41
  } from "./types.js";
42
+ import { webRiskClassifier } from "./web-risk-classifier.js";
42
43
  import { isWorkspaceScopedInvocation } from "./workspace-policy.js";
43
44
 
44
45
  // ── Risk classification cache ────────────────────────────────────────────────
@@ -48,10 +49,35 @@ import { isWorkspaceScopedInvocation } from "./workspace-policy.js";
48
49
  // Invalidated when trust rules change since risk classification for file tools
49
50
  // depends on skill source path checks which reference config, but the core
50
51
  // risk logic is input-deterministic.
52
+ /** The result of classifyRisk(): a risk level with an optional human-readable reason. */
53
+ export interface RiskClassification {
54
+ level: RiskLevel;
55
+ /** Human-readable explanation of why this risk level was assigned. */
56
+ reason?: string;
57
+ }
58
+
51
59
  const RISK_CACHE_MAX = 256;
52
- const riskCache = new Map<string, RiskLevel>();
60
+ const riskCache = new Map<string, RiskClassification>();
53
61
  let riskCacheInvalidationHookRegistered = false;
54
62
 
63
+ // ── Assessment cache ─────────────────────────────────────────────────────────
64
+ // Stores the full RiskAssessment from classifier-backed tools so that
65
+ // generateAllowlistOptions() can read classifier-produced allowlistOptions
66
+ // without re-classifying. Keyed on (toolName, inputHash) — a simpler key
67
+ // than the full risk cache since generateAllowlistOptions() does not receive
68
+ // workingDir or manifestOverride. Cleared alongside the risk cache.
69
+ const assessmentCache = new Map<string, RiskAssessment>();
70
+
71
+ function assessmentCacheKey(
72
+ toolName: string,
73
+ input: Record<string, unknown>,
74
+ ): string {
75
+ const { reason: _reason, activity: _activity, ...cacheableInput } = input;
76
+ const inputJson = JSON.stringify(cacheableInput);
77
+ const hash = createHash("sha256").update(inputJson).digest("hex");
78
+ return `${toolName}\0${hash}`;
79
+ }
80
+
55
81
  function riskCacheKey(
56
82
  toolName: string,
57
83
  input: Record<string, unknown>,
@@ -76,6 +102,7 @@ function riskCacheKey(
76
102
  /** Clear the risk classification cache. Called when trust rules change. */
77
103
  function clearRiskCache(): void {
78
104
  riskCache.clear();
105
+ assessmentCache.clear();
79
106
  }
80
107
 
81
108
  function ensureRiskCacheInvalidationHook(): void {
@@ -86,319 +113,8 @@ function ensureRiskCacheInvalidationHook(): void {
86
113
  onRulesChanged(clearRiskCache);
87
114
  }
88
115
 
89
- // Low-risk shell programs that are read-only / informational
90
- const LOW_RISK_PROGRAMS = new Set([
91
- "ls",
92
- "cat",
93
- "head",
94
- "tail",
95
- "less",
96
- "more",
97
- "wc",
98
- "file",
99
- "stat",
100
- "grep",
101
- "rg",
102
- "ag",
103
- "ack",
104
- "find",
105
- "fd",
106
- "which",
107
- "where",
108
- "whereis",
109
- "type",
110
- "echo",
111
- "printf",
112
- "date",
113
- "cal",
114
- "uptime",
115
- "whoami",
116
- "hostname",
117
- "uname",
118
- "pwd",
119
- "realpath",
120
- "dirname",
121
- "basename",
122
- "git",
123
- "node",
124
- "bun",
125
- "deno",
126
- "npm",
127
- "npx",
128
- "yarn",
129
- "pnpm",
130
- "python",
131
- "python3",
132
- "pip",
133
- "pip3",
134
- "man",
135
- "help",
136
- "info",
137
- "env",
138
- "printenv",
139
- "set",
140
- "diff",
141
- "sort",
142
- "uniq",
143
- "cut",
144
- "tr",
145
- "tee",
146
- "xargs",
147
- "jq",
148
- "yq",
149
- "http",
150
- "dig",
151
- "nslookup",
152
- "ping",
153
- "tree",
154
- "du",
155
- "df",
156
- ]);
157
-
158
- // High-risk shell programs / patterns
159
- const HIGH_RISK_PROGRAMS = new Set([
160
- "sudo",
161
- "su",
162
- "doas",
163
- "dd",
164
- "mkfs",
165
- "fdisk",
166
- "parted",
167
- "mount",
168
- "umount",
169
- "systemctl",
170
- "service",
171
- "launchctl",
172
- "useradd",
173
- "userdel",
174
- "usermod",
175
- "groupadd",
176
- "groupdel",
177
- "iptables",
178
- "ufw",
179
- "firewall-cmd",
180
- "reboot",
181
- "shutdown",
182
- "halt",
183
- "poweroff",
184
- "kill",
185
- "killall",
186
- "pkill",
187
- ]);
188
-
189
- // Git subcommands that are low-risk (read-only)
190
- const LOW_RISK_GIT_SUBCOMMANDS = new Set([
191
- "status",
192
- "log",
193
- "diff",
194
- "show",
195
- "branch",
196
- "tag",
197
- "remote",
198
- "stash",
199
- "blame",
200
- "shortlog",
201
- "describe",
202
- "rev-parse",
203
- "ls-files",
204
- "ls-tree",
205
- "cat-file",
206
- "reflog",
207
- ]);
208
-
209
- /**
210
- * Classify risk for `assistant` CLI subcommands. Multi-word subcommands
211
- * (e.g. `assistant oauth token`) are matched by walking the positional args.
212
- */
213
- function classifyAssistantSubcommand(args: string[]): RiskLevel {
214
- const sub = firstPositionalArg(args);
215
- if (!sub) return RiskLevel.Low;
216
-
217
- if (sub === "oauth") {
218
- const oauthSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
219
- if (oauthSub === "token") return RiskLevel.High;
220
- if (oauthSub === "mode") {
221
- // `oauth mode --set` is high risk; bare `oauth mode` (read) is low.
222
- // Match both `--set value` (two tokens) and `--set=value` (one token).
223
- if (args.some((a) => a === "--set" || a.startsWith("--set=")))
224
- return RiskLevel.High;
225
- return RiskLevel.Low;
226
- }
227
- if (oauthSub === "request") return RiskLevel.Medium;
228
- if (oauthSub === "connect" || oauthSub === "disconnect")
229
- return RiskLevel.Medium;
230
- return RiskLevel.Low;
231
- }
232
-
233
- if (sub === "credentials") {
234
- const credSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
235
- if (credSub === "reveal") return RiskLevel.High;
236
- if (credSub === "set" || credSub === "delete") return RiskLevel.High;
237
- return RiskLevel.Low;
238
- }
239
-
240
- if (sub === "keys") {
241
- const keysSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
242
- if (keysSub === "set" || keysSub === "delete") return RiskLevel.High;
243
- return RiskLevel.Low;
244
- }
245
-
246
- if (sub === "trust") {
247
- const trustSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
248
- if (trustSub === "remove" || trustSub === "clear") return RiskLevel.High;
249
- return RiskLevel.Low;
250
- }
251
-
252
- return RiskLevel.Low;
253
- }
254
-
255
- // Commands that wrap another program — the real program appears as the first
256
- // non-flag argument. When one of these is the segment program we look through
257
- // its args to find the effective program (e.g. `env curl …` → curl).
258
- const WRAPPER_PROGRAMS = new Set([
259
- "env",
260
- "nice",
261
- "nohup",
262
- "time",
263
- "command",
264
- "exec",
265
- "strace",
266
- "ltrace",
267
- "ionice",
268
- "taskset",
269
- "timeout",
270
- ]);
271
-
272
- // `env` flags that consume the next positional argument as their value.
273
- // Without this, `env -u curl echo` would incorrectly identify `curl` (the
274
- // value of -u) as the wrapped program instead of `echo`.
275
- const ENV_VALUE_FLAGS = new Set(["-u", "--unset", "-C", "--chdir"]);
276
-
277
- // `timeout` flags that consume the next positional argument as their value.
278
- const TIMEOUT_VALUE_FLAGS = new Set(["-s", "--signal", "-k", "--kill-after"]);
279
-
280
- // Wrapper programs where the first non-flag positional argument is a
281
- // configuration value (duration, CPU mask), not the wrapped program name.
282
- // For these wrappers, the second non-flag positional is the real program.
283
- const WRAPPER_SKIP_FIRST_POSITIONAL = new Set(["timeout", "taskset"]);
284
-
285
- // `git` global flags that consume the next positional argument as their value.
286
- // Without this, `git -C status commit` would incorrectly identify `status`
287
- // (the directory path) as the subcommand instead of `commit`.
288
- const GIT_VALUE_FLAGS = new Set([
289
- "-C",
290
- "-c",
291
- "--git-dir",
292
- "--work-tree",
293
- "--namespace",
294
- "--super-prefix",
295
- "--config-env",
296
- ]);
297
-
298
- /**
299
- * Return the first non-flag argument from an argument list, optionally
300
- * skipping value-taking flags. Flags are arguments that start with `-`.
301
- * This is used to skip global options (e.g. `--verbose`, `-h`, `-C <path>`)
302
- * when extracting the subcommand from CLIs like `git`, `vellum`, and
303
- * `assistant`.
304
- *
305
- * When `valueFlags` is provided, any flag in that set causes the next
306
- * argument to be skipped as well (it is the flag's value, not a positional).
307
- */
308
- function firstPositionalArg(
309
- args: string[],
310
- valueFlags?: Set<string>,
311
- ): string | undefined {
312
- for (let i = 0; i < args.length; i++) {
313
- const arg = args[i];
314
- if (arg.startsWith("-")) {
315
- if (valueFlags?.has(arg)) i++; // skip the next arg (the flag's value)
316
- continue;
317
- }
318
- return arg;
319
- }
320
- return undefined;
321
- }
322
-
323
- // Bare filenames that `rm` is allowed to delete at Medium risk (instead of
324
- // High) so workspace-scoped allow rules can approve them without the
325
- // dangerous `allowHighRisk` flag. Only matches when the args contain no
326
- // flags and exactly one of these filenames.
327
- const RM_SAFE_BARE_FILES = new Set(["BOOTSTRAP.md", "UPDATES.md"]);
328
-
329
- function isRmOfKnownSafeFile(args: string[]): boolean {
330
- if (args.length !== 1) return false;
331
- const target = args[0];
332
- if (target.startsWith("-") || target.includes("/")) return false;
333
- return RM_SAFE_BARE_FILES.has(target);
334
- }
335
-
336
- /**
337
- * Given a segment whose program is a known wrapper, return the first
338
- * non-flag argument (i.e. the wrapped program name). Returns `undefined`
339
- * when no suitable argument is found.
340
- *
341
- * Handles `env` specially: skips `VAR=value` pairs and value-taking flags
342
- * like `-u NAME` and `-C DIR`.
343
- *
344
- * Handles `timeout` and `taskset` specially: their first non-flag positional
345
- * argument is a duration or CPU mask, not the wrapped program. The second
346
- * non-flag positional is the real program.
347
- */
348
- function getWrappedProgram(seg: {
349
- program: string;
350
- args: string[];
351
- }): string | undefined {
352
- const isEnv = seg.program === "env";
353
- const isTimeout = seg.program === "timeout";
354
- const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
355
- let skippedFirstPositional = false;
356
- for (let i = 0; i < seg.args.length; i++) {
357
- const arg = seg.args[i];
358
- if (arg.startsWith("-")) {
359
- if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++; // skip the value argument
360
- if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++; // skip the value argument
361
- continue;
362
- }
363
- if (isEnv && arg.includes("=")) continue; // skip env VAR=value pairs
364
- if (skipFirst && !skippedFirstPositional) {
365
- skippedFirstPositional = true;
366
- continue; // skip the duration/CPU mask
367
- }
368
- return arg;
369
- }
370
- return undefined;
371
- }
372
-
373
- /**
374
- * Like `getWrappedProgram`, but also returns the remaining args after the
375
- * wrapped program name. This allows callers to propagate subcommand-aware
376
- * classification (e.g. `env assistant oauth token` → classify `oauth token`).
377
- */
378
- function getWrappedProgramWithArgs(seg: {
379
- program: string;
380
- args: string[];
381
- }): { program: string; args: string[] } | undefined {
382
- const isEnv = seg.program === "env";
383
- const isTimeout = seg.program === "timeout";
384
- const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
385
- let skippedFirstPositional = false;
386
- for (let i = 0; i < seg.args.length; i++) {
387
- const arg = seg.args[i];
388
- if (arg.startsWith("-")) {
389
- if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++;
390
- if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++;
391
- continue;
392
- }
393
- if (isEnv && arg.includes("=")) continue;
394
- if (skipFirst && !skippedFirstPositional) {
395
- skippedFirstPositional = true;
396
- continue; // skip the duration/CPU mask
397
- }
398
- return { program: arg, args: seg.args.slice(i + 1) };
399
- }
400
- return undefined;
401
- }
116
+ // ── Approval policy singleton ────────────────────────────────────────────────
117
+ const defaultApprovalPolicy = new DefaultApprovalPolicy();
402
118
 
403
119
  function getStringField(
404
120
  input: Record<string, unknown>,
@@ -582,11 +298,7 @@ async function buildCommandCandidates(
582
298
  return [`${toolName}:${skillId}`];
583
299
  }
584
300
 
585
- if (
586
- toolName === "web_fetch" ||
587
- toolName === "browser_navigate" ||
588
- toolName === "network_request"
589
- ) {
301
+ if (toolName === "web_fetch" || toolName === "network_request") {
590
302
  const rawUrl = getStringField(input, "url").trim();
591
303
  const candidates: string[] = [];
592
304
 
@@ -663,7 +375,7 @@ export async function classifyRisk(
663
375
  preParsed?: ParsedCommand,
664
376
  manifestOverride?: ManifestOverride,
665
377
  signal?: AbortSignal,
666
- ): Promise<RiskLevel> {
378
+ ): Promise<RiskClassification> {
667
379
  signal?.throwIfAborted();
668
380
  ensureRiskCacheInvalidationHook();
669
381
 
@@ -682,13 +394,98 @@ export async function classifyRisk(
682
394
  }
683
395
  }
684
396
 
685
- let result = await classifyRiskUncached(
686
- toolName,
687
- input,
688
- workingDir,
689
- preParsed,
690
- manifestOverride,
691
- );
397
+ // ── Bash/host_bash: delegate to the registry-driven BashRiskClassifier ────
398
+ let result: RiskClassification;
399
+ let classifierAssessment: RiskAssessment | undefined;
400
+ if (toolName === "bash" || toolName === "host_bash") {
401
+ const command = ((input.command as string) ?? "").trim();
402
+ if (!command) {
403
+ result = { level: RiskLevel.Low };
404
+ } else {
405
+ const assessment = await bashRiskClassifier.classify({
406
+ command,
407
+ toolName: toolName as "bash" | "host_bash",
408
+ });
409
+ classifierAssessment = assessment;
410
+ result = {
411
+ level: riskToRiskLevel(assessment.riskLevel),
412
+ reason: assessment.reason,
413
+ };
414
+ }
415
+ }
416
+ // ── File tools: delegate to FileRiskClassifier ──────────────────────────
417
+ else if (
418
+ [
419
+ "file_read",
420
+ "file_write",
421
+ "file_edit",
422
+ "host_file_read",
423
+ "host_file_write",
424
+ "host_file_edit",
425
+ ].includes(toolName)
426
+ ) {
427
+ const filePath = getStringField(input, "path", "file_path");
428
+ const isHostTool = toolName.startsWith("host_");
429
+ const assessment = await fileRiskClassifier.classify({
430
+ toolName: toolName as
431
+ | "file_read"
432
+ | "file_write"
433
+ | "file_edit"
434
+ | "host_file_read"
435
+ | "host_file_write"
436
+ | "host_file_edit",
437
+ filePath,
438
+ workingDir: isHostTool ? "/" : (workingDir ?? process.cwd()),
439
+ });
440
+ classifierAssessment = assessment;
441
+ result = {
442
+ level: riskToRiskLevel(assessment.riskLevel),
443
+ reason: assessment.reason,
444
+ };
445
+ }
446
+ // ── Web tools: delegate to WebRiskClassifier ────────────────────────────
447
+ else if (["web_fetch", "network_request", "web_search"].includes(toolName)) {
448
+ const assessment = await webRiskClassifier.classify({
449
+ toolName: toolName as "web_fetch" | "network_request" | "web_search",
450
+ url: getStringField(input, "url"),
451
+ allowPrivateNetwork: input.allow_private_network === true,
452
+ });
453
+ classifierAssessment = assessment;
454
+ result = {
455
+ level: riskToRiskLevel(assessment.riskLevel),
456
+ reason: assessment.reason,
457
+ };
458
+ }
459
+ // ── Skill tools: delegate to SkillLoadRiskClassifier ────────────────────
460
+ else if (
461
+ ["skill_load", "scaffold_managed_skill", "delete_managed_skill"].includes(
462
+ toolName,
463
+ )
464
+ ) {
465
+ const assessment = await skillLoadRiskClassifier.classify({
466
+ toolName: toolName as
467
+ | "skill_load"
468
+ | "scaffold_managed_skill"
469
+ | "delete_managed_skill",
470
+ skillSelector: getStringField(input, "skill", "skill_id").trim(),
471
+ });
472
+ classifierAssessment = assessment;
473
+ result = {
474
+ level: riskToRiskLevel(assessment.riskLevel),
475
+ reason: assessment.reason,
476
+ };
477
+ }
478
+ // ── Remaining tools: fall through to registry-based classification ──────
479
+ else {
480
+ result = {
481
+ level: await classifyRiskFromRegistry(
482
+ toolName,
483
+ input,
484
+ workingDir,
485
+ manifestOverride,
486
+ ),
487
+ };
488
+ }
692
489
 
693
490
  // Proxied bash commands route through the credential proxy which handles
694
491
  // per-request approval separately. Cap the bash tool's own risk at Medium
@@ -696,9 +493,9 @@ export async function classifyRisk(
696
493
  if (
697
494
  toolName === "bash" &&
698
495
  input.network_mode === "proxied" &&
699
- result === RiskLevel.High
496
+ result.level === RiskLevel.High
700
497
  ) {
701
- result = RiskLevel.Medium;
498
+ result = { level: RiskLevel.Medium, reason: result.reason };
702
499
  }
703
500
 
704
501
  if (cacheKey) {
@@ -709,237 +506,26 @@ export async function classifyRisk(
709
506
  riskCache.set(cacheKey, result);
710
507
  }
711
508
 
509
+ // Store the full assessment in a separate cache keyed on (toolName, input)
510
+ // so generateAllowlistOptions() can retrieve classifier-produced options.
511
+ if (classifierAssessment) {
512
+ const aKey = assessmentCacheKey(toolName, input);
513
+ if (assessmentCache.size >= RISK_CACHE_MAX) {
514
+ const oldest = assessmentCache.keys().next().value;
515
+ if (oldest !== undefined) assessmentCache.delete(oldest);
516
+ }
517
+ assessmentCache.set(aKey, classifierAssessment);
518
+ }
519
+
712
520
  return result;
713
521
  }
714
522
 
715
- async function classifyRiskUncached(
523
+ async function classifyRiskFromRegistry(
716
524
  toolName: string,
717
- input: Record<string, unknown>,
718
- workingDir?: string,
719
- preParsed?: ParsedCommand,
525
+ _input: Record<string, unknown>,
526
+ _workingDir?: string,
720
527
  manifestOverride?: ManifestOverride,
721
528
  ): Promise<RiskLevel> {
722
- if (toolName === "file_read") {
723
- const filePath = getStringField(input, "path", "file_path");
724
- if (isActorTokenSigningKeyPath(filePath, workingDir)) {
725
- return RiskLevel.High;
726
- }
727
- return RiskLevel.Low;
728
- }
729
- if (toolName === "file_write" || toolName === "file_edit") {
730
- const filePath = getStringField(input, "path", "file_path");
731
- if (
732
- filePath &&
733
- isSkillSourcePath(
734
- resolve(workingDir ?? process.cwd(), filePath),
735
- getConfig().skills.load.extraDirs,
736
- )
737
- ) {
738
- return RiskLevel.High;
739
- }
740
- if (filePath) {
741
- const normalizedHooksDir = normalizeDirPath(getWorkspaceHooksDir());
742
- const normalizedPath = normalizeFilePath(
743
- resolve(workingDir ?? process.cwd(), filePath),
744
- );
745
- const hooksDirNoTrailingSlash = normalizedHooksDir.slice(0, -1);
746
- if (
747
- normalizedPath === hooksDirNoTrailingSlash ||
748
- normalizedPath.startsWith(normalizedHooksDir)
749
- ) {
750
- return RiskLevel.High;
751
- }
752
- }
753
- return RiskLevel.Low;
754
- }
755
- if (toolName === "web_search") return RiskLevel.Low;
756
- if (toolName === "web_fetch") {
757
- // Private-network fetches are High risk so that blanket allow rules
758
- // (including the starter bundle) cannot silently bypass the prompt.
759
- return input.allow_private_network === true
760
- ? RiskLevel.High
761
- : RiskLevel.Low;
762
- }
763
- if (toolName === "browser_navigate") {
764
- return input.allow_private_network === true
765
- ? RiskLevel.High
766
- : RiskLevel.Low;
767
- }
768
- // All other browser tools are low risk — the browser is sandboxed and user-visible.
769
- if (toolName.startsWith("browser_")) return RiskLevel.Low;
770
- // Proxy-authenticated network requests are Medium risk — they carry injected
771
- // credentials and the user should approve the target host/origin.
772
- if (toolName === "network_request") return RiskLevel.Medium;
773
- if (toolName === "skill_load") return RiskLevel.Low;
774
-
775
- // Skill mutation tools are always High risk — they write or delete persistent
776
- // skill source code. These tools moved from core tool registry to bundled
777
- // skills, but their security classification must remain High regardless of
778
- // whether they appear in the tool registry.
779
- if (
780
- toolName === "scaffold_managed_skill" ||
781
- toolName === "delete_managed_skill"
782
- ) {
783
- return RiskLevel.High;
784
- }
785
-
786
- // Escalate host file mutations targeting skill source paths to High risk.
787
- // The host variants fall through to the tool registry (Medium) by default,
788
- // but writing to skill source code is a privilege-escalation vector.
789
- if (toolName === "host_file_write" || toolName === "host_file_edit") {
790
- const filePath = getStringField(input, "path", "file_path");
791
- if (
792
- filePath &&
793
- isSkillSourcePath(resolve(filePath), getConfig().skills.load.extraDirs)
794
- ) {
795
- return RiskLevel.High;
796
- }
797
- if (filePath) {
798
- const normalizedHooksDir = normalizeDirPath(getWorkspaceHooksDir());
799
- const normalizedPath = normalizeFilePath(resolve(filePath));
800
- const hooksDirNoTrailingSlash = normalizedHooksDir.slice(0, -1);
801
- if (
802
- normalizedPath === hooksDirNoTrailingSlash ||
803
- normalizedPath.startsWith(normalizedHooksDir)
804
- ) {
805
- return RiskLevel.High;
806
- }
807
- }
808
- // Fall through to the tool registry default (Medium) below.
809
- }
810
-
811
- if (toolName === "bash" || toolName === "host_bash") {
812
- const command = (input.command as string) ?? "";
813
- if (!command.trim()) return RiskLevel.Low;
814
-
815
- const parsed = preParsed ?? (await cachedParse(command));
816
-
817
- // Dangerous patterns → High
818
- if (parsed.dangerousPatterns.length > 0) return RiskLevel.High;
819
-
820
- // Opaque constructs → at least Medium (never Low)
821
- if (parsed.hasOpaqueConstructs) return RiskLevel.Medium;
822
-
823
- // Check each segment
824
- let maxRisk = RiskLevel.Low;
825
-
826
- for (const seg of parsed.segments) {
827
- const prog = seg.program;
828
-
829
- if (HIGH_RISK_PROGRAMS.has(prog)) return RiskLevel.High;
830
-
831
- if (prog === "rm") {
832
- // Only downgrade rm of known safe workspace files for sandboxed bash.
833
- // host_bash has a global ask rule that would prompt Medium-risk
834
- // commands, so rm on the host must always require explicit approval.
835
- if (toolName === "bash" && isRmOfKnownSafeFile(seg.args)) {
836
- maxRisk = RiskLevel.Medium;
837
- continue;
838
- }
839
- return RiskLevel.High;
840
- }
841
-
842
- if (
843
- prog === "chmod" ||
844
- prog === "chown" ||
845
- prog === "chgrp" ||
846
- prog === "sed" ||
847
- prog === "awk"
848
- ) {
849
- maxRisk = RiskLevel.Medium;
850
- continue;
851
- }
852
-
853
- // curl/wget can download and execute arbitrary code from the internet.
854
- // Also catch wrapped invocations like `env curl …` or `nice wget …`.
855
- if (prog === "curl" || prog === "wget") {
856
- maxRisk = RiskLevel.Medium;
857
- continue;
858
- }
859
-
860
- if (WRAPPER_PROGRAMS.has(prog)) {
861
- // `command -v` and `command -V` are read-only lookups (print where
862
- // a command lives) — don't escalate to high risk for those.
863
- if (
864
- prog === "command" &&
865
- seg.args.length > 0 &&
866
- (seg.args[0] === "-v" || seg.args[0] === "-V")
867
- ) {
868
- continue;
869
- }
870
- const wrapped = getWrappedProgram(seg);
871
- if (wrapped === "rm") return RiskLevel.High;
872
- if (wrapped && HIGH_RISK_PROGRAMS.has(wrapped)) return RiskLevel.High;
873
- if (wrapped === "curl" || wrapped === "wget") {
874
- maxRisk = RiskLevel.Medium;
875
- continue;
876
- }
877
- // Propagate subcommand-aware classification for wrapped git/assistant
878
- if (wrapped === "git") {
879
- const wrappedWithArgs = getWrappedProgramWithArgs(seg);
880
- if (wrappedWithArgs) {
881
- const subcommand = firstPositionalArg(
882
- wrappedWithArgs.args,
883
- GIT_VALUE_FLAGS,
884
- );
885
- if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
886
- continue;
887
- }
888
- maxRisk = RiskLevel.Medium;
889
- continue;
890
- }
891
- }
892
- if (wrapped === "assistant") {
893
- const wrappedWithArgs = getWrappedProgramWithArgs(seg);
894
- if (wrappedWithArgs) {
895
- const assistantRisk = classifyAssistantSubcommand(
896
- wrappedWithArgs.args,
897
- );
898
- if (assistantRisk === RiskLevel.High) return RiskLevel.High;
899
- if (assistantRisk === RiskLevel.Medium) {
900
- maxRisk = RiskLevel.Medium;
901
- }
902
- continue;
903
- }
904
- }
905
- }
906
-
907
- if (prog === "git") {
908
- const subcommand = firstPositionalArg(seg.args, GIT_VALUE_FLAGS);
909
- if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
910
- // Stay at current risk
911
- continue;
912
- }
913
- // Non-read-only git commands are medium
914
- maxRisk = RiskLevel.Medium;
915
- continue;
916
- }
917
-
918
- if (prog === "assistant") {
919
- const assistantRisk = classifyAssistantSubcommand(seg.args);
920
- if (assistantRisk === RiskLevel.High) return RiskLevel.High;
921
- if (assistantRisk === RiskLevel.Medium) {
922
- maxRisk = RiskLevel.Medium;
923
- }
924
- continue;
925
- }
926
-
927
- if (!LOW_RISK_PROGRAMS.has(prog)) {
928
- // Unknown program → medium
929
- if (maxRisk === RiskLevel.Low) {
930
- maxRisk = RiskLevel.Medium;
931
- }
932
- }
933
- }
934
-
935
- // If no segments could be extracted, treat as opaque
936
- if (parsed.segments.length === 0) {
937
- return RiskLevel.Medium;
938
- }
939
-
940
- return maxRisk;
941
- }
942
-
943
529
  // Check the tool registry for a declared default risk level
944
530
  const tool = getTool(toolName);
945
531
  if (tool) return tool.defaultRiskLevel;
@@ -959,27 +545,6 @@ async function classifyRiskUncached(
959
545
  return RiskLevel.Medium;
960
546
  }
961
547
 
962
- function isActorTokenSigningKeyPath(
963
- filePath: string | undefined,
964
- workingDir?: string,
965
- ): boolean {
966
- if (!filePath) return false;
967
- const cwd = workingDir ?? process.cwd();
968
- const resolvedPath = resolve(cwd, filePath);
969
- // Include both the per-instance protected dir AND the legacy global
970
- // ~/.vellum/protected path so upgraded machines with a host-wide signing
971
- // key still classify reads as High risk.
972
- const signingKeyPaths = Array.from(
973
- new Set([
974
- join(homedir(), ".vellum", "protected", "actor-token-signing-key"),
975
- join(getProtectedDir(), "actor-token-signing-key"),
976
- join(getDeprecatedDir(), "actor-token-signing-key"),
977
- resolve(cwd, "deprecated", "actor-token-signing-key"),
978
- ]),
979
- );
980
- return signingKeyPaths.includes(resolvedPath);
981
- }
982
-
983
548
  export async function check(
984
549
  toolName: string,
985
550
  input: Record<string, unknown>,
@@ -999,7 +564,7 @@ export async function check(
999
564
  }
1000
565
  }
1001
566
 
1002
- const risk = await classifyRisk(
567
+ const { level: risk, reason: riskReason } = await classifyRisk(
1003
568
  toolName,
1004
569
  input,
1005
570
  workingDir,
@@ -1024,129 +589,55 @@ export async function check(
1024
589
  policyContext,
1025
590
  );
1026
591
 
1027
- // Deny rules apply at ALL risk levels — including proxied network mode.
1028
- // Evaluate them first so hard blocks are never downgraded to a prompt.
1029
- if (matchedRule && matchedRule.decision === "deny") {
1030
- return {
1031
- decision: "deny",
1032
- reason: `Blocked by deny rule: ${matchedRule.pattern}`,
1033
- matchedRule,
1034
- };
1035
- }
1036
-
1037
- if (matchedRule) {
1038
- if (matchedRule.decision === "ask") {
1039
- // Ask rules always prompt — never auto-allow or auto-deny
1040
- return {
1041
- decision: "prompt",
1042
- reason: `Matched ask rule: ${matchedRule.pattern}`,
1043
- matchedRule,
1044
- };
1045
- }
1046
-
1047
- // Allow rule: auto-allow for non-High risk
1048
- if (risk !== RiskLevel.High) {
1049
- return {
1050
- decision: "allow",
1051
- reason: `Matched trust rule: ${matchedRule.pattern}`,
1052
- matchedRule,
1053
- };
1054
- }
1055
- // High risk with allow rule that explicitly permits high-risk → auto-allow
1056
- if (matchedRule.allowHighRisk === true) {
1057
- return {
1058
- decision: "allow",
1059
- reason: `Matched high-risk trust rule: ${matchedRule.pattern}`,
1060
- matchedRule,
1061
- };
1062
- }
1063
- // High risk with allow rule (without allowHighRisk) → fall through to prompt
1064
- }
1065
-
1066
- // No matching rule (or High risk with allow rule) → risk-based fallback
1067
-
1068
- // Third-party skill-origin tools default to prompting when no trust rule
1069
- // matches, regardless of risk level. Bundled skill tools are first-party
1070
- // and trusted, so they fall through to the normal risk-based policy.
1071
- // When manifestOverride is present, the tool comes from a skill manifest
1072
- // but isn't registered — treat it as a third-party skill tool.
1073
- if (!matchedRule) {
1074
- const tool = getTool(toolName);
1075
- if (tool?.origin === "skill" && !tool.ownerSkillBundled) {
1076
- return {
1077
- decision: "prompt",
1078
- reason: "Skill tool: requires approval by default",
1079
- };
1080
- }
1081
- if (!tool && manifestOverride) {
1082
- return {
1083
- decision: "prompt",
1084
- reason: "Skill tool: requires approval by default",
1085
- };
1086
- }
1087
- }
1088
-
1089
- // In strict mode, every tool without an explicit matching rule must be
1090
- // prompted — there is no implicit auto-allow for any risk level.
1091
- // This explicitly covers skill_load: activating a skill can grant the
1092
- // agent new capabilities, so in strict mode users must approve each
1093
- // skill load via an exact-version or wildcard trust rule.
1094
- const permissionsMode = getConfig().permissions.mode;
1095
-
1096
- if (permissionsMode === "strict" && !matchedRule) {
1097
- return {
1098
- decision: "prompt",
1099
- reason: `Strict mode: no matching rule, requires approval`,
1100
- };
1101
- }
1102
-
1103
- // Workspace mode: auto-allow workspace-scoped operations that don't have
1104
- // an explicit rule, but only when risk is Low. Medium and High risk operations
1105
- // fall through to risk-based policy and always require approval.
1106
- if (
1107
- permissionsMode === "workspace" &&
1108
- !matchedRule &&
1109
- risk === RiskLevel.Low
1110
- ) {
1111
- // Outside a container, bash runs on the host — don't auto-allow
1112
- if (toolName === "bash" && !getIsContainerized()) {
1113
- // Fall through to risk-based policy below
1114
- } else if (isWorkspaceScopedInvocation(toolName, input, workingDir)) {
1115
- return {
1116
- decision: "allow",
1117
- reason: "Workspace mode: workspace-scoped operation auto-allowed",
1118
- };
1119
- }
1120
- }
1121
-
1122
- // Auto-allow low-risk bundled skill tools even without explicit trust rules.
1123
- // These are first-party tools with a vetted risk declaration — applying the
1124
- // same policy as the per-tool default allow rules for browser tools, but
1125
- // generically so every new bundled skill benefits automatically.
1126
- // This block must come AFTER the strict mode check so that strict mode
1127
- // still prompts for bundled skill tools without explicit rules.
1128
- if (!matchedRule && risk === RiskLevel.Low) {
1129
- const tool = getTool(toolName);
1130
- if (tool?.origin === "skill" && tool.ownerSkillBundled) {
1131
- return {
1132
- decision: "allow",
1133
- reason: "Bundled skill tool: low risk, auto-allowed",
1134
- };
592
+ // Build approval context from local variables
593
+ const tool = getTool(toolName);
594
+ const config = getConfig();
595
+ const resolvedThreshold = resolveThreshold(
596
+ config.permissions.autoApproveUpTo,
597
+ policyContext?.executionContext,
598
+ );
599
+ const approvalContext: ApprovalContext = {
600
+ riskLevel: risk,
601
+ toolName,
602
+ matchedRule: matchedRule ?? undefined,
603
+ permissionsMode: config.permissions.mode,
604
+ isContainerized: getIsContainerized(),
605
+ isWorkspaceScoped:
606
+ risk === RiskLevel.Low
607
+ ? isWorkspaceScopedInvocation(toolName, input, workingDir)
608
+ : false,
609
+ toolOrigin:
610
+ tool?.origin === "skill" ? "skill" : tool ? "builtin" : undefined,
611
+ isSkillBundled: tool?.ownerSkillBundled ?? false,
612
+ hasManifestOverride: !!manifestOverride,
613
+ autoApproveUpTo: resolvedThreshold,
614
+ };
615
+
616
+ // Delegate the allow/prompt/deny decision to the approval policy
617
+ const approvalDecision = defaultApprovalPolicy.evaluate(approvalContext);
618
+
619
+ // Enrich the reason with the classifier's explanation when available.
620
+ // For risk-based fallback decisions (prompt/deny from High/Medium risk),
621
+ // incorporate the classifier reason so the user sees *why* the command
622
+ // was classified at that level (e.g. "High risk (Recursive force delete): requires approval").
623
+ let enrichedReason = approvalDecision.reason;
624
+ if (riskReason && !approvalDecision.matchedRule) {
625
+ const riskLabelMatch = enrichedReason.match(
626
+ /^(High|Medium|Low|high|medium|low) risk(.*)/i,
627
+ );
628
+ if (riskLabelMatch) {
629
+ const capitalizedLabel =
630
+ riskLabelMatch[1].charAt(0).toUpperCase() +
631
+ riskLabelMatch[1].slice(1).toLowerCase();
632
+ enrichedReason = `${capitalizedLabel} risk (${riskReason})${riskLabelMatch[2]}`;
1135
633
  }
1136
634
  }
1137
635
 
1138
- if (risk === RiskLevel.High) {
1139
- return {
1140
- decision: "prompt",
1141
- reason: `High risk: always requires approval`,
1142
- };
1143
- }
1144
-
1145
- if (risk === RiskLevel.Low) {
1146
- return { decision: "allow", reason: "Low risk: auto-allowed" };
1147
- }
1148
-
1149
- return { decision: "prompt", reason: `${risk} risk: requires approval` };
636
+ return {
637
+ decision: approvalDecision.decision,
638
+ reason: enrichedReason,
639
+ matchedRule: approvalDecision.matchedRule,
640
+ };
1150
641
  }
1151
642
 
1152
643
  const TOOL_DISPLAY_NAMES: Record<string, string> = {
@@ -1157,7 +648,6 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = {
1157
648
  host_file_write: "host file writes",
1158
649
  host_file_edit: "host file edits",
1159
650
  web_fetch: "URL fetches",
1160
- browser_navigate: "browser navigations",
1161
651
  network_request: "network requests",
1162
652
  };
1163
653
 
@@ -1185,6 +675,10 @@ function shellAllowlistStrategy(
1185
675
  input: Record<string, unknown>,
1186
676
  ): Promise<AllowlistOption[]> {
1187
677
  const command = ((input.command as string) ?? "").trim();
678
+ // TODO(phase-3): Wire RiskAssessment.scopeOptions into permission prompts
679
+ // and retire buildShellAllowlistOptions + buildShellCommandCandidates from
680
+ // shell-identity.ts. The classifier's generateScopeOptions produces the
681
+ // canonical scope ladder; this legacy path should not diverge further.
1188
682
  return buildShellAllowlistOptions(command);
1189
683
  }
1190
684
 
@@ -1366,7 +860,6 @@ const ALLOWLIST_STRATEGIES: Record<string, AllowlistStrategy> = {
1366
860
  host_file_write: fileAllowlistStrategy,
1367
861
  host_file_edit: fileAllowlistStrategy,
1368
862
  web_fetch: urlAllowlistStrategy,
1369
- browser_navigate: urlAllowlistStrategy,
1370
863
  network_request: urlAllowlistStrategy,
1371
864
  scaffold_managed_skill: managedSkillAllowlistStrategy,
1372
865
  delete_managed_skill: managedSkillAllowlistStrategy,
@@ -1380,6 +873,22 @@ export async function generateAllowlistOptions(
1380
873
  ): Promise<AllowlistOption[]> {
1381
874
  signal?.throwIfAborted();
1382
875
 
876
+ // Check if a classifier already produced allowlist options during
877
+ // classifyRisk(). If so, return those directly — avoids duplicate
878
+ // computation and keeps scope option generation unified with risk
879
+ // classification.
880
+ const aKey = assessmentCacheKey(toolName, input);
881
+ const cachedAssessment = assessmentCache.get(aKey);
882
+ if (
883
+ cachedAssessment?.allowlistOptions &&
884
+ cachedAssessment.allowlistOptions.length > 0
885
+ ) {
886
+ return cachedAssessment.allowlistOptions;
887
+ }
888
+
889
+ // Fall back to the per-tool strategy function for tools that don't have
890
+ // classifier-produced options (e.g. bash tools use the shell identity
891
+ // strategy, or when the cache was missed).
1383
892
  if (Object.hasOwn(ALLOWLIST_STRATEGIES, toolName)) {
1384
893
  return ALLOWLIST_STRATEGIES[toolName](toolName, input);
1385
894
  }