@vellumai/assistant 0.8.2 → 0.8.4

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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -2382,4 +2382,32 @@ describe("macOS host-browser proxy without extension registry", () => {
2382
2382
  );
2383
2383
  expect(result).toEqual({ ok: true, via: "extension" });
2384
2384
  });
2385
+ test("setCdpSessionId is forwarded to the underlying client through buildChainedClient", async () => {
2386
+ // This test exercises the real chained-client path that integrates
2387
+ // the extension CdpClient. Without it, the test suite would only ever
2388
+ // instantiate getCdpClient(), which returns the scopedClient directly.
2389
+ // buildChainedClient (used by the navigator executor) wraps that
2390
+ // client and must forward setCdpSessionId calls to the underlying
2391
+ // extension client — otherwise --new-tab would silently fail to
2392
+ // re-target CDP commands.
2393
+ const fakeProxy = makeAvailableProxy();
2394
+ mockSingletonProxy = fakeProxy;
2395
+ const ctx = makeContext({ conversationId: "set-session-id-test" });
2396
+ const client = getCdpClient(ctx);
2397
+ // The scopedClient should expose setCdpSessionId (which it didn't
2398
+ // before the fix).
2399
+ expect(typeof client.setCdpSessionId).toBe("function");
2400
+ // Call it; it should reach the underlying extension client without error.
2401
+ // (The fake client's setCdpSessionId is a no-op in this test, but in
2402
+ // production ExtensionCdpClient.setCdpSessionId updates cdpSessionId to
2403
+ // re-target follow-on commands at a specific tab.)
2404
+ const newTabId = "12345";
2405
+ client.setCdpSessionId(newTabId);
2406
+ // Verify the client is still functional after the session ID call.
2407
+ const result = await client.send<{ ok: boolean; via: string }>(
2408
+ "Page.navigate",
2409
+ { url: "https://example.com" },
2410
+ );
2411
+ expect(result).toEqual({ ok: true, via: "extension" });
2412
+ });
2385
2413
  });
@@ -88,6 +88,7 @@ describe("cdp-client re-exports", () => {
88
88
  throw new CdpError("disposed", "stub");
89
89
  },
90
90
  dispose: () => {},
91
+ setCdpSessionId: () => {},
91
92
  };
92
93
  expect(scoped.kind).toBe(kind);
93
94
  expect(scoped.conversationId).toBe("conv-123");
@@ -700,6 +700,16 @@ export class CdpInspectClient implements ScopedCdpClient {
700
700
  }
701
701
  }
702
702
 
703
+ /**
704
+ * CdpInspectClient doesn't support re-targeting to a specific tab/session.
705
+ * It connects directly to a remote CDP endpoint, which manages targets
706
+ * globally. This method is a no-op to satisfy the {@link ScopedCdpClient}
707
+ * interface.
708
+ */
709
+ setCdpSessionId(): void {
710
+ // no-op
711
+ }
712
+
703
713
  dispose(): void {
704
714
  if (this.disposed) return;
705
715
  this.disposed = true;
@@ -43,7 +43,7 @@ export class ExtensionCdpClient implements ScopedCdpClient {
43
43
  constructor(
44
44
  private readonly proxy: HostBrowserProxy,
45
45
  public readonly conversationId: string,
46
- private readonly cdpSessionId?: string,
46
+ private cdpSessionId?: string,
47
47
  /**
48
48
  * Caller's actor principal id. When provided, the proxy will refuse to
49
49
  * dispatch this CDP command to a host_browser-capable client owned by a
@@ -182,6 +182,20 @@ export class ExtensionCdpClient implements ScopedCdpClient {
182
182
  // it here. In-flight requests will be cancelled by the AbortSignal
183
183
  // the tool passes in, or by conversation teardown.
184
184
  }
185
+
186
+ /**
187
+ * Update the `cdpSessionId` used on subsequent {@link send} calls.
188
+ *
189
+ * Used by the navigate executor after opening a new tab via the
190
+ * `Vellum.createTab` pseudo-CDP method: the returned tabId becomes
191
+ * this client's pinned target so the follow-on `Page.navigate`
192
+ * (and every command on this conversation thereafter) routes to
193
+ * the freshly-created tab instead of the user's currently-active
194
+ * tab.
195
+ */
196
+ setCdpSessionId(cdpSessionId: string | undefined): void {
197
+ this.cdpSessionId = cdpSessionId;
198
+ }
185
199
  }
186
200
 
187
201
  /**
@@ -11,6 +11,7 @@ import { getConfig } from "../../../config/loader.js";
11
11
  import { HostBrowserProxy } from "../../../daemon/host-browser-proxy.js";
12
12
  import { getLogger } from "../../../util/logger.js";
13
13
  import type { ToolContext } from "../../types.js";
14
+ import { getPinnedTab } from "../pinned-tabs.js";
14
15
  import { createCdpInspectClient } from "./cdp-inspect-client.js";
15
16
  import { CdpError } from "./errors.js";
16
17
  import { createExtensionCdpClient } from "./extension-cdp-client.js";
@@ -222,7 +223,14 @@ export function buildPinnedCandidateList(
222
223
  const client = createExtensionCdpClient(
223
224
  hostBrowserProxy,
224
225
  conversationId,
225
- undefined,
226
+ // Conversation pins are scoped to "default routing on
227
+ // this conversation". When `target_client_id` is an
228
+ // explicit override, the caller is taking over routing
229
+ // and the pin (which may point at a tab on a different
230
+ // host client) must not be applied — otherwise we'd
231
+ // send tabId from host A to host B and get
232
+ // cdp_session_not_found.
233
+ targetClientId ? undefined : getPinnedTab(conversationId),
226
234
  sourceActorPrincipalId,
227
235
  targetClientId,
228
236
  );
@@ -330,6 +338,11 @@ export function buildCandidateList(context: ToolContext, targetClientId?: string
330
338
  kind: "extension",
331
339
  reason: `target_client_id override: ${targetClientId}`,
332
340
  create() {
341
+ // Explicit target_client_id override → ignore the
342
+ // conversation's pinned tab. Pins are conversation-scoped
343
+ // (not host-client-scoped) and would route a tabId from
344
+ // a different host to this one, producing
345
+ // cdp_session_not_found.
333
346
  const client = createExtensionCdpClient(
334
347
  hostBrowserProxy,
335
348
  conversationId,
@@ -358,7 +371,9 @@ export function buildCandidateList(context: ToolContext, targetClientId?: string
358
371
  const client = createExtensionCdpClient(
359
372
  hostBrowserProxy,
360
373
  conversationId,
361
- undefined,
374
+ // See comment above the pinned-mode call site for why pins
375
+ // are skipped under explicit target_client_id override.
376
+ targetClientId ? undefined : getPinnedTab(conversationId),
362
377
  sourceActorPrincipalId,
363
378
  targetClientId,
364
379
  );
@@ -484,6 +499,16 @@ export function buildChainedClient(
484
499
  kind: CdpClientKind;
485
500
  manager: BrowserSessionManager;
486
501
  sessionId: string;
502
+ /**
503
+ * Reference to the underlying CdpClient (returned from
504
+ * `candidate.create()`). Held so callers can reach methods that
505
+ * aren't routed through `manager.send()` — specifically
506
+ * {@link CdpClient.setCdpSessionId}, which re-targets follow-on
507
+ * CDP commands at a specific tab/target. Without this we'd have
508
+ * no way to call setCdpSessionId on the materialised client
509
+ * since `backend` only exposes the send/dispose surface.
510
+ */
511
+ client: CdpClient;
487
512
  } | null = null;
488
513
 
489
514
  /** Set to true after the first successful CDP command. */
@@ -491,6 +516,17 @@ export function buildChainedClient(
491
516
 
492
517
  let disposed = false;
493
518
 
519
+ /**
520
+ * setCdpSessionId may be called BEFORE the first send (i.e. before
521
+ * `active` is populated). When that happens we stash the value
522
+ * here and apply it to `active.client` as soon as a backend becomes
523
+ * sticky. In the current --new-tab flow this can't trigger (the
524
+ * createTab send establishes sticky first, then setCdpSessionId is
525
+ * called), but supporting the pre-sticky case keeps the contract
526
+ * simple for future callers.
527
+ */
528
+ let pendingCdpSessionId: string | undefined;
529
+
494
530
  /**
495
531
  * Track all materialised backends so dispose() can tear them all
496
532
  * down, even ones that were tried and failed before the sticky
@@ -545,6 +581,16 @@ export function buildChainedClient(
545
581
  active = established;
546
582
  sticky = true;
547
583
  currentKind = established.kind;
584
+ // Flush any pre-sticky setCdpSessionId call onto the
585
+ // freshly-materialised client. See pendingCdpSessionId
586
+ // comment above for why this exists.
587
+ if (
588
+ pendingCdpSessionId !== undefined &&
589
+ established.client.setCdpSessionId
590
+ ) {
591
+ established.client.setCdpSessionId(pendingCdpSessionId);
592
+ pendingCdpSessionId = undefined;
593
+ }
548
594
  },
549
595
  () => disposed,
550
596
  conversationId,
@@ -561,6 +607,31 @@ export function buildChainedClient(
561
607
  materialisedManagers.length = 0;
562
608
  active = null;
563
609
  },
610
+
611
+ /**
612
+ * Re-target follow-on CDP commands at a specific tab/target by
613
+ * updating the underlying client's `cdpSessionId`. The navigate
614
+ * executor calls this after `Vellum.createTab` returns so the
615
+ * subsequent `Page.navigate` (and every command on this
616
+ * conversation thereafter) routes to the new tab instead of the
617
+ * currently-active tab.
618
+ *
619
+ * Behaviour by state:
620
+ * - Sticky backend already established → forward to
621
+ * `active.client.setCdpSessionId` if the underlying client
622
+ * implements it (extension does; local/cdp-inspect don't and
623
+ * the optional chain no-ops).
624
+ * - No sticky backend yet → stash the value; it gets applied
625
+ * in the `onEstablished` callback when the first send walks
626
+ * the candidate list.
627
+ */
628
+ setCdpSessionId(cdpSessionId: string | undefined): void {
629
+ if (active?.client.setCdpSessionId) {
630
+ active.client.setCdpSessionId(cdpSessionId);
631
+ return;
632
+ }
633
+ pendingCdpSessionId = cdpSessionId;
634
+ },
564
635
  };
565
636
 
566
637
  return scopedClient;
@@ -591,6 +662,7 @@ async function sendWithFailover<T>(
591
662
  kind: CdpClientKind;
592
663
  manager: BrowserSessionManager;
593
664
  sessionId: string;
665
+ client: CdpClient;
594
666
  }) => void,
595
667
  isDisposed: () => boolean,
596
668
  conversationId: string,
@@ -619,9 +691,16 @@ async function sendWithFailover<T>(
619
691
  );
620
692
 
621
693
  let backend: BrowserBackend;
694
+ let underlyingClient: CdpClient;
622
695
  try {
623
696
  const created = candidate.create();
624
697
  backend = created.backend;
698
+ // Hold the underlying CdpClient so we can forward
699
+ // setCdpSessionId calls to it after the backend becomes
700
+ // sticky. Without this the scopedClient's setCdpSessionId
701
+ // method has no way to reach the materialised client (the
702
+ // BrowserSessionManager only exposes a send/dispose surface).
703
+ underlyingClient = created.client;
625
704
  } catch (err) {
626
705
  // Backend construction failed -- treat as transport error and
627
706
  // try the next candidate.
@@ -810,7 +889,12 @@ async function sendWithFailover<T>(
810
889
  { conversationId, candidateKind: candidate.kind, method },
811
890
  "CDP factory: candidate succeeded, backend is now sticky",
812
891
  );
813
- onEstablished({ kind: candidate.kind, manager, sessionId: session.id });
892
+ onEstablished({
893
+ kind: candidate.kind,
894
+ manager,
895
+ sessionId: session.id,
896
+ client: underlyingClient,
897
+ });
814
898
  return envelope.result as T;
815
899
  }
816
900
 
@@ -156,6 +156,15 @@ export class LocalCdpClient implements ScopedCdpClient {
156
156
  }
157
157
  }
158
158
 
159
+ /**
160
+ * LocalCdpClient doesn't support re-targeting to a specific tab/session.
161
+ * The local Chromium instance manages all tabs globally, not per-client.
162
+ * This method is a no-op to satisfy the {@link ScopedCdpClient} interface.
163
+ */
164
+ setCdpSessionId(): void {
165
+ // no-op
166
+ }
167
+
159
168
  dispose(): void {
160
169
  if (this.disposed) return;
161
170
  this.disposed = true;
@@ -34,6 +34,25 @@ export interface CdpClient {
34
34
  * is allowed but should surface as an error.
35
35
  */
36
36
  dispose(): void;
37
+
38
+ /**
39
+ * Update the `cdpSessionId` used on subsequent {@link CdpClient.send}
40
+ * calls. Backends that don't multiplex commands across multiple
41
+ * targets (local Playwright, cdp-inspect) may implement this as a
42
+ * no-op. The extension backend uses this after opening a new tab
43
+ * (via the `Vellum.createTab` pseudo-CDP method) to route
44
+ * follow-on commands to the freshly-created tab instead of the
45
+ * currently-active one.
46
+ *
47
+ * Pass `undefined` to clear an existing pinned session and revert
48
+ * to default routing (i.e. dispatcher resolves the active tab).
49
+ * Used in the `Vellum.createTab` no-tabId fallback path to avoid
50
+ * sending follow-on commands to a stale/dead tab when the previous
51
+ * pin is no longer valid.
52
+ *
53
+ * Optional — callers should null-check before invoking.
54
+ */
55
+ setCdpSessionId?(cdpSessionId: string | undefined): void;
37
56
  }
38
57
 
39
58
  /**
@@ -97,6 +116,23 @@ export interface ScopedCdpClient extends CdpClient {
97
116
  readonly kind: CdpClientKind;
98
117
  /** Stable conversation id this client is bound to. */
99
118
  readonly conversationId: string;
119
+ /**
120
+ * Re-target follow-on CDP commands at a specific tab/target. Calling
121
+ * this updates the underlying client's `cdpSessionId`. This is used by
122
+ * the navigator executor after `Vellum.createTab` returns to ensure the
123
+ * subsequent `Page.navigate` and all follow-on commands on this
124
+ * conversation route to the newly-created tab instead of the
125
+ * previously-active tab.
126
+ *
127
+ * Pass `undefined` to clear an existing pinned session on the chained
128
+ * client and on the underlying client (if it supports the method).
129
+ * Used in the `Vellum.createTab` no-tabId fallback path.
130
+ *
131
+ * If the underlying client doesn't implement this method (e.g., local
132
+ * or cdp-inspect clients), the call is silently ignored via optional
133
+ * chaining.
134
+ */
135
+ setCdpSessionId(cdpSessionId: string | undefined): void;
100
136
  }
101
137
 
102
138
  /**
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Per-conversation pinned-tab store for the Chrome extension browser
3
+ * backend.
4
+ *
5
+ * When a navigate is issued with `new_tab: true`, the executor first
6
+ * asks the extension to open a fresh tab (via the `Vellum.createTab`
7
+ * pseudo-CDP method), then records the returned tabId here against the
8
+ * conversation. Subsequent CDP commands constructed for the same
9
+ * conversation pick up this tabId as the `cdpSessionId` on the
10
+ * outgoing envelope, which causes the extension's
11
+ * `resolveHostBrowserTarget` to route to that specific tab instead of
12
+ * falling back to `chrome.tabs.query({ active: true })`.
13
+ *
14
+ * Persistence is process-lifetime (in-memory only). The daemon is a
15
+ * single process and all CLI invocations land in it via IPC, so a
16
+ * module-level Map is sufficient. A pin is cleared when the extension
17
+ * reports the underlying CDP target as invalidated (see
18
+ * `consumeInvalidatedTargetId` in `browser-session/events.ts`) — that
19
+ * eviction is wired through `BrowserSessionManager.invalidateByTargetId`
20
+ * and the host-browser session-invalidated event route.
21
+ */
22
+
23
+ import { log } from "../../cli/logger.js";
24
+
25
+ const pinnedTabs = new Map<string, string>();
26
+
27
+ /**
28
+ * Record a tabId as the pinned tab for the given conversation. The
29
+ * tabId is stored as a string because it travels on the wire as
30
+ * `cdpSessionId` (a string-typed field on the host-browser envelope).
31
+ */
32
+ export function setPinnedTab(conversationId: string, tabId: string): void {
33
+ if (!conversationId || !tabId) return;
34
+ pinnedTabs.set(conversationId, tabId);
35
+ log.debug(
36
+ { conversationId, tabId },
37
+ "Pinned extension tab for conversation",
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Return the tabId pinned to the given conversation, or undefined if
43
+ * no pin exists.
44
+ */
45
+ export function getPinnedTab(conversationId: string): string | undefined {
46
+ if (!conversationId) return undefined;
47
+ return pinnedTabs.get(conversationId);
48
+ }
49
+
50
+ /**
51
+ * Clear the pin for the given conversation. Idempotent — clearing a
52
+ * non-existent pin is a no-op.
53
+ */
54
+ export function clearPinnedTab(conversationId: string): void {
55
+ if (!conversationId) return;
56
+ if (pinnedTabs.delete(conversationId)) {
57
+ log.debug({ conversationId }, "Cleared pinned extension tab");
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Clear every pin pointing at a given tabId across all conversations.
63
+ * Used when the extension reports a target as invalidated (tab closed,
64
+ * crashed, navigated away from a debuggable URL, etc.) so we don't
65
+ * keep routing to a dead tab.
66
+ *
67
+ * Returns the number of conversations whose pin was cleared.
68
+ */
69
+ export function clearPinnedTabByTabId(tabId: string): number {
70
+ if (!tabId) return 0;
71
+ let cleared = 0;
72
+ for (const [conversationId, pinned] of pinnedTabs.entries()) {
73
+ if (pinned === tabId) {
74
+ pinnedTabs.delete(conversationId);
75
+ cleared++;
76
+ log.debug(
77
+ { conversationId, tabId },
78
+ "Cleared pinned extension tab due to invalidation",
79
+ );
80
+ }
81
+ }
82
+ return cleared;
83
+ }
84
+
85
+ /**
86
+ * Reset the entire pin store. Test-only.
87
+ */
88
+ export function __resetPinnedTabsForTests(): void {
89
+ pinnedTabs.clear();
90
+ }