@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
@@ -9,6 +9,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
9
9
  import { join, resolve } from "node:path";
10
10
 
11
11
  import { type ChannelId, parseInterfaceId } from "../channels/types.js";
12
+ import { getConfig } from "../config/loader.js";
12
13
  import { createContextSummaryMessage } from "../context/window-manager.js";
13
14
  import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
14
15
  import {
@@ -20,15 +21,16 @@ import {
20
21
  extractMemoryPrefixBlocks,
21
22
  } from "../memory/graph/conversation-graph-memory.js";
22
23
  import type { QdrantSparseVector } from "../memory/qdrant-client.js";
23
- import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
24
+ import {
25
+ readSlackMetadata,
26
+ readSlackMetadataFromMessageMetadata,
27
+ } from "../messaging/providers/slack/message-metadata.js";
24
28
  import {
25
29
  compareSlackTs,
26
30
  extractTagLineTexts,
27
- isReactionTagLine,
28
31
  isSlackTsAfter,
29
32
  type RenderableSlackMessage,
30
33
  type RenderedSlackTranscriptMessage,
31
- renderSlackTranscript,
32
34
  renderSlackTranscriptWithProvenance,
33
35
  } from "../messaging/providers/slack/render-transcript.js";
34
36
  import { getInjectors } from "../plugins/registry.js";
@@ -48,6 +50,7 @@ import {
48
50
  import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/acl-enforcement.js";
49
51
  import type { SubagentState } from "../subagent/types.js";
50
52
  import { TERMINAL_STATUSES } from "../subagent/types.js";
53
+ import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
51
54
  import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
52
55
  import { stripCommentLines } from "../util/strip-comment-lines.js";
53
56
  import { filterMessagesForUntrustedActor } from "./conversation-lifecycle.js";
@@ -680,18 +683,19 @@ export function injectChannelCapabilityContext(
680
683
  "- Do NOT reference the dashboard UI, settings panels, or visual preference pickers.",
681
684
  );
682
685
  if (!caps.supportsDynamicUi) {
683
- lines.push(
684
- "- Do NOT use ui_show, ui_update, or app_create — this channel cannot render them.",
685
- );
686
+ if (caps.channel === "slack") {
687
+ lines.push(
688
+ '- Do NOT use app_create. Only use ui_show/ui_update for card surfaces with template: "task_progress"; present all other information as text.',
689
+ );
690
+ } else {
691
+ lines.push(
692
+ "- Do NOT use ui_show, ui_update, or app_create — this channel cannot render them.",
693
+ );
694
+ }
686
695
  lines.push(
687
696
  "- Present information as well-formatted text instead of dynamic UI.",
688
697
  );
689
698
  }
690
- lines.push(
691
- "- Defer dashboard-specific actions (e.g. accent color selection) by telling the user",
692
- );
693
- lines.push(" they can complete those steps later from the desktop app.");
694
-
695
699
  if (caps.channel === "whatsapp") {
696
700
  lines.push(
697
701
  "- Do NOT use markdown tables — use bullet lists instead. No markdown headers — use **bold** or CAPS for emphasis.",
@@ -699,10 +703,6 @@ export function injectChannelCapabilityContext(
699
703
  }
700
704
  }
701
705
 
702
- if (!caps.supportsVoiceInput) {
703
- lines.push("- Do NOT ask the user to use voice or microphone input.");
704
- }
705
-
706
706
  // Inject group chat etiquette only when the chat type indicates a multi-party
707
707
  // conversation, avoiding misconditioned "stay silent" guidance in 1:1 DMs.
708
708
  if (isGroupChatType(caps.chatType)) {
@@ -798,6 +798,13 @@ export interface UnifiedTurnContextOptions {
798
798
  * the model can acknowledge long absences; otherwise omitted.
799
799
  */
800
800
  timeSinceLastMessage?: string | null;
801
+ /**
802
+ * Human-readable model profile description. Only populated when the active
803
+ * inference profile changed since the last turn (or on the first turn of a
804
+ * conversation) so the model knows which profile/model it is using without
805
+ * paying per-turn token cost.
806
+ */
807
+ modelProfile?: string | null;
801
808
  }
802
809
 
803
810
  /**
@@ -856,6 +863,9 @@ export function buildUnifiedTurnContextBlock(
856
863
  if (options.timeSinceLastMessage) {
857
864
  lines.push(`time_since_last_message: ${options.timeSinceLastMessage}`);
858
865
  }
866
+ if (options.modelProfile) {
867
+ lines.push(`model_profile: ${options.modelProfile}`);
868
+ }
859
869
  if (options.interfaceName) {
860
870
  lines.push(`interface: ${options.interfaceName}`);
861
871
  }
@@ -973,6 +983,9 @@ export function buildUnifiedTurnContextBlock(
973
983
  lines.push(
974
984
  `response_discretion: Not every message in a channel thread requires your response. If a message is clearly not directed at you (e.g. people talking among themselves, acknowledgements, reactions), output exactly <no_response/> as your entire reply to stay silent.`,
975
985
  );
986
+ if (options.channelName === "slack") {
987
+ lines.push("if you are going to do work, use task_progress");
988
+ }
976
989
  }
977
990
 
978
991
  lines.push("</turn_context>");
@@ -1032,17 +1045,6 @@ function injectTransportHints(message: Message, hints: string[]): Message {
1032
1045
  };
1033
1046
  }
1034
1047
 
1035
- function injectSlackRuntimeContextNotice(
1036
- message: Message,
1037
- notice: string,
1038
- ): Message {
1039
- const block = `<slack_context_notice>\n${notice}\n</slack_context_notice>`;
1040
- return {
1041
- ...message,
1042
- content: [{ type: "text", text: block }, ...message.content],
1043
- };
1044
- }
1045
-
1046
1048
  // ---------------------------------------------------------------------------
1047
1049
  // Slack chronological transcript assembly
1048
1050
  // ---------------------------------------------------------------------------
@@ -1111,6 +1113,27 @@ function messageRowsToSlackTranscriptRows(
1111
1113
  }));
1112
1114
  }
1113
1115
 
1116
+ function hasSlackMetadata(row: MessageRow): boolean {
1117
+ return (
1118
+ readSlackMetadataFromMessageMetadata(row.metadata, {
1119
+ allowFlatLegacy: true,
1120
+ }) !== null
1121
+ );
1122
+ }
1123
+
1124
+ function filterSlackConversationRowsForActor(
1125
+ rows: MessageRow[],
1126
+ trustClass: TrustClass | undefined,
1127
+ ): MessageRow[] {
1128
+ if (!isUntrustedTrustClass(trustClass)) return rows;
1129
+ const nonSlackVisibleRows = filterMessagesForUntrustedActor(rows);
1130
+ const nonSlackVisibleIds = new Set(nonSlackVisibleRows.map((row) => row.id));
1131
+ return rows.filter((row) => {
1132
+ if (hasSlackMetadata(row)) return true;
1133
+ return nonSlackVisibleIds.has(row.id);
1134
+ });
1135
+ }
1136
+
1114
1137
  /**
1115
1138
  * Extract the user-facing plain text from an already-parsed `ContentBlock[]`.
1116
1139
  * Only `text` blocks contribute to the rendered transcript line. Tool-use /
@@ -1268,6 +1291,60 @@ function rowToRenderable(row: SlackTranscriptInputRow): RenderableSlackMessage {
1268
1291
  };
1269
1292
  }
1270
1293
 
1294
+ const SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT = "New Assistant Thread";
1295
+
1296
+ function isSlackAssistantThreadPlaceholder(
1297
+ message: RenderableSlackMessage,
1298
+ canonicalConfiguredBotUserId: string | null,
1299
+ ): boolean {
1300
+ if (!canonicalConfiguredBotUserId) return false;
1301
+ const metadata = message.metadata;
1302
+ if (!metadata || metadata.eventKind !== "message") return false;
1303
+ const actorExternalUserId = metadata.actorExternalUserId?.trim();
1304
+ if (!actorExternalUserId) return false;
1305
+
1306
+ const canonicalActor =
1307
+ canonicalizeInboundIdentity("slack", actorExternalUserId) ??
1308
+ actorExternalUserId;
1309
+ const isThreadRoot =
1310
+ metadata.threadTs === undefined || metadata.threadTs === metadata.channelTs;
1311
+ const hasSlackFiles =
1312
+ Array.isArray(metadata.slackFiles) && metadata.slackFiles.length > 0;
1313
+
1314
+ return (
1315
+ message.role === "user" &&
1316
+ canonicalActor === canonicalConfiguredBotUserId &&
1317
+ isThreadRoot &&
1318
+ !hasSlackFiles &&
1319
+ message.content.replace(/\s+/g, " ").trim() ===
1320
+ SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT
1321
+ );
1322
+ }
1323
+
1324
+ function getCanonicalConfiguredSlackBotUserId(): string | null {
1325
+ const configuredBotUserId = getConfig().slack.botUserId.trim();
1326
+ if (!configuredBotUserId) return null;
1327
+ return (
1328
+ canonicalizeInboundIdentity("slack", configuredBotUserId) ??
1329
+ configuredBotUserId
1330
+ );
1331
+ }
1332
+
1333
+ function rowsToRenderableSlackMessages(
1334
+ rows: SlackTranscriptInputRow[],
1335
+ ): RenderableSlackMessage[] {
1336
+ const canonicalConfiguredBotUserId = getCanonicalConfiguredSlackBotUserId();
1337
+ return rows
1338
+ .map(rowToRenderable)
1339
+ .filter(
1340
+ (message) =>
1341
+ !isSlackAssistantThreadPlaceholder(
1342
+ message,
1343
+ canonicalConfiguredBotUserId,
1344
+ ),
1345
+ );
1346
+ }
1347
+
1271
1348
  /**
1272
1349
  * Compatibility projection for callers that still need the legacy
1273
1350
  * `Message[] | null` shape. New runtime callers should use
@@ -1363,7 +1440,7 @@ function assembleSlackChronologicalContext(
1363
1440
  if (capabilities.channel !== "slack") {
1364
1441
  return null;
1365
1442
  }
1366
- const renderable = rows.map(rowToRenderable);
1443
+ const renderable = rowsToRenderableSlackMessages(rows);
1367
1444
  const rendered = renderSlackTranscriptWithProvenance(renderable);
1368
1445
  const contextSummary = options.contextSummary?.trim();
1369
1446
  const renderedMessages = rendered.renderedMessages;
@@ -1372,6 +1449,7 @@ function assembleSlackChronologicalContext(
1372
1449
  {
1373
1450
  message: createContextSummaryMessage(contextSummary),
1374
1451
  sourceChannelTs: null,
1452
+ tagLineProvenance: "none",
1375
1453
  },
1376
1454
  ...renderedMessages,
1377
1455
  ];
@@ -1392,11 +1470,10 @@ function assembleSlackChronologicalContext(
1392
1470
  * Compatibility wrapper over `loadSlackChronologicalContext` for callers that
1393
1471
  * still need only the legacy `Message[] | null` projection.
1394
1472
  *
1395
- * When `trustClass` identifies an untrusted actor (guardian-scoped rows
1396
- * must not leak into the model context), rows are passed through
1397
- * `filterMessagesForUntrustedActor` before assembly mirroring the
1398
- * filtering applied in `loadFromDb` so the chronological transcript
1399
- * respects the same per-actor scoping as the default history path.
1473
+ * When `trustClass` identifies an untrusted actor, non-Slack/private rows
1474
+ * are passed through the default trust filter. Slack-tagged rows stay visible
1475
+ * because the transcript is scoped to the external Slack chat/thread, which
1476
+ * the inbound actor can already read in Slack.
1400
1477
  *
1401
1478
  * Returns `null` when the channel is not Slack — callers should fall
1402
1479
  * through to the default in-memory message history.
@@ -1445,9 +1522,10 @@ export function loadSlackChronologicalContext(
1445
1522
  }
1446
1523
  const loader = options.loader ?? defaultGetMessages;
1447
1524
  const allRows = loader(conversationId);
1448
- const scopedRows = isUntrustedTrustClass(options.trustClass)
1449
- ? filterMessagesForUntrustedActor(allRows)
1450
- : allRows;
1525
+ const scopedRows = filterSlackConversationRowsForActor(
1526
+ allRows,
1527
+ options.trustClass,
1528
+ );
1451
1529
  const rows = filterRowsAfterSlackCompactionBoundary(
1452
1530
  messageRowsToSlackTranscriptRows(scopedRows),
1453
1531
  options,
@@ -1555,29 +1633,29 @@ function buildActiveThreadBlockFromRenderable(
1555
1633
  if (members.length === 0) return null;
1556
1634
 
1557
1635
  // The active-thread block is flattened to plain text below, which discards
1558
- // `Message.role`. Assistant rows are relabeled in the post-render step:
1559
- // `renderSlackTranscript` emits assistant content with no tag-line wrapper
1560
- // (to prevent the model mimicking `[MM/DD/YY HH:MM]:` prefixes in outbound
1561
- // replies), so we prepend an explicit `@assistant:` label to the flattened
1562
- // line. Unnamed user rows (no real Slack displayName) get a `@user`
1563
- // senderLabel here so their tag line carries attribution through the
1564
- // renderer. Labeled user rows and assistant rows pass through unchanged.
1636
+ // `Message.role`. Assistant rows that render content-only are relabeled in
1637
+ // the post-render step. Timezone-aware assistant rows are already
1638
+ // bracket-tagged by the renderer and must not receive another prefix.
1639
+ // Unnamed user rows (no real Slack displayName) get a `@user` senderLabel
1640
+ // here so their tag line carries attribution through the renderer. Labeled
1641
+ // user rows and assistant rows pass through unchanged.
1565
1642
  const labeledMembers = members.map((m) => {
1566
1643
  if (m.role === "assistant") return m;
1567
1644
  if (m.senderLabel !== null) return m;
1568
1645
  return { ...m, senderLabel: "@user" };
1569
1646
  });
1570
1647
 
1571
- const rendered = renderSlackTranscript(labeledMembers);
1572
- if (rendered.length === 0) return null;
1573
- // Reaction / overflow-trailer lines already embed `@assistant` inline, so
1574
- // `isReactionTagLine` is used to skip those and avoid double-attribution
1575
- // (`@assistant: [... @assistant reacted ...]`). Regular content and the
1576
- // `[deleted]` sentinel get the prefix so attribution survives flattening.
1577
- const lines = rendered
1578
- .map((msg) => {
1579
- const text = extractTagLineTexts([msg])[0] ?? "";
1580
- return msg.role === "assistant" && !isReactionTagLine(text)
1648
+ const rendered = renderSlackTranscriptWithProvenance(labeledMembers);
1649
+ if (rendered.renderedMessages.length === 0) return null;
1650
+ // Reaction / overflow-trailer lines are renderer-owned Slack event lines,
1651
+ // and timezone-aware assistant rows already carry metadata-backed compact
1652
+ // attribution. Regular assistant content and the `[deleted]` sentinel get
1653
+ // the prefix so attribution survives flattening.
1654
+ const lines = rendered.renderedMessages
1655
+ .map((entry) => {
1656
+ const text = extractTagLineTexts([entry.message])[0] ?? "";
1657
+ return entry.message.role === "assistant" &&
1658
+ entry.tagLineProvenance === "none"
1581
1659
  ? `@assistant: ${text}`
1582
1660
  : text;
1583
1661
  })
@@ -1605,7 +1683,7 @@ export function assembleSlackActiveThreadFocusBlock(
1605
1683
  // conversation and omits the field for DMs, so gate the focus block
1606
1684
  // on the positive `"channel"` match.
1607
1685
  if (capabilities.chatType !== "channel") return null;
1608
- const renderable = rows.map(rowToRenderable);
1686
+ const renderable = rowsToRenderableSlackMessages(rows);
1609
1687
  const activeThreadTs = detectActiveThreadTs(renderable);
1610
1688
  if (!activeThreadTs) return null;
1611
1689
  return buildActiveThreadBlockFromRenderable(renderable, activeThreadTs);
@@ -1631,9 +1709,10 @@ export function loadSlackActiveThreadFocusBlock(
1631
1709
  if (capabilities.chatType !== "channel") return null;
1632
1710
  const loader = options.loader ?? defaultGetMessages;
1633
1711
  const allRows = loader(conversationId);
1634
- const scopedRows = isUntrustedTrustClass(options.trustClass)
1635
- ? filterMessagesForUntrustedActor(allRows)
1636
- : allRows;
1712
+ const scopedRows = filterSlackConversationRowsForActor(
1713
+ allRows,
1714
+ options.trustClass,
1715
+ );
1637
1716
  const rows = filterRowsAfterSlackCompactionBoundary(
1638
1717
  messageRowsToSlackTranscriptRows(scopedRows),
1639
1718
  options,
@@ -1652,6 +1731,7 @@ const RUNTIME_INJECTION_PREFIXES = [
1652
1731
  "<interface_turn_context>", // backward-compat: strip legacy separate interface blocks
1653
1732
  // NOTE: <turn_context> is intentionally NOT stripped — unified turn context
1654
1733
  // blocks persist in history so the assistant retains temporal/actor grounding.
1734
+ "<background_turn>",
1655
1735
  "<memory_context __injected>",
1656
1736
  "<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
1657
1737
  // The static `memory-v2-static` block (opens `<memory>\n…`) IS stripped
@@ -1680,7 +1760,6 @@ const RUNTIME_INJECTION_PREFIXES = [
1680
1760
  "<pkb>", // backward-compat: strip legacy tag from pre-rename history
1681
1761
  "<system_reminder>",
1682
1762
  "<transport_hints>",
1683
- "<slack_context_notice>",
1684
1763
  // The Slack active-thread focus block is non-persisted and injected on
1685
1764
  // the FINAL user turn only. Strip it here so re-assembly during compaction
1686
1765
  // and overflow recovery does not duplicate it across turns.
@@ -1966,8 +2045,14 @@ export interface RuntimeInjectionOptions {
1966
2045
  nowScratchpad?: string | null;
1967
2046
  subagentStatusBlock?: string | null;
1968
2047
  isNonInteractive?: boolean;
2048
+ /**
2049
+ * True when the active conversation's type is "background" or "scheduled".
2050
+ * Forwarded to {@link TurnInjectionInputs.isBackgroundConversation} so the
2051
+ * `background-turn` injector can wrap the tail user message with the
2052
+ * configured reminder.
2053
+ */
2054
+ isBackgroundConversation?: boolean;
1969
2055
  transportHints?: string[] | null;
1970
- slackRuntimeContextNotice?: string | null;
1971
2056
  /**
1972
2057
  * Pre-rendered Slack chronological transcript that replaces the
1973
2058
  * default `runMessages` history for any Slack conversation (channels
@@ -2056,6 +2141,7 @@ function buildTurnInjectionInputs(
2056
2141
  voiceCallControlPrompt: options.voiceCallControlPrompt,
2057
2142
  transportHints: options.transportHints,
2058
2143
  isNonInteractive: options.isNonInteractive,
2144
+ isBackgroundConversation: options.isBackgroundConversation,
2059
2145
  activeDocuments: options.activeDocuments,
2060
2146
  };
2061
2147
  }
@@ -2307,23 +2393,6 @@ export async function applyRuntimeInjections(
2307
2393
  }
2308
2394
  }
2309
2395
 
2310
- if (
2311
- mode === "full" &&
2312
- slackConversation &&
2313
- options.slackRuntimeContextNotice
2314
- ) {
2315
- const userTail = result[result.length - 1];
2316
- if (userTail && userTail.role === "user") {
2317
- result = [
2318
- ...result.slice(0, -1),
2319
- injectSlackRuntimeContextNotice(
2320
- userTail,
2321
- options.slackRuntimeContextNotice,
2322
- ),
2323
- ];
2324
- }
2325
- }
2326
-
2327
2396
  if (mode === "full" && options.channelCommandContext) {
2328
2397
  const userTail = result[result.length - 1];
2329
2398
  if (userTail && userTail.role === "user") {
@@ -14,7 +14,8 @@ import { getVisibleProviderCatalog } from "../providers/provider-catalog-visibil
14
14
  export type SlashResolution =
15
15
  | { kind: "passthrough"; content: string }
16
16
  | { kind: "unknown"; message: string }
17
- | { kind: "compact"; targetInputTokensOverride?: number };
17
+ | { kind: "compact"; targetInputTokensOverride?: number }
18
+ | { kind: "clean" };
18
19
 
19
20
  const COMPACT_USAGE_HINT =
20
21
  "Usage: `/compact [<tokens>]` (e.g. `/compact 30000`, `/compact 30k`, `/compact 1m`).";
@@ -52,6 +53,23 @@ function parseCompactCommand(trimmed: string): CompactParse | null {
52
53
  return { kind: "compact", targetInputTokensOverride: tokens };
53
54
  }
54
55
 
56
+ type CleanParse = { kind: "clean" } | { kind: "unknown"; message: string };
57
+
58
+ const CLEAN_COMMAND_PATTERN = /^\/clean(?:\s+(.+?))?\s*$/i;
59
+
60
+ function parseCleanCommand(trimmed: string): CleanParse | null {
61
+ const match = trimmed.match(CLEAN_COMMAND_PATTERN);
62
+ if (!match) return null;
63
+ const rest = match[1]?.trim();
64
+ if (rest) {
65
+ return {
66
+ kind: "unknown",
67
+ message: `\`/clean\` does not take arguments. Usage: \`/clean\`.`,
68
+ };
69
+ }
70
+ return { kind: "clean" };
71
+ }
72
+
55
73
  // ── /context and /status commands ────────────────────────────────────
56
74
 
57
75
  export interface SlashContext {
@@ -302,10 +320,14 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
302
320
  return { kind: "unknown", message: lines.join("\n") };
303
321
  }
304
322
 
323
+ const CLEAN_HELP_LINE =
324
+ "/clean — Strip injected runtime context and reset memory injection state (no summarization)";
325
+
305
326
  function resolveCommandsList(context?: SlashContext): string[] {
306
327
  const fallbackLines = [
307
328
  "/commands — List all available commands",
308
329
  "/compact — Force context compaction immediately",
330
+ CLEAN_HELP_LINE,
309
331
  ];
310
332
  if (context) {
311
333
  fallbackLines.push("/context — Show conversation context usage");
@@ -322,6 +344,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
322
344
  return [
323
345
  "/commands — List all available commands",
324
346
  "/compact — Force context compaction immediately",
347
+ CLEAN_HELP_LINE,
325
348
  "/context — Show conversation context usage",
326
349
  "/model — List or switch inference profile",
327
350
  "/models — List all available models",
@@ -335,6 +358,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
335
358
  return [
336
359
  "/commands — List all available commands",
337
360
  "/compact — Force context compaction immediately",
361
+ CLEAN_HELP_LINE,
338
362
  "/context — Show conversation context usage",
339
363
  "/model — List or switch inference profile",
340
364
  "/models — List all available models",
@@ -347,6 +371,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
347
371
  return [
348
372
  "/commands — List all available commands",
349
373
  "/compact — Force context compaction immediately",
374
+ CLEAN_HELP_LINE,
350
375
  "/context — Show conversation context usage",
351
376
  "/model — List or switch inference profile",
352
377
  "/models — List all available models",
@@ -366,7 +391,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
366
391
  */
367
392
  export function classifySlash(
368
393
  content: string,
369
- ): "passthrough" | "compact" | "unknown" {
394
+ ): "passthrough" | "compact" | "clean" | "unknown" {
370
395
  const trimmed = content.trim();
371
396
  if (parseModelCommand(trimmed) != null) {
372
397
  return "unknown";
@@ -381,6 +406,8 @@ export function classifySlash(
381
406
  if (trimmed === "/models") return "unknown";
382
407
  const compactParse = parseCompactCommand(trimmed);
383
408
  if (compactParse) return compactParse.kind;
409
+ const cleanParse = parseCleanCommand(trimmed);
410
+ if (cleanParse) return cleanParse.kind;
384
411
  if (trimmed === "/context") return "unknown";
385
412
  if (trimmed === "/status") return "unknown";
386
413
  if (trimmed === "/commands") return "unknown";
@@ -388,9 +415,10 @@ export function classifySlash(
388
415
  }
389
416
 
390
417
  /**
391
- * Resolve built-in slash commands (/models, /context, /status, /commands, /compact).
392
- * Returns `unknown` with a deterministic message, `compact` for forced compaction,
393
- * or the (possibly rewritten) content as `passthrough`.
418
+ * Resolve built-in slash commands (/models, /context, /status, /commands,
419
+ * /compact, /clean). Returns `unknown` with a deterministic message,
420
+ * `compact` for forced compaction, `clean` for injection stripping, or the
421
+ * (possibly rewritten) content as `passthrough`.
394
422
  */
395
423
  export async function resolveSlash(
396
424
  content: string,
@@ -424,6 +452,10 @@ export async function resolveSlash(
424
452
  const compactParse = parseCompactCommand(trimmed);
425
453
  if (compactParse) return compactParse;
426
454
 
455
+ // Handle /clean command (strip injections, no summarization).
456
+ const cleanParse = parseCleanCommand(trimmed);
457
+ if (cleanParse) return cleanParse;
458
+
427
459
  // Handle /context and legacy /status commands
428
460
  if (trimmed === "/context" || trimmed === "/status") {
429
461
  if (!context) {
@@ -390,6 +390,40 @@ function normalizeTaskProgressCardPatch(
390
390
  return normalizedPatch;
391
391
  }
392
392
 
393
+ function isTaskProgressCardData(data: SurfaceData | Record<string, unknown>) {
394
+ return (data as Record<string, unknown>).template === "task_progress";
395
+ }
396
+
397
+ function isSlackTaskProgressUiException(
398
+ ctx: SurfaceConversationContext,
399
+ toolName: string,
400
+ input: Record<string, unknown>,
401
+ ): boolean {
402
+ if (ctx.channelCapabilities?.channel !== "slack") return false;
403
+ if (toolName === "ui_show") {
404
+ const surfaceType = input.surface_type as SurfaceType;
405
+ if (surfaceType !== "card") return false;
406
+ const rawData = isPlainObject(input.data) ? input.data : {};
407
+ const data = normalizeCardShowData(input, rawData);
408
+ return isTaskProgressCardData(data);
409
+ }
410
+ if (toolName === "ui_update") {
411
+ const surfaceId = input.surface_id;
412
+ if (typeof surfaceId !== "string") return false;
413
+ const stored = ctx.surfaceState.get(surfaceId);
414
+ if (!stored || stored.surfaceType !== "card") return false;
415
+ if (!isTaskProgressCardData(stored.data)) return false;
416
+ const rawPatch = isPlainObject(input.data) ? input.data : {};
417
+ const patch = normalizeTaskProgressCardPatch(
418
+ stored.data as CardSurfaceData,
419
+ rawPatch,
420
+ );
421
+ const mergedData = { ...stored.data, ...patch } as SurfaceData;
422
+ return isTaskProgressCardData(mergedData);
423
+ }
424
+ return false;
425
+ }
426
+
393
427
  /**
394
428
  * Subset of Conversation state that surface helpers need access to.
395
429
  * The Conversation class implements this interface so its instances can be
@@ -1638,7 +1672,12 @@ export async function handleSurfaceAction(
1638
1672
  // One-shot interactive surfaces — auto-complete now that the message has
1639
1673
  // been accepted. Deferred until after rejection check so the surface stays
1640
1674
  // active and retryable if the queue was full.
1641
- const ONE_SHOT_SURFACE_TYPES = ["form", "confirmation", "file_upload"];
1675
+ const ONE_SHOT_SURFACE_TYPES = [
1676
+ "form",
1677
+ "confirmation",
1678
+ "file_upload",
1679
+ "task_preferences",
1680
+ ];
1642
1681
  if (ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)) {
1643
1682
  broadcastMessage({
1644
1683
  type: "ui_surface_complete",
@@ -2135,7 +2174,11 @@ export async function surfaceProxyResolver(
2135
2174
 
2136
2175
  if (toolName === "ui_show" || toolName === "ui_update") {
2137
2176
  const caps = ctx.channelCapabilities;
2138
- if (caps && !caps.supportsDynamicUi) {
2177
+ if (
2178
+ caps &&
2179
+ !caps.supportsDynamicUi &&
2180
+ !isSlackTaskProgressUiException(ctx, toolName, input)
2181
+ ) {
2139
2182
  log.info(
2140
2183
  { toolName, channel: caps.channel, conversationId: ctx.conversationId },
2141
2184
  "Blocked UI surface tool on channel without dynamic UI support",
@@ -331,6 +331,7 @@ export interface SkillProjectionContext {
331
331
  // ── Conditional tool sets ────────────────────────────────────────────
332
332
 
333
333
  const UI_SURFACE_TOOL_NAMES = new Set(["ui_show", "ui_update", "ui_dismiss"]);
334
+ const SLACK_TASK_PROGRESS_UI_TOOL_NAMES = new Set(["ui_show", "ui_update"]);
334
335
  /**
335
336
  * Single source of truth for which tools are host tools and the capability
336
337
  * each one requires from the connected client interface. Adding a tool here
@@ -437,6 +438,12 @@ export function isToolActiveForContext(
437
438
  return false;
438
439
  }
439
440
  if (UI_SURFACE_TOOL_NAMES.has(name)) {
441
+ if (
442
+ ctx.channelCapabilities?.channel === "slack" &&
443
+ SLACK_TASK_PROGRESS_UI_TOOL_NAMES.has(name)
444
+ ) {
445
+ return !ctx.hasNoClient;
446
+ }
440
447
  return ctx.channelCapabilities?.supportsDynamicUi ?? !ctx.hasNoClient;
441
448
  }
442
449
  if (HOST_TOOL_NAMES.has(name)) {