@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
@@ -10,10 +10,13 @@
10
10
 
11
11
  import { z } from "zod";
12
12
 
13
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
13
14
  import { getConfigReadOnly } from "../../config/loader.js";
14
15
  import { getDb } from "../../memory/db-connection.js";
15
16
  import {
16
17
  AuthSchema,
18
+ type ConnectionModel,
19
+ ConnectionModelSchema,
17
20
  ConnectionProviderSchema,
18
21
  ConnectionStatusSchema,
19
22
  ProviderConnectionSchema,
@@ -25,8 +28,14 @@ import {
25
28
  getConnection,
26
29
  listConnections,
27
30
  MANAGED_CONNECTION_NAMES,
31
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
28
32
  updateConnection,
29
33
  } from "../../providers/inference/connections.js";
34
+ import {
35
+ isPrivateOrLocalHost,
36
+ resolveHostAddresses,
37
+ resolveRequestAddress,
38
+ } from "../../tools/network/url-safety.js";
30
39
  import { BadRequestError, ConflictError, NotFoundError } from "./errors.js";
31
40
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
32
41
 
@@ -35,6 +44,125 @@ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
35
44
  // ---------------------------------------------------------------------------
36
45
 
37
46
  const providerConnectionResponseSchema = ProviderConnectionSchema;
47
+ const OPENAI_COMPATIBLE_ENDPOINTS_FLAG = "openai-compatible-endpoints";
48
+
49
+ function openAICompatibleEndpointsEnabled(): boolean {
50
+ return isAssistantFeatureFlagEnabled(
51
+ OPENAI_COMPATIBLE_ENDPOINTS_FLAG,
52
+ getConfigReadOnly(),
53
+ );
54
+ }
55
+
56
+ function rejectDisabledOpenAICompatibleProvider(provider: string): void {
57
+ if (provider !== "openai-compatible") return;
58
+ if (openAICompatibleEndpointsEnabled()) return;
59
+ throw new BadRequestError(
60
+ "OpenAI-compatible endpoints are disabled. Enable the openai-compatible-endpoints feature flag to configure this provider.",
61
+ );
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Custom provider field parsing (openai-compatible base_url + models)
66
+ // ---------------------------------------------------------------------------
67
+
68
+ /**
69
+ * Parse and validate `base_url` and `models` from the request body.
70
+ *
71
+ * `base_url` is only accepted for providers in
72
+ * `PROVIDERS_REQUIRING_BASE_URL_AND_MODELS` (currently `openai-compatible`).
73
+ * For all other providers, supplying `base_url` returns a 400. This prevents
74
+ * API-key exfiltration: an attacker cannot create an `anthropic` connection
75
+ * with a `base_url` pointing to their own server, which would redirect all
76
+ * LLM calls (and the API key) to the attacker.
77
+ *
78
+ * Even for `openai-compatible`, the `base_url` must not point to private
79
+ * networks or cloud metadata endpoints (SSRF protection).
80
+ */
81
+ async function parseCustomProviderFields(
82
+ body: Record<string, unknown>,
83
+ provider: string,
84
+ ): Promise<{
85
+ baseUrl?: string | null;
86
+ models?: ConnectionModel[] | null;
87
+ }> {
88
+ const out: {
89
+ baseUrl?: string | null;
90
+ models?: ConnectionModel[] | null;
91
+ } = {};
92
+
93
+ if ("base_url" in body) {
94
+ const raw = body.base_url;
95
+
96
+ // Gate: base_url is only valid for openai-compatible providers.
97
+ if (
98
+ raw !== null &&
99
+ raw !== undefined &&
100
+ !PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)
101
+ ) {
102
+ throw new BadRequestError(
103
+ `base_url is only valid for openai-compatible providers. Remove base_url or use the openai-compatible provider type.`,
104
+ );
105
+ }
106
+
107
+ if (raw === null) {
108
+ out.baseUrl = null;
109
+ } else if (typeof raw === "string" && raw.length > 0) {
110
+ let parsed: URL;
111
+ try {
112
+ parsed = new URL(raw);
113
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
114
+ throw new BadRequestError(`Invalid base_url: must be an http(s) URL`);
115
+ }
116
+ } catch (err) {
117
+ if (err instanceof BadRequestError) throw err;
118
+ throw new BadRequestError(
119
+ `Invalid base_url: must be a valid http(s) URL`,
120
+ );
121
+ }
122
+
123
+ // SSRF protection: reject private IPs, localhost, cloud metadata endpoints.
124
+ const hostname = parsed.hostname;
125
+ if (isPrivateOrLocalHost(hostname)) {
126
+ throw new BadRequestError(
127
+ `Invalid base_url: must not point to a private or local network address.`,
128
+ );
129
+ }
130
+
131
+ // DNS resolution check: hostname may resolve to a private IP.
132
+ const resolved = await resolveRequestAddress(
133
+ hostname,
134
+ resolveHostAddresses,
135
+ /* allowPrivateNetwork */ false,
136
+ );
137
+ if (resolved.blockedAddress) {
138
+ throw new BadRequestError(
139
+ `Invalid base_url: hostname resolves to a private network address.`,
140
+ );
141
+ }
142
+
143
+ out.baseUrl = raw;
144
+ } else {
145
+ throw new BadRequestError(
146
+ `Invalid base_url: must be a non-empty string or null`,
147
+ );
148
+ }
149
+ }
150
+
151
+ if ("models" in body) {
152
+ const raw = body.models;
153
+ if (raw === null) {
154
+ out.models = null;
155
+ } else {
156
+ const parsed = z.array(ConnectionModelSchema).safeParse(raw);
157
+ if (!parsed.success) {
158
+ throw new BadRequestError(`Invalid models: ${parsed.error.message}`);
159
+ }
160
+ out.models = parsed.data;
161
+ }
162
+ }
163
+
164
+ return out;
165
+ }
38
166
 
39
167
  // ---------------------------------------------------------------------------
40
168
  // Handlers
@@ -42,10 +170,18 @@ const providerConnectionResponseSchema = ProviderConnectionSchema;
42
170
 
43
171
  function handleListConnections({ queryParams = {} }: RouteHandlerArgs) {
44
172
  const provider = queryParams.provider;
173
+ if (provider) rejectDisabledOpenAICompatibleProvider(provider);
45
174
  const connections = listConnections(
46
175
  getDb(),
47
176
  provider ? { provider } : undefined,
48
177
  );
178
+ if (!openAICompatibleEndpointsEnabled()) {
179
+ return {
180
+ connections: connections.filter(
181
+ (conn) => conn.provider !== "openai-compatible",
182
+ ),
183
+ };
184
+ }
49
185
  return { connections };
50
186
  }
51
187
 
@@ -55,11 +191,17 @@ function handleGetConnection({ pathParams = {} }: RouteHandlerArgs) {
55
191
 
56
192
  const conn = getConnection(getDb(), name);
57
193
  if (!conn) throw new NotFoundError(`Connection "${name}" not found.`);
194
+ if (
195
+ conn.provider === "openai-compatible" &&
196
+ !openAICompatibleEndpointsEnabled()
197
+ ) {
198
+ throw new NotFoundError(`Connection "${name}" not found.`);
199
+ }
58
200
 
59
201
  return conn;
60
202
  }
61
203
 
62
- function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
204
+ async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
63
205
  const name = body.name;
64
206
  const provider = body.provider;
65
207
  const auth = body.auth;
@@ -74,6 +216,7 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
74
216
  `Invalid provider "${String(provider)}". Valid: ${VALID_CONNECTION_PROVIDERS.join(", ")}`,
75
217
  );
76
218
  }
219
+ rejectDisabledOpenAICompatibleProvider(providerResult.data);
77
220
 
78
221
  const authResult = AuthSchema.safeParse(auth);
79
222
  if (!authResult.success) {
@@ -99,12 +242,15 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
99
242
  );
100
243
  }
101
244
 
245
+ const customFields = await parseCustomProviderFields(body, providerResult.data);
246
+
102
247
  const result = createConnection(getDb(), {
103
248
  name,
104
249
  provider: providerResult.data,
105
250
  auth: authResult.data,
106
251
  ...(statusResult ? { status: statusResult.data } : {}),
107
252
  ...(labelRaw !== undefined ? { label: labelRaw as string | null } : {}),
253
+ ...customFields,
108
254
  });
109
255
 
110
256
  if (!result.ok) {
@@ -118,19 +264,38 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
118
264
  `Invalid provider "${result.error.provider}". Valid: ${VALID_CONNECTION_PROVIDERS.join(", ")}`,
119
265
  );
120
266
  }
267
+ if (result.error.code === "base_url_required") {
268
+ throw new BadRequestError(
269
+ "base_url is required for openai-compatible connections.",
270
+ );
271
+ }
272
+ if (result.error.code === "models_required") {
273
+ throw new BadRequestError(
274
+ "At least one model is required for openai-compatible connections.",
275
+ );
276
+ }
121
277
  throw new BadRequestError("Invalid auth configuration.");
122
278
  }
123
279
 
124
280
  return result.connection;
125
281
  }
126
282
 
127
- function handleUpdateConnection({
283
+ async function handleUpdateConnection({
128
284
  pathParams = {},
129
285
  body = {},
130
286
  }: RouteHandlerArgs) {
131
287
  const { name } = pathParams;
132
288
  if (!name) throw new BadRequestError("name is required");
133
289
 
290
+ const existing = getConnection(getDb(), name);
291
+ if (!existing) throw new NotFoundError(`Connection "${name}" not found.`);
292
+ if (
293
+ existing.provider === "openai-compatible" &&
294
+ !openAICompatibleEndpointsEnabled()
295
+ ) {
296
+ throw new NotFoundError(`Connection "${name}" not found.`);
297
+ }
298
+
134
299
  const auth = body.auth;
135
300
  const authResult = AuthSchema.safeParse(auth);
136
301
  if (!authResult.success) {
@@ -169,16 +334,29 @@ function handleUpdateConnection({
169
334
  );
170
335
  }
171
336
 
337
+ const customFields = await parseCustomProviderFields(body, existing.provider);
338
+
172
339
  const result = updateConnection(getDb(), name, {
173
340
  auth: authResult.data,
174
341
  ...(statusResult ? { status: statusResult.data } : {}),
175
342
  ...(labelRaw !== undefined ? { label: labelRaw as string | null } : {}),
343
+ ...customFields,
176
344
  });
177
345
 
178
346
  if (!result.ok) {
179
347
  if (result.error.code === "not_found") {
180
348
  throw new NotFoundError(`Connection "${name}" not found.`);
181
349
  }
350
+ if (result.error.code === "base_url_required") {
351
+ throw new BadRequestError(
352
+ "base_url is required for openai-compatible connections.",
353
+ );
354
+ }
355
+ if (result.error.code === "models_required") {
356
+ throw new BadRequestError(
357
+ "At least one model is required for openai-compatible connections.",
358
+ );
359
+ }
182
360
  throw new BadRequestError("Invalid auth configuration.");
183
361
  }
184
362
 
@@ -191,7 +369,14 @@ function handleDeleteConnection({ pathParams = {} }: RouteHandlerArgs) {
191
369
 
192
370
  // Existence check first so a stale `llm.default.provider_connection`
193
371
  // reference to a missing connection returns 404 (not 409).
194
- if (!getConnection(getDb(), name)) {
372
+ const existing = getConnection(getDb(), name);
373
+ if (!existing) {
374
+ throw new NotFoundError(`Connection "${name}" not found.`);
375
+ }
376
+ if (
377
+ existing.provider === "openai-compatible" &&
378
+ !openAICompatibleEndpointsEnabled()
379
+ ) {
195
380
  throw new NotFoundError(`Connection "${name}" not found.`);
196
381
  }
197
382
 
@@ -301,6 +486,8 @@ export const ROUTES: RouteDefinition[] = [
301
486
  auth: AuthSchema,
302
487
  label: z.string().min(1).optional(),
303
488
  status: ConnectionStatusSchema.optional(),
489
+ base_url: z.string().url().nullable().optional(),
490
+ models: z.array(ConnectionModelSchema).nullable().optional(),
304
491
  }),
305
492
  responseBody: providerConnectionResponseSchema,
306
493
  responseStatus: "201",
@@ -324,6 +511,8 @@ export const ROUTES: RouteDefinition[] = [
324
511
  auth: AuthSchema,
325
512
  status: ConnectionStatusSchema.optional(),
326
513
  label: z.string().min(1).nullable().optional(),
514
+ base_url: z.string().url().nullable().optional(),
515
+ models: z.array(ConnectionModelSchema).nullable().optional(),
327
516
  }),
328
517
  responseBody: providerConnectionResponseSchema,
329
518
  additionalResponses: {
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Route handlers for A2A integration config endpoints.
3
+ *
4
+ * GET /v1/integrations/a2a/config — get current A2A config status
5
+ * POST /v1/integrations/a2a/config — enable A2A channel
6
+ * DELETE /v1/integrations/a2a/config — disable A2A channel
7
+ * POST /v1/integrations/a2a/invite — create a shareable A2A invite token
8
+ * POST /v1/integrations/a2a/invite/complete — sender-side invite completion
9
+ * POST /v1/integrations/a2a/invite/redeem — receiver-side invite redemption
10
+ * POST /v1/integrations/a2a/invite/accept — self-hosted broker: orchestrate complete + redeem
11
+ */
12
+
13
+ import { isA2AEnabled } from "../../../a2a/feature-gate.js";
14
+ import { getConfig } from "../../../config/loader.js";
15
+ import {
16
+ acceptA2AInvite,
17
+ clearA2AConfig,
18
+ completeA2AInvite,
19
+ createA2AInvite,
20
+ getA2AConfig,
21
+ redeemA2AInvite,
22
+ setA2AConfig,
23
+ } from "../../../daemon/handlers/config-a2a.js";
24
+ import { BadGatewayError, BadRequestError } from "../errors.js";
25
+ import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Helpers
29
+ // ---------------------------------------------------------------------------
30
+
31
+ function assertA2AFlag(): void {
32
+ if (!isA2AEnabled(getConfig())) {
33
+ throw new BadRequestError("A2A channel is not available");
34
+ }
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Handlers
39
+ // ---------------------------------------------------------------------------
40
+
41
+ function handleGetA2AConfig() {
42
+ assertA2AFlag();
43
+ return getA2AConfig();
44
+ }
45
+
46
+ function handleSetA2AConfig() {
47
+ assertA2AFlag();
48
+ const result = setA2AConfig();
49
+ if (!result.success) {
50
+ throw new BadRequestError(result.error ?? "Failed to enable A2A");
51
+ }
52
+ return result;
53
+ }
54
+
55
+ function handleClearA2AConfig() {
56
+ assertA2AFlag();
57
+ return clearA2AConfig();
58
+ }
59
+
60
+ function handleCreateA2AInvite({ body = {} }: RouteHandlerArgs) {
61
+ assertA2AFlag();
62
+ const { expiresInHours } = body as { expiresInHours?: unknown };
63
+ if (expiresInHours !== undefined) {
64
+ if (
65
+ typeof expiresInHours !== "number" ||
66
+ !Number.isFinite(expiresInHours) ||
67
+ expiresInHours <= 0
68
+ ) {
69
+ throw new BadRequestError(
70
+ "expiresInHours must be a positive finite number",
71
+ );
72
+ }
73
+ }
74
+ const result = createA2AInvite({
75
+ expiresInHours: expiresInHours as number | undefined,
76
+ });
77
+ if (!result.success) {
78
+ throw new BadRequestError(result.error ?? "Failed to create A2A invite");
79
+ }
80
+ return result;
81
+ }
82
+
83
+ function handleCompleteA2AInvite({ body = {} }: RouteHandlerArgs) {
84
+ const { token, senderAssistantId, acceptor } = body as {
85
+ token?: unknown;
86
+ senderAssistantId?: unknown;
87
+ acceptor?: {
88
+ assistantId?: unknown;
89
+ displayName?: unknown;
90
+ gatewayUrl?: unknown;
91
+ };
92
+ };
93
+
94
+ if (typeof token !== "string" || !token) {
95
+ throw new BadRequestError(
96
+ "token is required and must be a non-empty string",
97
+ );
98
+ }
99
+ if (typeof senderAssistantId !== "string" || !senderAssistantId) {
100
+ throw new BadRequestError(
101
+ "senderAssistantId is required and must be a non-empty string",
102
+ );
103
+ }
104
+ if (
105
+ !acceptor ||
106
+ typeof acceptor.assistantId !== "string" ||
107
+ !acceptor.assistantId ||
108
+ typeof acceptor.displayName !== "string" ||
109
+ !acceptor.displayName ||
110
+ typeof acceptor.gatewayUrl !== "string" ||
111
+ !acceptor.gatewayUrl
112
+ ) {
113
+ throw new BadRequestError(
114
+ "acceptor must include non-empty assistantId, displayName, and gatewayUrl",
115
+ );
116
+ }
117
+
118
+ const result = completeA2AInvite({
119
+ token,
120
+ senderAssistantId,
121
+ acceptor: {
122
+ assistantId: acceptor.assistantId,
123
+ displayName: acceptor.displayName,
124
+ gatewayUrl: acceptor.gatewayUrl,
125
+ },
126
+ });
127
+ if (!result.success) {
128
+ throw new BadRequestError(result.error ?? "Failed to complete A2A invite");
129
+ }
130
+ return result;
131
+ }
132
+
133
+ function handleRedeemA2AInvite({ body = {} }: RouteHandlerArgs) {
134
+ const { sender } = body as {
135
+ sender?: {
136
+ assistantId?: unknown;
137
+ displayName?: unknown;
138
+ gatewayUrl?: unknown;
139
+ };
140
+ };
141
+
142
+ if (
143
+ !sender ||
144
+ typeof sender.assistantId !== "string" ||
145
+ !sender.assistantId ||
146
+ typeof sender.displayName !== "string" ||
147
+ !sender.displayName ||
148
+ typeof sender.gatewayUrl !== "string" ||
149
+ !sender.gatewayUrl
150
+ ) {
151
+ throw new BadRequestError(
152
+ "sender must include non-empty assistantId, displayName, and gatewayUrl",
153
+ );
154
+ }
155
+
156
+ const result = redeemA2AInvite({
157
+ sender: {
158
+ assistantId: sender.assistantId,
159
+ displayName: sender.displayName,
160
+ gatewayUrl: sender.gatewayUrl,
161
+ },
162
+ });
163
+ if (!result.success) {
164
+ throw new BadRequestError(result.error ?? "Failed to redeem A2A invite");
165
+ }
166
+ return result;
167
+ }
168
+
169
+ async function handleAcceptA2AInvite({ body = {} }: RouteHandlerArgs) {
170
+ assertA2AFlag();
171
+ const { senderGatewayUrl, senderAssistantId, token } = body as {
172
+ senderGatewayUrl?: unknown;
173
+ senderAssistantId?: unknown;
174
+ token?: unknown;
175
+ };
176
+
177
+ if (typeof senderGatewayUrl !== "string" || !senderGatewayUrl) {
178
+ throw new BadRequestError(
179
+ "senderGatewayUrl is required and must be a non-empty string",
180
+ );
181
+ }
182
+ try {
183
+ new URL(senderGatewayUrl);
184
+ } catch {
185
+ throw new BadRequestError("senderGatewayUrl must be a valid URL");
186
+ }
187
+ if (typeof senderAssistantId !== "string" || !senderAssistantId) {
188
+ throw new BadRequestError(
189
+ "senderAssistantId is required and must be a non-empty string",
190
+ );
191
+ }
192
+ if (typeof token !== "string" || !token) {
193
+ throw new BadRequestError(
194
+ "token is required and must be a non-empty string",
195
+ );
196
+ }
197
+
198
+ const result = await acceptA2AInvite({
199
+ senderGatewayUrl,
200
+ senderAssistantId,
201
+ token,
202
+ });
203
+ if (!result.success) {
204
+ const isSenderFault =
205
+ result.errorCode === "sender_unreachable" ||
206
+ result.errorCode === "complete_failed";
207
+ if (isSenderFault) {
208
+ throw new BadGatewayError(result.error ?? "Failed to accept A2A invite");
209
+ }
210
+ throw new BadRequestError(result.error ?? "Failed to accept A2A invite");
211
+ }
212
+ return result;
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Route definitions
217
+ // ---------------------------------------------------------------------------
218
+
219
+ export const ROUTES: RouteDefinition[] = [
220
+ {
221
+ operationId: "integrations_a2a_config_get",
222
+ endpoint: "integrations/a2a/config",
223
+ method: "GET",
224
+ summary: "Get A2A config",
225
+ description: "Check current A2A channel configuration status.",
226
+ tags: ["integrations"],
227
+ requirePolicyEnforcement: true,
228
+ handler: () => handleGetA2AConfig(),
229
+ },
230
+ {
231
+ operationId: "integrations_a2a_config_post",
232
+ endpoint: "integrations/a2a/config",
233
+ method: "POST",
234
+ summary: "Enable A2A channel",
235
+ description: "Enable the A2A channel for inter-assistant communication.",
236
+ tags: ["integrations"],
237
+ requirePolicyEnforcement: true,
238
+ handler: () => handleSetA2AConfig(),
239
+ },
240
+ {
241
+ operationId: "integrations_a2a_config_delete",
242
+ endpoint: "integrations/a2a/config",
243
+ method: "DELETE",
244
+ summary: "Disable A2A channel",
245
+ description: "Disable the A2A channel.",
246
+ tags: ["integrations"],
247
+ requirePolicyEnforcement: true,
248
+ handler: () => handleClearA2AConfig(),
249
+ },
250
+ {
251
+ operationId: "integrations_a2a_invite_post",
252
+ endpoint: "integrations/a2a/invite",
253
+ method: "POST",
254
+ summary: "Create A2A invite",
255
+ description:
256
+ "Create a shareable A2A invite token for link-based contact creation.",
257
+ tags: ["integrations"],
258
+ requirePolicyEnforcement: true,
259
+ handler: handleCreateA2AInvite,
260
+ },
261
+ {
262
+ operationId: "integrations_a2a_invite_complete_post",
263
+ endpoint: "integrations/a2a/invite/complete",
264
+ method: "POST",
265
+ summary: "Complete A2A invite (sender side)",
266
+ description:
267
+ "Called by the platform to finalize the sender side of a link-based A2A connection.",
268
+ tags: ["integrations"],
269
+ requirePolicyEnforcement: true,
270
+ handler: handleCompleteA2AInvite,
271
+ },
272
+ {
273
+ operationId: "integrations_a2a_invite_redeem_post",
274
+ endpoint: "integrations/a2a/invite/redeem",
275
+ method: "POST",
276
+ summary: "Redeem A2A invite (receiver side)",
277
+ description:
278
+ "Called by the platform to create a trusted contact on the receiver side of a link-based A2A connection.",
279
+ tags: ["integrations"],
280
+ requirePolicyEnforcement: true,
281
+ handler: handleRedeemA2AInvite,
282
+ },
283
+ {
284
+ operationId: "integrations_a2a_invite_accept_post",
285
+ endpoint: "integrations/a2a/invite/accept",
286
+ method: "POST",
287
+ summary: "Accept A2A invite (self-hosted broker)",
288
+ description:
289
+ "Orchestrate cross-daemon invite acceptance for self-hosted deployments. Calls the sender's invite/complete, then creates a local contact via invite/redeem.",
290
+ tags: ["integrations"],
291
+ requirePolicyEnforcement: true,
292
+ handler: handleAcceptA2AInvite,
293
+ },
294
+ ];
@@ -1,10 +1,20 @@
1
+ import { resolveDefaultProfileKey } from "../../config/llm-resolver.js";
2
+ import { loadConfig } from "../../config/loader.js";
1
3
  import { CALL_SITE_CATALOG, CALL_SITE_DOMAINS } from "../../config/schemas/call-site-catalog.js";
4
+ import type { LLMCallSite } from "../../config/schemas/llm.js";
2
5
  import type { RouteDefinition } from "./types.js";
3
6
 
4
7
  async function handleGetCallSites() {
8
+ const { llm } = loadConfig();
5
9
  return {
6
10
  domains: CALL_SITE_DOMAINS,
7
- callSites: CALL_SITE_CATALOG,
11
+ callSites: CALL_SITE_CATALOG.map((entry) => ({
12
+ ...entry,
13
+ defaultProfile: resolveDefaultProfileKey(
14
+ entry.id as LLMCallSite,
15
+ llm,
16
+ ),
17
+ })),
8
18
  };
9
19
  }
10
20
 
@@ -36,6 +36,7 @@ import {
36
36
  getWorkspaceConfigPath,
37
37
  } from "../../util/platform.js";
38
38
  import { APP_VERSION, COMMIT_SHA } from "../../version.js";
39
+ import { assistantEventHub } from "../assistant-event-hub.js";
39
40
  import { createTarGz } from "./archive-utils.js";
40
41
  import { InternalError } from "./errors.js";
41
42
  import { collectWorkspaceData } from "./log-export/workspace-allowlist.js";
@@ -270,6 +271,44 @@ async function handleExport({
270
271
  );
271
272
  }
272
273
 
274
+ // --- Connected clients (`assistant clients list --json`) ---
275
+ try {
276
+ const clients = assistantEventHub.listClients();
277
+ const clientsList = {
278
+ clients: clients.map((c) => ({
279
+ clientId: c.clientId,
280
+ interfaceId: c.interfaceId,
281
+ capabilities: c.capabilities,
282
+ machineName: c.machineName,
283
+ connectedAt: c.connectedAt.toISOString(),
284
+ lastActiveAt: c.lastActiveAt.toISOString(),
285
+ })),
286
+ };
287
+ writeFileSync(
288
+ join(staging, "clients-list.json"),
289
+ JSON.stringify(clientsList, null, 2),
290
+ "utf-8",
291
+ );
292
+ } catch (err) {
293
+ const message = err instanceof Error ? err.message : String(err);
294
+ writeFileSync(
295
+ join(staging, "clients-list-error.json"),
296
+ JSON.stringify(
297
+ {
298
+ error: message,
299
+ collectedAt: new Date().toISOString(),
300
+ },
301
+ null,
302
+ 2,
303
+ ),
304
+ "utf-8",
305
+ );
306
+ log.warn(
307
+ { err },
308
+ "Failed to collect connected clients list, continuing without it",
309
+ );
310
+ }
311
+
273
312
  // --- Export manifest ---
274
313
  const manifestType = conversationId
275
314
  ? ("conversation-export" as const)