@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
@@ -163,4 +163,193 @@ describe("executeBrowserStatus", () => {
163
163
  expect(extension.verified).toBe("active_probe");
164
164
  expect(extension.details.restrictedActiveTab).toBe(true);
165
165
  });
166
+
167
+ // ── macOS host-browser proxy mode tests ─────────────────────────────
168
+
169
+ test("macOS: reports host browser proxy as available when proxy is bound and connected", async () => {
170
+ const result = await executeBrowserStatus(
171
+ {},
172
+ makeContext({
173
+ transportInterface: "macos",
174
+ hostBrowserProxy: {
175
+ isAvailable: () => true,
176
+ } as ToolContext["hostBrowserProxy"],
177
+ }),
178
+ );
179
+ expect(result.isError).toBe(false);
180
+ const payload = JSON.parse(result.content);
181
+ const extension = payload.modes.find(
182
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
183
+ );
184
+ expect(extension).toBeDefined();
185
+ expect(extension.available).toBe(true);
186
+ expect(extension.verified).toBe("active_probe");
187
+ expect(extension.summary).toContain("macOS host browser proxy");
188
+ expect(extension.details.transport).toBe("macos-sse");
189
+ });
190
+
191
+ test("macOS: reports transport as extension-ws when hostBrowserRegistryRouted is true", async () => {
192
+ const result = await executeBrowserStatus(
193
+ {},
194
+ makeContext({
195
+ transportInterface: "macos",
196
+ hostBrowserRegistryRouted: true,
197
+ hostBrowserProxy: {
198
+ isAvailable: () => true,
199
+ } as ToolContext["hostBrowserProxy"],
200
+ }),
201
+ );
202
+ expect(result.isError).toBe(false);
203
+ const payload = JSON.parse(result.content);
204
+ const extension = payload.modes.find(
205
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
206
+ );
207
+ expect(extension).toBeDefined();
208
+ expect(extension.available).toBe(true);
209
+ expect(extension.details.transport).toBe("extension-ws");
210
+ });
211
+
212
+ test("macOS: reports proxy unbound with macOS-specific actions when no proxy is present", async () => {
213
+ const result = await executeBrowserStatus(
214
+ {},
215
+ makeContext({
216
+ transportInterface: "macos",
217
+ }),
218
+ );
219
+ expect(result.isError).toBe(false);
220
+ const payload = JSON.parse(result.content);
221
+ const extension = payload.modes.find(
222
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
223
+ );
224
+ expect(extension).toBeDefined();
225
+ expect(extension.available).toBe(false);
226
+ expect(extension.summary).toContain("macOS host browser proxy");
227
+ expect(extension.summary).toContain("desktop client");
228
+ expect(extension.details.transport).toBe("macos-sse");
229
+ // macOS-specific user actions should mention the desktop app, not the extension
230
+ expect(
231
+ extension.userActions.some((a: string) => a.includes("desktop app")),
232
+ ).toBe(true);
233
+ });
234
+
235
+ test("macOS: reports proxy disconnected with reconnect actions when proxy is bound but not available", async () => {
236
+ const result = await executeBrowserStatus(
237
+ {},
238
+ makeContext({
239
+ transportInterface: "macos",
240
+ hostBrowserProxy: {
241
+ isAvailable: () => false,
242
+ } as ToolContext["hostBrowserProxy"],
243
+ }),
244
+ );
245
+ expect(result.isError).toBe(false);
246
+ const payload = JSON.parse(result.content);
247
+ const extension = payload.modes.find(
248
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
249
+ );
250
+ expect(extension).toBeDefined();
251
+ expect(extension.available).toBe(false);
252
+ expect(extension.summary).toContain("macOS host browser proxy");
253
+ expect(extension.summary).toContain("SSE transport");
254
+ expect(extension.details.transport).toBe("macos-sse");
255
+ // Should suggest reconnection, not extension install
256
+ expect(
257
+ extension.userActions.some((a: string) => a.includes("desktop app")),
258
+ ).toBe(true);
259
+ });
260
+
261
+ test("macOS: probe failure diagnostics include transport-specific remediation", async () => {
262
+ probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
263
+ probeErrors[BROWSER_STATUS_MODE.EXTENSION] = new CdpError(
264
+ "transport_error",
265
+ "transport disconnected before response",
266
+ );
267
+
268
+ const result = await executeBrowserStatus(
269
+ {},
270
+ makeContext({
271
+ transportInterface: "macos",
272
+ hostBrowserProxy: {
273
+ isAvailable: () => true,
274
+ } as ToolContext["hostBrowserProxy"],
275
+ }),
276
+ );
277
+ expect(result.isError).toBe(false);
278
+ const payload = JSON.parse(result.content);
279
+ const extension = payload.modes.find(
280
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
281
+ );
282
+ expect(extension).toBeDefined();
283
+ expect(extension.available).toBe(false);
284
+ expect(extension.summary).toContain("macOS host browser proxy");
285
+ // Should have remediation actions mentioning SSE bridge
286
+ expect(
287
+ extension.userActions.some((a: string) => a.includes("SSE bridge")),
288
+ ).toBe(true);
289
+ });
290
+
291
+ test("recommendation order follows auto candidate precedence for macOS with available extension proxy", async () => {
292
+ const result = await executeBrowserStatus(
293
+ {},
294
+ makeContext({
295
+ transportInterface: "macos",
296
+ hostBrowserProxy: {
297
+ isAvailable: () => true,
298
+ } as ToolContext["hostBrowserProxy"],
299
+ }),
300
+ );
301
+ expect(result.isError).toBe(false);
302
+ const payload = JSON.parse(result.content);
303
+ // Extension is the top auto candidate and is available, so it should be recommended
304
+ expect(payload.recommendedMode).toBe(BROWSER_STATUS_MODE.EXTENSION);
305
+ expect(payload.autoCandidateOrder[0]).toBe(BROWSER_STATUS_MODE.EXTENSION);
306
+ });
307
+
308
+ test("recommendation falls to cdp-inspect when macOS proxy is unavailable", async () => {
309
+ // Extension probe fails
310
+ probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
311
+ probeErrors[BROWSER_STATUS_MODE.EXTENSION] = new CdpError(
312
+ "transport_error",
313
+ "proxy not connected",
314
+ );
315
+
316
+ const result = await executeBrowserStatus(
317
+ {},
318
+ makeContext({
319
+ transportInterface: "macos",
320
+ // No proxy bound, so extension unavailable
321
+ }),
322
+ );
323
+ expect(result.isError).toBe(false);
324
+ const payload = JSON.parse(result.content);
325
+ // Extension is unavailable (no proxy), so recommendation should fall to next available
326
+ expect(payload.recommendedMode).toBe(BROWSER_STATUS_MODE.CDP_INSPECT);
327
+ });
328
+
329
+ test("macOS: restricted chrome:// page probe includes macOS transport details", async () => {
330
+ probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
331
+ probeErrors[BROWSER_STATUS_MODE.EXTENSION] = new CdpError(
332
+ "cdp_error",
333
+ "Cannot access a chrome:// URL",
334
+ );
335
+
336
+ const result = await executeBrowserStatus(
337
+ {},
338
+ makeContext({
339
+ transportInterface: "macos",
340
+ hostBrowserProxy: {
341
+ isAvailable: () => true,
342
+ } as ToolContext["hostBrowserProxy"],
343
+ }),
344
+ );
345
+ expect(result.isError).toBe(false);
346
+ const payload = JSON.parse(result.content);
347
+ const extension = payload.modes.find(
348
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
349
+ );
350
+ expect(extension).toBeDefined();
351
+ expect(extension.available).toBe(true);
352
+ expect(extension.summary).toContain("macOS host browser proxy");
353
+ expect(extension.details.transport).toBe("macos-sse");
354
+ });
166
355
  });
@@ -88,9 +88,9 @@ type StatusCheckMode = BrowserStatusMode;
88
88
  const MODE_TRADEOFFS: Record<StatusCheckMode, string[]> = {
89
89
  [BROWSER_STATUS_MODE.EXTENSION]: [
90
90
  "This is the preferred approach for all things browser-use.",
91
- "It requires a one-time install of the Vellum Assistant Chrome Extension.",
91
+ "On macOS, the host browser proxy is provisioned automatically via the desktop client's SSE bridge — no extension install required.",
92
+ "When the Chrome extension is also installed, it takes priority for direct WebSocket routing to the user's Chrome session.",
92
93
  "More secure than relying on Chrome's native remote debugging functionality.",
93
- "Requires the Vellum extension to be paired and actively connected.",
94
94
  ],
95
95
  [BROWSER_STATUS_MODE.CDP_INSPECT]: [
96
96
  "This is the second-best approach for all things browser-use, after the native Vellum Assistant Chrome Extension.",
@@ -190,9 +190,10 @@ export function parseBrowserMode(
190
190
  const REMEDIATION_HINTS: Record<string, string[]> = {
191
191
  // Extension backend
192
192
  "extension:transport_error": [
193
- "Ensure the Vellum browser extension is installed and enabled.",
194
- "Check that the extension WebSocket connection is active (extension popup → status).",
195
- "Try reconnecting the extension or reloading the extension service worker.",
193
+ "Ensure the Vellum browser extension is installed and enabled, or that the macOS desktop client is running for host browser proxy mode.",
194
+ "For extension mode: check that the extension WebSocket connection is active (extension popup → status).",
195
+ "For macOS host browser proxy: verify the desktop client is running and has an active SSE connection to the assistant.",
196
+ "Try reconnecting the extension or restarting the desktop client.",
196
197
  ],
197
198
  // cdp-inspect backend — discovery-level failures
198
199
  "cdp-inspect:unreachable": [
@@ -2098,6 +2099,43 @@ function extensionSetupActions(): string[] {
2098
2099
  ];
2099
2100
  }
2100
2101
 
2102
+ function macOSHostBrowserSetupActions(): string[] {
2103
+ return [
2104
+ "Ensure the Vellum desktop app is running and connected to the assistant.",
2105
+ "Open Google Chrome on the desktop machine so the host browser proxy can attach.",
2106
+ "If the desktop client is not running, launch it and wait for the SSE connection to establish.",
2107
+ ];
2108
+ }
2109
+
2110
+ function macOSHostBrowserReconnectActions(): string[] {
2111
+ return [
2112
+ "Verify the Vellum desktop app is still running and has an active network connection.",
2113
+ "If the desktop client was recently restarted, send a new message to re-establish the SSE bridge.",
2114
+ "Ensure Chrome is open on the desktop machine for the host browser proxy to target.",
2115
+ ];
2116
+ }
2117
+
2118
+ function macOSHostBrowserProbeFailureActions(error: CdpError): string[] {
2119
+ const actions: string[] = [
2120
+ "Ensure Google Chrome is running on the desktop machine.",
2121
+ ];
2122
+ const message = error.message.toLowerCase();
2123
+ if (message.includes("timeout") || message.includes("timed out")) {
2124
+ actions.push(
2125
+ "The desktop client may be unresponsive — try restarting the Vellum desktop app.",
2126
+ );
2127
+ }
2128
+ if (message.includes("transport") || message.includes("disconnected")) {
2129
+ actions.push(
2130
+ "The SSE bridge between the assistant and the desktop client appears broken. Send a new message to re-establish the connection.",
2131
+ );
2132
+ }
2133
+ actions.push(
2134
+ "Switch Chrome to a regular http(s) tab (not chrome://...) and retry.",
2135
+ );
2136
+ return dedupeStrings(actions);
2137
+ }
2138
+
2101
2139
  function cdpInspectSetupActions(): string[] {
2102
2140
  return [
2103
2141
  "Update Chrome to the latest version by going to chrome://settings/help",
@@ -2247,6 +2285,7 @@ async function checkExtensionModeStatus(
2247
2285
  ): Promise<BrowserStatusModeResult> {
2248
2286
  const proxyBound = Boolean(context.hostBrowserProxy);
2249
2287
  const proxyConnected = context.hostBrowserProxy?.isAvailable() ?? false;
2288
+ const isMacOS = context.transportInterface === "macos";
2250
2289
 
2251
2290
  if (!proxyBound) {
2252
2291
  return {
@@ -2254,13 +2293,20 @@ async function checkExtensionModeStatus(
2254
2293
  available: false,
2255
2294
  verified: "preflight",
2256
2295
  autoCandidate,
2257
- summary:
2258
- "Extension mode is unavailable: no host browser proxy is bound to this conversation.",
2259
- userActions: extensionSetupActions(),
2296
+ summary: isMacOS
2297
+ ? "Extension mode is unavailable: the macOS host browser proxy is not bound to this conversation. Ensure the desktop client is connected."
2298
+ : "Extension mode is unavailable: no host browser proxy is bound to this conversation.",
2299
+ userActions: isMacOS
2300
+ ? macOSHostBrowserSetupActions()
2301
+ : extensionSetupActions(),
2260
2302
  tradeoffs: modeTradeoffs(BROWSER_STATUS_MODE.EXTENSION),
2261
2303
  details: {
2262
2304
  proxyBound,
2263
2305
  proxyConnected,
2306
+ transport:
2307
+ isMacOS && !context.hostBrowserRegistryRouted
2308
+ ? "macos-sse"
2309
+ : "extension-ws",
2264
2310
  },
2265
2311
  };
2266
2312
  }
@@ -2271,13 +2317,20 @@ async function checkExtensionModeStatus(
2271
2317
  available: false,
2272
2318
  verified: "preflight",
2273
2319
  autoCandidate,
2274
- summary:
2275
- "Extension mode is unavailable: the extension transport is currently disconnected.",
2276
- userActions: extensionSetupActions(),
2320
+ summary: isMacOS
2321
+ ? "Extension mode is unavailable: the macOS host browser proxy is bound but the SSE transport is currently disconnected. Verify the desktop client is running and connected."
2322
+ : "Extension mode is unavailable: the extension transport is currently disconnected.",
2323
+ userActions: isMacOS
2324
+ ? macOSHostBrowserReconnectActions()
2325
+ : extensionSetupActions(),
2277
2326
  tradeoffs: modeTradeoffs(BROWSER_STATUS_MODE.EXTENSION),
2278
2327
  details: {
2279
2328
  proxyBound,
2280
2329
  proxyConnected,
2330
+ transport:
2331
+ isMacOS && !context.hostBrowserRegistryRouted
2332
+ ? "macos-sse"
2333
+ : "extension-ws",
2281
2334
  },
2282
2335
  };
2283
2336
  }
@@ -2292,13 +2345,19 @@ async function checkExtensionModeStatus(
2292
2345
  available: true,
2293
2346
  verified: "active_probe",
2294
2347
  autoCandidate,
2295
- summary: "Extension mode is ready and responded to an active CDP probe.",
2348
+ summary: isMacOS
2349
+ ? "Extension mode is ready via macOS host browser proxy and responded to an active CDP probe."
2350
+ : "Extension mode is ready and responded to an active CDP probe.",
2296
2351
  userActions: [],
2297
2352
  tradeoffs: modeTradeoffs(BROWSER_STATUS_MODE.EXTENSION),
2298
2353
  details: {
2299
2354
  proxyBound,
2300
2355
  proxyConnected,
2301
2356
  backendKind: probe.backendKind,
2357
+ transport:
2358
+ isMacOS && !context.hostBrowserRegistryRouted
2359
+ ? "macos-sse"
2360
+ : "extension-ws",
2302
2361
  },
2303
2362
  };
2304
2363
  }
@@ -2309,8 +2368,9 @@ async function checkExtensionModeStatus(
2309
2368
  available: true,
2310
2369
  verified: "active_probe",
2311
2370
  autoCandidate,
2312
- summary:
2313
- "Extension mode transport is connected, but the active Chrome tab is a restricted chrome:// page. Switch to a regular website tab if browser actions fail.",
2371
+ summary: isMacOS
2372
+ ? "Extension mode transport is connected via macOS host browser proxy, but the active Chrome tab is a restricted chrome:// page. Switch to a regular website tab if browser actions fail."
2373
+ : "Extension mode transport is connected, but the active Chrome tab is a restricted chrome:// page. Switch to a regular website tab if browser actions fail.",
2314
2374
  userActions: [
2315
2375
  "Switch Chrome to a regular http(s) tab (not chrome://...) and retry.",
2316
2376
  ],
@@ -2322,6 +2382,10 @@ async function checkExtensionModeStatus(
2322
2382
  errorCode: probe.error.code,
2323
2383
  diagnostic: probe.diagnostic,
2324
2384
  attemptDiagnostics: probe.error.attemptDiagnostics ?? [],
2385
+ transport:
2386
+ isMacOS && !context.hostBrowserRegistryRouted
2387
+ ? "macos-sse"
2388
+ : "extension-ws",
2325
2389
  },
2326
2390
  };
2327
2391
  }
@@ -2331,11 +2395,12 @@ async function checkExtensionModeStatus(
2331
2395
  available: false,
2332
2396
  verified: "active_probe",
2333
2397
  autoCandidate,
2334
- summary: `Extension mode probe failed: ${probe.error.message}`,
2335
- userActions: probeFailureActions(
2336
- BROWSER_STATUS_MODE.EXTENSION,
2337
- probe.error,
2338
- ),
2398
+ summary: isMacOS
2399
+ ? `Extension mode probe failed via macOS host browser proxy: ${probe.error.message}`
2400
+ : `Extension mode probe failed: ${probe.error.message}`,
2401
+ userActions: isMacOS
2402
+ ? macOSHostBrowserProbeFailureActions(probe.error)
2403
+ : probeFailureActions(BROWSER_STATUS_MODE.EXTENSION, probe.error),
2339
2404
  tradeoffs: modeTradeoffs(BROWSER_STATUS_MODE.EXTENSION),
2340
2405
  details: {
2341
2406
  proxyBound,
@@ -2343,6 +2408,10 @@ async function checkExtensionModeStatus(
2343
2408
  errorCode: probe.error.code,
2344
2409
  diagnostic: probe.diagnostic,
2345
2410
  attemptDiagnostics: probe.error.attemptDiagnostics ?? [],
2411
+ transport:
2412
+ isMacOS && !context.hostBrowserRegistryRouted
2413
+ ? "macos-sse"
2414
+ : "extension-ws",
2346
2415
  },
2347
2416
  };
2348
2417
  }
@@ -356,4 +356,234 @@ describe("ExtensionCdpClient", () => {
356
356
  expect(err.message).toBe("CDP call aborted");
357
357
  expect(err.cdpMethod).toBe("Browser.getVersion");
358
358
  });
359
+
360
+ // ── Structured transport error classification ────────────────────────
361
+
362
+ test("structured error with code 'transport_error' is classified as transport_error", async () => {
363
+ const errorBody = {
364
+ code: "transport_error",
365
+ message: "Extension WebSocket disconnected",
366
+ };
367
+ const { proxy } = fakeProxy(async () => ({
368
+ content: JSON.stringify(errorBody),
369
+ isError: true,
370
+ }));
371
+
372
+ const client = createExtensionCdpClient(proxy, "conv-transport-1");
373
+
374
+ let caught: unknown;
375
+ try {
376
+ await client.send("Page.navigate", { url: "https://example.com" });
377
+ } catch (err) {
378
+ caught = err;
379
+ }
380
+
381
+ expect(caught).toBeInstanceOf(CdpError);
382
+ const err = caught as CdpError;
383
+ expect(err.code).toBe("transport_error");
384
+ expect(err.message).toBe("Extension WebSocket disconnected");
385
+ expect(err.cdpMethod).toBe("Page.navigate");
386
+ expect(err.cdpParams).toEqual({ url: "https://example.com" });
387
+ expect(err.underlying).toEqual(errorBody);
388
+ });
389
+
390
+ test("structured error with code 'unreachable' is classified as transport_error", async () => {
391
+ const errorBody = {
392
+ code: "unreachable",
393
+ message: "Host browser not reachable",
394
+ };
395
+ const { proxy } = fakeProxy(async () => ({
396
+ content: JSON.stringify(errorBody),
397
+ isError: true,
398
+ }));
399
+
400
+ const client = createExtensionCdpClient(proxy, "conv-transport-2");
401
+
402
+ let caught: unknown;
403
+ try {
404
+ await client.send("Runtime.evaluate", { expression: "1+1" });
405
+ } catch (err) {
406
+ caught = err;
407
+ }
408
+
409
+ expect(caught).toBeInstanceOf(CdpError);
410
+ const err = caught as CdpError;
411
+ expect(err.code).toBe("transport_error");
412
+ expect(err.message).toBe("Host browser not reachable");
413
+ expect(err.underlying).toEqual(errorBody);
414
+ });
415
+
416
+ test("structured error with code 'timeout' is classified as transport_error", async () => {
417
+ const errorBody = {
418
+ code: "timeout",
419
+ message: "CDP call timed out",
420
+ };
421
+ const { proxy } = fakeProxy(async () => ({
422
+ content: JSON.stringify(errorBody),
423
+ isError: true,
424
+ }));
425
+
426
+ const client = createExtensionCdpClient(proxy, "conv-transport-3");
427
+
428
+ let caught: unknown;
429
+ try {
430
+ await client.send("Page.captureScreenshot");
431
+ } catch (err) {
432
+ caught = err;
433
+ }
434
+
435
+ expect(caught).toBeInstanceOf(CdpError);
436
+ const err = caught as CdpError;
437
+ expect(err.code).toBe("transport_error");
438
+ expect(err.message).toBe("CDP call timed out");
439
+ expect(err.underlying).toEqual(errorBody);
440
+ });
441
+
442
+ test("structured error with code 'non_loopback' is classified as transport_error", async () => {
443
+ const errorBody = {
444
+ code: "non_loopback",
445
+ message: "Only loopback addresses are allowed",
446
+ };
447
+ const { proxy } = fakeProxy(async () => ({
448
+ content: JSON.stringify(errorBody),
449
+ isError: true,
450
+ }));
451
+
452
+ const client = createExtensionCdpClient(proxy, "conv-transport-4");
453
+
454
+ let caught: unknown;
455
+ try {
456
+ await client.send("Browser.getVersion");
457
+ } catch (err) {
458
+ caught = err;
459
+ }
460
+
461
+ expect(caught).toBeInstanceOf(CdpError);
462
+ const err = caught as CdpError;
463
+ expect(err.code).toBe("transport_error");
464
+ expect(err.message).toBe("Only loopback addresses are allowed");
465
+ expect(err.underlying).toEqual(errorBody);
466
+ });
467
+
468
+ // ── Command-level CDP errors remain cdp_error ────────────────────────
469
+
470
+ test("structured error with numeric code (CDP JSON-RPC) remains cdp_error", async () => {
471
+ const errorBody = {
472
+ code: -32000,
473
+ message: "Cannot find context with specified id",
474
+ };
475
+ const { proxy } = fakeProxy(async () => ({
476
+ content: JSON.stringify(errorBody),
477
+ isError: true,
478
+ }));
479
+
480
+ const client = createExtensionCdpClient(proxy, "conv-cdp-1");
481
+
482
+ let caught: unknown;
483
+ try {
484
+ await client.send("Runtime.evaluate", { expression: "boom" });
485
+ } catch (err) {
486
+ caught = err;
487
+ }
488
+
489
+ expect(caught).toBeInstanceOf(CdpError);
490
+ const err = caught as CdpError;
491
+ expect(err.code).toBe("cdp_error");
492
+ expect(err.message).toBe("Cannot find context with specified id");
493
+ });
494
+
495
+ test("structured error with unknown string code remains cdp_error", async () => {
496
+ const errorBody = {
497
+ code: "some_unknown_code",
498
+ message: "Unrecognized error",
499
+ };
500
+ const { proxy } = fakeProxy(async () => ({
501
+ content: JSON.stringify(errorBody),
502
+ isError: true,
503
+ }));
504
+
505
+ const client = createExtensionCdpClient(proxy, "conv-cdp-2");
506
+
507
+ let caught: unknown;
508
+ try {
509
+ await client.send("Page.navigate", { url: "https://example.com" });
510
+ } catch (err) {
511
+ caught = err;
512
+ }
513
+
514
+ expect(caught).toBeInstanceOf(CdpError);
515
+ const err = caught as CdpError;
516
+ expect(err.code).toBe("cdp_error");
517
+ expect(err.message).toBe("Unrecognized error");
518
+ });
519
+
520
+ test("structured error without code field remains cdp_error", async () => {
521
+ const errorBody = {
522
+ message: "Something went wrong",
523
+ };
524
+ const { proxy } = fakeProxy(async () => ({
525
+ content: JSON.stringify(errorBody),
526
+ isError: true,
527
+ }));
528
+
529
+ const client = createExtensionCdpClient(proxy, "conv-cdp-3");
530
+
531
+ let caught: unknown;
532
+ try {
533
+ await client.send("DOM.getDocument");
534
+ } catch (err) {
535
+ caught = err;
536
+ }
537
+
538
+ expect(caught).toBeInstanceOf(CdpError);
539
+ const err = caught as CdpError;
540
+ expect(err.code).toBe("cdp_error");
541
+ expect(err.message).toBe("Something went wrong");
542
+ });
543
+
544
+ test("structured error with code: null remains cdp_error", async () => {
545
+ const errorBody = {
546
+ code: null,
547
+ message: "Null code error",
548
+ };
549
+ const { proxy } = fakeProxy(async () => ({
550
+ content: JSON.stringify(errorBody),
551
+ isError: true,
552
+ }));
553
+
554
+ const client = createExtensionCdpClient(proxy, "conv-cdp-4");
555
+
556
+ let caught: unknown;
557
+ try {
558
+ await client.send("Browser.getVersion");
559
+ } catch (err) {
560
+ caught = err;
561
+ }
562
+
563
+ expect(caught).toBeInstanceOf(CdpError);
564
+ const err = caught as CdpError;
565
+ expect(err.code).toBe("cdp_error");
566
+ });
567
+
568
+ test("plain-text isError content still throws cdp_error (no failover)", async () => {
569
+ const { proxy } = fakeProxy(async () => ({
570
+ content: "Connection lost unexpectedly",
571
+ isError: true,
572
+ }));
573
+
574
+ const client = createExtensionCdpClient(proxy, "conv-cdp-5");
575
+
576
+ let caught: unknown;
577
+ try {
578
+ await client.send("Page.navigate", { url: "https://example.com" });
579
+ } catch (err) {
580
+ caught = err;
581
+ }
582
+
583
+ expect(caught).toBeInstanceOf(CdpError);
584
+ const err = caught as CdpError;
585
+ expect(err.code).toBe("cdp_error");
586
+ expect(err.message).toBe("Connection lost unexpectedly");
587
+ expect(err.underlying).toBe("Connection lost unexpectedly");
588
+ });
359
589
  });