@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
@@ -0,0 +1,449 @@
1
+ /**
2
+ * A2A channel configuration handler.
3
+ *
4
+ * - getA2AConfig() — read a2a.enabled, count active a2a contact_channels
5
+ * - setA2AConfig() — set a2a.enabled = true
6
+ * - clearA2AConfig() — set a2a.enabled = false
7
+ * - createA2AInvite() — create a shareable invite token for link-based contact creation
8
+ * - completeA2AInvite() — sender-side: claim token and return sender identity
9
+ * - redeemA2AInvite() — receiver-side: create trusted contact from sender identity
10
+ * - acceptA2AInvite() — self-hosted broker: orchestrate complete + redeem across daemons
11
+ */
12
+
13
+ import {
14
+ getConfig,
15
+ invalidateConfigCache,
16
+ loadRawConfig,
17
+ saveRawConfig,
18
+ setNestedValue,
19
+ } from "../../config/loader.js";
20
+ import {
21
+ findContactByAddress,
22
+ searchContacts,
23
+ upsertContact,
24
+ } from "../../contacts/contact-store.js";
25
+ import type { VellumAssistantMetadata } from "../../contacts/types.js";
26
+ import { getPublicBaseUrl } from "../../inbound/public-ingress-urls.js";
27
+ import { getDb } from "../../memory/db-connection.js";
28
+ import {
29
+ claimA2AInvite,
30
+ createInvite,
31
+ hashToken,
32
+ } from "../../memory/invite-store.js";
33
+ import { assistantContactMetadata } from "../../memory/schema.js";
34
+ import type { HttpErrorResponse } from "../../runtime/http-errors.js";
35
+ import { getLogger } from "../../util/logger.js";
36
+ import { getAssistantName } from "../identity-helpers.js";
37
+ const log = getLogger("config-a2a");
38
+
39
+ // ── Result types ────────────────────────────────────────────────────
40
+
41
+ export interface A2AConfigResult {
42
+ success: boolean;
43
+ enabled: boolean;
44
+ activeConnections: number;
45
+ error?: string;
46
+ }
47
+
48
+ export interface CreateA2AInviteResult {
49
+ success: boolean;
50
+ inviteId?: string;
51
+ token?: string;
52
+ expiresAt?: number;
53
+ senderGatewayUrl?: string;
54
+ error?: string;
55
+ }
56
+
57
+ export interface CompleteA2AInviteResult {
58
+ success: boolean;
59
+ sender?: { assistantId: string; displayName: string; gatewayUrl: string };
60
+ error?: string;
61
+ }
62
+
63
+ export interface RedeemA2AInviteResult {
64
+ success: boolean;
65
+ contactId?: string;
66
+ alreadyConnected?: boolean;
67
+ error?: string;
68
+ }
69
+
70
+ export interface AcceptA2AInviteResult {
71
+ success: boolean;
72
+ contactId?: string;
73
+ alreadyConnected?: boolean;
74
+ error?: string;
75
+ errorCode?: string;
76
+ }
77
+
78
+ // ── Config operations ───────────────────────────────────────────────
79
+
80
+ export function getA2AConfig(): A2AConfigResult {
81
+ const config = getConfig();
82
+ const enabled = config.a2a?.enabled ?? false;
83
+
84
+ const contacts = searchContacts({ channelType: "a2a" });
85
+ const activeConnections = contacts.reduce((count, c) => {
86
+ return (
87
+ count +
88
+ c.channels.filter((ch) => ch.type === "a2a" && ch.status === "active")
89
+ .length
90
+ );
91
+ }, 0);
92
+
93
+ return { success: true, enabled, activeConnections };
94
+ }
95
+
96
+ export function setA2AConfig(): A2AConfigResult {
97
+ const raw = loadRawConfig();
98
+ setNestedValue(raw, "a2a.enabled", true);
99
+ saveRawConfig(raw);
100
+ invalidateConfigCache();
101
+
102
+ const result = getA2AConfig();
103
+ return { ...result, success: true };
104
+ }
105
+
106
+ export function clearA2AConfig(): A2AConfigResult {
107
+ const raw = loadRawConfig();
108
+ setNestedValue(raw, "a2a.enabled", false);
109
+ saveRawConfig(raw);
110
+ invalidateConfigCache();
111
+
112
+ return { success: true, enabled: false, activeConnections: 0 };
113
+ }
114
+
115
+ // ── A2A invite creation ────────────────────────────────────────────
116
+
117
+ export function createA2AInvite(params: {
118
+ expiresInHours?: number;
119
+ }): CreateA2AInviteResult {
120
+ // 1. Ensure A2A channel is enabled (auto-enable on first invite)
121
+ const config = getA2AConfig();
122
+ if (!config.enabled) {
123
+ setA2AConfig();
124
+ }
125
+
126
+ // 2. Resolve public base URL
127
+ let publicBaseUrl: string;
128
+ try {
129
+ publicBaseUrl = getPublicBaseUrl(getConfig());
130
+ } catch {
131
+ return {
132
+ success: false,
133
+ error:
134
+ "No public base URL configured. Set ingress.publicBaseUrl in config.",
135
+ };
136
+ }
137
+
138
+ // 3. Create placeholder contact (no channels — will be bound on acceptance)
139
+ const contact = upsertContact({
140
+ displayName: "Pending A2A invite",
141
+ contactType: "assistant",
142
+ role: "contact",
143
+ });
144
+
145
+ // 4. Create the invite
146
+ const expiresInMs = (params.expiresInHours ?? 72) * 60 * 60 * 1000;
147
+ const { invite, rawToken } = createInvite({
148
+ sourceChannel: "a2a",
149
+ contactId: contact.id,
150
+ maxUses: 1,
151
+ expiresInMs,
152
+ });
153
+
154
+ return {
155
+ success: true,
156
+ inviteId: invite.id,
157
+ token: rawToken,
158
+ expiresAt: invite.expiresAt,
159
+ senderGatewayUrl: publicBaseUrl,
160
+ };
161
+ }
162
+
163
+ // ── A2A invite completion (sender side) ───────────────────────────
164
+
165
+ export function completeA2AInvite(params: {
166
+ token: string;
167
+ senderAssistantId: string;
168
+ acceptor: {
169
+ assistantId: string;
170
+ displayName: string;
171
+ gatewayUrl: string;
172
+ };
173
+ }): CompleteA2AInviteResult {
174
+ // Resolve sender identity before any mutations so we fail cleanly
175
+ const displayName = getAssistantName() ?? "Vellum Assistant";
176
+ let gatewayUrl: string;
177
+ try {
178
+ gatewayUrl = getPublicBaseUrl(getConfig());
179
+ } catch {
180
+ return {
181
+ success: false,
182
+ error:
183
+ "No public base URL configured. Set ingress.publicBaseUrl in config.",
184
+ };
185
+ }
186
+
187
+ const tokenHash = hashToken(params.token);
188
+ const claimResult = claimA2AInvite({
189
+ tokenHash,
190
+ redeemedByExternalUserId: params.acceptor.assistantId,
191
+ });
192
+
193
+ if (!claimResult.claimed || !claimResult.invite) {
194
+ return { success: false, error: claimResult.error };
195
+ }
196
+
197
+ const invite = claimResult.invite;
198
+
199
+ // Promote the placeholder contact with the acceptor's identity
200
+ upsertContact({
201
+ id: invite.contactId,
202
+ displayName: params.acceptor.displayName,
203
+ contactType: "assistant",
204
+ role: "contact",
205
+ channels: [
206
+ {
207
+ type: "a2a",
208
+ address: params.acceptor.assistantId.toLowerCase(),
209
+ externalUserId: params.acceptor.assistantId,
210
+ status: "active",
211
+ policy: "allow",
212
+ },
213
+ ],
214
+ });
215
+
216
+ // Write assistant contact metadata
217
+ const db = getDb();
218
+ const metadataJson = JSON.stringify({
219
+ assistantId: params.acceptor.assistantId,
220
+ gatewayUrl: params.acceptor.gatewayUrl,
221
+ } satisfies VellumAssistantMetadata);
222
+ db.insert(assistantContactMetadata)
223
+ .values({
224
+ contactId: invite.contactId,
225
+ species: "vellum",
226
+ metadata: metadataJson,
227
+ })
228
+ .onConflictDoUpdate({
229
+ target: assistantContactMetadata.contactId,
230
+ set: { species: "vellum", metadata: metadataJson },
231
+ })
232
+ .run();
233
+
234
+ return {
235
+ success: true,
236
+ sender: {
237
+ assistantId: params.senderAssistantId,
238
+ displayName,
239
+ gatewayUrl,
240
+ },
241
+ };
242
+ }
243
+
244
+ // ── A2A invite redemption (receiver side) ─────────────────────────
245
+
246
+ export function redeemA2AInvite(params: {
247
+ sender: {
248
+ assistantId: string;
249
+ displayName: string;
250
+ gatewayUrl: string;
251
+ };
252
+ }): RedeemA2AInviteResult {
253
+ // 1. Ensure A2A channel is enabled (auto-enable if needed)
254
+ const config = getA2AConfig();
255
+ if (!config.enabled) {
256
+ setA2AConfig();
257
+ }
258
+
259
+ // 2. Check for existing active contact with this sender
260
+ const existing = findContactByAddress("a2a", params.sender.assistantId);
261
+ if (
262
+ existing &&
263
+ existing.channels.some((ch) => ch.type === "a2a" && ch.status === "active")
264
+ ) {
265
+ return { success: true, alreadyConnected: true, contactId: existing.id };
266
+ }
267
+
268
+ // 3. Create the sender as a local trusted contact
269
+ const contact = upsertContact({
270
+ displayName: params.sender.displayName,
271
+ contactType: "assistant",
272
+ role: "contact",
273
+ channels: [
274
+ {
275
+ type: "a2a",
276
+ address: params.sender.assistantId.toLowerCase(),
277
+ externalUserId: params.sender.assistantId,
278
+ status: "active",
279
+ policy: "allow",
280
+ },
281
+ ],
282
+ });
283
+
284
+ // 4. Write assistant contact metadata
285
+ const db = getDb();
286
+ const metadataJson = JSON.stringify({
287
+ assistantId: params.sender.assistantId,
288
+ gatewayUrl: params.sender.gatewayUrl,
289
+ } satisfies VellumAssistantMetadata);
290
+ db.insert(assistantContactMetadata)
291
+ .values({
292
+ contactId: contact.id,
293
+ species: "vellum",
294
+ metadata: metadataJson,
295
+ })
296
+ .onConflictDoUpdate({
297
+ target: assistantContactMetadata.contactId,
298
+ set: { species: "vellum", metadata: metadataJson },
299
+ })
300
+ .run();
301
+
302
+ return { success: true, contactId: contact.id };
303
+ }
304
+
305
+ // ── Self-hosted broker ──────────────────────────────────────────────
306
+
307
+ const ACCEPT_TIMEOUT_MS = 15_000;
308
+
309
+ /**
310
+ * Extract a human-readable error message from a daemon HTTP error
311
+ * response. The daemon always returns `{ error: { code, message } }`
312
+ * (see `HttpErrorResponse` in `runtime/http-errors.ts`).
313
+ */
314
+ function extractDaemonErrorMessage(
315
+ body: Record<string, unknown>,
316
+ ): string | undefined {
317
+ const envelope = body as Partial<HttpErrorResponse>;
318
+ if (
319
+ typeof envelope.error === "object" &&
320
+ envelope.error !== null &&
321
+ typeof envelope.error.message === "string"
322
+ ) {
323
+ return envelope.error.message;
324
+ }
325
+ return undefined;
326
+ }
327
+
328
+ /**
329
+ * Orchestrate cross-daemon A2A invite acceptance for self-hosted
330
+ * deployments. Calls the sender's `invite/complete` endpoint, then
331
+ * creates a local contact via `redeemA2AInvite`.
332
+ *
333
+ * Trust model: the user explicitly chose to connect to `senderGatewayUrl`
334
+ * and provided a token from the sender's invite link. We trust the
335
+ * invite-link values (`senderAssistantId`, `senderGatewayUrl`) as the
336
+ * canonical sender identity, and only use the `complete` response for the
337
+ * sender's display name (which has no other source in self-hosted mode).
338
+ */
339
+ export async function acceptA2AInvite(params: {
340
+ senderGatewayUrl: string;
341
+ senderAssistantId: string;
342
+ token: string;
343
+ }): Promise<AcceptA2AInviteResult> {
344
+ const senderGatewayUrl = params.senderGatewayUrl.replace(/\/+$/, "");
345
+
346
+ // 1. Validate local config
347
+ const displayName = getAssistantName() ?? "Vellum Assistant";
348
+ let localGatewayUrl: string;
349
+ try {
350
+ localGatewayUrl = getPublicBaseUrl(getConfig());
351
+ } catch {
352
+ return {
353
+ success: false,
354
+ error:
355
+ "No public base URL configured. Set ingress.publicBaseUrl in config.",
356
+ errorCode: "no_public_url",
357
+ };
358
+ }
359
+
360
+ // 2. Short-circuit if already connected — avoids a network round-trip
361
+ // and consuming a token on the sender side.
362
+ const existing = findContactByAddress("a2a", params.senderAssistantId);
363
+ if (
364
+ existing &&
365
+ existing.channels.some((ch) => ch.type === "a2a" && ch.status === "active")
366
+ ) {
367
+ return { success: true, alreadyConnected: true, contactId: existing.id };
368
+ }
369
+
370
+ // 3. Call the sender's invite/complete endpoint
371
+ const completeUrl = `${senderGatewayUrl}/v1/integrations/a2a/invite/complete`;
372
+ const completeBody = {
373
+ token: params.token,
374
+ senderAssistantId: params.senderAssistantId,
375
+ acceptor: {
376
+ assistantId: localGatewayUrl,
377
+ displayName,
378
+ gatewayUrl: localGatewayUrl,
379
+ },
380
+ };
381
+
382
+ let completeData: Record<string, unknown>;
383
+ try {
384
+ const response = await fetch(completeUrl, {
385
+ method: "POST",
386
+ headers: { "Content-Type": "application/json" },
387
+ body: JSON.stringify(completeBody),
388
+ signal: AbortSignal.timeout(ACCEPT_TIMEOUT_MS),
389
+ });
390
+
391
+ completeData = (await response.json()) as Record<string, unknown>;
392
+
393
+ if (!response.ok) {
394
+ const error =
395
+ extractDaemonErrorMessage(completeData) ?? "Invite completion failed";
396
+ log.warn(
397
+ { senderGatewayUrl, status: response.status, error },
398
+ "Sender invite/complete returned error",
399
+ );
400
+ return { success: false, error, errorCode: "complete_failed" };
401
+ }
402
+ } catch (err) {
403
+ const message = err instanceof Error ? err.message : String(err);
404
+ log.warn(
405
+ { senderGatewayUrl, error: message },
406
+ "Failed to reach sender for invite/complete",
407
+ );
408
+ return {
409
+ success: false,
410
+ error: `Failed to reach sender: ${message}`,
411
+ errorCode: "sender_unreachable",
412
+ };
413
+ }
414
+
415
+ // 4. Extract sender display name from the complete response; use
416
+ // invite-link values for assistantId and gatewayUrl (trusted source).
417
+ const senderFromResponse = completeData.sender as
418
+ | { displayName?: string }
419
+ | undefined;
420
+
421
+ const senderIdentity = {
422
+ assistantId: params.senderAssistantId,
423
+ displayName:
424
+ (typeof senderFromResponse?.displayName === "string" &&
425
+ senderFromResponse.displayName) ||
426
+ params.senderAssistantId,
427
+ gatewayUrl: senderGatewayUrl,
428
+ };
429
+
430
+ // 5. Create the sender as a local trusted contact
431
+ const redeemResult = redeemA2AInvite({ sender: senderIdentity });
432
+ if (!redeemResult.success) {
433
+ log.warn(
434
+ { error: redeemResult.error },
435
+ "Local invite/redeem failed after successful complete",
436
+ );
437
+ return {
438
+ success: false,
439
+ error: redeemResult.error ?? "Failed to create sender contact",
440
+ errorCode: "redeem_failed",
441
+ };
442
+ }
443
+
444
+ return {
445
+ success: true,
446
+ contactId: redeemResult.contactId,
447
+ alreadyConnected: redeemResult.alreadyConnected,
448
+ };
449
+ }
@@ -65,6 +65,7 @@ describe("projectProviderForWire", () => {
65
65
  const wire = projectProviderForWire(gemini!);
66
66
  const modelIds = wire.models.map((model) => model.id);
67
67
  const expectedGemini3ModelIds = [
68
+ "gemini-3.5-flash",
68
69
  "gemini-3.1-pro-preview",
69
70
  "gemini-3.1-pro-preview-customtools",
70
71
  "gemini-3-flash-preview",
@@ -141,6 +141,7 @@ export async function regenerateResponse(
141
141
  const conversation = await getOrCreateConversation(conversationId);
142
142
  touchConversation(conversationId);
143
143
  conversation.updateClient(broadcastMessage, false);
144
+ getSubagentManager().updateParentSender(conversationId, broadcastMessage);
144
145
  const requestId = uuid();
145
146
  conversation.traceEmitter.emit("request_received", "Regenerate requested", {
146
147
  requestId,
@@ -201,6 +202,85 @@ export function deleteQueuedMessage(
201
202
  return { removed: false, reason: "message_not_found" };
202
203
  }
203
204
 
205
+ /**
206
+ * Steer a conversation to a specific queued message.
207
+ * Promotes the message to the head of the queue, marks the conversation
208
+ * as needing tool-result repair, and aborts the current generation so the
209
+ * drain path picks up the promoted message.
210
+ *
211
+ * Returns `{ steered: true }` on success, or `{ steered: false, reason }` on failure.
212
+ */
213
+ export function steerToMessage(
214
+ conversationId: string,
215
+ requestId: string,
216
+ ):
217
+ | { steered: true }
218
+ | {
219
+ steered: false;
220
+ reason:
221
+ | "conversation_not_found"
222
+ | "message_not_found"
223
+ | "not_processing";
224
+ } {
225
+ const conversation = findConversation(conversationId);
226
+ if (!conversation) {
227
+ log.warn(
228
+ { conversationId, requestId },
229
+ "No conversation found for steer_to_message",
230
+ );
231
+ return { steered: false, reason: "conversation_not_found" };
232
+ }
233
+
234
+ if (!conversation.isProcessing()) {
235
+ log.warn(
236
+ { conversationId, requestId },
237
+ "Cannot steer: conversation is not processing",
238
+ );
239
+ return { steered: false, reason: "not_processing" };
240
+ }
241
+
242
+ const promoted = conversation.queue.promoteToHead(requestId);
243
+ if (!promoted) {
244
+ log.warn(
245
+ { conversationId, requestId },
246
+ "Queued message not found for steering",
247
+ );
248
+ return { steered: false, reason: "message_not_found" };
249
+ }
250
+
251
+ // Mark the conversation for tool-result repair so the drain path can
252
+ // inject synthetic tool results for any pending tool_use blocks that
253
+ // were abandoned by the aborted generation.
254
+ conversation.pendingSteerRepair = true;
255
+
256
+ // Broadcast the steer event so clients can update their UI.
257
+ broadcastMessage({
258
+ type: "message_steered",
259
+ conversationId,
260
+ requestId,
261
+ });
262
+
263
+ log.info(
264
+ { conversationId, requestId },
265
+ "Steering to queued message — aborting current generation",
266
+ );
267
+
268
+ // Abort the in-flight generation. The agent loop's finally block calls
269
+ // drainQueue, which will pick up the promoted message at the head.
270
+ // Unlike abortConversation, we do NOT clear the queue or dispose
271
+ // prompters — we want the queue to drain with the promoted message first.
272
+ const reason = createAbortReason(
273
+ "preempted_by_new_message",
274
+ "steerToMessage",
275
+ conversationId,
276
+ );
277
+ conversation.abortController?.abort(reason);
278
+ // Deny pending confirmations so the abort unblocks immediately.
279
+ conversation.denyAllPendingConfirmations();
280
+
281
+ return { steered: true };
282
+ }
283
+
204
284
  // ---------------------------------------------------------------------------
205
285
  // HTTP handler (delegates to shared logic)
206
286
  // ---------------------------------------------------------------------------