@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
@@ -250,7 +250,7 @@ describe("pairDeliveryWithConversation", () => {
250
250
  // Should NOT be the JSON dump
251
251
  expect(contentArg).not.toContain('"raw"');
252
252
  // Should be the runtime-composed seed from copy.title/body
253
- expect(contentArg).toContain("Reminder");
253
+ expect(contentArg).toContain("Daily standup");
254
254
  });
255
255
 
256
256
  test("rejects very short conversationSeedMessage and uses runtime composer", async () => {
@@ -273,7 +273,7 @@ describe("pairDeliveryWithConversation", () => {
273
273
  const contentArg = addMessageMock.mock.calls[0]![2] as string;
274
274
  expect(contentArg).not.toBe("Hi");
275
275
  // Runtime composer builds from copy.title/body
276
- expect(contentArg).toContain("Reminder");
276
+ expect(contentArg).toContain("Test reminder");
277
277
  });
278
278
 
279
279
  test("passes skipIndexing option to addMessage", async () => {
@@ -43,7 +43,7 @@ mock.module("../memory/guardian-action-store.js", () => ({
43
43
  const mockProviderStub = { name: "mock-provider" };
44
44
  mock.module("../providers/registry.js", () => ({
45
45
  getProvider: () => mockProviderStub,
46
- initializeProviders: () => {},
46
+ initializeProviders: async () => {},
47
47
  listProviders: () => ["anthropic", "openai", "gemini"],
48
48
  resolveProviderFromConnection: async () => mockProviderStub,
49
49
  }));
@@ -18,7 +18,7 @@ mock.module("../memory/guardian-action-store.js", () => ({
18
18
 
19
19
  mock.module("../providers/registry.js", () => ({
20
20
  getProvider: () => ({ name: "mock-provider" }),
21
- initializeProviders: () => {},
21
+ initializeProviders: async () => {},
22
22
  }));
23
23
 
24
24
  mock.module("../config/loader.js", () => ({
@@ -41,7 +41,7 @@ mock.module("../memory/guardian-action-store.js", () => ({
41
41
 
42
42
  mock.module("../providers/registry.js", () => ({
43
43
  getProvider: () => ({ name: "mock-provider" }),
44
- initializeProviders: () => {},
44
+ initializeProviders: async () => {},
45
45
  }));
46
46
 
47
47
  mock.module("../config/loader.js", () => ({
@@ -12,6 +12,7 @@ mock.module("../config/loader.js", () => ({
12
12
  return {
13
13
  ...real,
14
14
  memory: { ...real.memory, v2: { ...real.memory.v2, enabled: false } },
15
+ slack: { ...real.slack, botUserId: "U_BOT" },
15
16
  };
16
17
  },
17
18
  }));
@@ -245,7 +246,7 @@ describe("injectChannelCapabilityContext", () => {
245
246
  expect(text).toContain("CHANNEL CONSTRAINTS");
246
247
  expect(text).toContain("Do NOT reference the dashboard UI");
247
248
  expect(text).toContain("Do NOT use ui_show");
248
- expect(text).toContain("Do NOT ask the user to use voice");
249
+ expect(text).not.toContain("microphone");
249
250
  expect(text).toContain("dashboard_capable: false");
250
251
  });
251
252
 
@@ -337,6 +338,36 @@ describe("injectChannelCapabilityContext", () => {
337
338
  expect(text).toContain("emoji reactions");
338
339
  });
339
340
 
341
+ test("allows only task_progress ui_show/ui_update guidance for Slack", () => {
342
+ const caps: ChannelCapabilities = {
343
+ channel: "slack",
344
+ dashboardCapable: false,
345
+ supportsDynamicUi: false,
346
+ supportsVoiceInput: false,
347
+ };
348
+
349
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
350
+ const text = (result.content[0] as { type: "text"; text: string }).text;
351
+ expect(text).toContain(
352
+ 'Only use ui_show/ui_update for card surfaces with template: "task_progress"',
353
+ );
354
+ expect(text).not.toContain("Do NOT use ui_show, ui_update, or app_create");
355
+ });
356
+
357
+ test("keeps blanket ui_show/ui_update prohibition for other non-dynamic channels", () => {
358
+ const caps: ChannelCapabilities = {
359
+ channel: "phone",
360
+ dashboardCapable: false,
361
+ supportsDynamicUi: false,
362
+ supportsVoiceInput: false,
363
+ };
364
+
365
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
366
+ const text = (result.content[0] as { type: "text"; text: string }).text;
367
+ expect(text).toContain("Do NOT use ui_show, ui_update, or app_create");
368
+ expect(text).not.toContain("Only use ui_show/ui_update");
369
+ });
370
+
340
371
  test("still injects for group chats even when all capabilities are true", () => {
341
372
  const caps: ChannelCapabilities = {
342
373
  channel: "slack",
@@ -611,7 +642,7 @@ describe("trust-gating via channel capabilities", () => {
611
642
  });
612
643
 
613
644
  test("non-dashboard channel adds constraint rules preventing UI references", () => {
614
- const caps = resolveChannelCapabilities("slack");
645
+ const caps = resolveChannelCapabilities("telegram");
615
646
  const message: Message = {
616
647
  role: "user",
617
648
  content: [{ type: "text", text: "Show me a chart" }],
@@ -624,7 +655,8 @@ describe("trust-gating via channel capabilities", () => {
624
655
  expect(injected).toContain("Do NOT reference the dashboard UI");
625
656
  expect(injected).toContain("Do NOT use ui_show, ui_update, or app_create");
626
657
  expect(injected).toContain("Present information as well-formatted text");
627
- expect(injected).toContain("desktop app");
658
+ expect(injected).not.toContain("accent color selection");
659
+ expect(injected).not.toContain("complete those steps");
628
660
  });
629
661
 
630
662
  test("vellum web interface allows dynamic UI but constrains dashboard references", () => {
@@ -930,6 +962,28 @@ describe("stripInjectionsForCompaction with NOW.md", () => {
930
962
  "Hello",
931
963
  );
932
964
  });
965
+
966
+ test("strips <background_turn> blocks", () => {
967
+ const messages: Message[] = [
968
+ {
969
+ role: "user",
970
+ content: [
971
+ {
972
+ type: "text",
973
+ text: "<background_turn>\nGuardian isn't watching — notify on anything noteworthy.\n</background_turn>",
974
+ },
975
+ { type: "text", text: "Hello" },
976
+ ],
977
+ },
978
+ ];
979
+
980
+ const result = stripInjectionsForCompaction(messages);
981
+ expect(result.length).toBe(1);
982
+ expect(result[0].content.length).toBe(1);
983
+ expect((result[0].content[0] as { type: "text"; text: string }).text).toBe(
984
+ "Hello",
985
+ );
986
+ });
933
987
  });
934
988
 
935
989
  // ---------------------------------------------------------------------------
@@ -1228,6 +1282,24 @@ describe("buildUnifiedTurnContextBlock", () => {
1228
1282
  expect(telegramText).toContain("<no_response/>");
1229
1283
  });
1230
1284
 
1285
+ test("adds task_progress hint only for Slack turns", () => {
1286
+ const slackText = buildUnifiedTurnContextBlock({
1287
+ timestamp: "2026-04-02T12:00:00Z",
1288
+ interfaceName: "slack",
1289
+ channelName: "slack",
1290
+ });
1291
+ const telegramText = buildUnifiedTurnContextBlock({
1292
+ timestamp: "2026-04-02T12:00:00Z",
1293
+ interfaceName: "telegram",
1294
+ channelName: "telegram",
1295
+ });
1296
+
1297
+ expect(slackText).toContain(
1298
+ "if you are going to do work, use task_progress",
1299
+ );
1300
+ expect(telegramText).not.toContain("use task_progress");
1301
+ });
1302
+
1231
1303
  test("dedup logic: fields matching canonical_actor_identity are omitted", () => {
1232
1304
  const uuid = "vellum-principal-b77e94f5-67c0-4599-8baa-871b925b3da8";
1233
1305
  const options: UnifiedTurnContextOptions = {
@@ -1880,7 +1952,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
1880
1952
  },
1881
1953
  ];
1882
1954
 
1883
- const FLAT_REMINDER = buildPkbReminder([], false);
1955
+ const FLAT_REMINDER = buildPkbReminder([]);
1884
1956
 
1885
1957
  // Use a platform-agnostic absolute workspace root so the tests work on
1886
1958
  // macOS and Linux runners alike. `pkbRoot` sits under `pkbWorkingDir` to
@@ -2136,7 +2208,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2136
2208
  role: "user",
2137
2209
  content: [
2138
2210
  { type: "text", text: "hello" },
2139
- { type: "text", text: buildPkbReminder([], false) },
2211
+ { type: "text", text: buildPkbReminder([]) },
2140
2212
  ],
2141
2213
  };
2142
2214
  const hintedMessage: Message = {
@@ -2145,7 +2217,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
2145
2217
  { type: "text", text: "hello" },
2146
2218
  {
2147
2219
  type: "text",
2148
- text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"], false),
2220
+ text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"]),
2149
2221
  },
2150
2222
  ],
2151
2223
  };
@@ -2273,7 +2345,6 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2273
2345
  const T1 = "1700000010.000002"; // top-level message starting thread B
2274
2346
  const T2 = "1700000030.000003"; // newer top-level message
2275
2347
  const ALIAS_T0 = parentAlias(T0);
2276
- const ALIAS_T1 = parentAlias(T1);
2277
2348
  const ALIAS_T2 = parentAlias(T2);
2278
2349
 
2279
2350
  const SLACK_CHANNEL_ID = "C0123CHANNEL";
@@ -2468,16 +2539,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2468
2539
  expect(lines[2]).toContain("Reply in thread B");
2469
2540
  expect(lines[3]).toContain("Reply in thread A");
2470
2541
  // Cross-thread visibility: thread B's reply is in the rendered output
2471
- // alongside thread A's reply.
2472
- expect(lines[2]).toContain(`→ ${ALIAS_T1}`);
2473
- expect(lines[3]).toContain(`→ ${ALIAS_T0}`);
2542
+ // alongside thread A's reply, without parent-arrow prefixes.
2543
+ expect(lines[2]).not.toContain("→ M");
2544
+ expect(lines[3]).not.toContain("→ M");
2474
2545
  // Sender labels appear.
2475
2546
  expect(lines[0]).toContain("alice");
2476
2547
  expect(lines[1]).toContain("bob");
2477
2548
  });
2478
2549
 
2479
2550
  // ── Scenario 2: reply to a top-level (starts new thread) ─────────────
2480
- test("scenario 2 — reply to top-level renders thread tag pointing at parent", async () => {
2551
+ test("scenario 2 — reply to top-level renders without parent arrow", async () => {
2481
2552
  const rows: MessageRow[] = [
2482
2553
  userRow({
2483
2554
  id: "m1",
@@ -2501,15 +2572,13 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2501
2572
  const lines = texts(result);
2502
2573
 
2503
2574
  expect(lines.length).toBe(2);
2504
- // Top-level has no thread tag.
2505
2575
  expect(lines[0]).not.toContain("→ M");
2506
- // Reply points at the parent's deterministic alias.
2507
- expect(lines[1]).toContain(`→ ${ALIAS_T0}`);
2576
+ expect(lines[1]).not.toContain("→ M");
2508
2577
  expect(lines[1]).toContain("Reply that starts a new thread");
2509
2578
  });
2510
2579
 
2511
2580
  // ── Scenario 3: reply to the most-recent top-level message ───────────
2512
- test("scenario 3 — reply to last top-level still renders thread tag", async () => {
2581
+ test("scenario 3 — reply to last top-level still renders chronologically", async () => {
2513
2582
  const rows: MessageRow[] = [
2514
2583
  userRow({
2515
2584
  id: "m1",
@@ -2539,13 +2608,12 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2539
2608
  const lines = texts(result);
2540
2609
 
2541
2610
  expect(lines.length).toBe(3);
2542
- // The reply targets the newer top-level alias, not the older one.
2543
- expect(lines[2]).toContain(`→ ${ALIAS_T1}`);
2544
- expect(lines[2]).not.toContain(`→ ${ALIAS_T0}`);
2611
+ expect(lines[2]).toContain("Reply to the newer top-level");
2612
+ expect(lines[2]).not.toContain("→ M");
2545
2613
  });
2546
2614
 
2547
2615
  // ── Scenario 4: brand-new top-level message ──────────────────────────
2548
- test("scenario 4 — new top-level message has no thread tag", async () => {
2616
+ test("scenario 4 — new top-level message has no parent arrow", async () => {
2549
2617
  const rows: MessageRow[] = [
2550
2618
  userRow({
2551
2619
  id: "m1",
@@ -2565,7 +2633,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2565
2633
  const lines = texts(result);
2566
2634
 
2567
2635
  expect(lines.length).toBe(2);
2568
- // Both lines render without a thread tag — they are siblings, not
2636
+ // Both lines render without a parent arrow — they are siblings, not
2569
2637
  // members of the same thread.
2570
2638
  expect(lines[0]).not.toContain("→ M");
2571
2639
  expect(lines[1]).not.toContain("→ M");
@@ -2580,9 +2648,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2580
2648
  // ── Scenario 5: legacy mixed with post-upgrade rows ──────────────────
2581
2649
  // Pre-upgrade rows have no `slackMeta` sub-key. Post-upgrade rows have
2582
2650
  // it. Both kinds must appear in the rendered transcript with legacy
2583
- // rows rendered flat (no thread tag) and post-upgrade rows carrying
2584
- // their thread tags. The renderer's chronological sort must intermix
2585
- // them on the appropriate timeline.
2651
+ // rows and post-upgrade rows both rendered without parent-arrow prefixes.
2652
+ // The renderer's chronological sort must intermix them on the appropriate
2653
+ // timeline.
2586
2654
  test("scenario 5 — legacy rows mixed with post-upgrade rows render chronologically", async () => {
2587
2655
  const rows: MessageRow[] = [
2588
2656
  // Legacy user row with a displayName hint only — no slackMeta.
@@ -2599,8 +2667,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2599
2667
  text: "Legacy assistant reply",
2600
2668
  }),
2601
2669
  // Post-upgrade row anchored to a thread parent that has no record
2602
- // in storage (legacy parent) — the renderer still emits the alias
2603
- // because the metadata is intact.
2670
+ // in storage (legacy parent).
2604
2671
  userRow({
2605
2672
  id: "m3",
2606
2673
  createdAt: 1700000000_000,
@@ -2623,11 +2690,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2623
2690
  expect(lines[0]).toContain("Legacy user message");
2624
2691
  expect(lines[1]).toContain("Legacy assistant reply");
2625
2692
  expect(lines[2]).toContain("Post-upgrade thread reply");
2626
- // Legacy rows render flat — no thread tag arrow.
2627
2693
  expect(lines[0]).not.toContain("→ M");
2628
2694
  expect(lines[1]).not.toContain("→ M");
2629
- // Post-upgrade row carries its thread tag.
2630
- expect(lines[2]).toContain(`→ ${ALIAS_T0}`);
2695
+ expect(lines[2]).not.toContain("→ M");
2631
2696
  // Sender labels: legacy rows carry no structured displayName, and the
2632
2697
  // role slot already conveys user-vs-assistant identity, so the row
2633
2698
  // mapper emits `null` senderLabel and the renderer omits the label
@@ -2945,37 +3010,6 @@ describe("Slack channel chronological rendering — multi-thread", () => {
2945
3010
  expect(allText).not.toContain("dm context");
2946
3011
  });
2947
3012
 
2948
- test("slack late-join notice is model-facing and non-persisted", async () => {
2949
- const slackChannelCaps: ChannelCapabilities = {
2950
- channel: "slack",
2951
- dashboardCapable: false,
2952
- supportsDynamicUi: false,
2953
- supportsVoiceInput: false,
2954
- chatType: "channel",
2955
- };
2956
- const notice =
2957
- "Slack context note: this turn joined an existing thread. 3 earlier thread messages were backfilled before the current message.";
2958
-
2959
- const { messages: result, blocks } = await applyRuntimeInjections(
2960
- [{ role: "user", content: [{ type: "text", text: "current turn" }] }],
2961
- {
2962
- channelCapabilities: slackChannelCaps,
2963
- slackRuntimeContextNotice: notice,
2964
- transportHints: [notice],
2965
- },
2966
- );
2967
-
2968
- const allText = result
2969
- .flatMap((m) => m.content)
2970
- .filter((b): b is { type: "text"; text: string } => b.type === "text")
2971
- .map((b) => b.text)
2972
- .join("\n");
2973
- expect(allText).toContain("<slack_context_notice>");
2974
- expect(allText).toContain(notice);
2975
- expect(allText).not.toContain("<transport_hints>");
2976
- expect(JSON.stringify(blocks)).not.toContain(notice);
2977
- });
2978
-
2979
3013
  // ── transport_hints kept for non-slack channels ───────────────────────
2980
3014
  test("non-slack conversations still receive <transport_hints>", async () => {
2981
3015
  const { messages: result } = await applyRuntimeInjections(
@@ -3001,10 +3035,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3001
3035
  });
3002
3036
 
3003
3037
  // ── trust-filter regression for loadSlackChronologicalMessages ───────
3004
- // For untrusted actors, guardian-scoped rows must be excluded
3005
- // from the chronological transcript the same way `loadFromDb` filters
3006
- // them out of the default history.
3007
- test("loadSlackChronologicalMessages filters guardian-scoped rows for untrusted actors", () => {
3038
+ // For untrusted actors, Slack-sourced rows are still shared channel/thread
3039
+ // context, while non-Slack guardian-scoped rows remain private.
3040
+ test("loadSlackChronologicalMessages keeps Slack-visible guardian rows for untrusted actors", () => {
3008
3041
  const caps: ChannelCapabilities = {
3009
3042
  channel: "slack",
3010
3043
  dashboardCapable: false,
@@ -3012,14 +3045,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3012
3045
  supportsVoiceInput: false,
3013
3046
  chatType: "channel",
3014
3047
  };
3015
- // Row 1 has no provenance → guardian-scoped (filtered out).
3016
- // Row 2 has provenance.trustClass === "trusted_contact" (kept).
3017
3048
  const rows: MessageRow[] = [
3018
3049
  userRow({
3019
3050
  id: "m1",
3020
3051
  createdAt: 1700000000_000,
3021
- text: "guardian-only context",
3052
+ text: "public guardian instruction",
3022
3053
  slackMeta: buildSlackMeta({ channelTs: T0, displayName: "alice" }),
3054
+ extraOuterMetadata: {
3055
+ provenanceTrustClass: "guardian",
3056
+ },
3023
3057
  }),
3024
3058
  userRow({
3025
3059
  id: "m2",
@@ -3030,6 +3064,14 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3030
3064
  provenanceTrustClass: "trusted_contact",
3031
3065
  },
3032
3066
  }),
3067
+ userRow({
3068
+ id: "m3",
3069
+ createdAt: 1700000020_000,
3070
+ text: "private guardian-only context",
3071
+ extraOuterMetadata: {
3072
+ provenanceTrustClass: "guardian",
3073
+ },
3074
+ }),
3033
3075
  ];
3034
3076
  const result = loadSlackChronologicalMessages("conv-1", caps, {
3035
3077
  loader: () => rows,
@@ -3041,8 +3083,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3041
3083
  .filter((b): b is { type: "text"; text: string } => b.type === "text")
3042
3084
  .map((b) => b.text)
3043
3085
  .join("\n");
3044
- expect(allText).not.toContain("guardian-only context");
3086
+ expect(allText).toContain("public guardian instruction");
3045
3087
  expect(allText).toContain("from untrusted actor");
3088
+ expect(allText).not.toContain("private guardian-only context");
3046
3089
  });
3047
3090
 
3048
3091
  test("loadSlackChronologicalContext preserves summary and filters by Slack watermark", () => {
@@ -3360,15 +3403,14 @@ describe("Slack channel chronological rendering — multi-thread", () => {
3360
3403
  expect(focusBlock).not.toBeNull();
3361
3404
  expect(focusBlock!).toContain("<active_thread>");
3362
3405
  expect(focusBlock!).toContain("</active_thread>");
3363
- // Parent (T0) is included, both by content and via the parent alias.
3406
+ // Parent (T0) is included by content.
3364
3407
  expect(focusBlock!).toContain("Top-level in thread A");
3365
3408
  // The new reply is included.
3366
3409
  expect(focusBlock!).toContain("New reply in thread A");
3367
- expect(focusBlock!).toContain(`→ ${ALIAS_T0}`);
3410
+ expect(focusBlock!).not.toContain("→ M");
3368
3411
  // Thread B's content is NOT in the focus block.
3369
3412
  expect(focusBlock!).not.toContain("Top-level in thread B");
3370
3413
  expect(focusBlock!).not.toContain("Cross-thread reply in B");
3371
- expect(focusBlock!).not.toContain(`→ ${ALIAS_T1}`);
3372
3414
 
3373
3415
  // The focus block is appended to the FINAL user message as a tail
3374
3416
  // text block — not to any earlier message.
@@ -3966,13 +4008,97 @@ describe("assembleSlackActiveThreadFocusBlock", () => {
3966
4008
  expect(result!).toContain("@assistant: Assistant reply");
3967
4009
  });
3968
4010
 
4011
+ test("timezone-aware assistant rows keep renderer attribution in active-thread focus block", () => {
4012
+ const rows: SlackTranscriptInputRow[] = [
4013
+ buildRow(
4014
+ "user",
4015
+ "Parent",
4016
+ 1_000,
4017
+ buildMeta({
4018
+ channelTs: PARENT_TS,
4019
+ displayName: "aaron",
4020
+ timestampTimezone: "America/Denver",
4021
+ timestampTimezoneLabel: "MT",
4022
+ }),
4023
+ ),
4024
+ buildRow(
4025
+ "assistant",
4026
+ "Assistant reply",
4027
+ 2_000,
4028
+ buildMeta({
4029
+ channelTs: "1700000005.000001",
4030
+ threadTs: PARENT_TS,
4031
+ timestampTimezone: "America/Denver",
4032
+ timestampTimezoneLabel: "MT",
4033
+ speakerTimezoneLabel: "ET",
4034
+ }),
4035
+ ),
4036
+ buildRow(
4037
+ "user",
4038
+ "Follow-up",
4039
+ 3_000,
4040
+ buildMeta({
4041
+ channelTs: REPLY_TS,
4042
+ threadTs: PARENT_TS,
4043
+ displayName: "aaron",
4044
+ timestampTimezone: "America/Denver",
4045
+ timestampTimezoneLabel: "MT",
4046
+ }),
4047
+ ),
4048
+ ];
4049
+
4050
+ const result = assembleSlackActiveThreadFocusBlock(rows, SLACK_CAPS);
4051
+ expect(result).not.toBeNull();
4052
+ expect(result!).toContain(
4053
+ "[nov 14 2023 3:13 PM MT assistant] Assistant reply",
4054
+ );
4055
+ expect(result!).not.toContain(
4056
+ "@assistant: [nov 14 2023 3:13 PM MT assistant]",
4057
+ );
4058
+ });
4059
+
4060
+ test("assistant content that only looks like a compact tag still gets active-thread attribution", () => {
4061
+ const compactLookingContent =
4062
+ "[nov 14 2023 3:13 PM MT assistant] Assistant reply";
4063
+ const rows: SlackTranscriptInputRow[] = [
4064
+ buildRow(
4065
+ "user",
4066
+ "Parent",
4067
+ 1_000,
4068
+ buildMeta({ channelTs: PARENT_TS, displayName: "@alice" }),
4069
+ ),
4070
+ buildRow(
4071
+ "assistant",
4072
+ compactLookingContent,
4073
+ 2_000,
4074
+ buildMeta({
4075
+ channelTs: "1700000005.000001",
4076
+ threadTs: PARENT_TS,
4077
+ }),
4078
+ ),
4079
+ buildRow(
4080
+ "user",
4081
+ "Follow-up",
4082
+ 3_000,
4083
+ buildMeta({
4084
+ channelTs: REPLY_TS,
4085
+ threadTs: PARENT_TS,
4086
+ displayName: "@alice",
4087
+ }),
4088
+ ),
4089
+ ];
4090
+
4091
+ const result = assembleSlackActiveThreadFocusBlock(rows, SLACK_CAPS);
4092
+ expect(result).not.toBeNull();
4093
+ expect(result!).toContain(`@assistant: ${compactLookingContent}`);
4094
+ });
4095
+
3969
4096
  test("assistant reaction overflow trailer is not double-attributed", () => {
3970
4097
  // When assistant reactions overflow the per-target cap, `renderSlackTranscript`
3971
4098
  // emits a trailer line (`[…and N more reactions to Mxxxxxx]`) whose role
3972
- // is inherited from the first overflowing reaction — i.e. `assistant`. The
3973
- // trailer embeds no actor attribution but ends with the parent alias and
3974
- // shares the same `M<hex>]` signature as a real reaction line, so it must
3975
- // be detected by `isReactionTagLine` and skipped by the prefix step.
4099
+ // is inherited from the first overflowing reaction — i.e. `assistant`.
4100
+ // Renderer provenance marks it as a Slack reaction line so the flattened
4101
+ // active-thread block does not add a content-message prefix.
3976
4102
  const PARENT_ALIAS_TS = PARENT_TS;
3977
4103
  const buildAssistantReaction = (ts: string, emoji: string) =>
3978
4104
  buildRow(
@@ -4057,6 +4183,7 @@ describe("assembleSlackChronologicalMessages", () => {
4057
4183
  // Anchor times mirror the renderer's HH:MM (UTC) output.
4058
4184
  // 14:25:00 UTC on 2023-11-14 = epoch second 1699971900.
4059
4185
  const TS_14_25 = "1699971900.000100"; // 14:25 UTC
4186
+ const TS_14_26 = "1699971960.000200"; // 14:26 UTC
4060
4187
  const TS_14_28 = "1699972080.000300"; // 14:28 UTC
4061
4188
  const MS_14_25 = 1699971900_000;
4062
4189
  const MS_14_26 = 1699971960_000;
@@ -4217,6 +4344,85 @@ describe("assembleSlackChronologicalMessages", () => {
4217
4344
  }
4218
4345
  });
4219
4346
 
4347
+ test("expanded Slack timezone metadata remains renderable", () => {
4348
+ const userMeta: SlackMessageMetadata = {
4349
+ source: "slack",
4350
+ channelId: DM_CHANNEL_ID,
4351
+ channelTs: TS_14_25,
4352
+ eventKind: "message",
4353
+ displayName: "@alice",
4354
+ actorTimezone: "America/New_York",
4355
+ actorTimezoneLabel: "ET",
4356
+ actorTimezoneOffsetSeconds: -18000,
4357
+ timestampTimezone: "America/New_York",
4358
+ timestampTimezoneLabel: "ET",
4359
+ speakerTimezoneLabel: "ET",
4360
+ };
4361
+ const rows: SlackTranscriptInputRow[] = [
4362
+ row("user", "timezone-aware hello", MS_14_25, metadataEnvelope(userMeta)),
4363
+ ];
4364
+
4365
+ const result = assembleSlackChronologicalMessages(rows, DM_CAPS);
4366
+ expect(result).not.toBeNull();
4367
+ expect(result!.map((m) => (m.content[0] as { text: string }).text)).toEqual(
4368
+ [
4369
+ `[nov 14 2023 9:25 AM ET @alice (ET)] ${slackExternal(
4370
+ "timezone-aware hello",
4371
+ "@alice",
4372
+ )}`,
4373
+ ],
4374
+ );
4375
+ });
4376
+
4377
+ test("Slack context skips configured assistant new-thread placeholder rows", () => {
4378
+ const placeholderMeta: SlackMessageMetadata = {
4379
+ source: "slack",
4380
+ channelId: DM_CHANNEL_ID,
4381
+ channelTs: TS_14_25,
4382
+ eventKind: "message",
4383
+ displayName: "Ada",
4384
+ actorExternalUserId: "U_BOT",
4385
+ };
4386
+ const otherBotMeta: SlackMessageMetadata = {
4387
+ source: "slack",
4388
+ channelId: DM_CHANNEL_ID,
4389
+ channelTs: TS_14_26,
4390
+ eventKind: "message",
4391
+ displayName: "Build Bot",
4392
+ actorExternalUserId: "B_OTHER",
4393
+ };
4394
+ const realBotMeta: SlackMessageMetadata = {
4395
+ source: "slack",
4396
+ channelId: DM_CHANNEL_ID,
4397
+ channelTs: TS_14_28,
4398
+ eventKind: "message",
4399
+ actorExternalUserId: "B_ASSISTANT",
4400
+ };
4401
+ const rows: SlackTranscriptInputRow[] = [
4402
+ row(
4403
+ "user",
4404
+ "New Assistant Thread",
4405
+ MS_14_25,
4406
+ metadataEnvelope(placeholderMeta),
4407
+ ),
4408
+ row(
4409
+ "user",
4410
+ "New Assistant Thread",
4411
+ MS_14_26,
4412
+ metadataEnvelope(otherBotMeta),
4413
+ ),
4414
+ row("user", "real bot context", MS_14_28, metadataEnvelope(realBotMeta)),
4415
+ ];
4416
+
4417
+ const result = assembleSlackChronologicalMessages(rows, DM_CAPS);
4418
+ expect(result).not.toBeNull();
4419
+ const rendered = JSON.stringify(result);
4420
+ expect(rendered).not.toContain("Ada");
4421
+ expect(rendered.split("New Assistant Thread").length - 1).toBe(1);
4422
+ expect(rendered).toContain("Build Bot");
4423
+ expect(rendered).toContain("real bot context");
4424
+ });
4425
+
4220
4426
  test("legacy-DM fixture: pre-upgrade rows (no slackMeta) interleave with post-upgrade rows", () => {
4221
4427
  // Mix:
4222
4428
  // - Two pre-upgrade rows (created before PR 16 wired slackMeta into
@@ -4430,17 +4636,16 @@ describe("assembleSlackChronologicalMessages", () => {
4430
4636
  });
4431
4637
  });
4432
4638
 
4433
- test("post-reconciliation: assistant rows with channelTs participate in thread tagging", () => {
4639
+ test("post-reconciliation: assistant rows with channelTs participate in chronological rendering", () => {
4434
4640
  // Once `deliverReplyViaCallback` reconciles `channelTs` from the
4435
4641
  // gateway's response, assistant rows carry a fully-formed slackMeta
4436
4642
  // envelope. They must then render through the Slack chronological
4437
4643
  // path (not the legacy fallback) so reply rows pointing at the
4438
- // assistant's prior message get a `→ Mxxxxxx` parent-alias arrow.
4644
+ // assistant's prior message appear in Slack timestamp order.
4439
4645
  //
4440
4646
  // This is the cross-thread visibility that the slack-thread-aware-
4441
4647
  // context plan promises: a follow-up user reply to the assistant's
4442
- // earlier post should render with a parent-alias arrow that the model
4443
- // can use to reason about which prior assistant message it threads off.
4648
+ // earlier post should render alongside the prior assistant row.
4444
4649
  const SLACK_CHANNEL_ID_2 = "C0THREAD";
4445
4650
  const ASSISTANT_TS = "1700001000.000111";
4446
4651
  const REPLY_TS = "1700001020.000222";
@@ -4485,13 +4690,13 @@ describe("assembleSlackChronologicalMessages", () => {
4485
4690
  expect(result).not.toBeNull();
4486
4691
  expect(result!.length).toBe(2);
4487
4692
 
4488
- // The user follow-up MUST carry a `→ Mxxxxxx` parent-alias arrow that
4489
- // points at the assistant's prior message. Before reconciliation, the
4490
- // assistant row was treated as legacy/null-metadata and excluded from
4491
- // alias issuance — the user reply rendered without the arrow.
4693
+ // The user follow-up keeps timestamp/sender attribution without carrying
4694
+ // the old parent-alias arrow.
4492
4695
  const replyText = (result![1].content[0] as { text: string }).text;
4493
- expect(replyText).toMatch(/→ M[0-9a-f]{6}/);
4494
- expect(replyText).toContain(parentAlias(ASSISTANT_TS));
4696
+ expect(replyText).toBe(
4697
+ `[11/14/23 22:30 @alice]: ${slackExternal("Following up", "@alice")}`,
4698
+ );
4699
+ expect(replyText).not.toContain("→ M");
4495
4700
  });
4496
4701
 
4497
4702
  test("post-reconciliation: assistant row appears in active-thread focus block", () => {
@@ -4827,7 +5032,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
4827
5032
  mode: "full",
4828
5033
  });
4829
5034
 
4830
- const expected = buildPkbReminder([], false);
5035
+ const expected = buildPkbReminder([]);
4831
5036
  expect(blocks.pkbSystemReminder).toBe(expected);
4832
5037
  });
4833
5038