@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,78 @@
1
+ /**
2
+ * Route definitions for content-source configuration.
3
+ *
4
+ * POST /v1/content-source — validate and persist a content source URL as sidecar
5
+ *
6
+ * Uses policyKey: "secrets" — writes workspace data, same policy tier as
7
+ * the existing secrets routes.
8
+ */
9
+
10
+ import { mkdirSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+
13
+ import { z } from "zod";
14
+
15
+ import { getWorkspaceDir } from "../../util/platform.js";
16
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ function writeSidecar(relPath: string, data: Record<string, unknown>): void {
23
+ const workspaceDir = getWorkspaceDir();
24
+ const absPath = join(workspaceDir, relPath);
25
+ mkdirSync(dirname(absPath), { recursive: true });
26
+ writeFileSync(absPath, JSON.stringify(data, null, 2), "utf-8");
27
+ }
28
+
29
+ function validateUrl(
30
+ raw: string,
31
+ ): { ok: true; normalized: string } | { ok: false; error: "invalid_url" } {
32
+ const trimmed = raw.trim();
33
+ let parsed: URL;
34
+ try {
35
+ parsed = new URL(trimmed);
36
+ } catch {
37
+ return { ok: false, error: "invalid_url" };
38
+ }
39
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
40
+ return { ok: false, error: "invalid_url" };
41
+ }
42
+ parsed.username = "";
43
+ parsed.password = "";
44
+ return { ok: true, normalized: parsed.href };
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // POST /v1/content-source
49
+ // ---------------------------------------------------------------------------
50
+
51
+ function handleContentSource(args: RouteHandlerArgs): Record<string, unknown> {
52
+ const rawUrl = typeof args.body?.url === "string" ? args.body.url : "";
53
+
54
+ const result = validateUrl(rawUrl);
55
+ if (!result.ok) {
56
+ return { ok: false, error: result.error };
57
+ }
58
+
59
+ writeSidecar("data/content-source.json", { url: result.normalized });
60
+
61
+ return { ok: true };
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Route definition
66
+ // ---------------------------------------------------------------------------
67
+
68
+ export const ROUTES: RouteDefinition[] = [
69
+ {
70
+ operationId: "content_source_set",
71
+ endpoint: "content-source",
72
+ method: "POST",
73
+ policyKey: "secrets",
74
+ summary: "Validate and persist a content source URL",
75
+ requestBody: z.object({ url: z.string() }),
76
+ handler: handleContentSource,
77
+ },
78
+ ];
@@ -11,6 +11,7 @@ import { z } from "zod";
11
11
 
12
12
  import { clearAllConversations as clearAllActive } from "../../daemon/handlers/conversations.js";
13
13
  import { formatJson, formatMarkdown } from "../../export/formatter.js";
14
+ import { ipcCall as ipcCallGateway } from "../../ipc/gateway-client.js";
14
15
  import {
15
16
  addMessage,
16
17
  createConversation,
@@ -19,8 +20,10 @@ import {
19
20
  } from "../../memory/conversation-crud.js";
20
21
  import { setConversationKey } from "../../memory/conversation-key-store.js";
21
22
  import { listConversations } from "../../memory/conversation-queries.js";
23
+ import { getBindingByConversation } from "../../memory/external-conversation-store.js";
24
+ import { sendSlackReply } from "../../messaging/providers/slack/send.js";
22
25
  import { getLogger } from "../../util/logger.js";
23
- import { BadRequestError, NotFoundError } from "./errors.js";
26
+ import { BadGatewayError, BadRequestError, NotFoundError } from "./errors.js";
24
27
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
25
28
 
26
29
  const log = getLogger("conversation-cli-routes");
@@ -150,6 +153,136 @@ async function handleClearCli(_args: RouteHandlerArgs) {
150
153
  return { cleared };
151
154
  }
152
155
 
156
+ // ---------------------------------------------------------------------------
157
+ // slack detach (CLI)
158
+ // ---------------------------------------------------------------------------
159
+
160
+ const slackDetachRequestSchema = z.object({
161
+ conversationId: z.string().trim().min(1).optional(),
162
+ channelId: z.string().trim().min(1).optional(),
163
+ threadTs: z.string().trim().min(1).optional(),
164
+ });
165
+
166
+ const slackDetachResponseSchema = z.object({
167
+ detached: z.boolean(),
168
+ channelId: z.string(),
169
+ threadTs: z.string(),
170
+ source: z.enum(["explicit", "conversation_binding"]),
171
+ conversationId: z.string().optional(),
172
+ });
173
+
174
+ type SlackDetachGatewayResponse = {
175
+ detached: boolean;
176
+ channelId: string;
177
+ threadTs: string;
178
+ };
179
+
180
+ const SLACK_DETACH_CONFIRMATION_TEXT =
181
+ "Muted this Slack thread. I won't respond to further replies here unless you mention me again.";
182
+
183
+ function isSlackDetachGatewayResponse(
184
+ value: unknown,
185
+ ): value is SlackDetachGatewayResponse {
186
+ return (
187
+ value != null &&
188
+ typeof value === "object" &&
189
+ typeof (value as SlackDetachGatewayResponse).detached === "boolean" &&
190
+ typeof (value as SlackDetachGatewayResponse).channelId === "string" &&
191
+ typeof (value as SlackDetachGatewayResponse).threadTs === "string"
192
+ );
193
+ }
194
+
195
+ async function handleSlackDetachCli({ body = {} }: RouteHandlerArgs) {
196
+ const parsed = slackDetachRequestSchema.parse(body);
197
+ const explicitChannelId = parsed.channelId;
198
+ const explicitThreadTs = parsed.threadTs;
199
+
200
+ let channelId: string;
201
+ let threadTs: string;
202
+ let source: "explicit" | "conversation_binding";
203
+ let conversationId: string | undefined;
204
+
205
+ if (explicitChannelId || explicitThreadTs) {
206
+ if (!explicitChannelId || !explicitThreadTs) {
207
+ throw new BadRequestError(
208
+ "Both channelId and threadTs are required when detaching by explicit Slack identifiers",
209
+ );
210
+ }
211
+ channelId = explicitChannelId;
212
+ threadTs = explicitThreadTs;
213
+ source = "explicit";
214
+ } else {
215
+ if (!parsed.conversationId) {
216
+ throw new BadRequestError(
217
+ "conversationId is required unless channelId and threadTs are provided",
218
+ );
219
+ }
220
+
221
+ const binding = getBindingByConversation(parsed.conversationId);
222
+ if (!binding) {
223
+ throw new NotFoundError(
224
+ `No channel binding found for conversation ${parsed.conversationId}`,
225
+ );
226
+ }
227
+ if (binding.sourceChannel !== "slack") {
228
+ throw new BadRequestError(
229
+ `Conversation ${parsed.conversationId} is bound to ${binding.sourceChannel}, not Slack`,
230
+ );
231
+ }
232
+ if (!binding.externalThreadId) {
233
+ throw new BadRequestError(
234
+ `Conversation ${parsed.conversationId} is not bound to a Slack thread`,
235
+ );
236
+ }
237
+
238
+ channelId = binding.externalChatId;
239
+ threadTs = binding.externalThreadId;
240
+ source = "conversation_binding";
241
+ conversationId = parsed.conversationId;
242
+ }
243
+
244
+ const gatewayResult = await ipcCallGateway(
245
+ "detach_slack_active_thread",
246
+ { channelId, threadTs },
247
+ 5_000,
248
+ );
249
+ if (!isSlackDetachGatewayResponse(gatewayResult)) {
250
+ throw new BadGatewayError(
251
+ "Could not detach Slack thread from assistant listening",
252
+ );
253
+ }
254
+
255
+ if (gatewayResult.detached) {
256
+ try {
257
+ await sendSlackReply(
258
+ gatewayResult.channelId,
259
+ SLACK_DETACH_CONFIRMATION_TEXT,
260
+ { threadTs: gatewayResult.threadTs },
261
+ );
262
+ } catch (err) {
263
+ log.warn(
264
+ {
265
+ err,
266
+ channelId: gatewayResult.channelId,
267
+ threadTs: gatewayResult.threadTs,
268
+ },
269
+ "Slack thread detached, but confirmation message failed",
270
+ );
271
+ throw new BadGatewayError(
272
+ "Detached Slack thread but could not send confirmation",
273
+ );
274
+ }
275
+ }
276
+
277
+ return {
278
+ detached: gatewayResult.detached,
279
+ channelId: gatewayResult.channelId,
280
+ threadTs: gatewayResult.threadTs,
281
+ source,
282
+ ...(conversationId ? { conversationId } : {}),
283
+ };
284
+ }
285
+
153
286
  // ---------------------------------------------------------------------------
154
287
  // Route definitions
155
288
  // ---------------------------------------------------------------------------
@@ -230,4 +363,16 @@ export const ROUTES: RouteDefinition[] = [
230
363
  }),
231
364
  handler: handleClearCli,
232
365
  },
366
+ {
367
+ operationId: "conversation_slack_detach_cli",
368
+ endpoint: "conversations/cli/slack/detach",
369
+ method: "POST",
370
+ summary: "Detach the assistant from a Slack thread (CLI)",
371
+ description:
372
+ "Stops Slack active-thread listening for a Slack thread. The CLI resolves current conversation defaults.",
373
+ tags: ["conversations"],
374
+ requestBody: slackDetachRequestSchema,
375
+ responseBody: slackDetachResponseSchema,
376
+ handler: handleSlackDetachCli,
377
+ },
233
378
  ];
@@ -15,6 +15,7 @@
15
15
  * GET /v1/messages/:id/llm-context — LLM request logs for a message
16
16
  * GET /v1/llm-request-logs/:id/payload — raw payload for a single log
17
17
  * DELETE /v1/messages/queued/:id — delete queued message
18
+ * POST /v1/messages/queued/:id/steer — steer to a queued message
18
19
  */
19
20
 
20
21
  import { z } from "zod";
@@ -49,7 +50,10 @@ import {
49
50
  getMessageContent,
50
51
  performConversationSearch,
51
52
  } from "../../daemon/handlers/conversation-history.js";
52
- import { deleteQueuedMessage } from "../../daemon/handlers/conversations.js";
53
+ import {
54
+ deleteQueuedMessage,
55
+ steerToMessage,
56
+ } from "../../daemon/handlers/conversations.js";
53
57
  import {
54
58
  CONFIG_RELOAD_DEBOUNCE_MS,
55
59
  log,
@@ -59,12 +63,20 @@ import {
59
63
  getConversation,
60
64
  getMessageById,
61
65
  } from "../../memory/conversation-crud.js";
66
+ import { getDb } from "../../memory/db-connection.js";
62
67
  import { clearEmbeddingBackendCache } from "../../memory/embedding-backend.js";
63
68
  import { getLlmRequestLogSource } from "../../memory/llm-request-log-source.js";
64
69
  import { getMemoryRecallLogByMessageIds } from "../../memory/memory-recall-log-store.js";
65
70
  import { getMemoryV2ActivationLogByMessageIds } from "../../memory/memory-v2-activation-log-store.js";
66
71
  import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
72
+ import {
73
+ createConnection,
74
+ listConnections,
75
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
76
+ } from "../../providers/inference/connections.js";
77
+ import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
67
78
  import { initializeProviders } from "../../providers/registry.js";
79
+ import { credentialKey } from "../../security/credential-key.js";
68
80
  import { validateAllowlistFile } from "../../security/secret-allowlist.js";
69
81
  import { resolvePricingForUsage } from "../../util/pricing.js";
70
82
  import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
@@ -379,13 +391,42 @@ function readPlainObject(value: unknown): Record<string, unknown> | undefined {
379
391
 
380
392
  function handleGetConfig() {
381
393
  try {
382
- return applyContextDefaultsToRawConfig(loadRawConfig());
394
+ const config = applyContextDefaultsToRawConfig(loadRawConfig());
395
+ enrichProfilesWithVisionFlag(config);
396
+ return config;
383
397
  } catch (err) {
384
398
  const message = err instanceof Error ? err.message : String(err);
385
399
  throw new InternalError(`Failed to read config: ${message}`);
386
400
  }
387
401
  }
388
402
 
403
+ /**
404
+ * Annotate each profile in `config.llm.profiles` with `supportsVision`
405
+ * resolved from the model catalog. The flag is wire-only — it is never
406
+ * persisted to disk. Unknown (provider, model) pairs default to `true`
407
+ * (fail-open) so image upload remains available for custom / unlisted models.
408
+ */
409
+ function enrichProfilesWithVisionFlag(config: unknown): void {
410
+ const root = readPlainObject(config);
411
+ if (!root) return;
412
+ const llm = readPlainObject(root.llm);
413
+ if (!llm) return;
414
+ const profiles = readPlainObject(llm.profiles);
415
+ if (!profiles) return;
416
+
417
+ for (const profile of Object.values(profiles)) {
418
+ const entry = readPlainObject(profile);
419
+ if (!entry) continue;
420
+ const provider = entry.provider;
421
+ const model = entry.model;
422
+ if (typeof provider !== "string" || typeof model !== "string") continue;
423
+
424
+ const catalogProvider = PROVIDER_CATALOG.find((p) => p.id === provider);
425
+ const catalogModel = catalogProvider?.models.find((m) => m.id === model);
426
+ entry.supportsVision = catalogModel?.supportsVision ?? true;
427
+ }
428
+ }
429
+
389
430
  /**
390
431
  * Return the JSON Schema for the assistant config (full or scoped).
391
432
  *
@@ -617,6 +658,36 @@ async function handleReplaceInferenceProfile({
617
658
  );
618
659
  }
619
660
  }
661
+ // When the UI sends provider but no provider_connection ("Any active X
662
+ // connection"), derive the connection now so the config deep-merge doesn't
663
+ // inherit a stale connection from the default layer.
664
+ const fragment = parsed.data as Record<string, unknown>;
665
+ if (!isManaged && fragment.provider && !fragment.provider_connection) {
666
+ const provider = fragment.provider as string;
667
+ const db = getDb();
668
+ const candidates = listConnections(db, { provider });
669
+ const active = candidates.find((c) => c.status === "active");
670
+ if (active) {
671
+ fragment.provider_connection = active.name;
672
+ } else if (!PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
673
+ const connectionName = `${provider}-personal`;
674
+ const isKeyless = provider === "ollama";
675
+ const result = createConnection(db, {
676
+ name: connectionName,
677
+ provider,
678
+ auth: isKeyless
679
+ ? { type: "none" }
680
+ : {
681
+ type: "api_key",
682
+ credential: credentialKey(provider, "api_key"),
683
+ },
684
+ });
685
+ if (result.ok) {
686
+ fragment.provider_connection = connectionName;
687
+ }
688
+ }
689
+ }
690
+
620
691
  const raw = loadRawConfig();
621
692
  if (isManaged) {
622
693
  // Partial overlay: keep every existing key intact, only update label
@@ -624,17 +695,9 @@ async function handleReplaceInferenceProfile({
624
695
  // here would wipe the UI-owned seed fields (provider, model, advanced
625
696
  // params) because that function assumes the body carries the full UI
626
697
  // surface.
627
- patchManagedProfileFields(
628
- raw,
629
- name,
630
- parsed.data as Record<string, unknown>,
631
- );
698
+ patchManagedProfileFields(raw, name, fragment);
632
699
  } else {
633
- replaceInferenceProfileConfig(
634
- raw,
635
- name,
636
- parsed.data as Record<string, unknown>,
637
- );
700
+ replaceInferenceProfileConfig(raw, name, fragment);
638
701
  }
639
702
  // Route through `commitConfigWrite` so profile edits flow through the
640
703
  // post-write side effects shared with `handlePatchConfig` /
@@ -795,6 +858,12 @@ async function handleGetLlmContext({ pathParams = {} }: RouteHandlerArgs) {
795
858
  requestPayload: null,
796
859
  responsePayload: null,
797
860
  createdAt: log.createdAt,
861
+ // Agent-loop exit reason for the iteration that produced this
862
+ // call, stamped onto the most-recent unstamped log by
863
+ // `conversation-agent-loop-handlers.ts` after the loop yields.
864
+ // Only the terminal call in each loop iteration carries a value;
865
+ // non-terminal calls land here as `null`.
866
+ agentLoopExitReason: log.agentLoopExitReason ?? null,
798
867
  ...result,
799
868
  };
800
869
  }),
@@ -850,6 +919,36 @@ function handleDeleteQueuedMessage({
850
919
  throw new NotFoundError("Queued message not found");
851
920
  }
852
921
 
922
+ function handleSteerToMessage({
923
+ queryParams = {},
924
+ pathParams = {},
925
+ body,
926
+ }: RouteHandlerArgs) {
927
+ const conversationId =
928
+ queryParams.conversationId ??
929
+ (body && typeof body === "object" && "conversationId" in body
930
+ ? (body as Record<string, unknown>).conversationId
931
+ : undefined);
932
+ if (!conversationId || typeof conversationId !== "string") {
933
+ throw new BadRequestError(
934
+ "Missing required parameter: conversationId",
935
+ );
936
+ }
937
+ const result = steerToMessage(conversationId, pathParams.id ?? "");
938
+ if (result.steered) {
939
+ return { ok: true, conversationId, requestId: pathParams.id };
940
+ }
941
+ if (result.reason === "conversation_not_found") {
942
+ throw new NotFoundError("Conversation not found");
943
+ }
944
+ if (result.reason === "not_processing") {
945
+ throw new BadRequestError(
946
+ "Cannot steer: conversation is not currently processing",
947
+ );
948
+ }
949
+ throw new NotFoundError("Queued message not found");
950
+ }
951
+
853
952
  // ---------------------------------------------------------------------------
854
953
  // Route definitions (shared HTTP + IPC)
855
954
  // ---------------------------------------------------------------------------
@@ -1081,4 +1180,23 @@ export const ROUTES: RouteDefinition[] = [
1081
1180
  ],
1082
1181
  handler: handleDeleteQueuedMessage,
1083
1182
  },
1183
+ {
1184
+ operationId: "messages_queued_steer",
1185
+ endpoint: "messages/queued/:id/steer",
1186
+ method: "POST",
1187
+ policyKey: "messages/queued",
1188
+ summary: "Steer to a queued message",
1189
+ description:
1190
+ "Promote a queued message to the head of the queue and abort the current generation so it is processed next.",
1191
+ tags: ["messages"],
1192
+ queryParams: [
1193
+ {
1194
+ name: "conversationId",
1195
+ schema: { type: "string" },
1196
+ required: true,
1197
+ description: "Conversation ID (required)",
1198
+ },
1199
+ ],
1200
+ handler: handleSteerToMessage,
1201
+ },
1084
1202
  ];