@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
@@ -0,0 +1,948 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { ApprovalContext, ApprovalDecision } from "./approval-policy.js";
4
+ import { DefaultApprovalPolicy, resolveThreshold } from "./approval-policy.js";
5
+ import type { TrustRule } from "./types.js";
6
+ import { RiskLevel } from "./types.js";
7
+
8
+ // ── Helpers ──────────────────────────────────────────────────────────────────
9
+
10
+ const policy = new DefaultApprovalPolicy();
11
+
12
+ function makeRule(
13
+ overrides: Partial<TrustRule> & { decision: TrustRule["decision"] },
14
+ ): TrustRule {
15
+ return {
16
+ id: "test-rule",
17
+ tool: "bash",
18
+ pattern: "test-pattern",
19
+ priority: 100,
20
+ createdAt: Date.now(),
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ function makeContext(overrides: Partial<ApprovalContext>): ApprovalContext {
26
+ return {
27
+ riskLevel: RiskLevel.Low,
28
+ toolName: "bash",
29
+ permissionsMode: "workspace",
30
+ isContainerized: false,
31
+ isWorkspaceScoped: false,
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ function evaluate(overrides: Partial<ApprovalContext>): ApprovalDecision {
37
+ return policy.evaluate(makeContext(overrides));
38
+ }
39
+
40
+ // ── Deny rule at each risk level ─────────────────────────────────────────────
41
+
42
+ describe("deny rule", () => {
43
+ const denyRule = makeRule({ decision: "deny" });
44
+
45
+ test("deny at Low risk", () => {
46
+ const result = evaluate({
47
+ riskLevel: RiskLevel.Low,
48
+ matchedRule: denyRule,
49
+ });
50
+ expect(result.decision).toBe("deny");
51
+ expect(result.reason).toContain("deny rule");
52
+ expect(result.matchedRule).toBe(denyRule);
53
+ });
54
+
55
+ test("deny at Medium risk", () => {
56
+ const result = evaluate({
57
+ riskLevel: RiskLevel.Medium,
58
+ matchedRule: denyRule,
59
+ });
60
+ expect(result.decision).toBe("deny");
61
+ });
62
+
63
+ test("deny at High risk", () => {
64
+ const result = evaluate({
65
+ riskLevel: RiskLevel.High,
66
+ matchedRule: denyRule,
67
+ });
68
+ expect(result.decision).toBe("deny");
69
+ });
70
+ });
71
+
72
+ // ── Ask rule at each risk level ──────────────────────────────────────────────
73
+
74
+ describe("ask rule", () => {
75
+ const askRule = makeRule({ decision: "ask" });
76
+
77
+ test("ask at Low risk", () => {
78
+ const result = evaluate({ riskLevel: RiskLevel.Low, matchedRule: askRule });
79
+ expect(result.decision).toBe("prompt");
80
+ expect(result.reason).toContain("ask rule");
81
+ expect(result.matchedRule).toBe(askRule);
82
+ });
83
+
84
+ test("ask at Medium risk", () => {
85
+ const result = evaluate({
86
+ riskLevel: RiskLevel.Medium,
87
+ matchedRule: askRule,
88
+ });
89
+ expect(result.decision).toBe("prompt");
90
+ });
91
+
92
+ test("ask at High risk", () => {
93
+ const result = evaluate({
94
+ riskLevel: RiskLevel.High,
95
+ matchedRule: askRule,
96
+ });
97
+ expect(result.decision).toBe("prompt");
98
+ });
99
+ });
100
+
101
+ // ── Allow rule at each risk level ────────────────────────────────────────────
102
+
103
+ describe("allow rule", () => {
104
+ const allowRule = makeRule({ decision: "allow" });
105
+
106
+ test("allow at Low risk", () => {
107
+ const result = evaluate({
108
+ riskLevel: RiskLevel.Low,
109
+ matchedRule: allowRule,
110
+ });
111
+ expect(result.decision).toBe("allow");
112
+ expect(result.reason).toContain("Matched trust rule");
113
+ expect(result.matchedRule).toBe(allowRule);
114
+ });
115
+
116
+ test("allow at Medium risk", () => {
117
+ const result = evaluate({
118
+ riskLevel: RiskLevel.Medium,
119
+ matchedRule: allowRule,
120
+ });
121
+ expect(result.decision).toBe("allow");
122
+ expect(result.reason).toContain("Matched trust rule");
123
+ expect(result.matchedRule).toBe(allowRule);
124
+ });
125
+
126
+ test("allow at High risk — non-containerized bash → prompt, no matchedRule in decision", () => {
127
+ const result = evaluate({
128
+ riskLevel: RiskLevel.High,
129
+ toolName: "bash",
130
+ matchedRule: allowRule,
131
+ isContainerized: false,
132
+ });
133
+ expect(result.decision).toBe("prompt");
134
+ expect(result.reason).toContain("high risk");
135
+ // Decision is driven by risk-based fallback, not the rule
136
+ expect(result.matchedRule).toBeUndefined();
137
+ });
138
+
139
+ test("allow at High risk — containerized bash → allow (auto-allow), matchedRule present", () => {
140
+ const result = evaluate({
141
+ riskLevel: RiskLevel.High,
142
+ toolName: "bash",
143
+ matchedRule: allowRule,
144
+ isContainerized: true,
145
+ });
146
+ expect(result.decision).toBe("allow");
147
+ expect(result.reason).toContain("auto-allow-high-risk");
148
+ expect(result.matchedRule).toBe(allowRule);
149
+ });
150
+
151
+ test("allow at High risk — non-bash tool, containerized → prompt, no matchedRule in decision", () => {
152
+ const result = evaluate({
153
+ riskLevel: RiskLevel.High,
154
+ toolName: "file_write",
155
+ matchedRule: allowRule,
156
+ isContainerized: true,
157
+ });
158
+ expect(result.decision).toBe("prompt");
159
+ expect(result.reason).toContain("high risk");
160
+ // Decision is driven by risk-based fallback, not the rule
161
+ expect(result.matchedRule).toBeUndefined();
162
+ });
163
+
164
+ test("allow at High risk — non-bash tool, non-containerized → prompt, no matchedRule in decision", () => {
165
+ const result = evaluate({
166
+ riskLevel: RiskLevel.High,
167
+ toolName: "file_write",
168
+ matchedRule: allowRule,
169
+ isContainerized: false,
170
+ });
171
+ expect(result.decision).toBe("prompt");
172
+ expect(result.reason).toContain("high risk");
173
+ // Decision is driven by risk-based fallback, not the rule
174
+ expect(result.matchedRule).toBeUndefined();
175
+ });
176
+ });
177
+
178
+ // ── No rule: third-party skill tool ──────────────────────────────────────────
179
+
180
+ describe("no rule — third-party skill tool", () => {
181
+ test("skill origin, not bundled → prompt", () => {
182
+ const result = evaluate({
183
+ riskLevel: RiskLevel.Low,
184
+ toolName: "custom_tool",
185
+ toolOrigin: "skill",
186
+ isSkillBundled: false,
187
+ });
188
+ expect(result.decision).toBe("prompt");
189
+ expect(result.reason).toContain("Skill tool");
190
+ });
191
+
192
+ test("skill origin, not bundled, Medium risk → prompt", () => {
193
+ const result = evaluate({
194
+ riskLevel: RiskLevel.Medium,
195
+ toolName: "custom_tool",
196
+ toolOrigin: "skill",
197
+ isSkillBundled: false,
198
+ });
199
+ expect(result.decision).toBe("prompt");
200
+ expect(result.reason).toContain("Skill tool");
201
+ });
202
+
203
+ test("no tool origin but hasManifestOverride → prompt (unregistered skill tool)", () => {
204
+ const result = evaluate({
205
+ riskLevel: RiskLevel.Low,
206
+ toolName: "unknown_tool",
207
+ hasManifestOverride: true,
208
+ });
209
+ expect(result.decision).toBe("prompt");
210
+ expect(result.reason).toContain("Skill tool");
211
+ });
212
+
213
+ test("skill origin, bundled → falls through (not third-party)", () => {
214
+ const result = evaluate({
215
+ riskLevel: RiskLevel.Low,
216
+ toolName: "bundled_tool",
217
+ toolOrigin: "skill",
218
+ isSkillBundled: true,
219
+ });
220
+ // Bundled skill + Low risk + no rule → handled by step 9 or 11
221
+ expect(result.decision).toBe("allow");
222
+ });
223
+ });
224
+
225
+ // ── No rule: strict mode ─────────────────────────────────────────────────────
226
+
227
+ describe("no rule — strict mode", () => {
228
+ test("strict mode, Low risk → prompt", () => {
229
+ const result = evaluate({
230
+ riskLevel: RiskLevel.Low,
231
+ toolName: "file_read",
232
+ permissionsMode: "strict",
233
+ });
234
+ expect(result.decision).toBe("prompt");
235
+ expect(result.reason).toContain("Strict mode");
236
+ });
237
+
238
+ test("strict mode, Medium risk → prompt", () => {
239
+ const result = evaluate({
240
+ riskLevel: RiskLevel.Medium,
241
+ toolName: "file_read",
242
+ permissionsMode: "strict",
243
+ });
244
+ expect(result.decision).toBe("prompt");
245
+ expect(result.reason).toContain("Strict mode");
246
+ });
247
+
248
+ test("strict mode, High risk → prompt", () => {
249
+ const result = evaluate({
250
+ riskLevel: RiskLevel.High,
251
+ toolName: "file_read",
252
+ permissionsMode: "strict",
253
+ });
254
+ expect(result.decision).toBe("prompt");
255
+ expect(result.reason).toContain("Strict mode");
256
+ });
257
+
258
+ test("strict mode blocks bundled skill tools without explicit rule", () => {
259
+ const result = evaluate({
260
+ riskLevel: RiskLevel.Low,
261
+ toolName: "bundled_tool",
262
+ permissionsMode: "strict",
263
+ toolOrigin: "skill",
264
+ isSkillBundled: true,
265
+ });
266
+ expect(result.decision).toBe("prompt");
267
+ expect(result.reason).toContain("Strict mode");
268
+ });
269
+ });
270
+
271
+ // ── No rule: workspace mode ──────────────────────────────────────────────────
272
+
273
+ describe("no rule — workspace mode", () => {
274
+ test("workspace mode, Low risk, workspace-scoped → allow", () => {
275
+ const result = evaluate({
276
+ riskLevel: RiskLevel.Low,
277
+ toolName: "file_read",
278
+ permissionsMode: "workspace",
279
+ isWorkspaceScoped: true,
280
+ });
281
+ expect(result.decision).toBe("allow");
282
+ expect(result.reason).toContain("Workspace mode");
283
+ });
284
+
285
+ test("workspace mode, Low risk, NOT workspace-scoped → falls through", () => {
286
+ const result = evaluate({
287
+ riskLevel: RiskLevel.Low,
288
+ toolName: "file_read",
289
+ permissionsMode: "workspace",
290
+ isWorkspaceScoped: false,
291
+ });
292
+ // Falls through to risk-based: Low → allow (within default "low" threshold)
293
+ expect(result.decision).toBe("allow");
294
+ expect(result.reason).toContain("low risk");
295
+ });
296
+
297
+ test("workspace mode, Medium risk → falls through to risk-based prompt", () => {
298
+ const result = evaluate({
299
+ riskLevel: RiskLevel.Medium,
300
+ toolName: "file_write",
301
+ permissionsMode: "workspace",
302
+ isWorkspaceScoped: true,
303
+ });
304
+ expect(result.decision).toBe("prompt");
305
+ expect(result.reason).toContain("medium risk");
306
+ });
307
+
308
+ test("workspace mode, bash, NOT containerized, Low risk, workspace-scoped → falls through (no auto-allow for host bash)", () => {
309
+ const result = evaluate({
310
+ riskLevel: RiskLevel.Low,
311
+ toolName: "bash",
312
+ permissionsMode: "workspace",
313
+ isContainerized: false,
314
+ isWorkspaceScoped: true,
315
+ });
316
+ // Non-containerized bash falls through the workspace check.
317
+ // Then hits risk-based: Low → allow (within default "low" threshold)
318
+ expect(result.decision).toBe("allow");
319
+ expect(result.reason).toContain("low risk");
320
+ });
321
+
322
+ test("workspace mode, bash, containerized, Low risk, workspace-scoped → allow via workspace mode", () => {
323
+ const result = evaluate({
324
+ riskLevel: RiskLevel.Low,
325
+ toolName: "bash",
326
+ permissionsMode: "workspace",
327
+ isContainerized: true,
328
+ isWorkspaceScoped: true,
329
+ });
330
+ expect(result.decision).toBe("allow");
331
+ expect(result.reason).toContain("Workspace mode");
332
+ });
333
+ });
334
+
335
+ // ── No rule: bundled skill tool ──────────────────────────────────────────────
336
+
337
+ describe("no rule — bundled skill tool", () => {
338
+ test("bundled skill, Low risk → allow", () => {
339
+ const result = evaluate({
340
+ riskLevel: RiskLevel.Low,
341
+ toolName: "bundled_tool",
342
+ permissionsMode: "workspace",
343
+ toolOrigin: "skill",
344
+ isSkillBundled: true,
345
+ });
346
+ expect(result.decision).toBe("allow");
347
+ expect(result.reason).toContain("Bundled skill");
348
+ });
349
+
350
+ test("bundled skill, Medium risk → prompt (only Low auto-allows)", () => {
351
+ const result = evaluate({
352
+ riskLevel: RiskLevel.Medium,
353
+ toolName: "bundled_tool",
354
+ permissionsMode: "workspace",
355
+ toolOrigin: "skill",
356
+ isSkillBundled: true,
357
+ });
358
+ expect(result.decision).toBe("prompt");
359
+ expect(result.reason).toContain("medium risk");
360
+ });
361
+
362
+ test("bundled skill, High risk → prompt", () => {
363
+ const result = evaluate({
364
+ riskLevel: RiskLevel.High,
365
+ toolName: "bundled_tool",
366
+ permissionsMode: "workspace",
367
+ toolOrigin: "skill",
368
+ isSkillBundled: true,
369
+ });
370
+ expect(result.decision).toBe("prompt");
371
+ expect(result.reason).toContain("high risk");
372
+ });
373
+ });
374
+
375
+ // ── Risk-based fallback ──────────────────────────────────────────────────────
376
+
377
+ describe("risk-based fallback (no rule, no special case)", () => {
378
+ test("High risk → prompt", () => {
379
+ const result = evaluate({
380
+ riskLevel: RiskLevel.High,
381
+ toolName: "some_tool",
382
+ });
383
+ expect(result.decision).toBe("prompt");
384
+ expect(result.reason).toContain("high risk");
385
+ });
386
+
387
+ test("Low risk → allow", () => {
388
+ const result = evaluate({
389
+ riskLevel: RiskLevel.Low,
390
+ toolName: "some_tool",
391
+ });
392
+ expect(result.decision).toBe("allow");
393
+ expect(result.reason).toContain("low risk");
394
+ });
395
+
396
+ test("Medium risk → prompt", () => {
397
+ const result = evaluate({
398
+ riskLevel: RiskLevel.Medium,
399
+ toolName: "some_tool",
400
+ });
401
+ expect(result.decision).toBe("prompt");
402
+ expect(result.reason).toContain("medium risk");
403
+ });
404
+ });
405
+
406
+ // ── Edge cases and combined scenarios ────────────────────────────────────────
407
+
408
+ describe("edge cases", () => {
409
+ test("deny rule takes precedence over allow-everything else", () => {
410
+ const denyRule = makeRule({ decision: "deny" });
411
+ const result = evaluate({
412
+ riskLevel: RiskLevel.Low,
413
+ toolName: "bash",
414
+ matchedRule: denyRule,
415
+ isContainerized: true,
416
+ isWorkspaceScoped: true,
417
+ permissionsMode: "workspace",
418
+ });
419
+ expect(result.decision).toBe("deny");
420
+ });
421
+
422
+ test("ask rule takes precedence over allow-for-low", () => {
423
+ const askRule = makeRule({ decision: "ask" });
424
+ const result = evaluate({
425
+ riskLevel: RiskLevel.Low,
426
+ toolName: "bash",
427
+ matchedRule: askRule,
428
+ isContainerized: true,
429
+ });
430
+ expect(result.decision).toBe("prompt");
431
+ });
432
+
433
+ test("allow rule High risk falls through to prompt even with workspace mode", () => {
434
+ const allowRule = makeRule({ decision: "allow" });
435
+ const result = evaluate({
436
+ riskLevel: RiskLevel.High,
437
+ toolName: "file_write",
438
+ matchedRule: allowRule,
439
+ permissionsMode: "workspace",
440
+ isContainerized: false,
441
+ isWorkspaceScoped: true,
442
+ });
443
+ // Allow rule + High risk + non-bash → falls through to risk-based: High → prompt
444
+ expect(result.decision).toBe("prompt");
445
+ expect(result.reason).toContain("high risk");
446
+ });
447
+
448
+ test("reason includes the matched rule pattern", () => {
449
+ const rule = makeRule({ decision: "allow", pattern: "git status" });
450
+ const result = evaluate({
451
+ riskLevel: RiskLevel.Low,
452
+ matchedRule: rule,
453
+ });
454
+ expect(result.reason).toContain("git status");
455
+ });
456
+
457
+ test("deny reason includes the matched rule pattern", () => {
458
+ const rule = makeRule({ decision: "deny", pattern: "rm -rf /" });
459
+ const result = evaluate({
460
+ riskLevel: RiskLevel.Low,
461
+ matchedRule: rule,
462
+ });
463
+ expect(result.reason).toContain("rm -rf /");
464
+ });
465
+
466
+ test("strict mode with matched allow rule at Low risk → allow (rule takes precedence)", () => {
467
+ const allowRule = makeRule({ decision: "allow" });
468
+ const result = evaluate({
469
+ riskLevel: RiskLevel.Low,
470
+ toolName: "file_read",
471
+ matchedRule: allowRule,
472
+ permissionsMode: "strict",
473
+ });
474
+ expect(result.decision).toBe("allow");
475
+ });
476
+
477
+ test("workspace mode non-containerized bash, Low risk, workspace-scoped → low risk allow (not workspace allow)", () => {
478
+ // This is the subtle bash host exception. The workspace mode check
479
+ // specifically skips bash when not containerized, so it falls through
480
+ // to the risk-based path where Low risk still auto-allows.
481
+ const result = evaluate({
482
+ riskLevel: RiskLevel.Low,
483
+ toolName: "bash",
484
+ permissionsMode: "workspace",
485
+ isContainerized: false,
486
+ isWorkspaceScoped: true,
487
+ });
488
+ expect(result.decision).toBe("allow");
489
+ // The reason should be "low risk" not "Workspace mode" — the workspace
490
+ // auto-allow was bypassed because bash is on the host.
491
+ expect(result.reason).not.toContain("Workspace mode");
492
+ expect(result.reason).toContain("low risk");
493
+ });
494
+
495
+ test("hasManifestOverride with toolOrigin set to skill — third-party check triggers on origin", () => {
496
+ const result = evaluate({
497
+ riskLevel: RiskLevel.Low,
498
+ toolName: "manifest_tool",
499
+ toolOrigin: "skill",
500
+ isSkillBundled: false,
501
+ hasManifestOverride: true,
502
+ });
503
+ expect(result.decision).toBe("prompt");
504
+ expect(result.reason).toContain("Skill tool");
505
+ });
506
+
507
+ test("hasManifestOverride with toolOrigin set to builtin — falls through (not a skill)", () => {
508
+ const result = evaluate({
509
+ riskLevel: RiskLevel.Low,
510
+ toolName: "manifest_tool",
511
+ toolOrigin: "builtin",
512
+ hasManifestOverride: true,
513
+ });
514
+ // toolOrigin is "builtin", so the third-party skill check doesn't trigger.
515
+ // The hasManifestOverride check requires !toolOrigin, but toolOrigin is set.
516
+ // Falls through to risk-based: Low → allow (within default "low" threshold).
517
+ expect(result.decision).toBe("allow");
518
+ expect(result.reason).toContain("low risk");
519
+ });
520
+ });
521
+
522
+ // ── autoApproveUpTo threshold ─────────────────────────────────────────────────
523
+
524
+ describe("autoApproveUpTo threshold", () => {
525
+ describe('autoApproveUpTo: "none" — everything prompts', () => {
526
+ test("Low risk → prompt", () => {
527
+ const result = evaluate({
528
+ riskLevel: RiskLevel.Low,
529
+ toolName: "some_tool",
530
+ autoApproveUpTo: "none",
531
+ });
532
+ expect(result.decision).toBe("prompt");
533
+ expect(result.reason).toContain("above auto-approve threshold");
534
+ });
535
+
536
+ test("Medium risk → prompt", () => {
537
+ const result = evaluate({
538
+ riskLevel: RiskLevel.Medium,
539
+ toolName: "some_tool",
540
+ autoApproveUpTo: "none",
541
+ });
542
+ expect(result.decision).toBe("prompt");
543
+ expect(result.reason).toContain("above auto-approve threshold");
544
+ });
545
+
546
+ test("High risk → prompt", () => {
547
+ const result = evaluate({
548
+ riskLevel: RiskLevel.High,
549
+ toolName: "some_tool",
550
+ autoApproveUpTo: "none",
551
+ });
552
+ expect(result.decision).toBe("prompt");
553
+ expect(result.reason).toContain("above auto-approve threshold");
554
+ });
555
+ });
556
+
557
+ describe('autoApproveUpTo: "low" — default, matches existing behavior', () => {
558
+ test("Low risk → allow", () => {
559
+ const result = evaluate({
560
+ riskLevel: RiskLevel.Low,
561
+ toolName: "some_tool",
562
+ autoApproveUpTo: "low",
563
+ });
564
+ expect(result.decision).toBe("allow");
565
+ expect(result.reason).toContain("within auto-approve threshold");
566
+ });
567
+
568
+ test("Medium risk → prompt", () => {
569
+ const result = evaluate({
570
+ riskLevel: RiskLevel.Medium,
571
+ toolName: "some_tool",
572
+ autoApproveUpTo: "low",
573
+ });
574
+ expect(result.decision).toBe("prompt");
575
+ expect(result.reason).toContain("above auto-approve threshold");
576
+ });
577
+
578
+ test("High risk → prompt", () => {
579
+ const result = evaluate({
580
+ riskLevel: RiskLevel.High,
581
+ toolName: "some_tool",
582
+ autoApproveUpTo: "low",
583
+ });
584
+ expect(result.decision).toBe("prompt");
585
+ expect(result.reason).toContain("above auto-approve threshold");
586
+ });
587
+ });
588
+
589
+ describe('autoApproveUpTo: "medium" — Low and Medium auto-allow', () => {
590
+ test("Low risk → allow", () => {
591
+ const result = evaluate({
592
+ riskLevel: RiskLevel.Low,
593
+ toolName: "some_tool",
594
+ autoApproveUpTo: "medium",
595
+ });
596
+ expect(result.decision).toBe("allow");
597
+ expect(result.reason).toContain("within auto-approve threshold");
598
+ });
599
+
600
+ test("Medium risk → allow", () => {
601
+ const result = evaluate({
602
+ riskLevel: RiskLevel.Medium,
603
+ toolName: "some_tool",
604
+ autoApproveUpTo: "medium",
605
+ });
606
+ expect(result.decision).toBe("allow");
607
+ expect(result.reason).toContain("within auto-approve threshold");
608
+ });
609
+
610
+ test("High risk → prompt", () => {
611
+ const result = evaluate({
612
+ riskLevel: RiskLevel.High,
613
+ toolName: "some_tool",
614
+ autoApproveUpTo: "medium",
615
+ });
616
+ expect(result.decision).toBe("prompt");
617
+ expect(result.reason).toContain("above auto-approve threshold");
618
+ });
619
+ });
620
+
621
+ describe("threshold interacts correctly with rule-based decisions", () => {
622
+ test("deny rule still denies regardless of threshold", () => {
623
+ const denyRule = makeRule({ decision: "deny" });
624
+ const result = evaluate({
625
+ riskLevel: RiskLevel.Low,
626
+ toolName: "bash",
627
+ matchedRule: denyRule,
628
+ autoApproveUpTo: "medium",
629
+ });
630
+ expect(result.decision).toBe("deny");
631
+ expect(result.matchedRule).toBe(denyRule);
632
+ });
633
+
634
+ test("ask rule still prompts regardless of threshold", () => {
635
+ const askRule = makeRule({ decision: "ask" });
636
+ const result = evaluate({
637
+ riskLevel: RiskLevel.Low,
638
+ toolName: "bash",
639
+ matchedRule: askRule,
640
+ autoApproveUpTo: "medium",
641
+ });
642
+ expect(result.decision).toBe("prompt");
643
+ expect(result.matchedRule).toBe(askRule);
644
+ });
645
+
646
+ test("allow rule still allows non-High regardless of threshold", () => {
647
+ const allowRule = makeRule({ decision: "allow" });
648
+ const result = evaluate({
649
+ riskLevel: RiskLevel.Medium,
650
+ toolName: "file_write",
651
+ matchedRule: allowRule,
652
+ autoApproveUpTo: "none",
653
+ });
654
+ expect(result.decision).toBe("allow");
655
+ expect(result.matchedRule).toBe(allowRule);
656
+ });
657
+ });
658
+
659
+ describe("threshold interacts correctly with strict mode", () => {
660
+ test("strict mode still prompts even with medium threshold", () => {
661
+ const result = evaluate({
662
+ riskLevel: RiskLevel.Low,
663
+ toolName: "file_read",
664
+ permissionsMode: "strict",
665
+ autoApproveUpTo: "medium",
666
+ });
667
+ expect(result.decision).toBe("prompt");
668
+ expect(result.reason).toContain("Strict mode");
669
+ });
670
+ });
671
+
672
+ describe("threshold interacts correctly with workspace mode", () => {
673
+ test("workspace mode workspace-scoped Low still allows via workspace path (before threshold)", () => {
674
+ const result = evaluate({
675
+ riskLevel: RiskLevel.Low,
676
+ toolName: "file_read",
677
+ permissionsMode: "workspace",
678
+ isWorkspaceScoped: true,
679
+ autoApproveUpTo: "none",
680
+ });
681
+ // Workspace mode auto-allow fires before the threshold fallback
682
+ expect(result.decision).toBe("allow");
683
+ expect(result.reason).toContain("Workspace mode");
684
+ });
685
+
686
+ test("workspace mode non-workspace-scoped Low with none threshold → prompt", () => {
687
+ const result = evaluate({
688
+ riskLevel: RiskLevel.Low,
689
+ toolName: "file_read",
690
+ permissionsMode: "workspace",
691
+ isWorkspaceScoped: false,
692
+ autoApproveUpTo: "none",
693
+ });
694
+ // Falls through workspace check (not workspace-scoped), then threshold
695
+ // "none" means Low risk is above threshold → prompt
696
+ expect(result.decision).toBe("prompt");
697
+ expect(result.reason).toContain("above auto-approve threshold");
698
+ });
699
+ });
700
+
701
+ describe("threshold defaults to low when omitted", () => {
702
+ test("omitted autoApproveUpTo behaves as low", () => {
703
+ const result = evaluate({
704
+ riskLevel: RiskLevel.Low,
705
+ toolName: "some_tool",
706
+ // autoApproveUpTo not set
707
+ });
708
+ expect(result.decision).toBe("allow");
709
+ expect(result.reason).toContain("within auto-approve threshold");
710
+ });
711
+ });
712
+ });
713
+
714
+ // ── resolveThreshold ─────────────────────────────────────────────────────────
715
+
716
+ describe("resolveThreshold", () => {
717
+ describe("scalar form", () => {
718
+ test("returns scalar for conversation context", () => {
719
+ expect(resolveThreshold("low", "conversation")).toBe("low");
720
+ });
721
+
722
+ test("returns scalar for background context", () => {
723
+ expect(resolveThreshold("medium", "background")).toBe("medium");
724
+ });
725
+
726
+ test("returns scalar for headless context", () => {
727
+ expect(resolveThreshold("none", "headless")).toBe("none");
728
+ });
729
+
730
+ test("returns scalar when executionContext is omitted", () => {
731
+ expect(resolveThreshold("low")).toBe("low");
732
+ });
733
+ });
734
+
735
+ describe("object form", () => {
736
+ const perContext = {
737
+ conversation: "low" as const,
738
+ background: "medium" as const,
739
+ headless: "none" as const,
740
+ };
741
+
742
+ test("returns conversation threshold for conversation context", () => {
743
+ expect(resolveThreshold(perContext, "conversation")).toBe("low");
744
+ });
745
+
746
+ test("returns background threshold for background context", () => {
747
+ expect(resolveThreshold(perContext, "background")).toBe("medium");
748
+ });
749
+
750
+ test("returns headless threshold for headless context", () => {
751
+ expect(resolveThreshold(perContext, "headless")).toBe("none");
752
+ });
753
+
754
+ test("defaults to conversation when executionContext is omitted", () => {
755
+ expect(resolveThreshold(perContext)).toBe("low");
756
+ });
757
+ });
758
+
759
+ describe("per-context thresholds in policy evaluation", () => {
760
+ test("conversation context with low threshold prompts medium risk", () => {
761
+ const result = evaluate({
762
+ riskLevel: RiskLevel.Medium,
763
+ toolName: "some_tool",
764
+ autoApproveUpTo: "low",
765
+ });
766
+ expect(result.decision).toBe("prompt");
767
+ });
768
+
769
+ test("background context with medium threshold allows medium risk", () => {
770
+ const result = evaluate({
771
+ riskLevel: RiskLevel.Medium,
772
+ toolName: "some_tool",
773
+ autoApproveUpTo: "medium",
774
+ });
775
+ expect(result.decision).toBe("allow");
776
+ expect(result.reason).toContain("within auto-approve threshold");
777
+ });
778
+
779
+ test("headless context with none threshold prompts low risk", () => {
780
+ const result = evaluate({
781
+ riskLevel: RiskLevel.Low,
782
+ toolName: "some_tool",
783
+ autoApproveUpTo: "none",
784
+ });
785
+ expect(result.decision).toBe("prompt");
786
+ expect(result.reason).toContain("above auto-approve threshold");
787
+ });
788
+
789
+ test("background context with medium threshold prompts high risk", () => {
790
+ const result = evaluate({
791
+ riskLevel: RiskLevel.High,
792
+ toolName: "some_tool",
793
+ autoApproveUpTo: "medium",
794
+ });
795
+ expect(result.decision).toBe("prompt");
796
+ expect(result.reason).toContain("above auto-approve threshold");
797
+ });
798
+ });
799
+ });
800
+
801
+ // ── Guardian threshold-based auto-approve ────────────────────────────────────
802
+ // These tests verify the ordinal comparison used in permission-checker.ts
803
+ // to decide whether a guardian non-interactive session should auto-approve.
804
+ // The comparison logic: riskOrdinal <= thresholdOrdinal.
805
+
806
+ describe("guardian threshold-based auto-approve (ordinal comparison)", () => {
807
+ // Helper that mirrors the ordinal comparison from permission-checker.ts.
808
+ // This is the logic that replaces the old `riskLevel !== RiskLevel.High` check.
809
+ function isWithinThreshold(
810
+ riskLevel: RiskLevel,
811
+ bgThreshold: "none" | "low" | "medium",
812
+ ): boolean {
813
+ const thresholdOrdinal: Record<string, number> = {
814
+ none: -1,
815
+ low: 0,
816
+ medium: 1,
817
+ };
818
+ const riskOrdinal: Record<string, number> = {
819
+ [RiskLevel.Low]: 0,
820
+ [RiskLevel.Medium]: 1,
821
+ [RiskLevel.High]: 2,
822
+ };
823
+ return (
824
+ (riskOrdinal[riskLevel] ?? 2) <= (thresholdOrdinal[bgThreshold] ?? 0)
825
+ );
826
+ }
827
+
828
+ describe('default config (background: "medium") — behavioral parity with old riskLevel !== High', () => {
829
+ test("Low risk → within threshold (auto-approve)", () => {
830
+ const bgThreshold = resolveThreshold(
831
+ { conversation: "low", background: "medium", headless: "none" },
832
+ "background",
833
+ );
834
+ expect(bgThreshold).toBe("medium");
835
+ expect(isWithinThreshold(RiskLevel.Low, bgThreshold)).toBe(true);
836
+ });
837
+
838
+ test("Medium risk → within threshold (auto-approve)", () => {
839
+ const bgThreshold = resolveThreshold(
840
+ { conversation: "low", background: "medium", headless: "none" },
841
+ "background",
842
+ );
843
+ expect(isWithinThreshold(RiskLevel.Medium, bgThreshold)).toBe(true);
844
+ });
845
+
846
+ test("High risk → above threshold (prompt)", () => {
847
+ const bgThreshold = resolveThreshold(
848
+ { conversation: "low", background: "medium", headless: "none" },
849
+ "background",
850
+ );
851
+ expect(isWithinThreshold(RiskLevel.High, bgThreshold)).toBe(false);
852
+ });
853
+ });
854
+
855
+ describe('tighter config (background: "low") — only Low auto-approves', () => {
856
+ test("Low risk → within threshold", () => {
857
+ const bgThreshold = resolveThreshold(
858
+ { conversation: "low", background: "low", headless: "none" },
859
+ "background",
860
+ );
861
+ expect(bgThreshold).toBe("low");
862
+ expect(isWithinThreshold(RiskLevel.Low, bgThreshold)).toBe(true);
863
+ });
864
+
865
+ test("Medium risk → above threshold (prompt)", () => {
866
+ const bgThreshold = resolveThreshold(
867
+ { conversation: "low", background: "low", headless: "none" },
868
+ "background",
869
+ );
870
+ expect(isWithinThreshold(RiskLevel.Medium, bgThreshold)).toBe(false);
871
+ });
872
+
873
+ test("High risk → above threshold (prompt)", () => {
874
+ const bgThreshold = resolveThreshold(
875
+ { conversation: "low", background: "low", headless: "none" },
876
+ "background",
877
+ );
878
+ expect(isWithinThreshold(RiskLevel.High, bgThreshold)).toBe(false);
879
+ });
880
+ });
881
+
882
+ describe('strictest config (background: "none") — nothing auto-approves', () => {
883
+ test("Low risk → above threshold (prompt)", () => {
884
+ const bgThreshold = resolveThreshold(
885
+ { conversation: "low", background: "none", headless: "none" },
886
+ "background",
887
+ );
888
+ expect(bgThreshold).toBe("none");
889
+ expect(isWithinThreshold(RiskLevel.Low, bgThreshold)).toBe(false);
890
+ });
891
+
892
+ test("Medium risk → above threshold (prompt)", () => {
893
+ const bgThreshold = resolveThreshold(
894
+ { conversation: "low", background: "none", headless: "none" },
895
+ "background",
896
+ );
897
+ expect(isWithinThreshold(RiskLevel.Medium, bgThreshold)).toBe(false);
898
+ });
899
+
900
+ test("High risk → above threshold (prompt)", () => {
901
+ const bgThreshold = resolveThreshold(
902
+ { conversation: "low", background: "none", headless: "none" },
903
+ "background",
904
+ );
905
+ expect(isWithinThreshold(RiskLevel.High, bgThreshold)).toBe(false);
906
+ });
907
+ });
908
+
909
+ describe("scalar config form resolves correctly for background context", () => {
910
+ test('scalar "medium" → background resolves to medium', () => {
911
+ expect(resolveThreshold("medium", "background")).toBe("medium");
912
+ });
913
+
914
+ test('scalar "low" → background resolves to low', () => {
915
+ expect(resolveThreshold("low", "background")).toBe("low");
916
+ });
917
+
918
+ test('scalar "none" → background resolves to none', () => {
919
+ expect(resolveThreshold("none", "background")).toBe("none");
920
+ });
921
+ });
922
+
923
+ describe("default (undefined) resolves per-context defaults", () => {
924
+ test("undefined config → medium threshold for background", () => {
925
+ const bgThreshold = resolveThreshold(undefined, "background");
926
+ expect(bgThreshold).toBe("medium");
927
+ // Low and Medium risk are within threshold, High is not
928
+ expect(isWithinThreshold(RiskLevel.Low, bgThreshold)).toBe(true);
929
+ expect(isWithinThreshold(RiskLevel.Medium, bgThreshold)).toBe(true);
930
+ expect(isWithinThreshold(RiskLevel.High, bgThreshold)).toBe(false);
931
+ });
932
+
933
+ test("undefined config → low threshold for conversation", () => {
934
+ const convThreshold = resolveThreshold(undefined, "conversation");
935
+ expect(convThreshold).toBe("low");
936
+ });
937
+
938
+ test("undefined config → none threshold for headless", () => {
939
+ const hlThreshold = resolveThreshold(undefined, "headless");
940
+ expect(hlThreshold).toBe("none");
941
+ });
942
+
943
+ test("undefined config + no context → low (conversation default)", () => {
944
+ const threshold = resolveThreshold(undefined);
945
+ expect(threshold).toBe("low");
946
+ });
947
+ });
948
+ });