@vellumai/assistant 0.8.3 → 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 (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -1,14 +1,7 @@
1
1
  /**
2
2
  * Route handlers for conversation messages and suggestions.
3
3
  */
4
- import {
5
- existsSync,
6
- readdirSync,
7
- readFileSync,
8
- statSync,
9
- writeFileSync,
10
- } from "node:fs";
11
- import { join, relative } from "node:path";
4
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
12
5
 
13
6
  import { z } from "zod";
14
7
 
@@ -31,6 +24,7 @@ import { createApprovalConversationGenerator } from "../../daemon/approval-gener
31
24
  import type { Conversation } from "../../daemon/conversation.js";
32
25
  import {
33
26
  buildModelInfoEvent,
27
+ formatCleanResult,
34
28
  formatCompactResult,
35
29
  isModelSlashCommand,
36
30
  } from "../../daemon/conversation-process.js";
@@ -41,6 +35,7 @@ import {
41
35
  import { getOrCreateConversation as getOrCreateConversationInstance } from "../../daemon/conversation-store.js";
42
36
  import { canonicalizeTimeZone } from "../../daemon/date-context.js";
43
37
  import {
38
+ buildScanFirstMessage,
44
39
  getCannedFirstGreeting,
45
40
  isWakeUpGreeting,
46
41
  } from "../../daemon/first-greeting.js";
@@ -51,6 +46,7 @@ import {
51
46
  preactivateHostProxySkills,
52
47
  shouldAttachHostProxyForCapability,
53
48
  } from "../../daemon/host-proxy-preactivation.js";
49
+ import { getAssistantName } from "../../daemon/identity-helpers.js";
54
50
  import type { ServerMessage } from "../../daemon/message-protocol.js";
55
51
  import type {
56
52
  HostProxyTransportMetadata,
@@ -75,7 +71,7 @@ import {
75
71
  } from "../../memory/canonical-guardian-store.js";
76
72
  import {
77
73
  addMessage,
78
- getLastAssistantTimestampBefore,
74
+ getConversation,
79
75
  getMessages,
80
76
  getMessagesPaginated,
81
77
  hasMessages,
@@ -103,10 +99,7 @@ import type { Provider } from "../../providers/types.js";
103
99
  import { checkIngressForSecrets } from "../../security/secret-ingress.js";
104
100
  import { getSubagentManager } from "../../subagent/index.js";
105
101
  import { getLogger } from "../../util/logger.js";
106
- import {
107
- getInterfacesDir,
108
- getWorkspacePromptPath,
109
- } from "../../util/platform.js";
102
+ import { getWorkspacePromptPath } from "../../util/platform.js";
110
103
  import { silentlyWithLog } from "../../util/silently.js";
111
104
  import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
112
105
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
@@ -136,6 +129,7 @@ const log = getLogger("conversation-routes");
136
129
 
137
130
  /** Matches the `<no_response/>` sentinel used by channel delivery suppression. */
138
131
  const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/g;
132
+ const ATTACHMENT_ENTRY_RE = /^attachment:(\d+)$/;
139
133
 
140
134
  const SUGGESTION_CACHE_MAX = 100;
141
135
  const VALID_RISK_THRESHOLDS = ["none", "low", "medium", "high"] as const;
@@ -148,31 +142,67 @@ function isValidRiskThreshold(value: unknown): value is RiskThreshold {
148
142
  );
149
143
  }
150
144
 
145
+ /**
146
+ * True when a message's persisted metadata explicitly flags it as hidden.
147
+ * Used to suppress internal scaffolding messages from UI history while
148
+ * leaving them in the LLM-side context.
149
+ */
150
+ function isHiddenMessage(metadata: string | null): boolean {
151
+ if (!metadata) return false;
152
+ try {
153
+ const meta = JSON.parse(metadata) as { hidden?: unknown };
154
+ return meta?.hidden === true;
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
159
+
151
160
  function buildSlackHistoryMessage(
152
161
  slackMeta: SlackMessageMetadata | null,
162
+ opts?: { role?: string; assistantDisplayName?: string },
153
163
  ): RuntimeMessagePayload["slackMessage"] | undefined {
154
164
  if (!slackMeta) return undefined;
155
165
 
156
166
  const slackConfig = getConfig().slack;
167
+ const replyThreadTs =
168
+ slackMeta.threadTs && slackMeta.threadTs !== slackMeta.channelTs
169
+ ? slackMeta.threadTs
170
+ : undefined;
157
171
  const messageLink = buildSlackMessageDeepLinks({
158
172
  teamId: slackConfig?.teamId,
159
173
  teamUrl: slackConfig?.teamUrl,
160
174
  channelId: slackMeta.channelId,
161
175
  messageTs: slackMeta.channelTs,
176
+ ...(replyThreadTs ? { threadTs: replyThreadTs } : {}),
162
177
  });
163
- const threadLink = slackMeta.threadTs
178
+ const threadLink = replyThreadTs
164
179
  ? buildSlackMessageDeepLinks({
165
180
  teamId: slackConfig?.teamId,
166
181
  teamUrl: slackConfig?.teamUrl,
167
182
  channelId: slackMeta.channelId,
168
- messageTs: slackMeta.threadTs,
183
+ messageTs: replyThreadTs,
169
184
  })
170
185
  : undefined;
186
+ const assistantDisplayName =
187
+ opts?.role === "assistant" ? opts.assistantDisplayName : undefined;
188
+ const senderDisplayName =
189
+ slackMeta.displayName?.trim() || assistantDisplayName;
171
190
 
172
191
  return {
173
192
  channelId: slackMeta.channelId,
193
+ ...(slackMeta.channelName ? { channelName: slackMeta.channelName } : {}),
174
194
  channelTs: slackMeta.channelTs,
175
195
  ...(slackMeta.threadTs ? { threadTs: slackMeta.threadTs } : {}),
196
+ ...(senderDisplayName || slackMeta.actorExternalUserId
197
+ ? {
198
+ sender: {
199
+ ...(senderDisplayName ? { displayName: senderDisplayName } : {}),
200
+ ...(slackMeta.actorExternalUserId
201
+ ? { externalUserId: slackMeta.actorExternalUserId }
202
+ : {}),
203
+ },
204
+ }
205
+ : {}),
176
206
  ...(messageLink ? { messageLink } : {}),
177
207
  ...(threadLink ? { threadLink } : {}),
178
208
  };
@@ -390,32 +420,9 @@ async function tryConsumeCanonicalGuardianReply(params: {
390
420
  return { consumed: true, messageId };
391
421
  }
392
422
 
393
- function getInterfaceFilesWithMtimes(
394
- interfacesDir: string | null,
395
- ): Array<{ path: string; mtimeMs: number }> {
396
- if (!interfacesDir || !existsSync(interfacesDir)) return [];
397
- const results: Array<{ path: string; mtimeMs: number }> = [];
398
- const scan = (dir: string): void => {
399
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
400
- const fullPath = join(dir, entry.name);
401
- if (entry.isDirectory()) {
402
- scan(fullPath);
403
- } else {
404
- results.push({
405
- path: relative(interfacesDir, fullPath),
406
- mtimeMs: statSync(fullPath).mtimeMs,
407
- });
408
- }
409
- }
410
- };
411
- scan(interfacesDir);
412
- return results;
413
- }
414
-
415
- export function handleListMessages(
416
- { queryParams }: RouteHandlerArgs,
417
- interfacesDir: string | null,
418
- ): Record<string, unknown> {
423
+ export function handleListMessages({
424
+ queryParams,
425
+ }: RouteHandlerArgs): Record<string, unknown> {
419
426
  const conversationId = queryParams?.conversationId;
420
427
  const conversationKey = queryParams?.conversationKey;
421
428
 
@@ -423,8 +430,20 @@ export function handleListMessages(
423
430
  if (conversationId) {
424
431
  resolvedConversationId = conversationId;
425
432
  } else if (conversationKey) {
433
+ // Dual lookup, key-first: prefer the `conversation_keys` table — the
434
+ // canonical channel/external → internal-id mapping — so legacy or
435
+ // externally-sourced keys keep their explicit mapping precedence and
436
+ // never collide with an unrelated `conversations.id`. Fall back to a
437
+ // direct id lookup only when no mapping exists, which covers
438
+ // background/scheduled conversations bootstrapped without a
439
+ // `conversation_keys` row (web clients use the conversation list's
440
+ // `id` as `conversationKey` for those).
426
441
  const mapping = getConversationByKey(conversationKey);
427
- resolvedConversationId = mapping?.conversationId;
442
+ if (mapping) {
443
+ resolvedConversationId = mapping.conversationId;
444
+ } else if (getConversation(conversationKey)) {
445
+ resolvedConversationId = conversationKey;
446
+ }
428
447
  } else {
429
448
  throw new BadRequestError(
430
449
  "conversationKey or conversationId query parameter is required",
@@ -480,16 +499,30 @@ export function handleListMessages(
480
499
  let rawMessages: MessageRow[];
481
500
  let hasMore = false;
482
501
 
502
+ // Drop messages flagged as hidden in metadata (e.g. internal scaffolding
503
+ // like retrospective instructions). The LLM-side history loader
504
+ // (`getMessages` in memory/conversation-crud.ts) intentionally does not
505
+ // filter — hidden messages remain in agent context but are suppressed from
506
+ // the UI list. Filtering is pushed into the paginated query so `hasMore`
507
+ // and the cursor reflect visible rows; otherwise a fully-hidden page would
508
+ // return `hasMore: true` with no cursor and stall the web client.
509
+ // Hidden tool_use/tool_result pairs must be hidden together — if a hidden
510
+ // assistant message has tool_use blocks but its matching user tool_result
511
+ // is left visible, the result will render as a standalone orphan because
512
+ // `mergeToolResultsIntoAssistantMessages` has nothing to merge it into.
513
+ const visibleFilter = (m: MessageRow) => !isHiddenMessage(m.metadata);
514
+
483
515
  if (isPaginated) {
484
516
  const result = getMessagesPaginated(
485
517
  resolvedConversationId,
486
518
  limit,
487
519
  beforeTimestamp,
520
+ visibleFilter,
488
521
  );
489
522
  rawMessages = result.messages;
490
523
  hasMore = result.hasMore;
491
524
  } else {
492
- rawMessages = getMessages(resolvedConversationId);
525
+ rawMessages = getMessages(resolvedConversationId).filter(visibleFilter);
493
526
  }
494
527
 
495
528
  // During streaming, tool_use (assistant) and tool_result (user) events are
@@ -508,6 +541,7 @@ export function handleListMessages(
508
541
  // (consecutive tool refs grouped together).
509
542
  const { messages: consolidatedMessages, mergedIdMap } =
510
543
  mergeConsecutiveAssistantMessages(mergedMessages);
544
+ const assistantSlackDisplayName = getAssistantName()?.trim() || undefined;
511
545
 
512
546
  // Parse content blocks and extract text + tool calls
513
547
  const parsed = consolidatedMessages.map((msg) => {
@@ -559,6 +593,10 @@ export function handleListMessages(
559
593
  }
560
594
  const slackMessage = buildSlackHistoryMessage(
561
595
  readSlackMetadataFromMessageMetadata(msg.metadata),
596
+ {
597
+ role: msg.role,
598
+ assistantDisplayName: assistantSlackDisplayName,
599
+ },
562
600
  );
563
601
 
564
602
  // Strip <no_response/> markers from assistant messages so web/API
@@ -599,6 +637,7 @@ export function handleListMessages(
599
637
  textSegments: filteredSegments,
600
638
  contentOrder: filteredContentOrder,
601
639
  surfaces: rendered.surfaces,
640
+ attachmentRefs: rendered.attachments,
602
641
  slackMessage,
603
642
  ...(rendered.thinkingSegments.length > 0
604
643
  ? { thinkingSegments: rendered.thinkingSegments }
@@ -618,6 +657,7 @@ export function handleListMessages(
618
657
  textSegments: rendered.textSegments,
619
658
  contentOrder: rendered.contentOrder,
620
659
  surfaces: rendered.surfaces,
660
+ attachmentRefs: rendered.attachments,
621
661
  slackMessage,
622
662
  ...(rendered.thinkingSegments.length > 0
623
663
  ? { thinkingSegments: rendered.thinkingSegments }
@@ -627,15 +667,6 @@ export function handleListMessages(
627
667
  };
628
668
  });
629
669
 
630
- const interfaceFiles = getInterfaceFilesWithMtimes(interfacesDir);
631
-
632
- let prevAssistantTimestamp = 0;
633
- if (isPaginated && rawMessages.length > 0) {
634
- prevAssistantTimestamp = getLastAssistantTimestampBefore(
635
- resolvedConversationId!,
636
- rawMessages[0].createdAt,
637
- );
638
- }
639
670
  const messages: RuntimeMessagePayload[] = parsed.map((m) => {
640
671
  let msgAttachments: RuntimeAttachmentMetadata[] = [];
641
672
  if (m.id) {
@@ -682,19 +713,76 @@ export function handleListMessages(
682
713
  }
683
714
  }
684
715
 
685
- let interfaces: string[] | undefined;
686
- if (m.role === "assistant") {
687
- const msgTimestamp = new Date(m.timestamp).getTime();
688
- const dirtied = interfaceFiles
689
- .filter(
690
- (f) =>
691
- f.mtimeMs > prevAssistantTimestamp && f.mtimeMs <= msgTimestamp,
692
- )
693
- .map((f) => f.path);
694
- if (dirtied.length > 0) {
695
- interfaces = dirtied;
716
+ // Align msgAttachments order with the file-block order captured by
717
+ // renderHistoryContent. When a file block was persisted with
718
+ // `_attachmentId`, we can join on that id to position the chip inline
719
+ // (the `attachment:N` entries in contentOrder index into msgAttachments).
720
+ // DB rows without a matching ref go to the tail as orphan chips;
721
+ // unmatched refs drop their contentOrder entry and trigger a remap.
722
+ let alignedContentOrder = m.contentOrder;
723
+ if (
724
+ m.attachmentRefs.length > 0 &&
725
+ msgAttachments.length > 0 &&
726
+ m.contentOrder.length > 0
727
+ ) {
728
+ const byId = new Map<string, number>();
729
+ msgAttachments.forEach((att, idx) => {
730
+ if (att.id) byId.set(att.id, idx);
731
+ });
732
+ const consumed = new Set<number>();
733
+ const orderedRowIdx: Array<number | null> = m.attachmentRefs.map(
734
+ (ref) => {
735
+ if (!ref.attachmentId) return null;
736
+ const idx = byId.get(ref.attachmentId);
737
+ if (idx === undefined || consumed.has(idx)) return null;
738
+ consumed.add(idx);
739
+ return idx;
740
+ },
741
+ );
742
+ const matchedRows = orderedRowIdx.filter(
743
+ (idx): idx is number => idx !== null,
744
+ );
745
+ if (matchedRows.length > 0) {
746
+ const orphanRows: number[] = [];
747
+ for (let i = 0; i < msgAttachments.length; i++) {
748
+ if (!consumed.has(i)) orphanRows.push(i);
749
+ }
750
+ msgAttachments = [
751
+ ...matchedRows.map((i) => msgAttachments[i]),
752
+ ...orphanRows.map((i) => msgAttachments[i]),
753
+ ];
754
+ const refToNewIdx = new Map<number, number>();
755
+ let nextIdx = 0;
756
+ orderedRowIdx.forEach((rowIdx, refIdx) => {
757
+ if (rowIdx !== null) {
758
+ refToNewIdx.set(refIdx, nextIdx);
759
+ nextIdx++;
760
+ }
761
+ });
762
+ alignedContentOrder = m.contentOrder
763
+ .map((entry) => {
764
+ const match = entry.match(ATTACHMENT_ENTRY_RE);
765
+ if (!match) return entry;
766
+ const remapped = refToNewIdx.get(Number(match[1]));
767
+ return remapped !== undefined
768
+ ? `attachment:${remapped}`
769
+ : undefined;
770
+ })
771
+ .filter((e): e is string => e !== undefined);
772
+ } else {
773
+ // No refs carried an attachmentId we could match — strip any
774
+ // attachment:N entries so the client doesn't try to position
775
+ // attachments inline against a misaligned array.
776
+ alignedContentOrder = m.contentOrder.filter(
777
+ (entry) => !ATTACHMENT_ENTRY_RE.test(entry),
778
+ );
696
779
  }
697
- prevAssistantTimestamp = msgTimestamp;
780
+ } else if (m.attachmentRefs.length > 0 && msgAttachments.length === 0) {
781
+ // Refs were captured but no DB rows came back — drop the
782
+ // contentOrder entries to avoid out-of-bounds renders.
783
+ alignedContentOrder = m.contentOrder.filter(
784
+ (entry) => !ATTACHMENT_ENTRY_RE.test(entry),
785
+ );
698
786
  }
699
787
 
700
788
  // Use sentAt (actual event time) for the display timestamp when
@@ -717,13 +805,14 @@ export function handleListMessages(
717
805
  timestamp: new Date(displayTimestamp).toISOString(),
718
806
  attachments: msgAttachments,
719
807
  ...(m.toolCalls.length > 0 ? { toolCalls: m.toolCalls } : {}),
720
- ...(interfaces ? { interfaces } : {}),
721
808
  ...(m.surfaces.length > 0 ? { surfaces: m.surfaces } : {}),
722
809
  ...(m.textSegments.length > 0 ? { textSegments: m.textSegments } : {}),
723
810
  ...(m.thinkingSegments?.length
724
811
  ? { thinkingSegments: m.thinkingSegments }
725
812
  : {}),
726
- ...(m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
813
+ ...(alignedContentOrder.length > 0
814
+ ? { contentOrder: alignedContentOrder }
815
+ : {}),
727
816
  ...(m.subagentNotification
728
817
  ? { subagentNotification: m.subagentNotification }
729
818
  : {}),
@@ -1092,6 +1181,10 @@ export function persistOnboardingArtifacts(onboarding: {
1092
1181
  tone: string;
1093
1182
  userName?: string;
1094
1183
  assistantName?: string;
1184
+ priorAssistants?: string[];
1185
+ cohort?: string;
1186
+ websiteUrl?: string;
1187
+ contentSourceUrl?: string;
1095
1188
  }): void {
1096
1189
  writeOnboardingSidecar(onboarding);
1097
1190
 
@@ -1169,6 +1262,10 @@ export async function handleSendMessage(
1169
1262
  assistantName?: string;
1170
1263
  googleConnected?: boolean;
1171
1264
  googleScopes?: string[];
1265
+ priorAssistants?: string[];
1266
+ cohort?: string;
1267
+ websiteUrl?: string;
1268
+ contentSourceUrl?: string;
1172
1269
  };
1173
1270
  };
1174
1271
 
@@ -1500,12 +1597,34 @@ export async function handleSendMessage(
1500
1597
  );
1501
1598
  }
1502
1599
 
1503
- // ── Canned first-greeting fast path ──
1504
- // On a completely fresh workspace, skip LLM inference for the macOS
1505
- // wake-up greeting and return a pre-written response. When onboarding
1506
- // context is present the greeting is personalized using the selections;
1507
- // otherwise a generic greeting is served. Both paths are instant.
1508
- if (isWakeUpGreeting(trimmedContent, conversation.getMessages().length)) {
1600
+ // ── URL scan path: rewrite first message for scan onboarding ──
1601
+ // When onboarding provides a websiteUrl or contentSourceUrl and the
1602
+ // first message is the macOS wake-up greeting, bypass the canned
1603
+ // greeting and rewrite the user message to a scan instruction so real
1604
+ // LLM inference runs against the URL.
1605
+ const sanitizeUrl = (u?: string) =>
1606
+ u?.trim().replace(/[\r\n\t]/g, "") || undefined;
1607
+ const websiteUrl = sanitizeUrl(body.onboarding?.websiteUrl);
1608
+ const contentSourceUrl = sanitizeUrl(body.onboarding?.contentSourceUrl);
1609
+ const scanUrl = websiteUrl || contentSourceUrl;
1610
+ const isWakeUp = isWakeUpGreeting(
1611
+ trimmedContent,
1612
+ conversation.getMessages().length,
1613
+ );
1614
+ const isScanPath = !!scanUrl && isWakeUp;
1615
+
1616
+ let effectiveContent: string | undefined;
1617
+ if (isScanPath) {
1618
+ const scanVariant = websiteUrl
1619
+ ? ("website" as const)
1620
+ : ("content-source" as const);
1621
+ effectiveContent = buildScanFirstMessage(scanUrl, scanVariant);
1622
+ // Fall through to normal inference path below
1623
+ } else if (isWakeUp && body.onboarding?.cohort === "content-automation") {
1624
+ effectiveContent = "I want to write articles that rank better in GEO";
1625
+ // Fall through to normal inference path — the bootstrap template
1626
+ // and geo-writing skill handle this message.
1627
+ } else if (isWakeUp) {
1509
1628
  const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
1510
1629
 
1511
1630
  conversation.processing = true;
@@ -1569,6 +1688,7 @@ export async function handleSendMessage(
1569
1688
  tone: body.onboarding!.tone,
1570
1689
  googleConnected: body.onboarding!.googleConnected,
1571
1690
  googleScopes: body.onboarding!.googleScopes,
1691
+ priorAssistants: body.onboarding!.priorAssistants,
1572
1692
  });
1573
1693
  } catch (err) {
1574
1694
  log.warn({ err }, "Failed to record onboarding telemetry event");
@@ -1623,12 +1743,18 @@ export async function handleSendMessage(
1623
1743
  tone: body.onboarding!.tone,
1624
1744
  googleConnected: body.onboarding!.googleConnected,
1625
1745
  googleScopes: body.onboarding!.googleScopes,
1746
+ priorAssistants: body.onboarding!.priorAssistants,
1626
1747
  });
1627
1748
  } catch (err) {
1628
1749
  log.warn({ err }, "Failed to record onboarding telemetry event");
1629
1750
  }
1630
1751
  }
1631
1752
 
1753
+ // When the scan path rewrote the first message, prefer the rewritten
1754
+ // content for all downstream consumers (guardian reply, enqueue, agent
1755
+ // loop) so they see the scan instruction rather than the wake-up greeting.
1756
+ const contentAfterScan = effectiveContent ?? content ?? "";
1757
+
1632
1758
  const attachments = hasAttachments
1633
1759
  ? smDeps.resolveAttachments(attachmentIds)
1634
1760
  : [];
@@ -1648,7 +1774,7 @@ export async function handleSendMessage(
1648
1774
  conversationId: mapping.conversationId,
1649
1775
  sourceChannel,
1650
1776
  sourceInterface,
1651
- content: content ?? "",
1777
+ content: contentAfterScan,
1652
1778
  attachments,
1653
1779
  conversation,
1654
1780
  onEvent: broadcastMessage,
@@ -1682,7 +1808,7 @@ export async function handleSendMessage(
1682
1808
  // Queue the message so it's processed when the current turn completes
1683
1809
  const requestId = crypto.randomUUID();
1684
1810
  const enqueueResult = conversation.enqueueMessage(
1685
- content ?? "",
1811
+ contentAfterScan,
1686
1812
  attachments,
1687
1813
  broadcastMessage,
1688
1814
  requestId,
@@ -1754,6 +1880,7 @@ export async function handleSendMessage(
1754
1880
  accepted: true,
1755
1881
  queued: true,
1756
1882
  conversationId: mapping.conversationId,
1883
+ requestId,
1757
1884
  };
1758
1885
  }
1759
1886
 
@@ -1801,7 +1928,9 @@ export async function handleSendMessage(
1801
1928
  await conversation.ensureActorScopedHistory();
1802
1929
 
1803
1930
  // Resolve slash commands before persisting or running the agent loop.
1804
- const rawContent = content ?? "";
1931
+ // `contentAfterScan` already carries the scan-rewritten content when
1932
+ // applicable; reuse it here for consistency.
1933
+ const rawContent = contentAfterScan;
1805
1934
  const slashContext = buildSlashContextForContent(rawContent, {
1806
1935
  conversationId: mapping.conversationId,
1807
1936
  messageCount: conversation.getMessages().length,
@@ -2012,6 +2141,82 @@ export async function handleSendMessage(
2012
2141
  };
2013
2142
  }
2014
2143
 
2144
+ if (slashResult.kind === "clean") {
2145
+ conversation.processing = true;
2146
+ const provenance = provenanceFromTrustContext(conversation.trustContext);
2147
+ const channelMeta = {
2148
+ ...provenance,
2149
+ userMessageChannel: sourceChannel,
2150
+ assistantMessageChannel: sourceChannel,
2151
+ userMessageInterface: sourceInterface,
2152
+ assistantMessageInterface: sourceInterface,
2153
+ };
2154
+ const cleanMsg = createUserMessage(rawContent, attachments);
2155
+ const persisted = await addMessage(
2156
+ mapping.conversationId,
2157
+ "user",
2158
+ JSON.stringify(cleanMsg.content),
2159
+ channelMeta,
2160
+ );
2161
+ conversation.getMessages().push(cleanMsg);
2162
+
2163
+ const conversationId = mapping.conversationId;
2164
+
2165
+ let assistantMessagePersisted = false;
2166
+ try {
2167
+ broadcastMessage({
2168
+ type: "user_message_echo",
2169
+ text: rawContent,
2170
+ conversationId,
2171
+ messageId: persisted.id,
2172
+ clientMessageId,
2173
+ });
2174
+ publishConversationMessagesChanged(conversationId);
2175
+
2176
+ const result = await conversation.forceClean();
2177
+ const responseText = formatCleanResult(result);
2178
+
2179
+ const assistantMsg = createAssistantMessage(responseText);
2180
+ await addMessage(
2181
+ conversationId,
2182
+ "assistant",
2183
+ JSON.stringify(assistantMsg.content),
2184
+ channelMeta,
2185
+ );
2186
+ assistantMessagePersisted = true;
2187
+ conversation.getMessages().push(assistantMsg);
2188
+
2189
+ broadcastMessage({
2190
+ type: "assistant_text_delta",
2191
+ text: responseText,
2192
+ conversationId,
2193
+ });
2194
+ broadcastMessage({ type: "message_complete", conversationId });
2195
+ publishConversationMessagesChanged(conversationId);
2196
+ } catch (err) {
2197
+ if (assistantMessagePersisted) {
2198
+ publishConversationMessagesChanged(conversationId);
2199
+ }
2200
+ log.error({ err, conversationId }, "Clean command failed");
2201
+ broadcastMessage({
2202
+ type: "conversation_error",
2203
+ conversationId,
2204
+ code: "UNKNOWN",
2205
+ userMessage: `Clean failed: ${err instanceof Error ? err.message : String(err)}`,
2206
+ retryable: true,
2207
+ });
2208
+ } finally {
2209
+ conversation.processing = false;
2210
+ silentlyWithLog(conversation.drainQueue(), "clean-command queue drain");
2211
+ }
2212
+
2213
+ return {
2214
+ accepted: true,
2215
+ messageId: persisted.id,
2216
+ conversationId,
2217
+ };
2218
+ }
2219
+
2015
2220
  const resolvedContent = slashResult.content;
2016
2221
 
2017
2222
  const requestId = crypto.randomUUID();
@@ -2369,7 +2574,7 @@ export const ROUTES: RouteDefinition[] = [
2369
2574
  .optional()
2370
2575
  .describe("ID of the oldest message in this page"),
2371
2576
  }),
2372
- handler: (args) => handleListMessages(args, getInterfacesDir()),
2577
+ handler: (args) => handleListMessages(args),
2373
2578
  },
2374
2579
  {
2375
2580
  operationId: "messages_post",