@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
@@ -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,
@@ -101,11 +97,9 @@ import { writeOnboardingSection } from "../../prompts/persona-resolver.js";
101
97
  import { getConfiguredProvider } from "../../providers/provider-send-message.js";
102
98
  import type { Provider } from "../../providers/types.js";
103
99
  import { checkIngressForSecrets } from "../../security/secret-ingress.js";
100
+ import { getSubagentManager } from "../../subagent/index.js";
104
101
  import { getLogger } from "../../util/logger.js";
105
- import {
106
- getInterfacesDir,
107
- getWorkspacePromptPath,
108
- } from "../../util/platform.js";
102
+ import { getWorkspacePromptPath } from "../../util/platform.js";
109
103
  import { silentlyWithLog } from "../../util/silently.js";
110
104
  import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
111
105
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
@@ -135,6 +129,7 @@ const log = getLogger("conversation-routes");
135
129
 
136
130
  /** Matches the `<no_response/>` sentinel used by channel delivery suppression. */
137
131
  const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/g;
132
+ const ATTACHMENT_ENTRY_RE = /^attachment:(\d+)$/;
138
133
 
139
134
  const SUGGESTION_CACHE_MAX = 100;
140
135
  const VALID_RISK_THRESHOLDS = ["none", "low", "medium", "high"] as const;
@@ -147,31 +142,67 @@ function isValidRiskThreshold(value: unknown): value is RiskThreshold {
147
142
  );
148
143
  }
149
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
+
150
160
  function buildSlackHistoryMessage(
151
161
  slackMeta: SlackMessageMetadata | null,
162
+ opts?: { role?: string; assistantDisplayName?: string },
152
163
  ): RuntimeMessagePayload["slackMessage"] | undefined {
153
164
  if (!slackMeta) return undefined;
154
165
 
155
166
  const slackConfig = getConfig().slack;
167
+ const replyThreadTs =
168
+ slackMeta.threadTs && slackMeta.threadTs !== slackMeta.channelTs
169
+ ? slackMeta.threadTs
170
+ : undefined;
156
171
  const messageLink = buildSlackMessageDeepLinks({
157
172
  teamId: slackConfig?.teamId,
158
173
  teamUrl: slackConfig?.teamUrl,
159
174
  channelId: slackMeta.channelId,
160
175
  messageTs: slackMeta.channelTs,
176
+ ...(replyThreadTs ? { threadTs: replyThreadTs } : {}),
161
177
  });
162
- const threadLink = slackMeta.threadTs
178
+ const threadLink = replyThreadTs
163
179
  ? buildSlackMessageDeepLinks({
164
180
  teamId: slackConfig?.teamId,
165
181
  teamUrl: slackConfig?.teamUrl,
166
182
  channelId: slackMeta.channelId,
167
- messageTs: slackMeta.threadTs,
183
+ messageTs: replyThreadTs,
168
184
  })
169
185
  : undefined;
186
+ const assistantDisplayName =
187
+ opts?.role === "assistant" ? opts.assistantDisplayName : undefined;
188
+ const senderDisplayName =
189
+ slackMeta.displayName?.trim() || assistantDisplayName;
170
190
 
171
191
  return {
172
192
  channelId: slackMeta.channelId,
193
+ ...(slackMeta.channelName ? { channelName: slackMeta.channelName } : {}),
173
194
  channelTs: slackMeta.channelTs,
174
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
+ : {}),
175
206
  ...(messageLink ? { messageLink } : {}),
176
207
  ...(threadLink ? { threadLink } : {}),
177
208
  };
@@ -389,32 +420,9 @@ async function tryConsumeCanonicalGuardianReply(params: {
389
420
  return { consumed: true, messageId };
390
421
  }
391
422
 
392
- function getInterfaceFilesWithMtimes(
393
- interfacesDir: string | null,
394
- ): Array<{ path: string; mtimeMs: number }> {
395
- if (!interfacesDir || !existsSync(interfacesDir)) return [];
396
- const results: Array<{ path: string; mtimeMs: number }> = [];
397
- const scan = (dir: string): void => {
398
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
399
- const fullPath = join(dir, entry.name);
400
- if (entry.isDirectory()) {
401
- scan(fullPath);
402
- } else {
403
- results.push({
404
- path: relative(interfacesDir, fullPath),
405
- mtimeMs: statSync(fullPath).mtimeMs,
406
- });
407
- }
408
- }
409
- };
410
- scan(interfacesDir);
411
- return results;
412
- }
413
-
414
- export function handleListMessages(
415
- { queryParams }: RouteHandlerArgs,
416
- interfacesDir: string | null,
417
- ): Record<string, unknown> {
423
+ export function handleListMessages({
424
+ queryParams,
425
+ }: RouteHandlerArgs): Record<string, unknown> {
418
426
  const conversationId = queryParams?.conversationId;
419
427
  const conversationKey = queryParams?.conversationKey;
420
428
 
@@ -422,8 +430,20 @@ export function handleListMessages(
422
430
  if (conversationId) {
423
431
  resolvedConversationId = conversationId;
424
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).
425
441
  const mapping = getConversationByKey(conversationKey);
426
- resolvedConversationId = mapping?.conversationId;
442
+ if (mapping) {
443
+ resolvedConversationId = mapping.conversationId;
444
+ } else if (getConversation(conversationKey)) {
445
+ resolvedConversationId = conversationKey;
446
+ }
427
447
  } else {
428
448
  throw new BadRequestError(
429
449
  "conversationKey or conversationId query parameter is required",
@@ -479,16 +499,30 @@ export function handleListMessages(
479
499
  let rawMessages: MessageRow[];
480
500
  let hasMore = false;
481
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
+
482
515
  if (isPaginated) {
483
516
  const result = getMessagesPaginated(
484
517
  resolvedConversationId,
485
518
  limit,
486
519
  beforeTimestamp,
520
+ visibleFilter,
487
521
  );
488
522
  rawMessages = result.messages;
489
523
  hasMore = result.hasMore;
490
524
  } else {
491
- rawMessages = getMessages(resolvedConversationId);
525
+ rawMessages = getMessages(resolvedConversationId).filter(visibleFilter);
492
526
  }
493
527
 
494
528
  // During streaming, tool_use (assistant) and tool_result (user) events are
@@ -507,6 +541,7 @@ export function handleListMessages(
507
541
  // (consecutive tool refs grouped together).
508
542
  const { messages: consolidatedMessages, mergedIdMap } =
509
543
  mergeConsecutiveAssistantMessages(mergedMessages);
544
+ const assistantSlackDisplayName = getAssistantName()?.trim() || undefined;
510
545
 
511
546
  // Parse content blocks and extract text + tool calls
512
547
  const parsed = consolidatedMessages.map((msg) => {
@@ -558,6 +593,10 @@ export function handleListMessages(
558
593
  }
559
594
  const slackMessage = buildSlackHistoryMessage(
560
595
  readSlackMetadataFromMessageMetadata(msg.metadata),
596
+ {
597
+ role: msg.role,
598
+ assistantDisplayName: assistantSlackDisplayName,
599
+ },
561
600
  );
562
601
 
563
602
  // Strip <no_response/> markers from assistant messages so web/API
@@ -598,6 +637,7 @@ export function handleListMessages(
598
637
  textSegments: filteredSegments,
599
638
  contentOrder: filteredContentOrder,
600
639
  surfaces: rendered.surfaces,
640
+ attachmentRefs: rendered.attachments,
601
641
  slackMessage,
602
642
  ...(rendered.thinkingSegments.length > 0
603
643
  ? { thinkingSegments: rendered.thinkingSegments }
@@ -617,6 +657,7 @@ export function handleListMessages(
617
657
  textSegments: rendered.textSegments,
618
658
  contentOrder: rendered.contentOrder,
619
659
  surfaces: rendered.surfaces,
660
+ attachmentRefs: rendered.attachments,
620
661
  slackMessage,
621
662
  ...(rendered.thinkingSegments.length > 0
622
663
  ? { thinkingSegments: rendered.thinkingSegments }
@@ -626,15 +667,6 @@ export function handleListMessages(
626
667
  };
627
668
  });
628
669
 
629
- const interfaceFiles = getInterfaceFilesWithMtimes(interfacesDir);
630
-
631
- let prevAssistantTimestamp = 0;
632
- if (isPaginated && rawMessages.length > 0) {
633
- prevAssistantTimestamp = getLastAssistantTimestampBefore(
634
- resolvedConversationId!,
635
- rawMessages[0].createdAt,
636
- );
637
- }
638
670
  const messages: RuntimeMessagePayload[] = parsed.map((m) => {
639
671
  let msgAttachments: RuntimeAttachmentMetadata[] = [];
640
672
  if (m.id) {
@@ -681,19 +713,76 @@ export function handleListMessages(
681
713
  }
682
714
  }
683
715
 
684
- let interfaces: string[] | undefined;
685
- if (m.role === "assistant") {
686
- const msgTimestamp = new Date(m.timestamp).getTime();
687
- const dirtied = interfaceFiles
688
- .filter(
689
- (f) =>
690
- f.mtimeMs > prevAssistantTimestamp && f.mtimeMs <= msgTimestamp,
691
- )
692
- .map((f) => f.path);
693
- if (dirtied.length > 0) {
694
- 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
+ );
695
779
  }
696
- 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
+ );
697
786
  }
698
787
 
699
788
  // Use sentAt (actual event time) for the display timestamp when
@@ -716,13 +805,14 @@ export function handleListMessages(
716
805
  timestamp: new Date(displayTimestamp).toISOString(),
717
806
  attachments: msgAttachments,
718
807
  ...(m.toolCalls.length > 0 ? { toolCalls: m.toolCalls } : {}),
719
- ...(interfaces ? { interfaces } : {}),
720
808
  ...(m.surfaces.length > 0 ? { surfaces: m.surfaces } : {}),
721
809
  ...(m.textSegments.length > 0 ? { textSegments: m.textSegments } : {}),
722
810
  ...(m.thinkingSegments?.length
723
811
  ? { thinkingSegments: m.thinkingSegments }
724
812
  : {}),
725
- ...(m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
813
+ ...(alignedContentOrder.length > 0
814
+ ? { contentOrder: alignedContentOrder }
815
+ : {}),
726
816
  ...(m.subagentNotification
727
817
  ? { subagentNotification: m.subagentNotification }
728
818
  : {}),
@@ -1091,6 +1181,10 @@ export function persistOnboardingArtifacts(onboarding: {
1091
1181
  tone: string;
1092
1182
  userName?: string;
1093
1183
  assistantName?: string;
1184
+ priorAssistants?: string[];
1185
+ cohort?: string;
1186
+ websiteUrl?: string;
1187
+ contentSourceUrl?: string;
1094
1188
  }): void {
1095
1189
  writeOnboardingSidecar(onboarding);
1096
1190
 
@@ -1168,6 +1262,10 @@ export async function handleSendMessage(
1168
1262
  assistantName?: string;
1169
1263
  googleConnected?: boolean;
1170
1264
  googleScopes?: string[];
1265
+ priorAssistants?: string[];
1266
+ cohort?: string;
1267
+ websiteUrl?: string;
1268
+ contentSourceUrl?: string;
1171
1269
  };
1172
1270
  };
1173
1271
 
@@ -1492,13 +1590,41 @@ export async function handleSendMessage(
1492
1590
  // that can service host_browser_request events; we restore that single
1493
1591
  // proxy explicitly below without relaxing `hasNoClient`.
1494
1592
  conversation.updateClient(broadcastMessage, !isInteractive);
1593
+ if (isInteractive) {
1594
+ getSubagentManager().updateParentSender(
1595
+ mapping.conversationId,
1596
+ broadcastMessage,
1597
+ );
1598
+ }
1495
1599
 
1496
- // ── Canned first-greeting fast path ──
1497
- // On a completely fresh workspace, skip LLM inference for the macOS
1498
- // wake-up greeting and return a pre-written response. When onboarding
1499
- // context is present the greeting is personalized using the selections;
1500
- // otherwise a generic greeting is served. Both paths are instant.
1501
- 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) {
1502
1628
  const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
1503
1629
 
1504
1630
  conversation.processing = true;
@@ -1562,6 +1688,7 @@ export async function handleSendMessage(
1562
1688
  tone: body.onboarding!.tone,
1563
1689
  googleConnected: body.onboarding!.googleConnected,
1564
1690
  googleScopes: body.onboarding!.googleScopes,
1691
+ priorAssistants: body.onboarding!.priorAssistants,
1565
1692
  });
1566
1693
  } catch (err) {
1567
1694
  log.warn({ err }, "Failed to record onboarding telemetry event");
@@ -1616,12 +1743,18 @@ export async function handleSendMessage(
1616
1743
  tone: body.onboarding!.tone,
1617
1744
  googleConnected: body.onboarding!.googleConnected,
1618
1745
  googleScopes: body.onboarding!.googleScopes,
1746
+ priorAssistants: body.onboarding!.priorAssistants,
1619
1747
  });
1620
1748
  } catch (err) {
1621
1749
  log.warn({ err }, "Failed to record onboarding telemetry event");
1622
1750
  }
1623
1751
  }
1624
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
+
1625
1758
  const attachments = hasAttachments
1626
1759
  ? smDeps.resolveAttachments(attachmentIds)
1627
1760
  : [];
@@ -1641,7 +1774,7 @@ export async function handleSendMessage(
1641
1774
  conversationId: mapping.conversationId,
1642
1775
  sourceChannel,
1643
1776
  sourceInterface,
1644
- content: content ?? "",
1777
+ content: contentAfterScan,
1645
1778
  attachments,
1646
1779
  conversation,
1647
1780
  onEvent: broadcastMessage,
@@ -1675,7 +1808,7 @@ export async function handleSendMessage(
1675
1808
  // Queue the message so it's processed when the current turn completes
1676
1809
  const requestId = crypto.randomUUID();
1677
1810
  const enqueueResult = conversation.enqueueMessage(
1678
- content ?? "",
1811
+ contentAfterScan,
1679
1812
  attachments,
1680
1813
  broadcastMessage,
1681
1814
  requestId,
@@ -1747,6 +1880,7 @@ export async function handleSendMessage(
1747
1880
  accepted: true,
1748
1881
  queued: true,
1749
1882
  conversationId: mapping.conversationId,
1883
+ requestId,
1750
1884
  };
1751
1885
  }
1752
1886
 
@@ -1794,7 +1928,9 @@ export async function handleSendMessage(
1794
1928
  await conversation.ensureActorScopedHistory();
1795
1929
 
1796
1930
  // Resolve slash commands before persisting or running the agent loop.
1797
- const rawContent = content ?? "";
1931
+ // `contentAfterScan` already carries the scan-rewritten content when
1932
+ // applicable; reuse it here for consistency.
1933
+ const rawContent = contentAfterScan;
1798
1934
  const slashContext = buildSlashContextForContent(rawContent, {
1799
1935
  conversationId: mapping.conversationId,
1800
1936
  messageCount: conversation.getMessages().length,
@@ -2005,6 +2141,82 @@ export async function handleSendMessage(
2005
2141
  };
2006
2142
  }
2007
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
+
2008
2220
  const resolvedContent = slashResult.content;
2009
2221
 
2010
2222
  const requestId = crypto.randomUUID();
@@ -2362,7 +2574,7 @@ export const ROUTES: RouteDefinition[] = [
2362
2574
  .optional()
2363
2575
  .describe("ID of the oldest message in this page"),
2364
2576
  }),
2365
- handler: (args) => handleListMessages(args, getInterfacesDir()),
2577
+ handler: (args) => handleListMessages(args),
2366
2578
  },
2367
2579
  {
2368
2580
  operationId: "messages_post",