@vellumai/assistant 0.6.4 → 0.6.6

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 (1008) hide show
  1. package/.prettierignore +5 -0
  2. package/AGENTS.md +9 -1
  3. package/ARCHITECTURE.md +43 -49
  4. package/Dockerfile +17 -3
  5. package/README.md +3 -4
  6. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  7. package/bun.lock +8 -3
  8. package/docs/architecture/integrations.md +33 -59
  9. package/docs/architecture/memory.md +25 -30
  10. package/docs/architecture/security.md +19 -18
  11. package/docs/browser-use-architecture-phase2.md +63 -20
  12. package/docs/error-handling.md +111 -0
  13. package/docs/plugins.md +761 -0
  14. package/docs/skills.md +10 -10
  15. package/docs/stt-provider-onboarding.md +2 -1
  16. package/examples/plugins/echo/README.md +132 -0
  17. package/examples/plugins/echo/package.json +17 -0
  18. package/examples/plugins/echo/register.ts +187 -0
  19. package/knip.json +9 -2
  20. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  21. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  22. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  23. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  24. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  25. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  26. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  27. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  28. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  29. package/openapi.yaml +334 -78
  30. package/package.json +6 -3
  31. package/scripts/generate-openapi.ts +50 -11
  32. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  33. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  34. package/src/__tests__/agent-loop.test.ts +112 -1
  35. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  36. package/src/__tests__/anthropic-provider.test.ts +171 -2
  37. package/src/__tests__/app-compiler.test.ts +57 -0
  38. package/src/__tests__/approval-cascade.test.ts +36 -10
  39. package/src/__tests__/approval-routes-http.test.ts +134 -10
  40. package/src/__tests__/assistant-attachments.test.ts +44 -0
  41. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  42. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  43. package/src/__tests__/avatar-generator.test.ts +4 -2
  44. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  45. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  46. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  47. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  48. package/src/__tests__/btw-routes.test.ts +47 -1
  49. package/src/__tests__/bundled-asset.test.ts +6 -6
  50. package/src/__tests__/call-controller.test.ts +1 -2
  51. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  52. package/src/__tests__/catalog-cache.test.ts +96 -4
  53. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  54. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  55. package/src/__tests__/checker.test.ts +870 -655
  56. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  57. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  58. package/src/__tests__/compaction-events.test.ts +501 -0
  59. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  60. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  61. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  62. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  63. package/src/__tests__/config-analysis.test.ts +11 -28
  64. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  65. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  66. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  67. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  68. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  69. package/src/__tests__/config-schema.test.ts +440 -114
  70. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  71. package/src/__tests__/config-watcher.test.ts +2 -2
  72. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  73. package/src/__tests__/contacts-tools.test.ts +26 -0
  74. package/src/__tests__/contacts-write.test.ts +4 -4
  75. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  76. package/src/__tests__/context-token-estimator.test.ts +191 -1
  77. package/src/__tests__/context-window-manager.test.ts +883 -4
  78. package/src/__tests__/conversation-abort-tool-results.test.ts +32 -15
  79. package/src/__tests__/conversation-agent-loop-overflow.test.ts +86 -46
  80. package/src/__tests__/conversation-agent-loop.test.ts +435 -216
  81. package/src/__tests__/conversation-attachments.test.ts +1 -1
  82. package/src/__tests__/conversation-confirmation-signals.test.ts +36 -10
  83. package/src/__tests__/conversation-error.test.ts +37 -6
  84. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  85. package/src/__tests__/conversation-init.benchmark.test.ts +34 -12
  86. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  87. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  88. package/src/__tests__/conversation-pairing.test.ts +174 -10
  89. package/src/__tests__/conversation-pre-run-repair.test.ts +32 -15
  90. package/src/__tests__/conversation-process-callsite.test.ts +309 -0
  91. package/src/__tests__/conversation-provider-retry-repair.test.ts +44 -21
  92. package/src/__tests__/conversation-queue.test.ts +68 -38
  93. package/src/__tests__/conversation-routes-disk-view.test.ts +36 -7
  94. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  95. package/src/__tests__/conversation-runtime-assembly.test.ts +2877 -152
  96. package/src/__tests__/conversation-runtime-workspace.test.ts +35 -50
  97. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  98. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  99. package/src/__tests__/conversation-slash-queue.test.ts +39 -19
  100. package/src/__tests__/conversation-slash-unknown.test.ts +53 -16
  101. package/src/__tests__/conversation-speed-override.test.ts +36 -12
  102. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  103. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  104. package/src/__tests__/conversation-title-service.test.ts +118 -2
  105. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  106. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  107. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  108. package/src/__tests__/conversation-usage.test.ts +4 -2
  109. package/src/__tests__/conversation-workspace-cache-state.test.ts +33 -9
  110. package/src/__tests__/conversation-workspace-injection.test.ts +46 -15
  111. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -15
  112. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  113. package/src/__tests__/credential-health-service.test.ts +78 -9
  114. package/src/__tests__/credential-security-invariants.test.ts +5 -2
  115. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  116. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  117. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  118. package/src/__tests__/credentials-cli.test.ts +1 -9
  119. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  120. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  121. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  122. package/src/__tests__/delete-propagation.test.ts +437 -0
  123. package/src/__tests__/dm-backfill.test.ts +417 -0
  124. package/src/__tests__/dm-persistence.test.ts +227 -0
  125. package/src/__tests__/edit-propagation.test.ts +280 -0
  126. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  127. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  128. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  129. package/src/__tests__/estimator-calibration.test.ts +213 -0
  130. package/src/__tests__/extension-id-sync-guard.test.ts +29 -10
  131. package/src/__tests__/file-write-tool.test.ts +151 -1
  132. package/src/__tests__/filing-service.test.ts +255 -0
  133. package/src/__tests__/first-greeting.test.ts +247 -5
  134. package/src/__tests__/gemini-provider.test.ts +0 -3
  135. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  136. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  137. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  138. package/src/__tests__/heartbeat-service.test.ts +96 -15
  139. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  140. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  141. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  142. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  143. package/src/__tests__/host-shell-tool.test.ts +124 -18
  144. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  145. package/src/__tests__/image-credentials.test.ts +137 -0
  146. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  147. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  148. package/src/__tests__/injector-chain.test.ts +526 -0
  149. package/src/__tests__/intent-routing.test.ts +1 -66
  150. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  151. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  152. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  153. package/src/__tests__/llm-resolver.test.ts +214 -0
  154. package/src/__tests__/llm-schema.test.ts +223 -0
  155. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  156. package/src/__tests__/media-generate-image.test.ts +119 -13
  157. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  158. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  159. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  160. package/src/__tests__/migration-import-from-url.test.ts +621 -0
  161. package/src/__tests__/model-intents.test.ts +11 -83
  162. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  163. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  164. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  165. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  166. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  167. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  168. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  169. package/src/__tests__/oauth-cli.test.ts +14 -12
  170. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  171. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  172. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  173. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  174. package/src/__tests__/oauth-store.test.ts +46 -78
  175. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  176. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  177. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  178. package/src/__tests__/openai-image-service.test.ts +368 -0
  179. package/src/__tests__/openai-provider.test.ts +7 -0
  180. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  181. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  182. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  183. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  184. package/src/__tests__/permission-checker-host-gate.test.ts +1 -25
  185. package/src/__tests__/permission-mode.test.ts +16 -0
  186. package/src/__tests__/permission-types.test.ts +0 -1
  187. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  188. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  189. package/src/__tests__/persona-resolver.test.ts +13 -13
  190. package/src/__tests__/pipeline-runner.test.ts +565 -0
  191. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  192. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  193. package/src/__tests__/platform.test.ts +5 -2
  194. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  195. package/src/__tests__/plugin-registry.test.ts +273 -0
  196. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  197. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  198. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  199. package/src/__tests__/plugin-types.test.ts +320 -0
  200. package/src/__tests__/pricing.test.ts +93 -14
  201. package/src/__tests__/profiler-routes.test.ts +1 -1
  202. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  203. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  204. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  205. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  206. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  207. package/src/__tests__/proxy-approval-callback.test.ts +69 -9
  208. package/src/__tests__/reaction-persistence.test.ts +561 -0
  209. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  210. package/src/__tests__/registry.test.ts +0 -2
  211. package/src/__tests__/relay-server.test.ts +1 -1
  212. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  213. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  214. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  215. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  216. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  217. package/src/__tests__/schedule-routes.test.ts +131 -1
  218. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  219. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  220. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  221. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  222. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  223. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  224. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  225. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  226. package/src/__tests__/server-history-render.test.ts +31 -0
  227. package/src/__tests__/shell-identity.test.ts +0 -134
  228. package/src/__tests__/shell-parser-property.test.ts +13 -13
  229. package/src/__tests__/skill-cache-store.test.ts +182 -0
  230. package/src/__tests__/skills.test.ts +19 -33
  231. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  232. package/src/__tests__/slack-skill.test.ts +3 -8
  233. package/src/__tests__/starter-bundle.test.ts +35 -0
  234. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  235. package/src/__tests__/suggestion-routes.test.ts +259 -3
  236. package/src/__tests__/system-prompt.test.ts +22 -35
  237. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  238. package/src/__tests__/task-runner.test.ts +3 -1
  239. package/src/__tests__/task-scheduler.test.ts +3 -15
  240. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  241. package/src/__tests__/terminal-tools.test.ts +8 -0
  242. package/src/__tests__/test-preload.ts +11 -0
  243. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  244. package/src/__tests__/thread-backfill.test.ts +941 -0
  245. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  246. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  247. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  248. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  249. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -8
  250. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  251. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  252. package/src/__tests__/tool-executor.test.ts +201 -94
  253. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  254. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  255. package/src/__tests__/trust-store.test.ts +442 -109
  256. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  257. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  258. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  259. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  260. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  261. package/src/__tests__/volume-security-guard.test.ts +3 -2
  262. package/src/__tests__/web-search-history.test.ts +337 -0
  263. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  264. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  265. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  266. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  267. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  268. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  269. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  270. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  271. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  272. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  273. package/src/__tests__/workspace-policy.test.ts +22 -16
  274. package/src/acp/client-handler.ts +1 -2
  275. package/src/agent/loop.ts +545 -115
  276. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  277. package/src/approvals/guardian-request-resolvers.ts +80 -0
  278. package/src/avatar/resvg-lazy.test.ts +136 -0
  279. package/src/avatar/resvg-lazy.ts +82 -9
  280. package/src/avatar/traits-png-sync.ts +21 -1
  281. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  282. package/src/backup/backup-worker.ts +3 -15
  283. package/src/browser/__tests__/operations.test.ts +163 -0
  284. package/src/browser/identifiers.ts +51 -0
  285. package/src/browser/operations.ts +660 -0
  286. package/src/browser/types.ts +81 -0
  287. package/src/bundler/app-compiler.ts +84 -1
  288. package/src/calls/call-state.ts +2 -2
  289. package/src/calls/guardian-question-copy.ts +2 -2
  290. package/src/calls/telephony-stt-routing.ts +1 -1
  291. package/src/calls/voice-session-bridge.ts +1 -0
  292. package/src/channels/__tests__/types.test.ts +3 -3
  293. package/src/channels/types.ts +6 -4
  294. package/src/cli/AGENTS.md +1 -1
  295. package/src/cli/__tests__/notifications.test.ts +87 -211
  296. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  297. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  298. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  299. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  300. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  301. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  302. package/src/cli/commands/__tests__/image-generation.test.ts +886 -0
  303. package/src/cli/commands/__tests__/inference-send.test.ts +463 -0
  304. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  305. package/src/cli/commands/__tests__/task.test.ts +913 -0
  306. package/src/cli/commands/__tests__/tts-synthesize.test.ts +606 -0
  307. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  308. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  309. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  310. package/src/cli/commands/attachment.ts +182 -0
  311. package/src/cli/commands/backup.ts +2 -2
  312. package/src/cli/commands/browser.ts +350 -0
  313. package/src/cli/commands/cache.ts +341 -0
  314. package/src/cli/commands/clients.ts +138 -0
  315. package/src/cli/commands/completions.ts +2 -12
  316. package/src/cli/commands/config.ts +6 -6
  317. package/src/cli/commands/conversations-import.ts +347 -0
  318. package/src/cli/commands/conversations.ts +69 -8
  319. package/src/cli/commands/email.ts +234 -194
  320. package/src/cli/commands/image-generation.ts +299 -0
  321. package/src/cli/commands/inference.ts +200 -0
  322. package/src/cli/commands/memory.ts +127 -17
  323. package/src/cli/commands/notifications.ts +68 -103
  324. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  325. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  326. package/src/cli/commands/oauth/connect.ts +2 -2
  327. package/src/cli/commands/oauth/providers.ts +176 -8
  328. package/src/cli/commands/oauth/status.ts +46 -36
  329. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  330. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  331. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  332. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  333. package/src/cli/commands/skills.ts +3 -4
  334. package/src/cli/commands/stt.ts +339 -0
  335. package/src/cli/commands/task.ts +795 -0
  336. package/src/cli/commands/trust.ts +50 -19
  337. package/src/cli/commands/tts.ts +273 -0
  338. package/src/cli/commands/ui.ts +670 -0
  339. package/src/cli/commands/watchers.ts +509 -0
  340. package/src/cli/lib/daemon-credential-client.ts +0 -19
  341. package/src/cli/program.ts +39 -24
  342. package/src/cli.ts +0 -37
  343. package/src/config/__tests__/backup-schema.test.ts +7 -2
  344. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  345. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  346. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  347. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  348. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  349. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  350. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  351. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  352. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  353. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  354. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  355. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  356. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +20 -1
  357. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  358. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  359. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +69 -12
  360. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  361. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  362. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  363. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  364. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  365. package/src/config/bundled-tool-registry.ts +0 -190
  366. package/src/config/env.ts +7 -2
  367. package/src/config/feature-flag-registry.json +42 -10
  368. package/src/config/llm-resolver.ts +128 -0
  369. package/src/config/loader.ts +194 -10
  370. package/src/config/raw-config-utils.ts +30 -2
  371. package/src/config/sanitize-for-transfer.ts +35 -0
  372. package/src/config/schema.ts +49 -41
  373. package/src/config/schemas/analysis.ts +3 -22
  374. package/src/config/schemas/backup.ts +1 -1
  375. package/src/config/schemas/calls.ts +0 -4
  376. package/src/config/schemas/conversations.ts +16 -0
  377. package/src/config/schemas/filing.ts +2 -7
  378. package/src/config/schemas/heartbeat.ts +0 -5
  379. package/src/config/schemas/inference.ts +3 -23
  380. package/src/config/schemas/llm.ts +317 -0
  381. package/src/config/schemas/memory-processing.ts +1 -9
  382. package/src/config/schemas/notifications.ts +4 -11
  383. package/src/config/schemas/platform.ts +3 -9
  384. package/src/config/schemas/security.ts +33 -0
  385. package/src/config/schemas/services.ts +9 -4
  386. package/src/config/schemas/stt.ts +1 -0
  387. package/src/config/schemas/tts.ts +64 -0
  388. package/src/config/schemas/updates.ts +1 -1
  389. package/src/config/schemas/workspace-git.ts +3 -40
  390. package/src/config/skill-state.ts +6 -2
  391. package/src/config/skills.ts +96 -7
  392. package/src/context/__tests__/compact-prompt.test.ts +63 -0
  393. package/src/context/__tests__/microcompact.test.ts +805 -0
  394. package/src/context/estimator-calibration.ts +136 -0
  395. package/src/context/microcompact.ts +443 -0
  396. package/src/context/prompts/compact.md +26 -0
  397. package/src/context/token-estimator.ts +61 -3
  398. package/src/context/tool-result-truncation.ts +3 -63
  399. package/src/context/window-manager.ts +417 -39
  400. package/src/credential-execution/approval-bridge.ts +0 -1
  401. package/src/credential-execution/executable-discovery.ts +19 -8
  402. package/src/credential-execution/process-manager.test.ts +109 -0
  403. package/src/credential-execution/process-manager.ts +65 -2
  404. package/src/credential-health/credential-health-service.ts +19 -6
  405. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  406. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  407. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  408. package/src/daemon/approval-generators.ts +29 -4
  409. package/src/daemon/assistant-attachments.ts +24 -13
  410. package/src/daemon/classifier.ts +2 -2
  411. package/src/daemon/config-watcher.ts +0 -3
  412. package/src/daemon/context-overflow-policy.ts +4 -13
  413. package/src/daemon/context-overflow-reducer.ts +4 -1
  414. package/src/daemon/conversation-agent-loop-handlers.ts +162 -34
  415. package/src/daemon/conversation-agent-loop.ts +1282 -599
  416. package/src/daemon/conversation-attachments.ts +2 -6
  417. package/src/daemon/conversation-error.ts +36 -1
  418. package/src/daemon/conversation-history.ts +10 -19
  419. package/src/daemon/conversation-lifecycle.ts +59 -17
  420. package/src/daemon/conversation-messaging.ts +73 -4
  421. package/src/daemon/conversation-notifiers.ts +2 -110
  422. package/src/daemon/conversation-process.ts +24 -11
  423. package/src/daemon/conversation-queue-manager.ts +3 -0
  424. package/src/daemon/conversation-runtime-assembly.ts +1063 -211
  425. package/src/daemon/conversation-slash.ts +2 -2
  426. package/src/daemon/conversation-surfaces.ts +389 -1
  427. package/src/daemon/conversation-tool-setup.ts +51 -9
  428. package/src/daemon/conversation-usage.ts +1 -1
  429. package/src/daemon/conversation.ts +197 -64
  430. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  431. package/src/daemon/external-skills-bootstrap.ts +41 -0
  432. package/src/daemon/first-greeting.ts +191 -14
  433. package/src/daemon/guardian-action-generators.ts +34 -14
  434. package/src/daemon/handlers/config-model.test.ts +86 -0
  435. package/src/daemon/handlers/config-model.ts +65 -12
  436. package/src/daemon/handlers/conversations.ts +9 -2
  437. package/src/daemon/handlers/shared.ts +39 -11
  438. package/src/daemon/handlers/skills.ts +7 -3
  439. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  440. package/src/daemon/lifecycle.ts +109 -82
  441. package/src/daemon/message-types/computer-use.ts +2 -34
  442. package/src/daemon/message-types/conversations.ts +63 -0
  443. package/src/daemon/message-types/messages.ts +21 -1
  444. package/src/daemon/message-types/trust.ts +0 -2
  445. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  446. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  447. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  448. package/src/daemon/pkb-context-tracker.ts +125 -0
  449. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  450. package/src/daemon/pkb-reminder-builder.ts +31 -0
  451. package/src/daemon/providers-setup.ts +6 -0
  452. package/src/daemon/server.ts +122 -12
  453. package/src/daemon/shutdown-handlers.ts +2 -12
  454. package/src/daemon/tool-side-effects.ts +14 -65
  455. package/src/daemon/web-search-history.ts +126 -0
  456. package/src/events/domain-events.ts +0 -1
  457. package/src/filing/filing-service.ts +9 -10
  458. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  459. package/src/heartbeat/heartbeat-service.ts +99 -28
  460. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  461. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  462. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  463. package/src/home/assistant-feed-authoring.ts +4 -0
  464. package/src/home/emit-feed-event.ts +11 -0
  465. package/src/home/feed-scheduler.ts +20 -4
  466. package/src/home/feed-types.ts +97 -4
  467. package/src/home/relationship-state-writer.ts +2 -2
  468. package/src/home/rewrite-command-preview.ts +66 -0
  469. package/src/home/rollup-producer.ts +34 -5
  470. package/src/home/suggested-prompts.ts +101 -0
  471. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  472. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  473. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  474. package/src/ipc/__tests__/socket-path.test.ts +34 -0
  475. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  476. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  477. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  478. package/src/ipc/cli-client.ts +2 -1
  479. package/src/ipc/cli-server.ts +26 -8
  480. package/src/ipc/gateway-client.ts +6 -3
  481. package/src/ipc/routes/attachment.ts +114 -0
  482. package/src/ipc/routes/browser-context.ts +63 -0
  483. package/src/ipc/routes/browser.ts +97 -0
  484. package/src/ipc/routes/cache.ts +96 -0
  485. package/src/ipc/routes/get-contact.ts +16 -0
  486. package/src/ipc/routes/index.ts +31 -1
  487. package/src/ipc/routes/list-clients.ts +31 -0
  488. package/src/ipc/routes/merge-contacts.ts +17 -0
  489. package/src/ipc/routes/notification.ts +133 -0
  490. package/src/ipc/routes/rename-conversation.ts +59 -0
  491. package/src/ipc/routes/search-contacts.ts +19 -0
  492. package/src/ipc/routes/task-queue.ts +226 -0
  493. package/src/ipc/routes/task.ts +173 -0
  494. package/src/ipc/routes/ui-request.ts +50 -0
  495. package/src/ipc/routes/upsert-contact.ts +25 -0
  496. package/src/ipc/routes/watcher.ts +203 -0
  497. package/src/ipc/socket-path.ts +76 -0
  498. package/src/media/app-icon-generator.ts +23 -46
  499. package/src/media/avatar-router.ts +26 -41
  500. package/src/media/gemini-image-service.ts +8 -41
  501. package/src/media/image-credentials.ts +73 -0
  502. package/src/media/image-service.ts +85 -0
  503. package/src/media/openai-image-service.ts +131 -0
  504. package/src/media/types.ts +46 -0
  505. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  506. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  507. package/src/memory/admin.ts +18 -0
  508. package/src/memory/conversation-analyze-job.ts +14 -13
  509. package/src/memory/conversation-attention-store.ts +13 -6
  510. package/src/memory/conversation-crud.ts +133 -3
  511. package/src/memory/conversation-group-migration.ts +38 -6
  512. package/src/memory/conversation-queries.ts +57 -4
  513. package/src/memory/conversation-title-service.ts +32 -4
  514. package/src/memory/db-init.ts +10 -0
  515. package/src/memory/embedding-backend.ts +1 -1
  516. package/src/memory/embedding-gemini.test.ts +41 -2
  517. package/src/memory/embedding-gemini.ts +6 -1
  518. package/src/memory/graph/bootstrap.test.ts +282 -0
  519. package/src/memory/graph/bootstrap.ts +8 -5
  520. package/src/memory/graph/compaction.ts +299 -0
  521. package/src/memory/graph/consolidation.ts +4 -4
  522. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  523. package/src/memory/graph/extraction.test.ts +272 -2
  524. package/src/memory/graph/extraction.ts +183 -53
  525. package/src/memory/graph/graph-search.test.ts +93 -0
  526. package/src/memory/graph/graph-search.ts +4 -1
  527. package/src/memory/graph/inspect.ts +2 -2
  528. package/src/memory/graph/narrative.ts +2 -2
  529. package/src/memory/graph/pattern-scan.ts +2 -2
  530. package/src/memory/graph/retriever.test.ts +459 -0
  531. package/src/memory/graph/retriever.ts +237 -48
  532. package/src/memory/graph/store.ts +41 -0
  533. package/src/memory/graph/tool-handlers.ts +27 -0
  534. package/src/memory/graph/tools.ts +6 -1
  535. package/src/memory/indexer.ts +5 -5
  536. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  537. package/src/memory/job-handlers/summarization.ts +2 -2
  538. package/src/memory/job-utils.ts +7 -1
  539. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  540. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  541. package/src/memory/jobs-store.ts +44 -3
  542. package/src/memory/jobs-worker.ts +4 -0
  543. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  544. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  545. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  546. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  547. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  548. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  549. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  550. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  551. package/src/memory/migrations/index.ts +5 -0
  552. package/src/memory/pkb/pkb-index.test.ts +369 -0
  553. package/src/memory/pkb/pkb-index.ts +255 -0
  554. package/src/memory/pkb/pkb-reconcile.test.ts +252 -0
  555. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  556. package/src/memory/pkb/pkb-search.test.ts +499 -0
  557. package/src/memory/pkb/pkb-search.ts +159 -0
  558. package/src/memory/pkb/types.ts +53 -0
  559. package/src/memory/qdrant-client.test.ts +60 -0
  560. package/src/memory/qdrant-client.ts +147 -1
  561. package/src/memory/schema/infrastructure.ts +1 -0
  562. package/src/memory/schema/oauth.ts +4 -1
  563. package/src/memory/slack-thread-store.ts +37 -0
  564. package/src/messaging/providers/gmail/adapter.ts +6 -16
  565. package/src/messaging/providers/gmail/client.ts +22 -0
  566. package/src/messaging/providers/gmail/types.ts +7 -0
  567. package/src/messaging/providers/slack/adapter.ts +14 -2
  568. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  569. package/src/messaging/providers/slack/backfill.ts +101 -0
  570. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  571. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  572. package/src/messaging/providers/slack/render-transcript.test.ts +1421 -0
  573. package/src/messaging/providers/slack/render-transcript.ts +501 -0
  574. package/src/messaging/style-analyzer.ts +5 -2
  575. package/src/notifications/README.md +9 -5
  576. package/src/notifications/conversation-pairing.ts +78 -19
  577. package/src/notifications/copy-composer.ts +0 -5
  578. package/src/notifications/decision-engine.ts +3 -9
  579. package/src/notifications/emit-signal.ts +1 -1
  580. package/src/notifications/preference-extractor.ts +2 -6
  581. package/src/notifications/signal.ts +1 -2
  582. package/src/oauth/AGENTS.md +1 -1
  583. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  584. package/src/oauth/connect-orchestrator.ts +8 -34
  585. package/src/oauth/connect-types.ts +6 -10
  586. package/src/oauth/manual-token-connection.ts +23 -0
  587. package/src/oauth/oauth-store.ts +31 -14
  588. package/src/oauth/platform-connection.test.ts +47 -0
  589. package/src/oauth/platform-connection.ts +15 -5
  590. package/src/oauth/provider-serializer.ts +6 -1
  591. package/src/oauth/seed-providers.ts +56 -106
  592. package/src/outbound-proxy/http-forwarder.ts +9 -0
  593. package/src/permissions/approval-policy.test.ts +1223 -0
  594. package/src/permissions/approval-policy.ts +309 -0
  595. package/src/permissions/arg-parser.test.ts +161 -0
  596. package/src/permissions/arg-parser.ts +141 -0
  597. package/src/permissions/bash-risk-classifier.test.ts +1620 -0
  598. package/src/permissions/bash-risk-classifier.ts +950 -0
  599. package/src/permissions/checker.ts +348 -711
  600. package/src/permissions/command-registry.test.ts +774 -0
  601. package/src/permissions/command-registry.ts +1005 -0
  602. package/src/permissions/defaults.ts +28 -79
  603. package/src/permissions/file-risk-classifier.test.ts +535 -0
  604. package/src/permissions/file-risk-classifier.ts +274 -0
  605. package/src/permissions/gateway-threshold-reader.ts +196 -0
  606. package/src/permissions/prompter.ts +4 -0
  607. package/src/permissions/risk-types.ts +262 -0
  608. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  609. package/src/permissions/schedule-risk-classifier.ts +85 -0
  610. package/src/permissions/secret-prompter.ts +53 -2
  611. package/src/permissions/shell-identity.ts +2 -42
  612. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  613. package/src/permissions/skill-risk-classifier.ts +214 -0
  614. package/src/permissions/trust-client.ts +52 -25
  615. package/src/permissions/trust-store-interface.ts +1 -6
  616. package/src/permissions/trust-store.ts +161 -62
  617. package/src/permissions/types.ts +25 -14
  618. package/src/permissions/web-risk-classifier.test.ts +170 -0
  619. package/src/permissions/web-risk-classifier.ts +89 -0
  620. package/src/permissions/workspace-policy.ts +9 -19
  621. package/src/platform/client.ts +19 -1
  622. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  623. package/src/plugins/defaults/compaction.ts +145 -0
  624. package/src/plugins/defaults/empty-response.ts +126 -0
  625. package/src/plugins/defaults/history-repair.ts +85 -0
  626. package/src/plugins/defaults/index.ts +116 -0
  627. package/src/plugins/defaults/injectors.ts +491 -0
  628. package/src/plugins/defaults/llm-call.ts +82 -0
  629. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  630. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  631. package/src/plugins/defaults/persistence.ts +129 -0
  632. package/src/plugins/defaults/title-generate.ts +95 -0
  633. package/src/plugins/defaults/token-estimate.ts +104 -0
  634. package/src/plugins/defaults/tool-error.ts +126 -0
  635. package/src/plugins/defaults/tool-execute.ts +89 -0
  636. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  637. package/src/plugins/pipeline.ts +316 -0
  638. package/src/plugins/plugin-skill-contributions.ts +292 -0
  639. package/src/plugins/registry.ts +241 -0
  640. package/src/plugins/types.ts +1134 -0
  641. package/src/plugins/user-loader.ts +177 -0
  642. package/src/prompts/persona-resolver.ts +3 -3
  643. package/src/prompts/system-prompt.ts +19 -20
  644. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  645. package/src/prompts/templates/SOUL.md +2 -2
  646. package/src/prompts/update-bulletin-job.ts +190 -0
  647. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  648. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  649. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  650. package/src/providers/anthropic/client.ts +183 -14
  651. package/src/providers/call-site-routing.ts +71 -0
  652. package/src/providers/gemini/client.ts +65 -2
  653. package/src/providers/managed-proxy/constants.ts +2 -1
  654. package/src/providers/model-catalog.ts +524 -33
  655. package/src/providers/model-intents.ts +4 -4
  656. package/src/providers/openai/chat-completions-provider.ts +57 -1
  657. package/src/providers/openai/responses-provider.ts +86 -9
  658. package/src/providers/openrouter/client.ts +80 -9
  659. package/src/providers/provider-env-vars.ts +56 -0
  660. package/src/providers/provider-send-message.ts +22 -5
  661. package/src/providers/ratelimit.ts +4 -0
  662. package/src/providers/registry.ts +19 -8
  663. package/src/providers/retry.ts +174 -39
  664. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  665. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  666. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  667. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  668. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  669. package/src/providers/speech-to-text/resolve.ts +7 -0
  670. package/src/providers/speech-to-text/xai-realtime.test.ts +646 -0
  671. package/src/providers/speech-to-text/xai-realtime.ts +821 -0
  672. package/src/providers/speech-to-text/xai.test.ts +155 -0
  673. package/src/providers/speech-to-text/xai.ts +97 -0
  674. package/src/providers/types.ts +93 -3
  675. package/src/runtime/AGENTS.md +27 -18
  676. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  677. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  678. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  679. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  680. package/src/runtime/agent-wake.ts +63 -22
  681. package/src/runtime/auth/route-policy.ts +4 -0
  682. package/src/runtime/btw-sidechain.ts +13 -3
  683. package/src/runtime/channel-reply-delivery.ts +106 -2
  684. package/src/runtime/client-registry.ts +261 -0
  685. package/src/runtime/decision-token.ts +116 -0
  686. package/src/runtime/gateway-client.ts +2 -2
  687. package/src/runtime/http-router.ts +32 -0
  688. package/src/runtime/http-server.ts +129 -9
  689. package/src/runtime/http-types.ts +23 -3
  690. package/src/runtime/interactive-ui.ts +362 -0
  691. package/src/runtime/invite-instruction-generator.ts +2 -2
  692. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  693. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  694. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  695. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  696. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  697. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  698. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  699. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  700. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  701. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  702. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  703. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  704. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  705. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  706. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  707. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  708. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  709. package/src/runtime/routes/approval-prompt-ts-tracker.ts +78 -0
  710. package/src/runtime/routes/approval-routes.ts +29 -17
  711. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  712. package/src/runtime/routes/avatar-routes.ts +20 -4
  713. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  714. package/src/runtime/routes/btw-routes.ts +1 -4
  715. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  716. package/src/runtime/routes/conversation-routes.ts +351 -138
  717. package/src/runtime/routes/debug-routes.ts +1 -1
  718. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  719. package/src/runtime/routes/events-routes.ts +16 -0
  720. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  721. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  722. package/src/runtime/routes/home-feed-routes.ts +120 -2
  723. package/src/runtime/routes/inbound-message-handler.ts +987 -2
  724. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  725. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  726. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  727. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  728. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  729. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  730. package/src/runtime/routes/migration-routes.ts +720 -127
  731. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  732. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  733. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  734. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  735. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  736. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  737. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  738. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  739. package/src/runtime/routes/playground/deps.ts +56 -0
  740. package/src/runtime/routes/playground/force-compact.ts +73 -0
  741. package/src/runtime/routes/playground/guard.ts +37 -0
  742. package/src/runtime/routes/playground/index.ts +28 -0
  743. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  744. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  745. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  746. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  747. package/src/runtime/routes/playground/state.ts +78 -0
  748. package/src/runtime/routes/schedule-routes.ts +89 -8
  749. package/src/runtime/routes/settings-routes.ts +4 -2
  750. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  751. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  752. package/src/runtime/routes/work-items-routes.ts +3 -2
  753. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  754. package/src/runtime/services/analyze-conversation.ts +12 -16
  755. package/src/runtime/skill-route-registry.ts +97 -15
  756. package/src/schedule/run-script.ts +68 -0
  757. package/src/schedule/schedule-store.ts +7 -1
  758. package/src/schedule/scheduler.ts +56 -8
  759. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  760. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  761. package/src/security/oauth2.ts +98 -35
  762. package/src/security/secure-keys.ts +7 -8
  763. package/src/security/token-manager.ts +27 -13
  764. package/src/security/untrusted-content.ts +102 -0
  765. package/src/skills/catalog-cache.ts +35 -9
  766. package/src/skills/catalog-install.ts +31 -3
  767. package/src/skills/skill-cache-store.ts +97 -0
  768. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  769. package/src/stt/daemon-batch-transcriber.ts +33 -0
  770. package/src/stt/stt-stream-session.ts +8 -1
  771. package/src/stt/types.ts +5 -1
  772. package/src/subagent/manager.ts +41 -13
  773. package/src/tasks/ephemeral-permissions.ts +9 -4
  774. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  775. package/src/tools/browser/__tests__/browser-status.test.ts +234 -2
  776. package/src/tools/browser/browser-execution.ts +150 -54
  777. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  778. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  779. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  780. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  781. package/src/tools/browser/cdp-client/factory.ts +15 -4
  782. package/src/tools/credentials/tool-policy.ts +39 -5
  783. package/src/tools/credentials/vault.ts +9 -4
  784. package/src/tools/executor.ts +129 -73
  785. package/src/tools/filesystem/write.ts +52 -0
  786. package/src/tools/host-terminal/host-shell.ts +45 -5
  787. package/src/tools/memory/register.test.ts +185 -0
  788. package/src/tools/memory/register.ts +3 -1
  789. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  790. package/src/tools/network/web-fetch.ts +20 -10
  791. package/src/tools/network/web-search.ts +19 -4
  792. package/src/tools/permission-checker.ts +116 -46
  793. package/src/tools/policy-context.ts +29 -8
  794. package/src/tools/registry.ts +195 -6
  795. package/src/tools/schedule/create.ts +23 -8
  796. package/src/tools/schedule/update.ts +3 -1
  797. package/src/tools/secret-detection-handler.ts +0 -51
  798. package/src/tools/side-effects.ts +0 -11
  799. package/src/tools/skills/execute.ts +2 -2
  800. package/src/tools/skills/sandbox-runner.ts +5 -2
  801. package/src/tools/system/avatar-generator.ts +6 -2
  802. package/src/tools/terminal/backends/native.ts +51 -2
  803. package/src/tools/terminal/safe-env.ts +3 -2
  804. package/src/tools/terminal/shell.ts +1 -0
  805. package/src/tools/tool-manifest.ts +6 -21
  806. package/src/tools/types.ts +40 -5
  807. package/src/tools/verification-control-plane-policy.ts +1 -1
  808. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  809. package/src/tts/provider-catalog.ts +18 -0
  810. package/src/tts/providers/index.ts +2 -0
  811. package/src/tts/providers/xai-provider.ts +224 -0
  812. package/src/tts/types.ts +46 -0
  813. package/src/types/tar-stream.d.ts +66 -0
  814. package/src/util/json.ts +17 -0
  815. package/src/util/platform.ts +9 -4
  816. package/src/util/pricing.ts +41 -8
  817. package/src/watcher/engine.ts +1 -1
  818. package/src/watcher/providers/google-calendar.ts +134 -8
  819. package/src/watcher/providers/outlook-calendar.ts +42 -2
  820. package/src/workspace/git-service.ts +23 -4
  821. package/src/workspace/migrations/006-services-config.ts +2 -4
  822. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  823. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  824. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  825. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  826. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +56 -0
  827. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  828. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  829. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  830. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  831. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  832. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  833. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  834. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  835. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  836. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  837. package/src/workspace/migrations/AGENTS.md +1 -1
  838. package/src/workspace/migrations/registry.ts +28 -0
  839. package/src/workspace/provider-commit-message-generator.ts +19 -38
  840. package/tsconfig.json +1 -1
  841. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  842. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  843. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  844. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  845. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  846. package/src/__tests__/gmail-preferences.test.ts +0 -117
  847. package/src/__tests__/hooks-blocking.test.ts +0 -178
  848. package/src/__tests__/hooks-cli.test.ts +0 -182
  849. package/src/__tests__/hooks-config.test.ts +0 -108
  850. package/src/__tests__/hooks-discovery.test.ts +0 -211
  851. package/src/__tests__/hooks-integration.test.ts +0 -196
  852. package/src/__tests__/hooks-manager.test.ts +0 -226
  853. package/src/__tests__/hooks-runner.test.ts +0 -175
  854. package/src/__tests__/hooks-settings.test.ts +0 -160
  855. package/src/__tests__/hooks-templates.test.ts +0 -169
  856. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  857. package/src/__tests__/hooks-watch.test.ts +0 -112
  858. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  859. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  860. package/src/__tests__/outlook-attachments.test.ts +0 -301
  861. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  862. package/src/__tests__/outlook-categories.test.ts +0 -212
  863. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  864. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  865. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  866. package/src/__tests__/outlook-trash.test.ts +0 -77
  867. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  868. package/src/__tests__/send-notification-tool.test.ts +0 -83
  869. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  870. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  871. package/src/__tests__/update-bulletin.test.ts +0 -478
  872. package/src/__tests__/update-template-contract.test.ts +0 -29
  873. package/src/cli/commands/doctor.ts +0 -341
  874. package/src/cli/commands/shotgun.ts +0 -266
  875. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  876. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  877. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  878. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  879. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  880. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  881. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  882. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  883. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  884. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  885. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  886. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  887. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  888. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  889. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  890. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  891. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  892. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  893. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  894. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  895. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  896. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  897. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  898. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  899. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -66
  900. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  901. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  902. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  903. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  904. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  905. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  906. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  907. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  908. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  909. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  910. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  911. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  912. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  913. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  914. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  915. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  916. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  917. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  918. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  919. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  920. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  921. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  922. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  923. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  924. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  925. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  926. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  927. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  928. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  929. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  930. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  931. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  932. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  933. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  934. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  935. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  936. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  937. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  938. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  939. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  940. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  941. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  942. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  943. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  944. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  945. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  946. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  947. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  948. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  949. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  950. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  951. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  952. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  953. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  954. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  955. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  956. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  957. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  958. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  959. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  960. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  961. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  962. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  963. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  964. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  965. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  966. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  967. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  968. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  969. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  970. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  971. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  972. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  973. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  974. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  975. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  976. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  977. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  978. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  979. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  980. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  981. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  982. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  983. package/src/daemon/context-overflow-approval.ts +0 -52
  984. package/src/daemon/watch-handler.ts +0 -399
  985. package/src/hooks/cli.ts +0 -253
  986. package/src/hooks/config.ts +0 -100
  987. package/src/hooks/discovery.ts +0 -135
  988. package/src/hooks/manager.ts +0 -179
  989. package/src/hooks/runner.ts +0 -117
  990. package/src/hooks/templates.ts +0 -77
  991. package/src/hooks/types.ts +0 -75
  992. package/src/oauth/scope-policy.ts +0 -89
  993. package/src/prompts/templates/UPDATES.md +0 -50
  994. package/src/prompts/update-bulletin-format.ts +0 -85
  995. package/src/prompts/update-bulletin-state.ts +0 -58
  996. package/src/prompts/update-bulletin-template-path.ts +0 -13
  997. package/src/prompts/update-bulletin.ts +0 -139
  998. package/src/runtime/gateway-internal-client.ts +0 -94
  999. package/src/runtime/routes/watch-routes.ts +0 -156
  1000. package/src/shared/provider-env-vars.ts +0 -19
  1001. package/src/signals/shotgun.ts +0 -203
  1002. package/src/tools/watch/screen-watch.ts +0 -144
  1003. package/src/tools/watch/watch-state.ts +0 -142
  1004. package/src/tools/watcher/create.ts +0 -86
  1005. package/src/tools/watcher/delete.ts +0 -36
  1006. package/src/tools/watcher/digest.ts +0 -54
  1007. package/src/tools/watcher/list.ts +0 -83
  1008. package/src/tools/watcher/update.ts +0 -71
@@ -0,0 +1,950 @@
1
+ /**
2
+ * Bash risk classifier — data-driven command risk classification.
3
+ *
4
+ * Implements RiskClassifier<BashClassifierInput> using the default command
5
+ * registry and user rules. This is the primary classifier for bash/host_bash
6
+ * tools — checker.ts delegates to `bashRiskClassifier.classify()` and maps
7
+ * the result to the permission system's RiskLevel enum.
8
+ *
9
+ * @see /docs/bash-risk-classifier-design.md
10
+ */
11
+
12
+ import type {
13
+ CommandSegment,
14
+ ParsedCommand,
15
+ } from "../tools/terminal/parser.js";
16
+ import { getLogger } from "../util/logger.js";
17
+ import { parseArgs } from "./arg-parser.js";
18
+ import { DEFAULT_COMMAND_REGISTRY } from "./command-registry.js";
19
+ import type {
20
+ ArgRule,
21
+ ArgSchema,
22
+ BashClassifierInput,
23
+ CommandRiskSpec,
24
+ Risk,
25
+ RiskAssessment,
26
+ RiskClassifier,
27
+ ScopeOption,
28
+ UserRule,
29
+ } from "./risk-types.js";
30
+ import { cachedParse } from "./shell-identity.js";
31
+ import type { AllowlistOption } from "./types.js";
32
+
33
+ const log = getLogger("bash-risk-classifier");
34
+
35
+ // ── Risk ordering helpers ────────────────────────────────────────────────────
36
+
37
+ const RISK_ORD: Record<Risk, number> = {
38
+ low: 0,
39
+ medium: 1,
40
+ unknown: 2,
41
+ high: 3,
42
+ };
43
+
44
+ /**
45
+ * Numeric ordering for risk comparison.
46
+ *
47
+ * `high` outranks `unknown`: if any segment is definitively high-risk, the
48
+ * overall command is high — the known-dangerous signal dominates. `unknown`
49
+ * sits between medium and high: an unrecognized command is riskier than a
50
+ * known-medium one, but not as definitive as a known-high one.
51
+ */
52
+ export function riskOrd(risk: Risk): number {
53
+ return RISK_ORD[risk];
54
+ }
55
+
56
+ /** Return the higher of two risk levels. */
57
+ export function maxRisk(a: Risk, b: Risk): Risk {
58
+ return riskOrd(a) >= riskOrd(b) ? a : b;
59
+ }
60
+
61
+ /** Escalate a risk level by one step: low→medium, medium→high, high→high. */
62
+ export function escalateOne(risk: Risk): Risk {
63
+ switch (risk) {
64
+ case "low":
65
+ return "medium";
66
+ case "medium":
67
+ return "high";
68
+ case "high":
69
+ return "high";
70
+ case "unknown":
71
+ return "unknown";
72
+ }
73
+ }
74
+
75
+ // ── Compiled regex cache ─────────────────────────────────────────────────────
76
+ // The registry is static, so we can compile and cache RegExp instances for
77
+ // arg rules' valuePatterns. This avoids re-compiling on every classify call.
78
+
79
+ const compiledPatterns = new Map<string, RegExp>();
80
+
81
+ function getCompiledPattern(pattern: string): RegExp {
82
+ let re = compiledPatterns.get(pattern);
83
+ if (!re) {
84
+ re = new RegExp(pattern);
85
+ compiledPatterns.set(pattern, re);
86
+ }
87
+ return re;
88
+ }
89
+
90
+ /** Clear the compiled regex cache. Exposed for tests and hot-swap scenarios. */
91
+ export function clearCompiledPatterns(): void {
92
+ compiledPatterns.clear();
93
+ }
94
+
95
+ // ── Arg rule matching ────────────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Check whether an arg matches an ArgRule.
99
+ *
100
+ * - If `flags` is set, the arg must be one of those flags. If `valuePattern`
101
+ * is also set, the arg must match both the flag list AND the pattern.
102
+ * - If only `valuePattern` is set (no flags), the arg is matched against the
103
+ * pattern (positional / any-arg matching).
104
+ * - If neither is set, the rule always matches (flag-presence-only rules
105
+ * should have flags set).
106
+ */
107
+ export function matchesArgRule(rule: ArgRule, arg: string): boolean {
108
+ if (rule.flags && rule.flags.length > 0) {
109
+ // Check for inline --flag=value form
110
+ const eqIdx = arg.indexOf("=");
111
+ if (eqIdx > 0) {
112
+ const flagPart = arg.slice(0, eqIdx);
113
+ const valuePart = arg.slice(eqIdx + 1);
114
+ if (rule.flags.includes(flagPart)) {
115
+ // Flag matched via --flag=value. Check valuePattern against the value portion.
116
+ if (rule.valuePattern) {
117
+ return getCompiledPattern(rule.valuePattern).test(valuePart);
118
+ }
119
+ return true;
120
+ }
121
+ }
122
+
123
+ // Standard flag match: arg must be one of the listed flags exactly
124
+ if (!rule.flags.includes(arg)) return false;
125
+ // If there's also a valuePattern but no inline value, the next-arg
126
+ // lookahead in classifySegment handles matching. For the flag-only
127
+ // check here, a flag match without inline value and with a valuePattern
128
+ // is a partial match — the caller handles the lookahead.
129
+ if (rule.valuePattern) {
130
+ // Don't match here — let the lookahead in classifySegment handle it.
131
+ // Return false so the caller knows to try next-arg matching.
132
+ return false;
133
+ }
134
+ return true;
135
+ }
136
+
137
+ if (rule.valuePattern) {
138
+ return getCompiledPattern(rule.valuePattern).test(arg);
139
+ }
140
+
141
+ // No flags and no valuePattern — always matches (unusual but allowed)
142
+ return true;
143
+ }
144
+
145
+ // ── Wrapper unwrapping ───────────────────────────────────────────────────────
146
+
147
+ const WRAPPER_SKIP_FIRST_POSITIONAL = new Set(["timeout", "taskset"]);
148
+ const ENV_VALUE_FLAGS = new Set(["-u", "--unset", "-C", "--chdir"]);
149
+ const TIMEOUT_VALUE_FLAGS = new Set(["-s", "--signal", "-k", "--kill-after"]);
150
+
151
+ /**
152
+ * Given a wrapper segment, extract the wrapped program and its args.
153
+ * Returns undefined when no suitable argument is found.
154
+ */
155
+ function getWrappedProgramWithArgs(seg: {
156
+ program: string;
157
+ args: string[];
158
+ }): { program: string; args: string[] } | undefined {
159
+ const isEnv = seg.program === "env";
160
+ const isTimeout = seg.program === "timeout";
161
+ const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
162
+ let skippedFirstPositional = false;
163
+ for (let i = 0; i < seg.args.length; i++) {
164
+ const arg = seg.args[i];
165
+ if (arg.startsWith("-")) {
166
+ if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++;
167
+ if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++;
168
+ continue;
169
+ }
170
+ if (isEnv && arg.includes("=")) continue;
171
+ if (skipFirst && !skippedFirstPositional) {
172
+ skippedFirstPositional = true;
173
+ continue;
174
+ }
175
+ return { program: arg, args: seg.args.slice(i + 1) };
176
+ }
177
+ return undefined;
178
+ }
179
+
180
+ /**
181
+ * Extract the first positional (non-flag) arg, skipping value-consuming flags.
182
+ * Delegates to the shared `parseArgs()` utility for consistent arg parsing.
183
+ */
184
+ function firstPositionalArg(
185
+ args: string[],
186
+ valueFlags?: Set<string>,
187
+ ): string | undefined {
188
+ const schema: ArgSchema = valueFlags
189
+ ? { valueFlags: [...valueFlags], positionals: "none" }
190
+ : { positionals: "none" };
191
+ const parsed = parseArgs(args, schema);
192
+ return parsed.positionals[0];
193
+ }
194
+
195
+ // ── Safe-file downgrade for rm ────────────────────────────────────────────────
196
+ // Bare filenames that `rm` is allowed to delete at Medium risk (instead of
197
+ // High) in sandboxed bash.
198
+ const RM_SAFE_BARE_FILES = new Set(["BOOTSTRAP.md", "UPDATES.md"]);
199
+
200
+ // Flags that don't affect rm safety — they don't enable recursive deletion or
201
+ // change which files are targeted.
202
+ const RM_BENIGN_FLAGS = new Set([
203
+ "-f",
204
+ "-i",
205
+ "-v",
206
+ "--force",
207
+ "--interactive",
208
+ "--verbose",
209
+ ]);
210
+
211
+ // ── Segment classification ───────────────────────────────────────────────────
212
+
213
+ /**
214
+ * Resolve a CommandRiskSpec through subcommand hierarchy.
215
+ *
216
+ * For commands like `git push --force`, walks the subcommand tree:
217
+ * git → git.subcommands.push
218
+ *
219
+ * Returns the resolved spec and the remaining args after subcommand resolution.
220
+ */
221
+ function resolveSubcommand(
222
+ spec: CommandRiskSpec,
223
+ args: string[],
224
+ ): { spec: CommandRiskSpec; remainingArgs: string[] } {
225
+ if (!spec.subcommands || args.length === 0) {
226
+ return { spec, remainingArgs: args };
227
+ }
228
+
229
+ const valueFlagsList = spec.argSchema?.valueFlags;
230
+ const valueFlags = valueFlagsList ? new Set(valueFlagsList) : undefined;
231
+ const subcommandName = firstPositionalArg(args, valueFlags);
232
+
233
+ if (!subcommandName || !spec.subcommands[subcommandName]) {
234
+ return { spec, remainingArgs: args };
235
+ }
236
+
237
+ const subSpec = spec.subcommands[subcommandName];
238
+ const subIdx = args.indexOf(subcommandName);
239
+ const remainingArgs = args.slice(subIdx + 1);
240
+
241
+ // Recurse for nested subcommands (e.g., git stash drop, gh pr view)
242
+ return resolveSubcommand(subSpec, remainingArgs);
243
+ }
244
+
245
+ /**
246
+ * Classify a single command segment against user rules and the registry.
247
+ *
248
+ * @param toolName - Which tool is being invoked. Used for sandbox-specific
249
+ * downgrades (e.g. rm safe-file downgrade only applies in sandboxed "bash",
250
+ * not "host_bash").
251
+ */
252
+ export function classifySegment(
253
+ segment: CommandSegment,
254
+ userRules: UserRule[],
255
+ registry: Record<string, CommandRiskSpec>,
256
+ toolName: "bash" | "host_bash" = "bash",
257
+ ): { risk: Risk; reason: string; matchType: RiskAssessment["matchType"] } {
258
+ // 1. Check user rules first (highest priority)
259
+ // TODO: implement user rule matching with specificity ordering.
260
+ // For now, userRules is always empty so this is a no-op.
261
+ for (const rule of userRules) {
262
+ const re = getCompiledPattern(rule.pattern);
263
+ if (re.test(segment.command)) {
264
+ return { risk: rule.risk, reason: rule.label, matchType: "user_rule" };
265
+ }
266
+ }
267
+
268
+ // 2. Look up command in default registry
269
+ // Use Object.hasOwn to avoid prototype pollution — program names like
270
+ // "toString" or "hasOwnProperty" exist on Object.prototype and would
271
+ // return truthy for `registry[name]` even though they're not real entries.
272
+ let programName = segment.program;
273
+ let spec = Object.hasOwn(registry, programName)
274
+ ? registry[programName]
275
+ : undefined;
276
+
277
+ if (!spec) {
278
+ // Strip path prefix: /usr/bin/rm → rm
279
+ const bare = programName.split("/").pop();
280
+ if (bare) {
281
+ programName = bare;
282
+ spec = Object.hasOwn(registry, programName)
283
+ ? registry[programName]
284
+ : undefined;
285
+ }
286
+ }
287
+
288
+ if (!spec) {
289
+ return {
290
+ risk: "unknown",
291
+ reason: `Unknown command: ${segment.program}`,
292
+ matchType: "unknown",
293
+ };
294
+ }
295
+
296
+ // 3. Handle wrappers — unwrap and classify inner command (recursive)
297
+ // When a wrapper's first arg matches a nonExecFlags entry, the wrapper is
298
+ // in a non-exec mode (e.g. `command -v`, `env -0`). Skip unwrapping and
299
+ // fall through to arg/base risk evaluation.
300
+ if (spec.isWrapper) {
301
+ // nonExecFlags only checks args[0] — a flag in a later position won't
302
+ // suppress unwrapping. This is intentional: wrappers place their mode
303
+ // flag first (e.g. `command -v`, `timeout --help`).
304
+ const isNonExecMode =
305
+ spec.nonExecFlags &&
306
+ segment.args.length > 0 &&
307
+ spec.nonExecFlags.includes(segment.args[0]);
308
+
309
+ if (!isNonExecMode) {
310
+ const inner = getWrappedProgramWithArgs(segment);
311
+ if (inner) {
312
+ // Build a synthetic segment for the inner command
313
+ const innerSegment: CommandSegment = {
314
+ command: [inner.program, ...inner.args].join(" "),
315
+ program: inner.program,
316
+ args: inner.args,
317
+ operator: segment.operator,
318
+ };
319
+ const innerResult = classifySegment(
320
+ innerSegment,
321
+ userRules,
322
+ registry,
323
+ toolName,
324
+ );
325
+ return {
326
+ risk: maxRisk(spec.baseRisk as Risk, innerResult.risk),
327
+ reason:
328
+ innerResult.reason || `${programName} wrapping ${inner.program}`,
329
+ matchType: innerResult.matchType,
330
+ };
331
+ }
332
+ // Wrapper with no inner command (bare `sudo`, `env`)
333
+ return {
334
+ risk: spec.baseRisk,
335
+ reason: spec.reason || programName,
336
+ matchType: "registry",
337
+ };
338
+ }
339
+ // Non-exec mode: fall through to subcommand/arg rule evaluation
340
+ }
341
+
342
+ // 4. Subcommand resolution
343
+ const { spec: resolvedSpec, remainingArgs: _remainingArgs } =
344
+ resolveSubcommand(spec, segment.args);
345
+
346
+ // 5. Evaluate arg rules
347
+ //
348
+ // Arg rules can both escalate AND de-escalate from baseRisk.
349
+ //
350
+ // De-escalation is only safe when ALL non-flag args are covered by rules.
351
+ // If any arg goes unmatched, baseRisk is the floor — we can't assume an
352
+ // unknown arg is safe. Example: `rm /tmp/foo /etc/passwd` should stay high
353
+ // even though /tmp/foo matches the rm:tmp de-escalation rule, because
354
+ // /etc/passwd is unmatched.
355
+ //
356
+ // Escalation always applies — any matched rule that's higher than baseRisk
357
+ // raises the risk regardless of unmatched args.
358
+ let risk: Risk = resolvedSpec.baseRisk;
359
+ let reason = resolvedSpec.reason || `${segment.program} (default)`;
360
+
361
+ const argRules = resolvedSpec.argRules;
362
+ if (argRules && argRules.length > 0) {
363
+ let anyArgRuleMatched = false;
364
+ let hasUnmatchedNonFlagArg = false;
365
+ let argRuleMaxRisk: Risk = "low";
366
+ let argRuleReason = "";
367
+
368
+ const allArgs = segment.args;
369
+
370
+ // Parse args using the resolved spec's argSchema for structured lookups.
371
+ const schema = resolvedSpec.argSchema ?? {};
372
+ const parsed = parseArgs(allArgs, schema);
373
+
374
+ // Track which positionals have been covered by a rule.
375
+ const matchedPositionalIndices = new Set<number>();
376
+
377
+ for (const rule of argRules) {
378
+ if (rule.flags && rule.flags.length > 0 && rule.valuePattern) {
379
+ // ── Rules with flags + valuePattern ──────────────────────────────
380
+ // Look up each rule flag in parsed.flags. If the flag has a string
381
+ // value (consumed by parseArgs), test that value against the pattern.
382
+ // This replaces the manual next-token lookahead.
383
+ // Also check for --flag=value forms already handled by parseArgs.
384
+ //
385
+ // Known limitation: parseArgs stores flags in a Map (last value wins),
386
+ // so repeated flags like `curl -d @/etc/shadow -d safe` only check
387
+ // the last value. A future improvement could store flag values as
388
+ // arrays to catch all occurrences.
389
+ let flagValueMatched = false;
390
+ for (const flag of rule.flags) {
391
+ const flagVal = parsed.flags.get(flag);
392
+ if (typeof flagVal === "string") {
393
+ if (getCompiledPattern(rule.valuePattern).test(flagVal)) {
394
+ flagValueMatched = true;
395
+ break;
396
+ }
397
+ }
398
+ }
399
+
400
+ // Also check raw args for inline --flag=value forms where the flag
401
+ // name is NOT in the argSchema.valueFlags (parseArgs wouldn't split
402
+ // it). matchesArgRule handles this case.
403
+ if (!flagValueMatched) {
404
+ for (const arg of allArgs) {
405
+ if (matchesArgRule(rule, arg)) {
406
+ flagValueMatched = true;
407
+ break;
408
+ }
409
+ }
410
+ }
411
+
412
+ if (flagValueMatched) {
413
+ if (
414
+ !anyArgRuleMatched ||
415
+ riskOrd(rule.risk) > riskOrd(argRuleMaxRisk)
416
+ ) {
417
+ argRuleMaxRisk = rule.risk;
418
+ argRuleReason = rule.reason;
419
+ }
420
+ anyArgRuleMatched = true;
421
+ }
422
+ } else if (rule.flags && rule.flags.length > 0) {
423
+ // ── Rules with flags only (no valuePattern) ──────────────────────
424
+ // Check flag presence in parsed.flags Map.
425
+ // Also scan raw allArgs for combined short flags like `-rf` that
426
+ // parseArgs treats as a single boolean flag token.
427
+ let flagMatched = false;
428
+ for (const flag of rule.flags) {
429
+ if (parsed.flags.has(flag)) {
430
+ flagMatched = true;
431
+ break;
432
+ }
433
+ }
434
+
435
+ // Fallback: scan raw args for combined short flags (e.g. `-rf`)
436
+ // and --flag=value forms (e.g. `--set=managed`) that parseArgs
437
+ // doesn't split when the flag isn't in argSchema.valueFlags.
438
+ // matchesArgRule handles both cases via its flag splitting logic.
439
+ if (!flagMatched) {
440
+ for (const arg of allArgs) {
441
+ if (matchesArgRule(rule, arg)) {
442
+ flagMatched = true;
443
+ break;
444
+ }
445
+ }
446
+ }
447
+
448
+ if (flagMatched) {
449
+ if (
450
+ !anyArgRuleMatched ||
451
+ riskOrd(rule.risk) > riskOrd(argRuleMaxRisk)
452
+ ) {
453
+ argRuleMaxRisk = rule.risk;
454
+ argRuleReason = rule.reason;
455
+ }
456
+ anyArgRuleMatched = true;
457
+ }
458
+ } else if (rule.valuePattern) {
459
+ // ── Rules with valuePattern only (no flags) ──────────────────────
460
+ // Test each positional against the pattern.
461
+ const re = getCompiledPattern(rule.valuePattern);
462
+ let positionalMatched = false;
463
+ for (let pi = 0; pi < parsed.positionals.length; pi++) {
464
+ if (re.test(parsed.positionals[pi])) {
465
+ positionalMatched = true;
466
+ matchedPositionalIndices.add(pi);
467
+ }
468
+ }
469
+
470
+ // Also check raw allArgs for backward compatibility — some patterns
471
+ // may match flag-like tokens or args that parseArgs classified
472
+ // differently.
473
+ if (!positionalMatched) {
474
+ for (const arg of allArgs) {
475
+ if (re.test(arg)) {
476
+ positionalMatched = true;
477
+ break;
478
+ }
479
+ }
480
+ }
481
+
482
+ if (positionalMatched) {
483
+ if (
484
+ !anyArgRuleMatched ||
485
+ riskOrd(rule.risk) > riskOrd(argRuleMaxRisk)
486
+ ) {
487
+ argRuleMaxRisk = rule.risk;
488
+ argRuleReason = rule.reason;
489
+ }
490
+ anyArgRuleMatched = true;
491
+ }
492
+ } else {
493
+ // No flags and no valuePattern — always matches (unusual but allowed)
494
+ if (
495
+ !anyArgRuleMatched ||
496
+ riskOrd(rule.risk) > riskOrd(argRuleMaxRisk)
497
+ ) {
498
+ argRuleMaxRisk = rule.risk;
499
+ argRuleReason = rule.reason;
500
+ }
501
+ anyArgRuleMatched = true;
502
+ }
503
+ }
504
+
505
+ // Check for unmatched positionals — any positional not covered by a
506
+ // valuePattern-only rule prevents de-escalation.
507
+ for (let pi = 0; pi < parsed.positionals.length; pi++) {
508
+ if (!matchedPositionalIndices.has(pi)) {
509
+ hasUnmatchedNonFlagArg = true;
510
+ break;
511
+ }
512
+ }
513
+
514
+ // Also check raw allArgs for non-flag args that parseArgs may have
515
+ // classified as flags (e.g. combined short flags like `-rf` are boolean
516
+ // flags in parseArgs but are non-flag args in the old iteration model).
517
+ // We only need to track unmatched non-flag args from the raw iteration
518
+ // perspective for backward compatibility.
519
+ if (!hasUnmatchedNonFlagArg) {
520
+ for (const arg of allArgs) {
521
+ if (arg.startsWith("-")) continue;
522
+ // Check if this positional was matched by any rule
523
+ let wasMatched = false;
524
+ for (const rule of argRules) {
525
+ if (matchesArgRule(rule, arg)) {
526
+ wasMatched = true;
527
+ break;
528
+ }
529
+ // Check flag+value lookahead match (arg as a flag value)
530
+ if (rule.flags && rule.valuePattern && rule.flags.includes(arg)) {
531
+ // This arg is a flag that matched a rule flag — it's structural
532
+ wasMatched = true;
533
+ break;
534
+ }
535
+ }
536
+ if (!wasMatched) {
537
+ hasUnmatchedNonFlagArg = true;
538
+ break;
539
+ }
540
+ }
541
+ }
542
+
543
+ if (anyArgRuleMatched) {
544
+ if (riskOrd(argRuleMaxRisk) >= riskOrd(risk)) {
545
+ // Escalation: always apply (matched rule is >= baseRisk)
546
+ risk = argRuleMaxRisk;
547
+ reason = argRuleReason;
548
+ } else if (!hasUnmatchedNonFlagArg) {
549
+ // De-escalation: only safe when ALL non-flag args matched rules.
550
+ // Every arg is accounted for, so the lower risk is justified.
551
+ risk = argRuleMaxRisk;
552
+ reason = argRuleReason;
553
+ }
554
+ // Otherwise: some args matched low rules but other args went unmatched.
555
+ // Keep baseRisk as the floor — can't safely de-escalate.
556
+ }
557
+ }
558
+
559
+ // 6. Check for variable expansion in args (conservative escalation)
560
+ // Use max(computedRisk, baseRisk) as the floor for escalation so that
561
+ // de-escalated commands still escalate from at least baseRisk.
562
+ // Example: `curl http://localhost:$PORT` — arg rule de-escalates to low,
563
+ // but baseRisk=medium is the floor, so escalateOne(medium) → high.
564
+ if (segment.args.some((a) => a.includes("$"))) {
565
+ const escalationBase = maxRisk(risk, resolvedSpec.baseRisk);
566
+ const escalated = escalateOne(escalationBase);
567
+ if (riskOrd(escalated) > riskOrd(risk)) {
568
+ risk = escalated;
569
+ reason = `${segment.program} with variable expansion`;
570
+ }
571
+ }
572
+
573
+ // 7. rm safe-file downgrade (sandbox only)
574
+ // When rm targets a single known safe bare file (with only benign flags),
575
+ // downgrade to medium in sandboxed bash. host_bash keeps high because it has a
576
+ // global ask rule that would prompt medium-risk commands.
577
+ if (programName === "rm" && toolName === "bash" && risk === "high") {
578
+ // Strip benign flags (-f, -i, -v) and check if exactly one bare filename remains
579
+ const positionalArgs = segment.args.filter((a) => !a.startsWith("-"));
580
+ const flagArgs = segment.args.filter((a) => a.startsWith("-"));
581
+ const allFlagsBenign = flagArgs.every((f) => RM_BENIGN_FLAGS.has(f));
582
+
583
+ if (
584
+ positionalArgs.length === 1 &&
585
+ allFlagsBenign &&
586
+ !positionalArgs[0].includes("/") &&
587
+ RM_SAFE_BARE_FILES.has(positionalArgs[0])
588
+ ) {
589
+ risk = "medium";
590
+ reason = `rm of known safe file: ${positionalArgs[0]}`;
591
+ }
592
+ }
593
+
594
+ return { risk, reason, matchType: "registry" };
595
+ }
596
+
597
+ // ── Scope option generation ──────────────────────────────────────────────────
598
+
599
+ /**
600
+ * Generate scope options (narrowest to broadest) from a parsed command.
601
+ *
602
+ * Algorithm:
603
+ * 1. Exact command (all args literal)
604
+ * 2. Wildcard positionals right-to-left (one at a time)
605
+ * 3. Drop flags (keep command + subcommand)
606
+ * 4. Wildcard at subcommand level
607
+ * 5. Wildcard at command level
608
+ * 6. Deduplicate
609
+ *
610
+ * For commands with complexSyntax, only offer exact and command-level wildcard.
611
+ */
612
+ export function generateScopeOptions(
613
+ parsed: ParsedCommand,
614
+ registry: Record<string, CommandRiskSpec> = DEFAULT_COMMAND_REGISTRY,
615
+ ): ScopeOption[] {
616
+ if (parsed.segments.length === 0) return [];
617
+
618
+ const options: ScopeOption[] = [];
619
+ const seen = new Set<string>();
620
+
621
+ function addOption(pattern: string, label: string): void {
622
+ if (seen.has(pattern)) return;
623
+ seen.add(pattern);
624
+ options.push({ pattern, label });
625
+ }
626
+
627
+ // For multi-segment commands (pipelines, &&, etc.), use the full command as
628
+ // exact match and individual segment programs for broader options.
629
+ // Reconstruct using actual operators from the parsed segments (not hardcoded " | ").
630
+ if (parsed.segments.length > 1) {
631
+ const parts: string[] = [];
632
+ for (let i = 0; i < parsed.segments.length; i++) {
633
+ const seg = parsed.segments[i];
634
+ if (i > 0 && seg.operator) {
635
+ parts.push(seg.operator);
636
+ }
637
+ parts.push(seg.command);
638
+ }
639
+ const fullCommand = parts.join(" ");
640
+ addOption(`^${escapeRegex(fullCommand)}$`, fullCommand);
641
+ // Add command-level wildcards for each unique program
642
+ const programs = new Set(parsed.segments.map((s) => s.program));
643
+ for (const prog of programs) {
644
+ addOption(`^${escapeRegex(prog)}\\b`, `${prog} *`);
645
+ }
646
+ return options;
647
+ }
648
+
649
+ // Single segment
650
+ const seg = parsed.segments[0];
651
+ const programName = seg.program;
652
+
653
+ // Check if command has complexSyntax
654
+ const spec = registry[programName];
655
+ const isComplex = spec?.complexSyntax === true;
656
+
657
+ // 1. Exact match
658
+ addOption(`^${escapeRegex(seg.command)}$`, seg.command);
659
+
660
+ if (isComplex) {
661
+ // For complex syntax, skip intermediate options
662
+ addOption(`^${escapeRegex(programName)}\\b`, `${programName} *`);
663
+ return options;
664
+ }
665
+
666
+ // Separate args into flags and positionals.
667
+ // When the command has an argSchema, use parseArgs for accurate flag/positional
668
+ // separation (correctly handles value-consuming flags like `find -name "*.ts"`).
669
+ // Otherwise, fall back to the naive `startsWith("-")` heuristic.
670
+ let flags: string[];
671
+ let positionals: string[];
672
+
673
+ if (spec?.argSchema) {
674
+ const parsedArgs = parseArgs(seg.args, spec.argSchema);
675
+ // Convert the flags Map to a flat string array: for value-consuming flags,
676
+ // include both the flag and its value as separate entries; for boolean flags,
677
+ // include just the flag.
678
+ flags = [];
679
+ for (const [flagName, flagValue] of parsedArgs.flags) {
680
+ flags.push(flagName);
681
+ if (typeof flagValue === "string") {
682
+ flags.push(flagValue);
683
+ }
684
+ }
685
+ positionals = parsedArgs.positionals;
686
+ } else {
687
+ flags = [];
688
+ positionals = [];
689
+ for (const arg of seg.args) {
690
+ if (arg.startsWith("-")) {
691
+ flags.push(arg);
692
+ } else {
693
+ positionals.push(arg);
694
+ }
695
+ }
696
+ }
697
+
698
+ // Detect subcommand
699
+ let subcommand: string | undefined;
700
+ if (spec?.subcommands && positionals.length > 0) {
701
+ const firstPos = positionals[0];
702
+ if (spec.subcommands[firstPos]) {
703
+ subcommand = firstPos;
704
+ }
705
+ }
706
+
707
+ // 2. Wildcard positionals right-to-left
708
+ // When a subcommand is detected, exclude it from the positionals that get
709
+ // wildcarded — it's placed explicitly before flags in the label.
710
+ const wildcardPositionals = subcommand ? positionals.slice(1) : positionals;
711
+ if (wildcardPositionals.length > 1) {
712
+ for (let drop = 1; drop < wildcardPositionals.length; drop++) {
713
+ const kept = wildcardPositionals.slice(
714
+ 0,
715
+ wildcardPositionals.length - drop,
716
+ );
717
+ const sub = subcommand ? [subcommand] : [];
718
+ const parts = [programName, ...sub, ...flags, ...kept].filter(Boolean);
719
+ const pattern = `^${parts.map(escapeRegex).join("\\s+")}\\s+.*$`;
720
+ const label = [programName, ...sub, ...flags, ...kept, "*"].join(" ");
721
+ addOption(pattern, label);
722
+ }
723
+ }
724
+
725
+ // 3. Drop flags (keep command + subcommand + wildcard)
726
+ if (flags.length > 0) {
727
+ const parts = subcommand ? [programName, subcommand] : [programName];
728
+ addOption(
729
+ `^${parts.map(escapeRegex).join("\\s+")}\\b`,
730
+ [...parts, "*"].join(" "),
731
+ );
732
+ }
733
+
734
+ // 4. Subcommand wildcard
735
+ if (subcommand) {
736
+ addOption(
737
+ `^${escapeRegex(programName)}\\s+${escapeRegex(subcommand)}\\b`,
738
+ `${programName} ${subcommand} *`,
739
+ );
740
+ }
741
+
742
+ // 5. Command-level wildcard
743
+ addOption(`^${escapeRegex(programName)}\\b`, `${programName} *`);
744
+
745
+ return options;
746
+ }
747
+
748
+ /** Escape a string for use in a regex. */
749
+ function escapeRegex(s: string): string {
750
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
751
+ }
752
+
753
+ // ── Scope → Allowlist conversion ─────────────────────────────────────────────
754
+
755
+ /**
756
+ * Extract stable tokens from a scope option label: program and subcommand
757
+ * words, skipping flags (starting with `-`) and wildcards (`*`).
758
+ * Returns an `action:` prefixed pattern that matches the action key
759
+ * candidates produced by `buildCommandCandidates()`.
760
+ */
761
+ function labelToActionPattern(label: string): string {
762
+ const tokens = label
763
+ .split(/\s+/)
764
+ .filter((t) => !t.startsWith("-") && t !== "*");
765
+ return `action:${tokens.join(" ")}`;
766
+ }
767
+
768
+ /**
769
+ * Convert classifier-produced `ScopeOption[]` to `AllowlistOption[]` format.
770
+ *
771
+ * Patterns must be glob-compatible (not regex) because trust rules use
772
+ * Minimatch for matching against command candidates produced by
773
+ * `buildCommandCandidates()`. The format:
774
+ * - First option (exact match): raw command string
775
+ * - Intermediate options: `action:<program> <subcommand>` patterns that match
776
+ * action key candidates (labels reorder args so can't be used as globs directly)
777
+ * - Command-level wildcards: `action:<program>` matching the broadest action key
778
+ *
779
+ * Deduplicates by pattern to avoid redundant options when multiple scope levels
780
+ * collapse to the same action key.
781
+ */
782
+ export function scopeOptionsToAllowlistOptions(
783
+ scopeOptions: ScopeOption[],
784
+ _parsed: ParsedCommand,
785
+ ): AllowlistOption[] {
786
+ if (scopeOptions.length === 0) return [];
787
+
788
+ const results: AllowlistOption[] = [];
789
+ const seenPatterns = new Set<string>();
790
+
791
+ for (let i = 0; i < scopeOptions.length; i++) {
792
+ const opt = scopeOptions[i];
793
+ let description: string;
794
+ let pattern: string;
795
+
796
+ if (i === 0) {
797
+ // Exact match: raw command string (matches the raw candidate)
798
+ description = "This exact command";
799
+ pattern = opt.label;
800
+ } else if (
801
+ opt.label.endsWith(" *") &&
802
+ !opt.label.slice(0, -2).includes(" ")
803
+ ) {
804
+ // Command-level wildcard (label is "<program> *"): use action: prefix
805
+ // to match action key candidates from buildCommandCandidates()
806
+ const prog = opt.label.slice(0, -2);
807
+ description = `Any ${prog} command`;
808
+ pattern = `action:${prog}`;
809
+ } else {
810
+ // Intermediate wildcard: use action:<tokens> pattern to match action key
811
+ // candidates. We can't use the label as a glob directly because the scope
812
+ // ladder reorders args (flags before positionals), but command candidates
813
+ // preserve user arg order.
814
+ const actionPattern = labelToActionPattern(opt.label);
815
+ description = "Commands matching this pattern";
816
+ pattern = actionPattern;
817
+ }
818
+
819
+ // Deduplicate: skip options that produce the same pattern as a prior one
820
+ if (seenPatterns.has(pattern)) continue;
821
+ seenPatterns.add(pattern);
822
+
823
+ results.push({ label: opt.label, description, pattern });
824
+ }
825
+
826
+ return results;
827
+ }
828
+
829
+ // ── Main classifier ──────────────────────────────────────────────────────────
830
+
831
+ /**
832
+ * Bash risk classifier implementation.
833
+ *
834
+ * Primary classifier for bash/host_bash tools. checker.ts delegates to
835
+ * the singleton `bashRiskClassifier` instance for all bash command
836
+ * risk classification.
837
+ */
838
+ export class BashRiskClassifier implements RiskClassifier<BashClassifierInput> {
839
+ private readonly registry: Record<string, CommandRiskSpec>;
840
+ private readonly userRules: UserRule[];
841
+
842
+ constructor(
843
+ registry: Record<string, CommandRiskSpec> = DEFAULT_COMMAND_REGISTRY,
844
+ userRules: UserRule[] = [],
845
+ ) {
846
+ this.registry = registry;
847
+ this.userRules = userRules;
848
+ }
849
+
850
+ async classify(input: BashClassifierInput): Promise<RiskAssessment> {
851
+ const { command, toolName } = input;
852
+
853
+ if (!command.trim()) {
854
+ return {
855
+ riskLevel: "low",
856
+ reason: "Empty command",
857
+ scopeOptions: [],
858
+ matchType: "registry",
859
+ allowlistOptions: [],
860
+ };
861
+ }
862
+
863
+ const parsed = await cachedParse(command);
864
+
865
+ let maxRiskLevel: Risk = "low";
866
+ let maxReason = "";
867
+ let matchType: RiskAssessment["matchType"] = "registry";
868
+
869
+ // Classify each segment
870
+ for (const segment of parsed.segments) {
871
+ const result = classifySegment(
872
+ segment,
873
+ this.userRules,
874
+ this.registry,
875
+ toolName,
876
+ );
877
+ if (riskOrd(result.risk) > riskOrd(maxRiskLevel)) {
878
+ maxRiskLevel = result.risk;
879
+ maxReason = result.reason;
880
+ matchType = result.matchType;
881
+ } else if (!maxReason && result.reason) {
882
+ // Capture reason from first segment even if it doesn't escalate
883
+ // (avoids empty reason for all-low commands like `ls`)
884
+ maxReason = result.reason;
885
+ matchType = result.matchType;
886
+ }
887
+ }
888
+
889
+ // No segments → opaque
890
+ if (parsed.segments.length === 0) {
891
+ maxRiskLevel = "high";
892
+ maxReason = "No parseable command segments";
893
+ matchType = "unknown";
894
+ }
895
+
896
+ // Dangerous patterns escalate to at least high
897
+ if (parsed.dangerousPatterns.length > 0) {
898
+ if (riskOrd("high") > riskOrd(maxRiskLevel)) {
899
+ maxRiskLevel = "high";
900
+ }
901
+ maxReason = parsed.dangerousPatterns[0].description;
902
+ }
903
+
904
+ // Opaque constructs escalation:
905
+ // - With dangerous patterns present → escalate to high
906
+ // - Without dangerous patterns → escalate to medium only
907
+ if (parsed.hasOpaqueConstructs) {
908
+ const opaqueTarget: Risk =
909
+ parsed.dangerousPatterns.length > 0 ? "high" : "medium";
910
+ if (riskOrd(opaqueTarget) > riskOrd(maxRiskLevel)) {
911
+ maxRiskLevel = opaqueTarget;
912
+ }
913
+ if (!maxReason) {
914
+ maxReason = "Command contains opaque constructs";
915
+ }
916
+ }
917
+
918
+ const scopeOptions = generateScopeOptions(parsed, this.registry);
919
+ const allowlistOptions = scopeOptionsToAllowlistOptions(
920
+ scopeOptions,
921
+ parsed,
922
+ );
923
+
924
+ const assessment: RiskAssessment = {
925
+ riskLevel: maxRiskLevel,
926
+ reason: maxReason,
927
+ scopeOptions,
928
+ matchType,
929
+ allowlistOptions,
930
+ };
931
+
932
+ // Risk assessment analytics
933
+ const primaryProgram = parsed.segments[0]?.program ?? "(none)";
934
+ log.info(
935
+ {
936
+ command,
937
+ program: primaryProgram,
938
+ riskLevel: assessment.riskLevel,
939
+ reason: assessment.reason,
940
+ matchType: assessment.matchType,
941
+ },
942
+ "Risk assessment",
943
+ );
944
+
945
+ return assessment;
946
+ }
947
+ }
948
+
949
+ /** Singleton classifier instance with default registry. */
950
+ export const bashRiskClassifier = new BashRiskClassifier();