@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
package/AGENTS.md CHANGED
@@ -14,7 +14,15 @@ When you introduce a new env var that the assistant process needs to read at run
14
14
 
15
15
  ## Daemon startup philosophy
16
16
 
17
- The daemon must **never** block startup under *any circumstance*. All possible errors should be logged so that the assistant can recover from it's corrupted state after the fact.
17
+ The daemon must **never** block startup under _any circumstance_. All possible errors should be logged so that the assistant can recover from it's corrupted state after the fact.
18
+
19
+ ## Post-execution hooks
20
+
21
+ Tool post-execution hooks (`src/daemon/tool-side-effects.ts`) run after a tool executor returns. They are an **observation-and-notification layer** only: refresh client-side state, broadcast events, kick off orthogonal background work (e.g. icon generation). Hooks must not re-do work the executor already performed, and must not attempt recovery when the executor failed — failures surface in the tool result for the LLM to act on.
22
+
23
+ Do not coordinate hook behaviour by re-parsing the tool's JSON response to infer what the executor did (e.g. "if field X is missing, retry step Y"). That couples the LLM-facing response shape to internal daemon logic and breaks silently when the response shape evolves. Keep the hook's logic independent of the result payload, or if the hook genuinely needs executor-internal state, pass it through a typed side channel — never through a JSON round-trip.
24
+
25
+ Shared mutable resources written by more than one caller (e.g. `dist/` directories produced by `compileApp()`) must be serialised per-resource so concurrent callers cannot race on `rm -rf` + write sequences.
18
26
 
19
27
  ## Code comments
20
28
 
package/ARCHITECTURE.md CHANGED
@@ -1045,13 +1045,11 @@ When all four reducer tiers are exhausted and the provider still rejects, the ov
1045
1045
 
1046
1046
  | Session Type | Config Policy | Action |
1047
1047
  | --------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
1048
- | Interactive | `"summarize"` (default) | `request_user_approval` — prompt the user via `PermissionPrompter` before compressing the latest turn |
1048
+ | Interactive | `"summarize"` (default) | `auto_compress_latest_turn` — compress without asking |
1049
1049
  | Non-interactive | `"truncate"` (default) | `auto_compress_latest_turn` — compress without asking |
1050
1050
  | Any | `"drop"` | `fail_gracefully` — fall through to the final context-overflow fallback, which emits a `conversation_error` |
1051
1051
 
1052
- **Approval gate:** For interactive sessions, the pipeline uses `requestCompressionApproval()` in `context-overflow-approval.ts`, which presents a confirmation prompt through the existing `PermissionPrompter` flow (`POST /v1/confirm`). The prompt uses a reserved pseudo tool name (`context_overflow_compression`) so the UI can display a meaningful label. The decision is one-shot per overflow (no "always allow" option).
1053
-
1054
- **Deny handling:** If the user declines compression, the session emits a graceful assistant explanation message ("The conversation has grown too long...") instead of a `conversation_error`. The deny message is persisted to conversation history and delivered via `assistant_text_delta` events, so the user sees a normal chat bubble rather than an error toast. The turn ends cleanly without triggering the error classification pipeline.
1052
+ When standard compaction has been exhausted and the provider still reports a context overflow, the recovery pipeline forces an emergency compaction of the latest turn with aggressive settings (`force: true`, `minKeepRecentUserTurns: 0`). The user is not prompted compaction is always automatic. Users who want to opt out entirely can set `contextWindow.overflowRecovery.interactiveLatestTurnCompression` to `"drop"`, which short-circuits to a graceful failure instead.
1055
1053
 
1056
1054
  ### Config
1057
1055
 
@@ -1067,13 +1065,12 @@ All overflow recovery settings live under `contextWindow.overflowRecovery` in th
1067
1065
 
1068
1066
  ### Key Source Files
1069
1067
 
1070
- | File | Purpose |
1071
- | ----------------------------------------- | -------------------------------------------------------------------------------- |
1072
- | `src/daemon/context-overflow-reducer.ts` | Tiered reducer: four-tier pipeline with idempotent steps and cumulative state |
1073
- | `src/daemon/context-overflow-policy.ts` | Overflow policy resolver: maps config + interactivity to concrete action |
1074
- | `src/daemon/context-overflow-approval.ts` | Approval gate: prompts user for latest-turn compression via `PermissionPrompter` |
1075
- | `src/daemon/conversation-agent-loop.ts` | Integration: preflight budget check, convergence loop, approval/deny flow |
1076
- | `src/config/core-schema.ts` | `ContextOverflowRecoveryConfigSchema` with defaults and validation |
1068
+ | File | Purpose |
1069
+ | ---------------------------------------- | ----------------------------------------------------------------------------- |
1070
+ | `src/daemon/context-overflow-reducer.ts` | Tiered reducer: four-tier pipeline with idempotent steps and cumulative state |
1071
+ | `src/daemon/context-overflow-policy.ts` | Overflow policy resolver: maps config + interactivity to concrete action |
1072
+ | `src/daemon/conversation-agent-loop.ts` | Integration: preflight budget check, convergence loop, emergency compaction |
1073
+ | `src/config/core-schema.ts` | `ContextOverflowRecoveryConfigSchema` with defaults and validation |
1077
1074
 
1078
1075
  ---
1079
1076
 
@@ -1562,13 +1559,14 @@ graph TB
1562
1559
 
1563
1560
  FIND_RULE -->|"Deny rule"| DENY["decision: deny<br/>Blocked by rule"]
1564
1561
  FIND_RULE -->|"Ask rule"| PROMPT_ASK["decision: prompt<br/>Always ask user"]
1565
- FIND_RULE -->|"Allow rule"| RISK_CHECK{"Risk level?"}
1566
- FIND_RULE -->|"No match"| NO_MATCH{"Fallback logic"}
1562
+ FIND_RULE -->|"Allow rule / No match"| SANDBOX_CHECK{"sandboxAutoApprove?<br/>(bash + allowlisted +<br/>containerized)"}
1563
+
1564
+ SANDBOX_CHECK -->|"yes"| AUTO_SANDBOX["decision: allow<br/>Sandbox auto-approve"]
1565
+ SANDBOX_CHECK -->|"no, has Allow rule"| RISK_CHECK{"Risk level?"}
1566
+ SANDBOX_CHECK -->|"no, no match"| NO_MATCH{"Fallback logic"}
1567
1567
 
1568
1568
  RISK_CHECK -->|"Low / Medium"| AUTO_ALLOW["decision: allow<br/>Auto-allowed by rule"]
1569
- RISK_CHECK -->|"High"| HIGH_CHECK{"shouldAutoAllowHighRisk()<br/>(containerized bash?)"}
1570
- HIGH_CHECK -->|"yes"| AUTO_ALLOW
1571
- HIGH_CHECK -->|"no"| RISK_THRESHOLD{"Risk-based<br/>threshold fallback"}
1569
+ RISK_CHECK -->|"High"| RISK_THRESHOLD{"Risk-based<br/>threshold fallback"}
1572
1570
 
1573
1571
  NO_MATCH -->|"tool.origin === 'skill'"| PROMPT_SKILL["decision: prompt<br/>Skill tools always ask"]
1574
1572
  NO_MATCH -->|"strict mode"| PROMPT_STRICT["decision: prompt<br/>No implicit auto-allow"]
@@ -1711,7 +1709,7 @@ When a permission prompt is sent to the client (via `confirmation_request` SSE e
1711
1709
  | `allowlistOptions` | Suggested patterns for "always allow" rules |
1712
1710
  | `scopeOptions` | Suggested scopes for rule persistence |
1713
1711
 
1714
- The user can respond with: `allow` (one-time), `always_allow` (create allow rule), `deny` (one-time), or `always_deny` (create deny rule). High-risk operations with an allow rule in containerized environments are auto-allowed at runtime by `DefaultApprovalPolicy.shouldAutoAllowHighRisk()` without requiring persisted state. All other risk-based decisions use the `autoApproveUpTo` threshold (default: `"low"`) -- tools at or below the threshold are auto-allowed, those above are prompted.
1712
+ The user can respond with: `allow` (one-time), `always_allow` (create allow rule), `deny` (one-time), or `always_deny` (create deny rule). In containerized environments, commands tagged with `sandboxAutoApprove` in their risk spec are auto-allowed via the approval policy's sandbox auto-approve check; non-allowlisted commands (network tools, runtimes, package managers) use the user's `autoApproveUpTo` threshold. All other risk-based decisions use the `autoApproveUpTo` threshold (default: `"low"`) -- tools at or below the threshold are auto-allowed, those above are prompted.
1715
1713
 
1716
1714
  ### Canonical Paths
1717
1715
 
package/Dockerfile CHANGED
@@ -36,9 +36,6 @@ RUN set -eu; for pkg in /app/skills/*/package.json; do \
36
36
  (cd "$dir" && (bun install --frozen-lockfile 2>/dev/null || bun install)); \
37
37
  done
38
38
 
39
- # Copy source
40
- COPY assistant ./assistant
41
-
42
39
  # Final stage
43
40
  FROM debian:trixie-slim@sha256:4ffb3a1511099754cddc70eb1b12e50ffdb67619aa0ab6c13fcd800a78ef7c7a AS runner
44
41
 
@@ -133,8 +130,13 @@ EXPOSE 3001
133
130
  ENV RUNTIME_HTTP_PORT=3001
134
131
  ENV IS_CONTAINERIZED=true
135
132
 
136
- # Copy from builder
133
+ # Copy installed deps + shared packages + bundled skills from builder.
134
+ # Skills stay in the builder copy because they require per-skill `bun install` runs.
137
135
  COPY --from=builder /app /app
136
+
137
+ # Copy source separately to avoid invalidating builder layer.
138
+ COPY assistant ./
139
+
138
140
  RUN chmod +x /app/assistant/docker-entrypoint.sh
139
141
 
140
142
  # Run the daemon + http server
@@ -0,0 +1,283 @@
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ // ── Mocks ────────────────────────────────────────────────────────────────────
4
+
5
+ let mockFeatureFlagEnabled = true;
6
+
7
+ mock.module("../../src/config/assistant-feature-flags.js", () => ({
8
+ isAssistantFeatureFlagEnabled: (_key: string, _config: unknown) =>
9
+ mockFeatureFlagEnabled,
10
+ }));
11
+
12
+ mock.module("../../src/config/loader.js", () => ({
13
+ getConfig: () => ({}),
14
+ }));
15
+
16
+ // Track gatewayGet calls for assertion
17
+ const gatewayGetCalls: string[] = [];
18
+ let gatewayGetHandler: (path: string) => unknown = () => ({});
19
+
20
+ mock.module("../../src/runtime/gateway-internal-client.js", () => ({
21
+ GatewayRequestError: class GatewayRequestError extends Error {
22
+ statusCode: number;
23
+ gatewayError: string | undefined;
24
+ constructor(message: string, statusCode: number, gatewayError?: string) {
25
+ super(message);
26
+ this.name = "GatewayRequestError";
27
+ this.statusCode = statusCode;
28
+ this.gatewayError = gatewayError;
29
+ }
30
+ },
31
+ gatewayGet: async <T>(path: string): Promise<T> => {
32
+ gatewayGetCalls.push(path);
33
+ return gatewayGetHandler(path) as T;
34
+ },
35
+ }));
36
+
37
+ // Suppress logger output in tests
38
+ mock.module("../../src/util/logger.js", () => ({
39
+ getLogger: () => ({
40
+ warn: () => {},
41
+ info: () => {},
42
+ error: () => {},
43
+ debug: () => {},
44
+ }),
45
+ }));
46
+
47
+ import {
48
+ _clearGlobalCacheForTesting,
49
+ getAutoApproveThreshold,
50
+ } from "../../src/permissions/gateway-threshold-reader.js";
51
+ // Import GatewayRequestError from the mock so we can throw instances of it
52
+ const { GatewayRequestError } =
53
+ await import("../../src/runtime/gateway-internal-client.js");
54
+
55
+ // ── Helpers ──────────────────────────────────────────────────────────────────
56
+
57
+ function resetMocks(): void {
58
+ mockFeatureFlagEnabled = true;
59
+ gatewayGetCalls.length = 0;
60
+ gatewayGetHandler = () => ({});
61
+ _clearGlobalCacheForTesting();
62
+ }
63
+
64
+ afterEach(resetMocks);
65
+
66
+ // ── Tests ────────────────────────────────────────────────────────────────────
67
+
68
+ describe("getAutoApproveThreshold", () => {
69
+ test("returns undefined when feature flag is off", async () => {
70
+ mockFeatureFlagEnabled = false;
71
+ const result = await getAutoApproveThreshold("conv-123", "conversation");
72
+ expect(result).toBeUndefined();
73
+ // Should not make any gateway calls
74
+ expect(gatewayGetCalls).toHaveLength(0);
75
+ });
76
+
77
+ test("returns global defaults when gateway returns them", async () => {
78
+ gatewayGetHandler = (path: string) => {
79
+ if (path === "/v1/permissions/thresholds") {
80
+ return {
81
+ interactive: "medium",
82
+ background: "low",
83
+ headless: "none",
84
+ };
85
+ }
86
+ return {};
87
+ };
88
+
89
+ // conversation maps to interactive
90
+ expect(await getAutoApproveThreshold(undefined, "conversation")).toBe(
91
+ "medium",
92
+ );
93
+
94
+ _clearGlobalCacheForTesting();
95
+
96
+ expect(await getAutoApproveThreshold(undefined, "background")).toBe("low");
97
+
98
+ _clearGlobalCacheForTesting();
99
+
100
+ expect(await getAutoApproveThreshold(undefined, "headless")).toBe("none");
101
+ });
102
+
103
+ test("returns conversation override when it exists", async () => {
104
+ gatewayGetHandler = (path: string) => {
105
+ if (path === "/v1/permissions/thresholds/conversations/conv-xyz") {
106
+ return { threshold: "medium" };
107
+ }
108
+ if (path === "/v1/permissions/thresholds") {
109
+ return {
110
+ interactive: "low",
111
+ background: "medium",
112
+ headless: "none",
113
+ };
114
+ }
115
+ return {};
116
+ };
117
+
118
+ const result = await getAutoApproveThreshold("conv-xyz", "conversation");
119
+ expect(result).toBe("medium");
120
+ // Should have called the conversation endpoint, not the global one
121
+ expect(gatewayGetCalls).toEqual([
122
+ "/v1/permissions/thresholds/conversations/conv-xyz",
123
+ ]);
124
+ });
125
+
126
+ test("falls back to global when conversation override returns 404", async () => {
127
+ gatewayGetHandler = (path: string) => {
128
+ if (path.startsWith("/v1/permissions/thresholds/conversations/")) {
129
+ throw new GatewayRequestError("Not found", 404, "Not found");
130
+ }
131
+ if (path === "/v1/permissions/thresholds") {
132
+ return {
133
+ interactive: "low",
134
+ background: "medium",
135
+ headless: "none",
136
+ };
137
+ }
138
+ return {};
139
+ };
140
+
141
+ const result = await getAutoApproveThreshold("conv-123", "conversation");
142
+ expect(result).toBe("low");
143
+ // Should have called both endpoints
144
+ expect(gatewayGetCalls).toEqual([
145
+ "/v1/permissions/thresholds/conversations/conv-123",
146
+ "/v1/permissions/thresholds",
147
+ ]);
148
+ });
149
+
150
+ test("falls back to hardcoded defaults on gateway error", async () => {
151
+ gatewayGetHandler = () => {
152
+ throw new Error("Connection refused");
153
+ };
154
+
155
+ // conversation → "low"
156
+ expect(await getAutoApproveThreshold(undefined, "conversation")).toBe(
157
+ "low",
158
+ );
159
+
160
+ _clearGlobalCacheForTesting();
161
+
162
+ // background → "medium"
163
+ expect(await getAutoApproveThreshold(undefined, "background")).toBe(
164
+ "medium",
165
+ );
166
+
167
+ _clearGlobalCacheForTesting();
168
+
169
+ // headless → "none"
170
+ expect(await getAutoApproveThreshold(undefined, "headless")).toBe("none");
171
+ });
172
+
173
+ test("falls back to hardcoded defaults on non-404 conversation error", async () => {
174
+ gatewayGetHandler = (path: string) => {
175
+ if (path.startsWith("/v1/permissions/thresholds/conversations/")) {
176
+ throw new GatewayRequestError("Internal error", 500, "Server error");
177
+ }
178
+ // Should not reach global endpoint
179
+ return {
180
+ interactive: "medium",
181
+ background: "medium",
182
+ headless: "medium",
183
+ };
184
+ };
185
+
186
+ const result = await getAutoApproveThreshold("conv-123", "conversation");
187
+ // Should fall back to hardcoded default for conversation, not global endpoint
188
+ expect(result).toBe("low");
189
+ // Should have only called the conversation endpoint
190
+ expect(gatewayGetCalls).toEqual([
191
+ "/v1/permissions/thresholds/conversations/conv-123",
192
+ ]);
193
+ });
194
+
195
+ test("caching: second call within 30s does not re-fetch global", async () => {
196
+ let fetchCount = 0;
197
+ gatewayGetHandler = (path: string) => {
198
+ if (path === "/v1/permissions/thresholds") {
199
+ fetchCount++;
200
+ return {
201
+ interactive: "medium",
202
+ background: "low",
203
+ headless: "none",
204
+ };
205
+ }
206
+ return {};
207
+ };
208
+
209
+ // First call — should fetch
210
+ const first = await getAutoApproveThreshold(undefined, "conversation");
211
+ expect(first).toBe("medium");
212
+ expect(fetchCount).toBe(1);
213
+
214
+ // Second call — should use cache
215
+ const second = await getAutoApproveThreshold(undefined, "background");
216
+ expect(second).toBe("low");
217
+ expect(fetchCount).toBe(1); // Still 1, cache hit
218
+
219
+ // Third call — still cached
220
+ const third = await getAutoApproveThreshold(undefined, "headless");
221
+ expect(third).toBe("none");
222
+ expect(fetchCount).toBe(1); // Still 1
223
+
224
+ // After clearing cache, should re-fetch
225
+ _clearGlobalCacheForTesting();
226
+ const fourth = await getAutoApproveThreshold(undefined, "conversation");
227
+ expect(fourth).toBe("medium");
228
+ expect(fetchCount).toBe(2); // Incremented
229
+ });
230
+
231
+ test("defaults executionContext to conversation when omitted", async () => {
232
+ gatewayGetHandler = (path: string) => {
233
+ if (path === "/v1/permissions/thresholds") {
234
+ return {
235
+ interactive: "medium",
236
+ background: "low",
237
+ headless: "none",
238
+ };
239
+ }
240
+ return {};
241
+ };
242
+
243
+ // executionContext omitted — should default to "conversation" → interactive
244
+ const result = await getAutoApproveThreshold(undefined, undefined);
245
+ expect(result).toBe("medium");
246
+ });
247
+
248
+ test("skips conversation override when no conversationId", async () => {
249
+ gatewayGetHandler = (path: string) => {
250
+ if (path === "/v1/permissions/thresholds") {
251
+ return {
252
+ interactive: "low",
253
+ background: "medium",
254
+ headless: "none",
255
+ };
256
+ }
257
+ return {};
258
+ };
259
+
260
+ const result = await getAutoApproveThreshold(undefined, "conversation");
261
+ expect(result).toBe("low");
262
+ // Should only call global endpoint, not conversation
263
+ expect(gatewayGetCalls).toEqual(["/v1/permissions/thresholds"]);
264
+ });
265
+
266
+ test("skips conversation override for non-conversation contexts", async () => {
267
+ gatewayGetHandler = (path: string) => {
268
+ if (path === "/v1/permissions/thresholds") {
269
+ return {
270
+ interactive: "low",
271
+ background: "medium",
272
+ headless: "none",
273
+ };
274
+ }
275
+ return {};
276
+ };
277
+
278
+ // Even with a conversationId, background context should not check conversation override
279
+ const result = await getAutoApproveThreshold("conv-123", "background");
280
+ expect(result).toBe("medium");
281
+ expect(gatewayGetCalls).toEqual(["/v1/permissions/thresholds"]);
282
+ });
283
+ });
@@ -149,7 +149,7 @@ sequenceDiagram
149
149
  | Decision | Rationale |
150
150
  | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
151
  | PKCE by default, optional client_secret | Desktop apps prefer PKCE; some providers (Slack) require a secret, which is stored in the credential store (`oauth_app/{id}/client_secret`) for autonomous refresh |
152
- | Shared connect orchestrator | All OAuth providers route through `orchestrateOAuthConnect()`, which resolves profiles, enforces scope policy, runs the flow, stores tokens, and verifies identity. Adding a provider is a declarative profile entry, not new orchestration code |
152
+ | Shared connect orchestrator | All OAuth providers route through `orchestrateOAuthConnect()`, which resolves profiles, resolves scopes, runs the flow, stores tokens, and verifies identity. Adding a provider is a declarative profile entry, not new orchestration code |
153
153
  | Canonical credential naming | All reads and writes use `client_id`/`client_secret` as canonical field names |
154
154
  | Caller-driven callback transport | Transport (`loopback` or `gateway`) is chosen per-flow via the `callbackTransport` option on the connect API, defaulting to loopback. Desktop clients use loopback (no tunnel needed); web clients can pass `callback_transport: "gateway"`. Provider configuration no longer dictates transport. |
155
155
  | Unified `MessagingProvider` interface | All platforms implement the same contract; generic tools work immediately for new providers |
@@ -161,34 +161,33 @@ sequenceDiagram
161
161
 
162
162
  ### Source Files
163
163
 
164
- | File | Role |
165
- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
166
- | `assistant/src/security/oauth2.ts` | OAuth2 flow: PKCE or client_secret, Bun.serve callback, token exchange |
167
- | `assistant/src/security/token-manager.ts` | `withValidToken()` — auto-refresh, 401 retry, expiry buffer |
168
- | `assistant/src/messaging/provider.ts` | `MessagingProvider` interface |
169
- | `assistant/src/messaging/provider-types.ts` | Platform-agnostic types (Conversation, Message, SearchResult) |
170
- | `assistant/src/messaging/registry.ts` | Provider registry: register, lookup, list connected |
171
- | `assistant/src/messaging/style-analyzer.ts` | Writing style extraction from message corpus |
172
- | `assistant/src/messaging/draft-store.ts` | Local draft storage (platform/id JSON files) |
173
- | `assistant/src/messaging/providers/slack/` | Slack adapter, client, types |
174
- | `assistant/src/messaging/providers/gmail/` | Gmail adapter, client, types |
175
- | `assistant/src/config/bundled-skills/messaging/` | Core messaging skill (send, read, search, reply across platforms) |
176
- | `assistant/src/config/bundled-skills/sequences/` | Email sequence management skill (drip campaigns, enrollment, analytics) |
177
- | `assistant/src/watcher/providers/gmail.ts` | Gmail watcher using History API |
178
- | `assistant/src/watcher/providers/github.ts` | GitHub watcher for PRs, issues, review requests, and mentions |
179
- | `assistant/src/watcher/providers/linear.ts` | Linear watcher for assigned issues, status changes, and @mentions |
180
- | `assistant/src/oauth/seed-providers.ts` | Provider seed data: injection templates, identity config, setup metadata (seeded to DB on startup) |
181
- | `assistant/src/oauth/connect-orchestrator.ts` | Shared OAuth connect orchestrator: profile resolution, scope policy, flow execution, token storage |
182
- | `assistant/src/oauth/scope-policy.ts` | Deterministic scope resolution and policy enforcement |
183
- | `assistant/src/oauth/connect-types.ts` | Shared types: `OAuthScopePolicy`, `OAuthConnectResult` |
184
- | `assistant/src/oauth/token-persistence.ts` | Token storage helper: persists tokens, metadata, and runs post-connect hooks |
185
- | `assistant/src/daemon/handlers/oauth-connect.ts` | Generic OAuth connect handler (`oauth_connect_start` / `oauth_connect_result`) |
164
+ | File | Role |
165
+ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
166
+ | `assistant/src/security/oauth2.ts` | OAuth2 flow: PKCE or client_secret, Bun.serve callback, token exchange |
167
+ | `assistant/src/security/token-manager.ts` | `withValidToken()` — auto-refresh, 401 retry, expiry buffer |
168
+ | `assistant/src/messaging/provider.ts` | `MessagingProvider` interface |
169
+ | `assistant/src/messaging/provider-types.ts` | Platform-agnostic types (Conversation, Message, SearchResult) |
170
+ | `assistant/src/messaging/registry.ts` | Provider registry: register, lookup, list connected |
171
+ | `assistant/src/messaging/style-analyzer.ts` | Writing style extraction from message corpus |
172
+ | `assistant/src/messaging/draft-store.ts` | Local draft storage (platform/id JSON files) |
173
+ | `assistant/src/messaging/providers/slack/` | Slack adapter, client, types |
174
+ | `assistant/src/messaging/providers/gmail/` | Gmail adapter, client, types |
175
+ | `assistant/src/config/bundled-skills/messaging/` | Core messaging skill (send, read, search, reply across platforms) |
176
+ | `assistant/src/config/bundled-skills/sequences/` | Email sequence management skill (drip campaigns, enrollment, analytics) |
177
+ | `assistant/src/watcher/providers/gmail.ts` | Gmail watcher using History API |
178
+ | `assistant/src/watcher/providers/github.ts` | GitHub watcher for PRs, issues, review requests, and mentions |
179
+ | `assistant/src/watcher/providers/linear.ts` | Linear watcher for assigned issues, status changes, and @mentions |
180
+ | `assistant/src/oauth/seed-providers.ts` | Provider seed data: injection templates, identity config, setup metadata (seeded to DB on startup) |
181
+ | `assistant/src/oauth/connect-orchestrator.ts` | Shared OAuth connect orchestrator: profile resolution, scope resolution, flow execution, token storage |
182
+ | `assistant/src/oauth/connect-types.ts` | Shared types: `AvailableScopes`, `OAuthConnectResult` |
183
+ | `assistant/src/oauth/token-persistence.ts` | Token storage helper: persists tokens, metadata, and runs post-connect hooks |
184
+ | `assistant/src/daemon/handlers/oauth-connect.ts` | Generic OAuth connect handler (`oauth_connect_start` / `oauth_connect_result`) |
186
185
 
187
186
  ---
188
187
 
189
188
  ## OAuth Extensibility — DB-Driven Provider Config, Scope Policy, and Connect Orchestrator
190
189
 
191
- The OAuth extensibility layer makes adding a new OAuth provider a fully declarative operation. All provider configuration — protocol fields (auth URLs, token URLs, scopes, scope policy), behavioral fields (identity verification, injection templates, setup metadata), and display metadata — is stored in the `oauth_providers` SQLite table and seeded on startup via `seed-providers.ts`. The shared **connect orchestrator** handles the full flow from provider resolution through token storage.
190
+ The OAuth extensibility layer makes adding a new OAuth provider a fully declarative operation. All provider configuration — protocol fields (auth URLs, token URLs, scopes), behavioral fields (identity verification, injection templates, setup metadata), and display metadata — is stored in the `oauth_providers` SQLite table and seeded on startup via `seed-providers.ts`. The shared **connect orchestrator** handles the full flow from provider resolution through token storage.
192
191
 
193
192
  ### Provider Configuration (DB-Driven)
194
193
 
@@ -199,24 +198,19 @@ Each provider row includes:
199
198
  | Column group | Fields | Purpose |
200
199
  | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
201
200
  | **Protocol** | `authUrl`, `tokenUrl`, `tokenEndpointAuthMethod`, `extraParams`, `loopbackPort` | OAuth2 flow parameters |
202
- | **Scopes** | `defaultScopes`, `scopePolicy` | Deterministic scope resolution (user-customizable, preserved across seed restarts) |
201
+ | **Scopes** | `defaultScopes`, `availableScopes` | Default scopes for connect flow; informational available scopes for assistant context |
203
202
  | **Identity verification** | `identityUrl`, `identityMethod`, `identityHeaders`, `identityBody`, `identityResponsePaths`, `identityFormat`, `identityOkField` | Data-driven identity verifier fetches human-readable account info after token exchange |
204
203
  | **Injection templates** | `injectionTemplates` | Auto-applied credential injection rules for the script proxy |
205
204
  | **Setup metadata** | `displayName`, `description`, `dashboardUrl`, `appType`, `setupNotes`, `requiresClientSecret` | Metadata for the generic OAuth setup skill |
206
205
  | **Ping config** | `pingUrl`, `pingMethod`, `pingHeaders`, `pingBody` | Health-check endpoint for `assistant oauth ping` |
207
206
 
208
- ### Scope Policy Engine
207
+ ### Scope Resolution
209
208
 
210
- `assistant/src/oauth/scope-policy.ts` exports `resolveScopes(profile, requestedScopes)`, which deterministically computes the final scope set:
209
+ Scope resolution is simple no validation layer:
211
210
 
212
- 1. No requested scopes → returns `defaultScopes`.
213
- 2. Requested scopes provided → starts with defaults, then validates each additional scope:
214
- - Rejected if in `forbiddenScopes`.
215
- - Rejected if `allowAdditionalScopes` is `false`.
216
- - Rejected if not in `allowedOptionalScopes`.
217
- - Accepted otherwise, added to the union.
218
-
219
- Returns `{ ok: true, scopes }` or `{ ok: false, error, allowedScopes }`.
211
+ 1. No requested scopes → uses `defaultScopes`.
212
+ 2. Requested scopes provided → uses the requested scopes directly (no validation — the OAuth provider rejects invalid scopes).
213
+ 3. `availableScopes` is informational context surfaced via CLI for the assistant to consult.
220
214
 
221
215
  ### Connect Orchestrator
222
216
 
@@ -224,7 +218,7 @@ Returns `{ ok: true, scopes }` or `{ ok: false, error, allowedScopes }`.
224
218
 
225
219
  1. **Receive canonical provider name** — the orchestrator receives the canonical provider name directly (e.g. `google`, `slack`).
226
220
  2. **Load provider row** — reads all config from the `oauth_providers` DB table.
227
- 3. **Compute scopes** — `resolveScopes()` with scope policy enforcement.
221
+ 3. **Compute scopes** — uses requested scopes directly, or falls back to `defaultScopes`.
228
222
  4. **Build OAuth config** — assemble protocol-level config from the DB provider row.
229
223
  5. **Run flow** — interactive (opens browser, blocks until completion) or deferred (returns auth URL for the caller to deliver).
230
224
  6. **Verify identity** — runs the generic data-driven identity verifier using the provider row's identity columns.
@@ -246,7 +240,7 @@ This replaces provider-specific handlers — any provider in the registry can be
246
240
  ### Adding a New OAuth Provider
247
241
 
248
242
  1. **Add seed data** to `PROVIDER_SEED_DATA` in `assistant/src/oauth/seed-providers.ts`:
249
- - Set protocol fields: `authUrl`, `tokenUrl`, `defaultScopes`, `scopePolicy`, `loopbackPort`.
243
+ - Set protocol fields: `authUrl`, `tokenUrl`, `defaultScopes`, `availableScopes`, `loopbackPort`.
250
244
  - Set identity verification: `identityUrl`, `identityMethod`, `identityHeaders`, `identityResponsePaths`, `identityFormat`.
251
245
  - Set injection templates: `injectionTemplates` for providers whose tokens should be auto-injected by the script proxy.
252
246
  - Set setup metadata: `displayName`, `dashboardUrl`, `appType` enable the generic OAuth setup skill to guide users through app creation.
@@ -259,9 +253,8 @@ This replaces provider-specific handlers — any provider in the registry can be
259
253
  | File | Role |
260
254
  | ------------------------------------------------ | --------------------------------------------------------------------------- |
261
255
  | `assistant/src/oauth/seed-providers.ts` | Provider seed data and startup seeding |
262
- | `assistant/src/oauth/scope-policy.ts` | Scope resolution and policy enforcement (pure, no I/O) |
263
256
  | `assistant/src/oauth/connect-orchestrator.ts` | Shared connect orchestrator (profile → scopes → flow → tokens) |
264
- | `assistant/src/oauth/connect-types.ts` | Shared types (`OAuthScopePolicy`, `OAuthConnectResult`) |
257
+ | `assistant/src/oauth/connect-types.ts` | Shared types (`AvailableScopes`, `OAuthConnectResult`) |
265
258
  | `assistant/src/oauth/token-persistence.ts` | Token storage: credential store writes, metadata upsert, post-connect hooks |
266
259
  | `assistant/src/oauth/identity-verifier.ts` | Generic data-driven identity verifier (reads config from provider DB row) |
267
260
  | `assistant/src/daemon/handlers/oauth-connect.ts` | Generic `oauth_connect_start` / `oauth_connect_result` handler |