@vellumai/assistant 0.8.10 → 0.8.11-staging.1

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 (400) hide show
  1. package/bun.lock +62 -1
  2. package/docs/workspace-tools.md +196 -0
  3. package/examples/plugins/echo/README.md +3 -3
  4. package/knip.json +1 -0
  5. package/openapi.yaml +460 -128
  6. package/package.json +2 -1
  7. package/scripts/build-plugin-api.ts +299 -0
  8. package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
  9. package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
  10. package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
  12. package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
  13. package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
  14. package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
  15. package/src/__tests__/agent-loop-thinking.test.ts +4 -0
  16. package/src/__tests__/agent-loop.test.ts +578 -5
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  20. package/src/__tests__/btw-routes.test.ts +0 -1
  21. package/src/__tests__/build-persisted-content.test.ts +75 -1
  22. package/src/__tests__/catalog-install-normalize.test.ts +141 -0
  23. package/src/__tests__/ces-startup-timeout.test.ts +60 -0
  24. package/src/__tests__/compaction-events.test.ts +1 -0
  25. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  26. package/src/__tests__/context-overflow-reducer.test.ts +264 -124
  27. package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
  28. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  29. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
  30. package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
  31. package/src/__tests__/conversation-agent-loop.test.ts +131 -551
  32. package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
  33. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  34. package/src/__tests__/conversation-fork-crud.test.ts +259 -0
  35. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  36. package/src/__tests__/conversation-lifecycle.test.ts +257 -1
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
  39. package/src/__tests__/conversation-queue.test.ts +1 -39
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
  41. package/src/__tests__/conversation-skill-tools.test.ts +491 -5
  42. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  43. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  44. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  45. package/src/__tests__/conversation-store.test.ts +74 -0
  46. package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
  47. package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
  48. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
  52. package/src/__tests__/corrected-target.test.ts +93 -0
  53. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
  54. package/src/__tests__/credential-execution-tools.test.ts +23 -11
  55. package/src/__tests__/credential-security-invariants.test.ts +6 -1
  56. package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
  57. package/src/__tests__/device-id.test.ts +70 -1
  58. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  59. package/src/__tests__/empty-response-hook.test.ts +242 -66
  60. package/src/__tests__/external-plugin-loader.test.ts +0 -31
  61. package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
  62. package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
  63. package/src/__tests__/history-repair-hook.test.ts +228 -3
  64. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +254 -9
  66. package/src/__tests__/identity-routes.test.ts +1 -0
  67. package/src/__tests__/image-recovery-hook.test.ts +387 -0
  68. package/src/__tests__/injector-chain.test.ts +5 -4
  69. package/src/__tests__/injector-v3-suppression.test.ts +373 -47
  70. package/src/__tests__/intent-routing.test.ts +7 -0
  71. package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
  72. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  73. package/src/__tests__/oauth-store.test.ts +0 -85
  74. package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
  75. package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
  76. package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
  77. package/src/__tests__/persist-unsendable-image.test.ts +4 -4
  78. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  79. package/src/__tests__/plugin-bootstrap.test.ts +82 -73
  80. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  81. package/src/__tests__/plugin-types.test.ts +0 -8
  82. package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
  83. package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
  84. package/src/__tests__/registry.test.ts +240 -1
  85. package/src/__tests__/require-fresh-approval.test.ts +3 -0
  86. package/src/__tests__/schedule-routes.test.ts +116 -1
  87. package/src/__tests__/schedule-store.test.ts +28 -0
  88. package/src/__tests__/schedule-tools.test.ts +94 -1
  89. package/src/__tests__/server-history-render.test.ts +39 -0
  90. package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
  91. package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
  92. package/src/__tests__/skills.test.ts +202 -0
  93. package/src/__tests__/slim-skill-category.test.ts +195 -0
  94. package/src/__tests__/strip-memory-injections.test.ts +33 -39
  95. package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
  96. package/src/__tests__/title-generate-hook.test.ts +9 -7
  97. package/src/__tests__/tool-audit-listener.test.ts +264 -1
  98. package/src/__tests__/tool-error-hook.test.ts +4 -3
  99. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  100. package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
  101. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  102. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  103. package/src/__tests__/tools-get-route.test.ts +202 -0
  104. package/src/__tests__/workspace-tool-loader.test.ts +319 -0
  105. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  106. package/src/agent/loop.ts +569 -319
  107. package/src/api/events/tool-result.ts +9 -0
  108. package/src/api/events/tool-use-start.ts +7 -0
  109. package/src/api/index.ts +10 -0
  110. package/src/api/responses/conversation-message.ts +135 -27
  111. package/src/api/responses/memory-v3-selection-log.ts +4 -4
  112. package/src/approvals/guardian-request-resolvers.ts +26 -0
  113. package/src/browser-session/backends/host-bridge.ts +29 -0
  114. package/src/browser-session/index.ts +1 -0
  115. package/src/browser-session/types.ts +5 -1
  116. package/src/cli/commands/__tests__/schedules.test.ts +62 -4
  117. package/src/cli/commands/__tests__/skills.test.ts +53 -0
  118. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  119. package/src/cli/commands/inference-providers.ts +0 -8
  120. package/src/cli/commands/plugins.ts +2 -2
  121. package/src/cli/commands/schedules.ts +27 -4
  122. package/src/cli/commands/skills.ts +187 -146
  123. package/src/cli/commands/tools.ts +106 -0
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
  125. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
  126. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
  127. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
  128. package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
  129. package/src/cli/lib/install-from-github.ts +187 -117
  130. package/src/cli/lib/plugin-catalog-cache.ts +9 -9
  131. package/src/cli/lib/plugin-details.ts +38 -68
  132. package/src/cli/lib/plugin-marketplace.ts +42 -14
  133. package/src/cli/lib/search-plugins.ts +29 -129
  134. package/src/cli/program.ts +2 -0
  135. package/src/config/bundled-skills/acp/SKILL.md +1 -0
  136. package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
  137. package/src/config/bundled-skills/app-control/SKILL.md +1 -0
  138. package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
  139. package/src/config/bundled-skills/contacts/SKILL.md +1 -0
  140. package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
  141. package/src/config/bundled-skills/followups/SKILL.md +1 -0
  142. package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
  143. package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
  144. package/src/config/bundled-skills/messaging/SKILL.md +1 -0
  145. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
  146. package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +1 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
  149. package/src/config/bundled-skills/sequences/SKILL.md +1 -0
  150. package/src/config/bundled-skills/settings/SKILL.md +1 -0
  151. package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
  152. package/src/config/bundled-skills/subagent/SKILL.md +1 -0
  153. package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
  154. package/src/config/env-registry.ts +23 -0
  155. package/src/config/feature-flag-registry.json +17 -81
  156. package/src/config/loader.ts +5 -22
  157. package/src/config/schema.ts +2 -0
  158. package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
  159. package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
  160. package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
  161. package/src/config/schemas/compaction-logs.ts +79 -0
  162. package/src/config/schemas/memory-v2.ts +0 -8
  163. package/src/config/schemas/memory-v3.ts +104 -33
  164. package/src/config/seed-inference-profiles.ts +1 -1
  165. package/src/config/skills.ts +117 -47
  166. package/src/context/compactor.ts +11 -0
  167. package/src/context/strip-injections.ts +38 -4
  168. package/src/credential-execution/feature-gates.ts +0 -21
  169. package/src/credential-execution/startup-timeout.ts +32 -4
  170. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
  171. package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
  172. package/src/daemon/conversation-agent-loop.ts +123 -658
  173. package/src/daemon/conversation-error.ts +6 -33
  174. package/src/daemon/conversation-lifecycle.ts +1 -1
  175. package/src/daemon/conversation-runtime-assembly.ts +183 -22
  176. package/src/daemon/conversation-skill-tools.ts +137 -8
  177. package/src/daemon/conversation-slash.ts +0 -14
  178. package/src/daemon/conversation-store.ts +2 -19
  179. package/src/daemon/conversation-tool-setup.ts +87 -1
  180. package/src/daemon/conversation.ts +119 -50
  181. package/src/daemon/external-plugins-bootstrap.ts +36 -95
  182. package/src/daemon/handlers/config-channels.ts +11 -2
  183. package/src/daemon/handlers/shared.ts +9 -1
  184. package/src/daemon/handlers/skills.ts +10 -4
  185. package/src/daemon/host-app-control-proxy.ts +72 -57
  186. package/src/daemon/host-browser-proxy.ts +117 -22
  187. package/src/daemon/lifecycle.ts +34 -5
  188. package/src/daemon/message-protocol.ts +0 -7
  189. package/src/daemon/message-types/schedules.ts +1 -0
  190. package/src/daemon/message-types/skills.ts +17 -0
  191. package/src/daemon/providers-setup.ts +3 -0
  192. package/src/daemon/server.ts +3 -3
  193. package/src/daemon/tool-setup-types.ts +9 -3
  194. package/src/daemon/trust-context.ts +23 -0
  195. package/src/daemon/workspace-tools-watcher.ts +324 -0
  196. package/src/events/tool-audit-listener.ts +78 -16
  197. package/src/events/tool-metrics-listener.ts +2 -5
  198. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
  199. package/src/memory/__tests__/conversation-queries.test.ts +176 -0
  200. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
  201. package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
  202. package/src/memory/conversation-crud.ts +134 -3
  203. package/src/memory/conversation-queries.ts +64 -7
  204. package/src/memory/db-init.ts +14 -0
  205. package/src/memory/embedding-backend.test.ts +130 -1
  206. package/src/memory/embedding-backend.ts +79 -106
  207. package/src/memory/embedding-gemini.ts +5 -0
  208. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
  209. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
  210. package/src/memory/graph/conversation-graph-memory.ts +36 -25
  211. package/src/memory/graph/tool-handlers.ts +3 -0
  212. package/src/memory/job-handlers/cleanup.ts +3 -1
  213. package/src/memory/jobs-store.ts +0 -28
  214. package/src/memory/jobs-worker.ts +10 -22
  215. package/src/memory/memory-marker.ts +29 -0
  216. package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
  217. package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
  218. package/src/memory/migrations/270-schedule-description.ts +36 -0
  219. package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
  220. package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
  221. package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
  222. package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
  223. package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
  224. package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
  225. package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
  226. package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
  227. package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
  228. package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
  229. package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
  230. package/src/memory/migrations/index.ts +10 -0
  231. package/src/memory/migrations/registry.ts +8 -0
  232. package/src/memory/schema/conversations.ts +16 -0
  233. package/src/memory/schema/infrastructure.ts +26 -0
  234. package/src/memory/skill-loaded-events-store.test.ts +160 -0
  235. package/src/memory/skill-loaded-events-store.ts +95 -0
  236. package/src/memory/tool-executed-events-store.test.ts +219 -0
  237. package/src/memory/tool-executed-events-store.ts +102 -0
  238. package/src/memory/tool-usage-store.ts +15 -3
  239. package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
  240. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
  241. package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
  242. package/src/memory/v2/__tests__/page-store.test.ts +33 -0
  243. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
  244. package/src/memory/v2/activation-store.ts +50 -1
  245. package/src/memory/v2/consolidation-job.ts +113 -29
  246. package/src/memory/v2/injected-block-slugs.ts +79 -0
  247. package/src/memory/v2/injection.ts +6 -1
  248. package/src/memory/v2/prompts/consolidation.ts +414 -13
  249. package/src/memory/v2/router.ts +2 -28
  250. package/src/memory/v2/static-context.ts +1 -1
  251. package/src/memory/v2/types.ts +16 -0
  252. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  253. package/src/notifications/access-request-copy.ts +298 -0
  254. package/src/notifications/adapters/slack.ts +3 -3
  255. package/src/notifications/adapters/telegram.ts +2 -1
  256. package/src/notifications/copy-composer.ts +49 -267
  257. package/src/notifications/decision-engine.ts +16 -35
  258. package/src/notifications/home-feed-side-effect.ts +1 -6
  259. package/src/oauth/oauth-store.ts +0 -9
  260. package/src/permissions/checker.test.ts +83 -1
  261. package/src/permissions/checker.ts +25 -2
  262. package/src/permissions/gateway-threshold-reader.test.ts +182 -0
  263. package/src/permissions/gateway-threshold-reader.ts +80 -0
  264. package/src/platform/client.ts +1 -3
  265. package/src/platform/feature-gate.ts +3 -12
  266. package/src/plugin-api/constants.ts +4 -2
  267. package/src/plugin-api/index.ts +63 -11
  268. package/src/plugin-api/types.ts +236 -71
  269. package/src/plugins/defaults/compaction/compact.ts +66 -2
  270. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
  271. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  272. package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
  273. package/src/plugins/defaults/compaction/window-manager.ts +303 -1
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
  275. package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
  276. package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
  277. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
  278. package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
  279. package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
  280. package/src/plugins/defaults/history-repair/terminal.ts +39 -2
  281. package/src/plugins/defaults/image-recovery/detect.ts +25 -0
  282. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
  283. package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
  284. package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
  285. package/src/plugins/defaults/image-recovery/package.json +14 -0
  286. package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
  287. package/src/plugins/defaults/index.ts +71 -5
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
  289. package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
  291. package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
  304. package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
  305. package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
  306. package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
  307. package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
  308. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
  309. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
  310. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
  311. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
  312. package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
  313. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
  314. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
  315. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
  316. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
  317. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
  318. package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
  319. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
  320. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
  321. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
  322. package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
  323. package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
  324. package/src/plugins/pipeline.ts +8 -5
  325. package/src/plugins/types.ts +5 -51
  326. package/src/providers/cache-control.ts +26 -0
  327. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
  328. package/src/providers/model-catalog.ts +13 -1
  329. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
  330. package/src/providers/openai/chat-completions-provider.ts +46 -0
  331. package/src/providers/openai/responses-provider.ts +45 -0
  332. package/src/providers/registry.ts +0 -8
  333. package/src/runtime/__tests__/agent-wake.test.ts +0 -1
  334. package/src/runtime/agent-wake.ts +13 -0
  335. package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
  336. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
  337. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
  338. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
  339. package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
  340. package/src/runtime/routes/acp-routes.test.ts +106 -0
  341. package/src/runtime/routes/acp-routes.ts +248 -2
  342. package/src/runtime/routes/browser-tabs-routes.ts +1 -1
  343. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  344. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
  345. package/src/runtime/routes/consolidation-routes.ts +6 -82
  346. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  347. package/src/runtime/routes/conversation-management-routes.ts +71 -0
  348. package/src/runtime/routes/identity-routes.ts +8 -0
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  350. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
  351. package/src/runtime/routes/plugins-routes.ts +34 -45
  352. package/src/runtime/routes/schedule-routes.ts +43 -5
  353. package/src/runtime/routes/settings-routes.ts +140 -15
  354. package/src/runtime/routes/skills-routes.ts +18 -6
  355. package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
  356. package/src/runtime/services/conversation-serializer.ts +38 -1
  357. package/src/runtime/verification-outbound-actions.ts +147 -2
  358. package/src/runtime/verification-templates.ts +29 -3
  359. package/src/schedule/schedule-store.ts +19 -0
  360. package/src/skills/catalog-install.ts +77 -13
  361. package/src/tasks/task-scheduler.ts +1 -0
  362. package/src/telemetry/types.ts +66 -1
  363. package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
  364. package/src/telemetry/usage-telemetry-reporter.ts +213 -20
  365. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
  366. package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
  367. package/src/tools/browser/browser-execution.ts +27 -9
  368. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
  369. package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
  370. package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
  371. package/src/tools/browser/cdp-client/factory.ts +217 -17
  372. package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
  373. package/src/tools/browser/cdp-client/types.ts +22 -2
  374. package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
  375. package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
  376. package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
  377. package/src/tools/executor.ts +39 -7
  378. package/src/tools/registry.ts +387 -5
  379. package/src/tools/schedule/create.ts +16 -0
  380. package/src/tools/schedule/list.ts +12 -4
  381. package/src/tools/schedule/update.ts +12 -0
  382. package/src/tools/skills/load.ts +11 -6
  383. package/src/tools/terminal/safe-env.ts +2 -0
  384. package/src/tools/types.ts +65 -9
  385. package/src/tools/workspace-tools/loader.ts +673 -0
  386. package/src/usage/attribution.ts +28 -0
  387. package/src/util/device-id.ts +17 -3
  388. package/src/util/platform.ts +16 -0
  389. package/tsconfig.plugin-api.json +13 -0
  390. package/src/__tests__/plugin-external-api.test.ts +0 -68
  391. package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
  392. package/src/daemon/message-types/browser.ts +0 -10
  393. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  394. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
  395. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
  396. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
  397. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
  398. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
  399. package/src/plugins/external-api.ts +0 -114
  400. package/src/plugins/plugin-skill-contributions.ts +0 -292
@@ -23,8 +23,11 @@ import {
23
23
  import { queryUnreportedLifecycleEvents } from "../memory/lifecycle-events-store.js";
24
24
  import { queryUnreportedUsageEvents } from "../memory/llm-usage-store.js";
25
25
  import { queryUnreportedOnboardingEvents } from "../memory/onboarding-events-store.js";
26
+ import { queryUnreportedSkillLoadedEvents } from "../memory/skill-loaded-events-store.js";
27
+ import { queryUnreportedToolExecutedEvents } from "../memory/tool-executed-events-store.js";
26
28
  import { queryUnreportedTurnEvents } from "../memory/turn-events-store.js";
27
29
  import { VellumPlatformClient } from "../platform/client.js";
30
+ import type { UsageAttributionProfileSource } from "../usage/types.js";
28
31
  import { getDeviceId } from "../util/device-id.js";
29
32
  import { getLogger } from "../util/logger.js";
30
33
  import { APP_VERSION } from "../version.js";
@@ -56,6 +59,39 @@ const CHECKPOINT_KEY_AUTH_FALLBACK_WATERMARK =
56
59
  "telemetry:auth_fallback:last_reported_at";
57
60
  const CHECKPOINT_KEY_AUTH_FALLBACK_WATERMARK_ID =
58
61
  "telemetry:auth_fallback:last_reported_id";
62
+ const CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK =
63
+ "telemetry:tool_executed:last_reported_at";
64
+ const CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK_ID =
65
+ "telemetry:tool_executed:last_reported_id";
66
+ const CHECKPOINT_KEY_SKILL_LOADED_WATERMARK =
67
+ "telemetry:skill_loaded:last_reported_at";
68
+ const CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID =
69
+ "telemetry:skill_loaded:last_reported_id";
70
+ // Written into the `*_id` watermark checkpoints by the opt-out flush branch.
71
+ // Sorts lexicographically above every real row ID (all event stores generate
72
+ // lowercase v4 UUIDs), so the compound cursor's same-millisecond arm
73
+ // (`createdAt == watermark AND id > afterId`) can never match an opt-out row.
74
+ const OPT_OUT_WATERMARK_ID_SENTINEL = "ffffffff-ffff-ffff-ffff-ffffffffffff";
75
+ // (timestamp, id) checkpoint-key pairs for every event type's compound
76
+ // cursor — keep in sync when adding an event type.
77
+ const WATERMARK_KEY_PAIRS: ReadonlyArray<readonly [string, string]> = [
78
+ [CHECKPOINT_KEY_WATERMARK, CHECKPOINT_KEY_WATERMARK_ID],
79
+ [CHECKPOINT_KEY_TURN_WATERMARK, CHECKPOINT_KEY_TURN_WATERMARK_ID],
80
+ [CHECKPOINT_KEY_LIFECYCLE_WATERMARK, CHECKPOINT_KEY_LIFECYCLE_WATERMARK_ID],
81
+ [CHECKPOINT_KEY_ONBOARDING_WATERMARK, CHECKPOINT_KEY_ONBOARDING_WATERMARK_ID],
82
+ [
83
+ CHECKPOINT_KEY_AUTH_FALLBACK_WATERMARK,
84
+ CHECKPOINT_KEY_AUTH_FALLBACK_WATERMARK_ID,
85
+ ],
86
+ [
87
+ CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK,
88
+ CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK_ID,
89
+ ],
90
+ [
91
+ CHECKPOINT_KEY_SKILL_LOADED_WATERMARK,
92
+ CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID,
93
+ ],
94
+ ];
59
95
  const REPORT_INTERVAL_MS = 5 * 60 * 1000;
60
96
  const INITIAL_FLUSH_DELAY_MS = 30_000; // Delay first flush to let CES handshake complete
61
97
  const BATCH_SIZE = 500;
@@ -87,6 +123,41 @@ export class UsageTelemetryReporter {
87
123
  private timer: ReturnType<typeof setInterval> | null = null;
88
124
  private activeFlush: Promise<void> | null = null;
89
125
 
126
+ constructor() {
127
+ // `tool_invocations` is an always-on audit table that predates this
128
+ // reporter shipping its rows: an absent watermark means no flush (opted
129
+ // in or out) has ever advanced it, so rows recorded before this build —
130
+ // including any opted-out period under older builds that gated the
131
+ // reporter on collectUsageData — would otherwise ship retroactively.
132
+ // Initialize an absent watermark to "now" at construction. Construction
133
+ // happens during daemon startup before any tool runs, so no legitimate
134
+ // row falls behind the watermark — initializing at first FLUSH instead
135
+ // would drop tools used during the 30s+ flush delay. The checkpoint is
136
+ // persisted immediately so a crash before the first flush can't leave it
137
+ // absent and re-initialize later. An EXISTING watermark is never touched:
138
+ // opted-out sessions keep it advancing via the opt-out flush branch, and
139
+ // overwriting it here would drop a legitimate unshipped backlog.
140
+ // `skill_loaded` needs no init: recording is gated on collectUsageData,
141
+ // so opt-out rows never exist and its standard 0 default is safe.
142
+ //
143
+ // Best-effort: DB init failures are tolerated at daemon startup (degraded
144
+ // mode), so this must never throw out of the constructor — matching
145
+ // flush(), which treats DB errors as non-fatal.
146
+ try {
147
+ if (getMemoryCheckpoint(CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK) == null) {
148
+ setMemoryCheckpoint(
149
+ CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK,
150
+ String(Date.now()),
151
+ );
152
+ }
153
+ } catch (err) {
154
+ log.warn(
155
+ { err },
156
+ "tool_executed watermark init failed at construction — non-fatal; a later construction with a working DB re-runs the absent-checkpoint init",
157
+ );
158
+ }
159
+ }
160
+
90
161
  start(): void {
91
162
  // Delay the first flush to allow the credential infrastructure (CES
92
163
  // handshake) to complete. Without this delay, VellumPlatformClient.create()
@@ -134,21 +205,35 @@ export class UsageTelemetryReporter {
134
205
  try {
135
206
  if (batchCount >= MAX_CONSECUTIVE_BATCHES) return;
136
207
 
137
- // Respect runtime opt-out: if the user has disabled usage data collection,
138
- // skip the flush and advance watermarks so events recorded during the
139
- // opt-out window are never sent retroactively.
208
+ // Respect opt-out: if the user has disabled usage data collection, skip
209
+ // the flush and advance watermarks so events recorded during the
210
+ // opt-out window are never sent retroactively. The daemon runs the
211
+ // reporter even when opted out specifically so this branch keeps
212
+ // executing — every cycle plus the final flush in stop() — which is
213
+ // what lets a later opt-in (runtime or via restart) resume from a
214
+ // watermark that already covers the opt-out window. One caveat: a
215
+ // RUNTIME false→true flip can still ship up to one flush interval
216
+ // (≤5 min) of pre-toggle rows recorded since the last opted-out flush;
217
+ // the restart path is fully covered by the final flush in stop(). The
218
+ // caveat applies to the always-on tables without a write-time opt-out
219
+ // gate (llm_usage, turn events) and to tool_invocations rows recorded
220
+ // under builds predating the audit listener's write-time gate — new
221
+ // opted-out tool_invocations rows persist NULL telemetry columns and
222
+ // are unreportable by construction regardless of watermark timing.
140
223
  if (!getConfig().collectUsageData) {
141
- // Advance only the timestamp watermarks. Leave the ID watermarks
142
- // untouched so the compound-cursor branch stays active — setting them
143
- // to "" would make the truthy check fail, falling back to a
144
- // timestamp-only `gt(createdAt, watermark)` query that silently drops
145
- // events created in the same millisecond as the opt-out watermark.
224
+ // Advance the timestamp watermarks and pin the ID watermarks to a
225
+ // sentinel that sorts above any real UUID. The sentinel (rather than
226
+ // "") keeps the compound-cursor branch active — a falsy ID would
227
+ // downgrade the query to a timestamp-only `gt(createdAt, watermark)`
228
+ // — while making its same-millisecond arm unsatisfiable, so a row
229
+ // written in the same millisecond as this flush's Date.now() can
230
+ // never ship after a later opt-in. The next opted-in flush that
231
+ // ships events overwrites the sentinel with a real row ID.
146
232
  const now = String(Date.now());
147
- setMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK, now);
148
- setMemoryCheckpoint(CHECKPOINT_KEY_TURN_WATERMARK, now);
149
- setMemoryCheckpoint(CHECKPOINT_KEY_LIFECYCLE_WATERMARK, now);
150
- setMemoryCheckpoint(CHECKPOINT_KEY_ONBOARDING_WATERMARK, now);
151
- setMemoryCheckpoint(CHECKPOINT_KEY_AUTH_FALLBACK_WATERMARK, now);
233
+ for (const [timestampKey, idKey] of WATERMARK_KEY_PAIRS) {
234
+ setMemoryCheckpoint(timestampKey, now);
235
+ setMemoryCheckpoint(idKey, OPT_OUT_WATERMARK_ID_SENTINEL);
236
+ }
152
237
  return;
153
238
  }
154
239
 
@@ -189,6 +274,27 @@ export class UsageTelemetryReporter {
189
274
  getMemoryCheckpoint(CHECKPOINT_KEY_AUTH_FALLBACK_WATERMARK_ID) ??
190
275
  undefined;
191
276
 
277
+ // Read tool-executed watermark (compound cursor: createdAt + id).
278
+ // An absent checkpoint was initialized to construction time (see the
279
+ // constructor), guarding opt-out windows; the 0 fallback here is a
280
+ // defensive default matching the other event types. Legacy rows are
281
+ // excluded at the query level (see queryUnreportedToolExecutedEvents).
282
+ const toolExecutedWatermark = Number(
283
+ getMemoryCheckpoint(CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK) ?? "0",
284
+ );
285
+ const toolExecutedWatermarkId =
286
+ getMemoryCheckpoint(CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK_ID) ??
287
+ undefined;
288
+
289
+ // Read skill-loaded watermark (compound cursor: createdAt + id).
290
+ // Brand-new table, so the standard 0 default is safe.
291
+ const skillLoadedWatermark = Number(
292
+ getMemoryCheckpoint(CHECKPOINT_KEY_SKILL_LOADED_WATERMARK) ?? "0",
293
+ );
294
+ const skillLoadedWatermarkId =
295
+ getMemoryCheckpoint(CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID) ??
296
+ undefined;
297
+
192
298
  // Query unreported events
193
299
  const events = queryUnreportedUsageEvents(
194
300
  watermark,
@@ -215,13 +321,25 @@ export class UsageTelemetryReporter {
215
321
  authFallbackWatermarkId,
216
322
  BATCH_SIZE,
217
323
  );
324
+ const toolExecutedEvents = queryUnreportedToolExecutedEvents(
325
+ toolExecutedWatermark,
326
+ toolExecutedWatermarkId,
327
+ BATCH_SIZE,
328
+ );
329
+ const skillLoadedEvents = queryUnreportedSkillLoadedEvents(
330
+ skillLoadedWatermark,
331
+ skillLoadedWatermarkId,
332
+ BATCH_SIZE,
333
+ );
218
334
 
219
335
  if (
220
336
  events.length === 0 &&
221
337
  turnEvents.length === 0 &&
222
338
  lifecycleEvents.length === 0 &&
223
339
  onboardingEvents.length === 0 &&
224
- authFallbackEvents.length === 0
340
+ authFallbackEvents.length === 0 &&
341
+ toolExecutedEvents.length === 0 &&
342
+ skillLoadedEvents.length === 0
225
343
  )
226
344
  return;
227
345
 
@@ -236,6 +354,8 @@ export class UsageTelemetryReporter {
236
354
  lifecycleCount: lifecycleEvents.length,
237
355
  onboardingCount: onboardingEvents.length,
238
356
  authFallbackCount: authFallbackEvents.length,
357
+ toolExecutedCount: toolExecutedEvents.length,
358
+ skillLoadedCount: skillLoadedEvents.length,
239
359
  },
240
360
  "Telemetry flush: resolved auth context",
241
361
  );
@@ -375,11 +495,11 @@ export class UsageTelemetryReporter {
375
495
  ...(e.stepIndex != null ? { step_index: e.stepIndex } : {}),
376
496
  ...(e.completedAt ? { completed_at: e.completedAt } : {}),
377
497
  ...(e.funnelVersion ? { funnel_version: e.funnelVersion } : {}),
378
- // Onboarding events fall back to the envelope `assistant_version`
379
- // — same upload-time attribution risk as before this PR. Adding
380
- // the record-time column to `onboarding_events` (#30733) is a
381
- // separate follow-up that mirrors what this PR does for
382
- // `llm_usage_events`.
498
+ // Onboarding events fall back to the envelope `assistant_version`,
499
+ // so events recorded under an older build are attributed to the
500
+ // version running at upload time. Adding a record-time column to
501
+ // `onboarding_events` (mirroring `llm_usage_events`) is a known
502
+ // follow-up.
383
503
  assistant_version: APP_VERSION,
384
504
  }),
385
505
  ),
@@ -402,6 +522,50 @@ export class UsageTelemetryReporter {
402
522
  assistant_version: APP_VERSION,
403
523
  }),
404
524
  ),
525
+ ...toolExecutedEvents.map(
526
+ (e): TelemetryEvent => ({
527
+ type: "tool_executed",
528
+ daemon_event_id: e.id,
529
+ recorded_at: e.createdAt,
530
+ tool_name: e.toolName,
531
+ // The store filters out permission-denied rows, so the only
532
+ // non-success decision that reaches here is "error".
533
+ status: e.decision === "error" ? "errored" : "fulfilled",
534
+ duration_ms: e.durationMs,
535
+ arg_bytes: e.argBytes,
536
+ result_bytes: e.resultBytes,
537
+ conversation_id: e.conversationId,
538
+ provider: e.provider,
539
+ model: e.model,
540
+ inference_profile: e.inferenceProfile,
541
+ inference_profile_source:
542
+ e.inferenceProfileSource as UsageAttributionProfileSource | null,
543
+ // `tool_invocations` has no record-time version column — stamp
544
+ // the running binary's `APP_VERSION` so the wire value is
545
+ // concrete rather than an explicit null that would override the
546
+ // envelope under the platform's per-event-wins contract.
547
+ assistant_version: APP_VERSION,
548
+ }),
549
+ ),
550
+ ...skillLoadedEvents.map(
551
+ (e): TelemetryEvent => ({
552
+ type: "skill_loaded",
553
+ daemon_event_id: e.id,
554
+ recorded_at: e.createdAt,
555
+ skill_name: e.skillName,
556
+ skill_updated_at: e.skillUpdatedAt,
557
+ conversation_id: e.conversationId,
558
+ provider: e.provider,
559
+ model: e.model,
560
+ inference_profile: e.inferenceProfile,
561
+ inference_profile_source:
562
+ e.inferenceProfileSource as UsageAttributionProfileSource | null,
563
+ // `skill_loaded_events` has no record-time version column — same
564
+ // upload-time APP_VERSION stamping as the other non-llm_usage
565
+ // event types.
566
+ assistant_version: APP_VERSION,
567
+ }),
568
+ ),
405
569
  ];
406
570
 
407
571
  const organizationId = getPlatformOrganizationId() || undefined;
@@ -501,13 +665,42 @@ export class UsageTelemetryReporter {
501
665
  );
502
666
  }
503
667
 
668
+ // Advance tool-executed watermark (compound cursor)
669
+ if (toolExecutedEvents.length > 0) {
670
+ const lastToolExecuted =
671
+ toolExecutedEvents[toolExecutedEvents.length - 1];
672
+ setMemoryCheckpoint(
673
+ CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK,
674
+ String(lastToolExecuted.createdAt),
675
+ );
676
+ setMemoryCheckpoint(
677
+ CHECKPOINT_KEY_TOOL_EXECUTED_WATERMARK_ID,
678
+ lastToolExecuted.id,
679
+ );
680
+ }
681
+
682
+ // Advance skill-loaded watermark (compound cursor)
683
+ if (skillLoadedEvents.length > 0) {
684
+ const lastSkillLoaded = skillLoadedEvents[skillLoadedEvents.length - 1];
685
+ setMemoryCheckpoint(
686
+ CHECKPOINT_KEY_SKILL_LOADED_WATERMARK,
687
+ String(lastSkillLoaded.createdAt),
688
+ );
689
+ setMemoryCheckpoint(
690
+ CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID,
691
+ lastSkillLoaded.id,
692
+ );
693
+ }
694
+
504
695
  // If we got a full batch of any type, there may be more — recurse
505
696
  if (
506
697
  events.length === BATCH_SIZE ||
507
698
  turnEvents.length === BATCH_SIZE ||
508
699
  lifecycleEvents.length === BATCH_SIZE ||
509
700
  onboardingEvents.length === BATCH_SIZE ||
510
- authFallbackEvents.length === BATCH_SIZE
701
+ authFallbackEvents.length === BATCH_SIZE ||
702
+ toolExecutedEvents.length === BATCH_SIZE ||
703
+ skillLoadedEvents.length === BATCH_SIZE
511
704
  ) {
512
705
  await this._doFlush(batchCount + 1);
513
706
  }
@@ -11,14 +11,15 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
11
11
 
12
12
  import { createMockLoggerModule } from "../../../__tests__/helpers/mock-logger.js";
13
13
  import type { ToolContext } from "../../types.js";
14
- import type { BrowserMode, CdpClientKind } from "../cdp-client/types.js";
14
+ import { CdpError } from "../cdp-client/errors.js";
15
+ import type { CdpClientKind, InternalBrowserMode } from "../cdp-client/types.js";
15
16
 
16
17
  // ---------------------------------------------------------------------------
17
18
  // Captured call state
18
19
  // ---------------------------------------------------------------------------
19
20
 
20
21
  interface CdpClientCallOpts {
21
- mode?: BrowserMode;
22
+ mode?: InternalBrowserMode;
22
23
  targetClientId?: string;
23
24
  }
24
25
 
@@ -197,4 +198,50 @@ describe("acquireCdpClientWithMode: target_client_id overrides sticky backend mo
197
198
  expect(getCdpClientCalls[0].mode).toBe("extension");
198
199
  expect(getCdpClientCalls[0].targetClientId).toBe("host-client-fresh");
199
200
  });
201
+
202
+ test("sticky host-bridge + no target_client_id → getCdpClient receives mode:host-bridge", async () => {
203
+ // A prior turn succeeded on the desktop SSE bridge; the memo holds the
204
+ // internal-only "host-bridge" kind and must round-trip through the
205
+ // factory's pinned path.
206
+ stickyKind = "host-bridge";
207
+
208
+ await executeBrowserAttach({}, makeContext("sticky-bridge-honored"));
209
+
210
+ expect(getCdpClientMock).toHaveBeenCalledTimes(1);
211
+ expect(getCdpClientCalls[0].mode).toBe("host-bridge");
212
+ });
213
+
214
+ test("sticky host-bridge + target_client_id → getCdpClient receives mode:extension", async () => {
215
+ stickyKind = "host-bridge";
216
+
217
+ await executeBrowserAttach(
218
+ { target_client_id: "host-client-bridge" },
219
+ makeContext("sticky-bridge-override"),
220
+ );
221
+
222
+ expect(getCdpClientMock).toHaveBeenCalledTimes(1);
223
+ expect(getCdpClientCalls[0].mode).toBe("extension");
224
+ expect(getCdpClientCalls[0].targetClientId).toBe("host-client-bridge");
225
+ });
226
+
227
+ test("sticky host-bridge gone → memo cleared and auto retry runs", async () => {
228
+ // The pinned host-bridge build throws (bridge disconnected or an
229
+ // extension connected since the memo was written); the acquire layer
230
+ // must clear the stale memo and retry with fresh auto selection.
231
+ stickyKind = "host-bridge";
232
+ getCdpClientMock.mockImplementationOnce(() => {
233
+ getCdpClientCalls.push({ mode: "host-bridge" });
234
+ throw new CdpError(
235
+ "transport_error",
236
+ 'Pinned mode "host-bridge" unavailable: a Chrome Extension is now connected',
237
+ );
238
+ });
239
+
240
+ await executeBrowserAttach({}, makeContext("sticky-bridge-fallback"));
241
+
242
+ expect(getCdpClientMock).toHaveBeenCalledTimes(2);
243
+ expect(getCdpClientCalls[0].mode).toBe("host-bridge");
244
+ expect(getCdpClientCalls[1].mode).toBe("auto");
245
+ expect(clearPreferredBackendKindMock).toHaveBeenCalledTimes(1);
246
+ });
200
247
  });
@@ -20,11 +20,13 @@ const probeErrors: Record<string, CdpError | null> = {
20
20
  [BROWSER_STATUS_MODE.LOCAL]: null,
21
21
  };
22
22
 
23
- const buildCandidateListMock = mock((_context: ToolContext) => [
24
- { kind: BROWSER_STATUS_MODE.EXTENSION, reason: "mock" },
25
- { kind: BROWSER_STATUS_MODE.CDP_INSPECT, reason: "mock" },
26
- { kind: BROWSER_STATUS_MODE.LOCAL, reason: "mock" },
27
- ]);
23
+ const buildCandidateListMock = mock(
24
+ (_context: ToolContext): Array<{ kind: string; reason: string }> => [
25
+ { kind: BROWSER_STATUS_MODE.EXTENSION, reason: "mock" },
26
+ { kind: BROWSER_STATUS_MODE.CDP_INSPECT, reason: "mock" },
27
+ { kind: BROWSER_STATUS_MODE.LOCAL, reason: "mock" },
28
+ ],
29
+ );
28
30
 
29
31
  const getCdpClientMock = mock(
30
32
  (_context: ToolContext, options?: { mode?: string }) => {
@@ -254,6 +256,28 @@ describe("executeBrowserStatus", () => {
254
256
  expect(payload.recommendedMode).toBe(BROWSER_STATUS_MODE.CDP_INSPECT);
255
257
  });
256
258
 
259
+ test("host-bridge auto candidate is reported but never recommended as a status mode", async () => {
260
+ // Bridge-only scenario: the auto candidate list leads with host-bridge,
261
+ // which is not a pinnable status mode — recommendation must fall to the
262
+ // first candidate that IS a checkable mode.
263
+ mockSingletonProxy = {
264
+ isAvailable: () => true,
265
+ hasExtensionClient: () => false,
266
+ request: () => {},
267
+ };
268
+ buildCandidateListMock.mockImplementationOnce((_context: ToolContext) => [
269
+ { kind: "host-bridge", reason: "mock bridge" },
270
+ { kind: BROWSER_STATUS_MODE.CDP_INSPECT, reason: "mock" },
271
+ { kind: BROWSER_STATUS_MODE.LOCAL, reason: "mock" },
272
+ ]);
273
+
274
+ const result = await executeBrowserStatus({}, makeContext());
275
+ expect(result.isError).toBe(false);
276
+ const payload = JSON.parse(result.content);
277
+ expect(payload.autoCandidateOrder[0]).toBe("host-bridge");
278
+ expect(payload.recommendedMode).toBe(BROWSER_STATUS_MODE.CDP_INSPECT);
279
+ });
280
+
257
281
  test("restricted chrome:// page probe includes transport details", async () => {
258
282
  mockSingletonProxy = { isAvailable: () => true, hasExtensionClient: () => true, request: () => {} };
259
283
  probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
@@ -70,6 +70,7 @@ import type {
70
70
  AttemptDiagnostic,
71
71
  CdpClient,
72
72
  CdpClientKind,
73
+ InternalBrowserMode,
73
74
  } from "./cdp-client/types.js";
74
75
  import { clearPinnedTab, setPinnedTab } from "./pinned-tabs.js";
75
76
  import { checkBrowserRuntime } from "./runtime-check.js";
@@ -227,6 +228,18 @@ const REMEDIATION_HINTS: Record<string, string[]> = {
227
228
  "Verify the configured host:port matches Chrome's DevTools listener.",
228
229
  "Consider using browser_mode: 'extension' or 'local' as an alternative.",
229
230
  ],
231
+ // Host-bridge backend (desktop SSE bridge → user's Chrome debug port)
232
+ "host-bridge:unreachable": [
233
+ "Ensure Chrome on the user's machine is on version 146 or higher (chrome://settings/help).",
234
+ 'Ensure "Allow remote debugging for this browser instance" is toggled on at chrome://inspect/#remote-debugging.',
235
+ "Verify the desktop client is running and has an active SSE connection to the assistant.",
236
+ "Installing the Vellum Chrome extension is the preferred path and avoids the debug-port requirement.",
237
+ ],
238
+ "host-bridge:transport_error": [
239
+ "The desktop client could not reach Chrome's remote-debugging endpoint on the user's machine.",
240
+ "Ensure Chrome is running with remote debugging enabled, or install the Vellum Chrome extension (preferred).",
241
+ "Verify the desktop client is running and connected.",
242
+ ],
230
243
  // Local/Playwright backend
231
244
  "local:transport_error": [
232
245
  "The local Playwright-managed browser failed to start or connect.",
@@ -390,7 +403,9 @@ function acquireCdpClientWithMode(
390
403
  );
391
404
  // target_client_id requires the extension proxy path — bypass any sticky
392
405
  // backend remembered from prior turns so the explicit target always wins.
393
- const effectiveMode: BrowserMode =
406
+ // InternalBrowserMode because the memo may hold "host-bridge", which is
407
+ // pinnable by the factory but never user-requestable.
408
+ const effectiveMode: InternalBrowserMode =
394
409
  targetClientId != null
395
410
  ? "extension"
396
411
  : browserMode === "auto" && rememberedKind !== null
@@ -1383,14 +1398,17 @@ export async function executeBrowserClose(
1383
1398
  };
1384
1399
  }
1385
1400
 
1386
- // Extension path: the user owns their Chrome tab — we must not
1387
- // close it. Detach the debugger (so the Chrome debugging banner
1388
- // clears promptly) and drop the cached snapshot state so stale
1401
+ // Non-local path: the user owns their Chrome tab — we must not
1402
+ // close it. On the extension backend, detach the debugger (so the
1403
+ // Chrome debugging banner clears promptly); other backends have no
1404
+ // Vellum.detach. Either way drop the cached snapshot state so stale
1389
1405
  // eids from prior snapshots cannot be resolved by later tool calls.
1390
- try {
1391
- await cdp.send("Vellum.detach", {}, context.signal);
1392
- } catch {
1393
- // Tolerate detach failures (already detached, tab closed, etc.)
1406
+ if (cdp.kind === "extension") {
1407
+ try {
1408
+ await cdp.send("Vellum.detach", {}, context.signal);
1409
+ } catch {
1410
+ // Tolerate detach failures (already detached, tab closed, etc.)
1411
+ }
1394
1412
  }
1395
1413
  browserManager.clearSnapshotBackendNodeMap(context.conversationId);
1396
1414
  browserManager.clearPreferredBackendKind(context.conversationId);
@@ -2382,7 +2400,7 @@ async function checkExtensionModeStatus(
2382
2400
  ): Promise<BrowserStatusModeResult> {
2383
2401
  const proxy = HostBrowserProxy.instance;
2384
2402
 
2385
- if (!proxy.hasExtensionClient()) {
2403
+ if (!proxy.hasExtensionClient(context.sourceActorPrincipalId)) {
2386
2404
  return {
2387
2405
  mode: BROWSER_STATUS_MODE.EXTENSION,
2388
2406
  available: false,