@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
@@ -78,7 +78,7 @@ describe("handleListMessages attachments", () => {
78
78
  const stored = uploadAttachment("photo.png", "image/png", IMAGE_BASE64);
79
79
  linkAttachmentToMessage(msg.id, stored.id, 0);
80
80
 
81
- const response = handleListMessages(createTestArgs(conv.id), null);
81
+ const response = handleListMessages(createTestArgs(conv.id));
82
82
  const body = response as { messages: MessagePayload[] };
83
83
 
84
84
  expect(body.messages).toHaveLength(1);
@@ -103,7 +103,7 @@ describe("handleListMessages attachments", () => {
103
103
  );
104
104
  linkAttachmentToMessage(msg.id, stored.id, 0);
105
105
 
106
- const response = handleListMessages(createTestArgs(conv.id), null);
106
+ const response = handleListMessages(createTestArgs(conv.id));
107
107
  const body = response as { messages: MessagePayload[] };
108
108
 
109
109
  expect(body.messages).toHaveLength(1);
@@ -125,7 +125,7 @@ describe("handleListMessages attachments", () => {
125
125
  const stored = uploadAttachment("result.png", "image/png", IMAGE_BASE64);
126
126
  linkAttachmentToMessage(msg.id, stored.id, 0);
127
127
 
128
- const response = handleListMessages(createTestArgs(conv.id), null);
128
+ const response = handleListMessages(createTestArgs(conv.id));
129
129
  const body = response as { messages: MessagePayload[] };
130
130
 
131
131
  expect(body.messages).toHaveLength(1);
@@ -153,7 +153,7 @@ describe("handleListMessages attachments", () => {
153
153
  linkAttachmentToMessage(msg.id, imgStored.id, 0);
154
154
  linkAttachmentToMessage(msg.id, docStored.id, 1);
155
155
 
156
- const response = handleListMessages(createTestArgs(conv.id), null);
156
+ const response = handleListMessages(createTestArgs(conv.id));
157
157
  const body = response as { messages: MessagePayload[] };
158
158
 
159
159
  const attachments = body.messages[0].attachments!;
@@ -177,7 +177,7 @@ describe("handleListMessages no_response filtering", () => {
177
177
  JSON.stringify([{ type: "text", text: "<no_response/>" }]),
178
178
  );
179
179
 
180
- const response = handleListMessages(createTestArgs(conv.id), null);
180
+ const response = handleListMessages(createTestArgs(conv.id));
181
181
  const body = response as {
182
182
  messages: { content: string; textSegments: string[] }[];
183
183
  };
@@ -199,7 +199,7 @@ describe("handleListMessages no_response filtering", () => {
199
199
  ]),
200
200
  );
201
201
 
202
- const response = handleListMessages(createTestArgs(conv.id), null);
202
+ const response = handleListMessages(createTestArgs(conv.id));
203
203
  const body = response as {
204
204
  messages: { content: string; textSegments: string[] }[];
205
205
  };
@@ -228,7 +228,7 @@ describe("handleListMessages no_response filtering", () => {
228
228
  ]),
229
229
  );
230
230
 
231
- const response = handleListMessages(createTestArgs(conv.id), null);
231
+ const response = handleListMessages(createTestArgs(conv.id));
232
232
  const body = response as {
233
233
  messages: {
234
234
  content: string;
@@ -252,7 +252,7 @@ describe("handleListMessages no_response filtering", () => {
252
252
  JSON.stringify([{ type: "text", text: "What does <no_response/> do?" }]),
253
253
  );
254
254
 
255
- const response = handleListMessages(createTestArgs(conv.id), null);
255
+ const response = handleListMessages(createTestArgs(conv.id));
256
256
  const body = response as {
257
257
  messages: { content: string }[];
258
258
  };
@@ -308,7 +308,7 @@ describe("handleListMessages pagination", () => {
308
308
  const conv = createConversation();
309
309
  await insertMessages(conv.id, 5);
310
310
 
311
- const response = handleListMessages(createTestArgs(conv.id), null);
311
+ const response = handleListMessages(createTestArgs(conv.id));
312
312
  const body = response as unknown as PaginatedResponse;
313
313
 
314
314
  expect(body.messages).toHaveLength(5);
@@ -322,7 +322,7 @@ describe("handleListMessages pagination", () => {
322
322
  await insertMessages(conv.id, 5);
323
323
 
324
324
  const args = createPaginatedArgs(conv.id, { limit: "3" });
325
- const response = handleListMessages(args, null);
325
+ const response = handleListMessages(args);
326
326
  const body = response as unknown as PaginatedResponse;
327
327
 
328
328
  // Option A: without beforeTimestamp, all messages are returned regardless of limit
@@ -339,7 +339,7 @@ describe("handleListMessages pagination", () => {
339
339
  beforeTimestamp: String(msgs[7].createdAt),
340
340
  limit: "3",
341
341
  });
342
- const response = handleListMessages(args, null);
342
+ const response = handleListMessages(args);
343
343
  const body = response as unknown as PaginatedResponse;
344
344
 
345
345
  expect(body.messages).toHaveLength(3);
@@ -360,7 +360,7 @@ describe("handleListMessages pagination", () => {
360
360
  beforeTimestamp: String(msgs[1].createdAt),
361
361
  limit: "10",
362
362
  });
363
- const response = handleListMessages(args, null);
363
+ const response = handleListMessages(args);
364
364
  const body = response as unknown as PaginatedResponse;
365
365
 
366
366
  const ids = body.messages.map((m) => m.id);
@@ -378,7 +378,7 @@ describe("handleListMessages pagination", () => {
378
378
  beforeTimestamp: String(msgs[4].createdAt + 1),
379
379
  limit: "10",
380
380
  });
381
- const response = handleListMessages(args, null);
381
+ const response = handleListMessages(args);
382
382
  const body = response as unknown as PaginatedResponse;
383
383
 
384
384
  expect(body.messages).toHaveLength(5);
@@ -394,7 +394,7 @@ describe("handleListMessages pagination", () => {
394
394
  beforeTimestamp: String(msgs[4].createdAt + 1),
395
395
  limit: "3",
396
396
  });
397
- const response = handleListMessages(args, null);
397
+ const response = handleListMessages(args);
398
398
  const body = response as unknown as PaginatedResponse;
399
399
 
400
400
  expect(body.messages).toHaveLength(3);
@@ -405,7 +405,7 @@ describe("handleListMessages pagination", () => {
405
405
 
406
406
  test("empty / nonexistent conversation → empty messages, no pagination metadata", async () => {
407
407
  const args = createPaginatedArgs("nonexistent-conv-id");
408
- const response = handleListMessages(args, null);
408
+ const response = handleListMessages(args);
409
409
  const body = response as unknown as PaginatedResponse;
410
410
 
411
411
  expect(body.messages).toEqual([]);
@@ -418,13 +418,17 @@ describe("handleListMessages pagination", () => {
418
418
  const conv = createConversation();
419
419
  const args = createPaginatedArgs(conv.id, { limit: "abc" });
420
420
 
421
- expect(() => handleListMessages(args, null)).toThrow("limit must be a valid number");
421
+ expect(() => handleListMessages(args)).toThrow(
422
+ "limit must be a valid number",
423
+ );
422
424
  });
423
425
 
424
426
  test("invalid beforeTimestamp (NaN) → 400", async () => {
425
427
  const conv = createConversation();
426
428
  const args = createPaginatedArgs(conv.id, { beforeTimestamp: "abc" });
427
429
 
428
- expect(() => handleListMessages(args, null)).toThrow("beforeTimestamp must be a valid number");
430
+ expect(() => handleListMessages(args)).toThrow(
431
+ "beforeTimestamp must be a valid number",
432
+ );
429
433
  });
430
434
  });
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Tests for handleListMessages metadata.hidden filtering.
3
+ *
4
+ * Messages persisted with `metadata: { hidden: true }` (e.g. internal
5
+ * scaffolding like retrospective instructions) must be omitted from the
6
+ * UI history list while remaining visible to the LLM-side history loader.
7
+ */
8
+
9
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ mock.module("../util/logger.js", () => ({
12
+ getLogger: () =>
13
+ new Proxy({} as Record<string, unknown>, {
14
+ get: () => () => {},
15
+ }),
16
+ }));
17
+
18
+ mock.module("../config/loader.js", () => ({
19
+ getConfig: () => ({
20
+ ui: {},
21
+ model: "test",
22
+ provider: "test",
23
+ memory: { enabled: false },
24
+ rateLimit: { maxRequestsPerMinute: 0 },
25
+ }),
26
+ }));
27
+
28
+ import {
29
+ addMessage,
30
+ createConversation,
31
+ getMessages,
32
+ } from "../memory/conversation-crud.js";
33
+ import { getDb } from "../memory/db-connection.js";
34
+ import { initializeDb } from "../memory/db-init.js";
35
+ import { handleListMessages } from "../runtime/routes/conversation-routes.js";
36
+
37
+ initializeDb();
38
+
39
+ function resetTables() {
40
+ const db = getDb();
41
+ db.run("DELETE FROM message_attachments");
42
+ db.run("DELETE FROM attachments");
43
+ db.run("DELETE FROM messages");
44
+ db.run("DELETE FROM conversations");
45
+ }
46
+
47
+ interface MessagePayload {
48
+ role: string;
49
+ content: string;
50
+ }
51
+
52
+ describe("handleListMessages metadata.hidden filtering", () => {
53
+ beforeEach(resetTables);
54
+
55
+ test("UI serializer omits hidden messages but LLM-side getMessages includes them", async () => {
56
+ const conv = createConversation();
57
+ await addMessage(
58
+ conv.id,
59
+ "user",
60
+ JSON.stringify([{ type: "text", text: "first visible" }]),
61
+ );
62
+ await addMessage(
63
+ conv.id,
64
+ "assistant",
65
+ JSON.stringify([{ type: "text", text: "internal scaffolding" }]),
66
+ { hidden: true },
67
+ );
68
+ await addMessage(
69
+ conv.id,
70
+ "user",
71
+ JSON.stringify([{ type: "text", text: "second visible" }]),
72
+ );
73
+
74
+ const response = handleListMessages({
75
+ queryParams: { conversationId: conv.id },
76
+ });
77
+ const body = response as { messages: MessagePayload[] };
78
+
79
+ expect(body.messages).toHaveLength(2);
80
+ expect(body.messages[0].content).toBe("first visible");
81
+ expect(body.messages[1].content).toBe("second visible");
82
+ expect(
83
+ body.messages.some((m) => m.content.includes("internal scaffolding")),
84
+ ).toBe(false);
85
+
86
+ // LLM-side loader must include the hidden row so agent context is intact.
87
+ const llmRows = getMessages(conv.id);
88
+ expect(llmRows).toHaveLength(3);
89
+ expect(llmRows[1].metadata).toContain('"hidden":true');
90
+ });
91
+
92
+ test("messages without metadata or with hidden=false are returned", async () => {
93
+ const conv = createConversation();
94
+ await addMessage(
95
+ conv.id,
96
+ "user",
97
+ JSON.stringify([{ type: "text", text: "no metadata" }]),
98
+ );
99
+ await addMessage(
100
+ conv.id,
101
+ "assistant",
102
+ JSON.stringify([{ type: "text", text: "hidden false" }]),
103
+ { hidden: false },
104
+ );
105
+
106
+ const response = handleListMessages({
107
+ queryParams: { conversationId: conv.id },
108
+ });
109
+ const body = response as { messages: MessagePayload[] };
110
+
111
+ expect(body.messages).toHaveLength(2);
112
+ });
113
+
114
+ test("pagination skips hidden rows so hasMore and oldest cursor reflect visible rows", async () => {
115
+ const conv = createConversation();
116
+ // 4 visible older rows, then a block of 3 hidden rows, then 2 visible newer.
117
+ // With limit=2 and page=latest we should get the 2 newest visible rows,
118
+ // hasMore=true (older visible rows exist), and a cursor pointing at the
119
+ // oldest visible row in the page rather than null.
120
+ for (let i = 0; i < 4; i++) {
121
+ await addMessage(
122
+ conv.id,
123
+ "user",
124
+ JSON.stringify([{ type: "text", text: `old visible ${i}` }]),
125
+ );
126
+ }
127
+ for (let i = 0; i < 3; i++) {
128
+ await addMessage(
129
+ conv.id,
130
+ "assistant",
131
+ JSON.stringify([{ type: "text", text: `hidden ${i}` }]),
132
+ { hidden: true },
133
+ );
134
+ }
135
+ for (let i = 0; i < 2; i++) {
136
+ await addMessage(
137
+ conv.id,
138
+ "user",
139
+ JSON.stringify([{ type: "text", text: `new visible ${i}` }]),
140
+ );
141
+ }
142
+
143
+ const latest = handleListMessages({
144
+ queryParams: { conversationId: conv.id, page: "latest", limit: "2" },
145
+ }) as {
146
+ messages: MessagePayload[];
147
+ hasMore: boolean;
148
+ oldestTimestamp: number | null;
149
+ oldestMessageId: string | null;
150
+ };
151
+
152
+ expect(latest.messages.map((m) => m.content)).toEqual([
153
+ "new visible 0",
154
+ "new visible 1",
155
+ ]);
156
+ expect(latest.hasMore).toBe(true);
157
+ expect(latest.oldestTimestamp).not.toBeNull();
158
+ expect(latest.oldestMessageId).not.toBeNull();
159
+
160
+ // Older page request — anchored before the latest page's oldest row —
161
+ // should skip the hidden block entirely and return the next 2 visible rows.
162
+ const older = handleListMessages({
163
+ queryParams: {
164
+ conversationId: conv.id,
165
+ beforeTimestamp: String(latest.oldestTimestamp),
166
+ limit: "2",
167
+ },
168
+ }) as {
169
+ messages: MessagePayload[];
170
+ hasMore: boolean;
171
+ };
172
+
173
+ expect(older.messages.map((m) => m.content)).toEqual([
174
+ "old visible 2",
175
+ "old visible 3",
176
+ ]);
177
+ expect(older.hasMore).toBe(true);
178
+ });
179
+
180
+ test("pagination drains DB when every row in a page is hidden", async () => {
181
+ const conv = createConversation();
182
+ // 5 hidden rows then 2 visible older rows. With limit=2, the naive
183
+ // implementation fetches 3 newest (all hidden), filters to 0 visible, and
184
+ // returns hasMore=true with no cursor. We expect the loop to keep going
185
+ // and surface the visible rows instead.
186
+ for (let i = 0; i < 2; i++) {
187
+ await addMessage(
188
+ conv.id,
189
+ "user",
190
+ JSON.stringify([{ type: "text", text: `old visible ${i}` }]),
191
+ );
192
+ }
193
+ for (let i = 0; i < 5; i++) {
194
+ await addMessage(
195
+ conv.id,
196
+ "assistant",
197
+ JSON.stringify([{ type: "text", text: `hidden ${i}` }]),
198
+ { hidden: true },
199
+ );
200
+ }
201
+
202
+ const latest = handleListMessages({
203
+ queryParams: { conversationId: conv.id, page: "latest", limit: "2" },
204
+ }) as {
205
+ messages: MessagePayload[];
206
+ hasMore: boolean;
207
+ oldestTimestamp: number | null;
208
+ };
209
+
210
+ expect(latest.messages.map((m) => m.content)).toEqual([
211
+ "old visible 0",
212
+ "old visible 1",
213
+ ]);
214
+ expect(latest.hasMore).toBe(false);
215
+ expect(latest.oldestTimestamp).not.toBeNull();
216
+ });
217
+ });
@@ -32,6 +32,11 @@ mock.module("../config/loader.js", () => ({
32
32
  }),
33
33
  }));
34
34
 
35
+ let mockAssistantName: string | null = null;
36
+ mock.module("../daemon/identity-helpers.js", () => ({
37
+ getAssistantName: () => mockAssistantName,
38
+ }));
39
+
35
40
  import { createConversation } from "../memory/conversation-crud.js";
36
41
  import { getDb } from "../memory/db-connection.js";
37
42
  import { initializeDb } from "../memory/db-init.js";
@@ -89,8 +94,10 @@ interface MessagePayload {
89
94
  timestamp: string;
90
95
  slackMessage?: {
91
96
  channelId: string;
97
+ channelName?: string;
92
98
  channelTs: string;
93
99
  threadTs?: string;
100
+ sender?: { displayName?: string; externalUserId?: string };
94
101
  messageLink?: { appUrl?: string; webUrl?: string };
95
102
  threadLink?: { appUrl?: string; webUrl?: string };
96
103
  };
@@ -104,14 +111,16 @@ interface ListResponse {
104
111
  }
105
112
 
106
113
  function callList(query: Record<string, string>): ListResponse {
107
- return handleListMessages(
108
- { queryParams: query },
109
- null,
110
- ) as unknown as ListResponse;
114
+ return handleListMessages({
115
+ queryParams: query,
116
+ }) as unknown as ListResponse;
111
117
  }
112
118
 
113
119
  describe("handleListMessages page=latest", () => {
114
- beforeEach(resetTables);
120
+ beforeEach(() => {
121
+ resetTables();
122
+ mockAssistantName = null;
123
+ });
115
124
 
116
125
  test("page=latest with no limit returns all messages chronologically", () => {
117
126
  const conv = createConversation();
@@ -219,8 +228,11 @@ describe("handleListMessages page=latest", () => {
219
228
  slackMeta: writeSlackMetadata({
220
229
  source: "slack",
221
230
  channelId: "C123ABCDEF",
231
+ channelName: "engineering",
222
232
  channelTs: "1710000000.000200",
223
233
  threadTs: "1710000000.000100",
234
+ displayName: "Alice",
235
+ actorExternalUserId: "U_ALICE",
224
236
  eventKind: "message",
225
237
  }),
226
238
  }),
@@ -232,13 +244,18 @@ describe("handleListMessages page=latest", () => {
232
244
 
233
245
  expect(body.messages[0].slackMessage).toEqual({
234
246
  channelId: "C123ABCDEF",
247
+ channelName: "engineering",
235
248
  channelTs: "1710000000.000200",
236
249
  threadTs: "1710000000.000100",
250
+ sender: {
251
+ displayName: "Alice",
252
+ externalUserId: "U_ALICE",
253
+ },
237
254
  messageLink: {
238
255
  appUrl:
239
256
  "slack://channel?team=T123&id=C123ABCDEF&message=1710000000.000200",
240
257
  webUrl:
241
- "https://example.slack.com/archives/C123ABCDEF/p1710000000000200",
258
+ "https://example.slack.com/archives/C123ABCDEF/p1710000000000200?thread_ts=1710000000.000100&cid=C123ABCDEF",
242
259
  },
243
260
  threadLink: {
244
261
  appUrl:
@@ -249,6 +266,107 @@ describe("handleListMessages page=latest", () => {
249
266
  });
250
267
  });
251
268
 
269
+ test("top-level Slack messages with matching threadTs use plain permalinks", () => {
270
+ const conv = createConversation();
271
+ const db = getDb();
272
+ db.insert(messages)
273
+ .values({
274
+ id: "msg-slack-top-level",
275
+ conversationId: conv.id,
276
+ role: "user",
277
+ content: JSON.stringify([{ type: "text", text: "Slack top-level" }]),
278
+ metadata: JSON.stringify({
279
+ slackMeta: writeSlackMetadata({
280
+ source: "slack",
281
+ channelId: "C123ABCDEF",
282
+ channelName: "engineering",
283
+ channelTs: "1710000000.000200",
284
+ threadTs: "1710000000.000200",
285
+ displayName: "Alice",
286
+ actorExternalUserId: "U_ALICE",
287
+ eventKind: "message",
288
+ }),
289
+ }),
290
+ createdAt: 1,
291
+ })
292
+ .run();
293
+
294
+ const body = callList({ conversationId: conv.id, page: "latest" });
295
+
296
+ expect(body.messages[0].slackMessage).toEqual({
297
+ channelId: "C123ABCDEF",
298
+ channelName: "engineering",
299
+ channelTs: "1710000000.000200",
300
+ threadTs: "1710000000.000200",
301
+ sender: {
302
+ displayName: "Alice",
303
+ externalUserId: "U_ALICE",
304
+ },
305
+ messageLink: {
306
+ appUrl:
307
+ "slack://channel?team=T123&id=C123ABCDEF&message=1710000000.000200",
308
+ webUrl:
309
+ "https://example.slack.com/archives/C123ABCDEF/p1710000000000200",
310
+ },
311
+ });
312
+ });
313
+
314
+ test("assistant Slack messages without stored sender use the assistant name", () => {
315
+ mockAssistantName = "Nova";
316
+ const conv = createConversation();
317
+ const db = getDb();
318
+ db.insert(messages)
319
+ .values({
320
+ id: "msg-slack-assistant",
321
+ conversationId: conv.id,
322
+ role: "assistant",
323
+ content: JSON.stringify([{ type: "text", text: "Slack response" }]),
324
+ metadata: JSON.stringify({
325
+ slackMeta: writeSlackMetadata({
326
+ source: "slack",
327
+ channelId: "C123ABCDEF",
328
+ channelTs: "1710000000.000300",
329
+ eventKind: "message",
330
+ }),
331
+ }),
332
+ createdAt: 1,
333
+ })
334
+ .run();
335
+
336
+ const body = callList({ conversationId: conv.id, page: "latest" });
337
+
338
+ expect(body.messages[0].slackMessage?.sender).toEqual({
339
+ displayName: "Nova",
340
+ });
341
+ });
342
+
343
+ test("user Slack messages without stored sender do not use the assistant name", () => {
344
+ mockAssistantName = "Nova";
345
+ const conv = createConversation();
346
+ const db = getDb();
347
+ db.insert(messages)
348
+ .values({
349
+ id: "msg-slack-user",
350
+ conversationId: conv.id,
351
+ role: "user",
352
+ content: JSON.stringify([{ type: "text", text: "Slack request" }]),
353
+ metadata: JSON.stringify({
354
+ slackMeta: writeSlackMetadata({
355
+ source: "slack",
356
+ channelId: "C123ABCDEF",
357
+ channelTs: "1710000000.000300",
358
+ eventKind: "message",
359
+ }),
360
+ }),
361
+ createdAt: 1,
362
+ })
363
+ .run();
364
+
365
+ const body = callList({ conversationId: conv.id, page: "latest" });
366
+
367
+ expect(body.messages[0].slackMessage?.sender).toBeUndefined();
368
+ });
369
+
252
370
  test("page=latest on unresolved conversationKey returns null metadata contract", () => {
253
371
  const body = callList({
254
372
  conversationKey: "no-such-key",
@@ -274,16 +392,14 @@ describe("handleListMessages page=latest", () => {
274
392
  const conv = createConversation();
275
393
 
276
394
  expect(() =>
277
- handleListMessages(
278
- { queryParams: { conversationId: conv.id, page: "invalid" } },
279
- null,
280
- ),
395
+ handleListMessages({
396
+ queryParams: { conversationId: conv.id, page: "invalid" },
397
+ }),
281
398
  ).toThrow(BadRequestError);
282
399
  expect(() =>
283
- handleListMessages(
284
- { queryParams: { conversationId: conv.id, page: "invalid" } },
285
- null,
286
- ),
400
+ handleListMessages({
401
+ queryParams: { conversationId: conv.id, page: "invalid" },
402
+ }),
287
403
  ).toThrow("page must be 'latest' when provided");
288
404
  });
289
405
 
@@ -91,7 +91,7 @@ describe("handleListMessages tool_result merging", () => {
91
91
  ]),
92
92
  );
93
93
 
94
- const response = handleListMessages(createTestArgs(conv.id), null);
94
+ const response = handleListMessages(createTestArgs(conv.id));
95
95
  const body = response as { messages: MessagePayload[] };
96
96
 
97
97
  // Should be 2 messages: user prompt + assistant (tool_result user msg suppressed)
@@ -137,7 +137,7 @@ describe("handleListMessages tool_result merging", () => {
137
137
  ]),
138
138
  );
139
139
 
140
- const response = handleListMessages(createTestArgs(conv.id), null);
140
+ const response = handleListMessages(createTestArgs(conv.id));
141
141
  const body = response as { messages: MessagePayload[] };
142
142
 
143
143
  expect(body.messages).toHaveLength(2);
@@ -167,7 +167,7 @@ describe("handleListMessages tool_result merging", () => {
167
167
  JSON.stringify([{ type: "text", text: "how are you?" }]),
168
168
  );
169
169
 
170
- const response = handleListMessages(createTestArgs(conv.id), null);
170
+ const response = handleListMessages(createTestArgs(conv.id));
171
171
  const body = response as { messages: MessagePayload[] };
172
172
 
173
173
  expect(body.messages).toHaveLength(3);
@@ -175,11 +175,15 @@ describe("handleListMessages tool_result merging", () => {
175
175
  expect(body.messages[2].content).toBe("how are you?");
176
176
  });
177
177
 
178
- test("tool_result at start of array (no preceding assistant) is preserved", async () => {
178
+ test("tool_result at start of array (no preceding assistant) is dropped", async () => {
179
179
  const conv = createConversation();
180
- // Orphan tool_result with no preceding assistant (pagination boundary).
181
- // The preceding assistant tool_use lives in the previous page dropping
182
- // the result would be unrecoverable, so it is kept as-is.
180
+ // Orphan tool_result with no preceding assistant. Without the parent
181
+ // tool_use we can't tell the user what tool ran, so the result is
182
+ // meaningless renderHistoryContent drops it rather than synthesizing
183
+ // a phantom "unknown" tool call. See shared.ts comment.
184
+ // The user message itself is preserved at the pagination boundary
185
+ // (mergeToolResultsIntoAssistantMessages keeps it to avoid data loss
186
+ // in case the matching tool_use lives on the previous page).
183
187
  await addMessage(
184
188
  conv.id,
185
189
  "user",
@@ -197,17 +201,14 @@ describe("handleListMessages tool_result merging", () => {
197
201
  JSON.stringify([{ type: "text", text: "response" }]),
198
202
  );
199
203
 
200
- const response = handleListMessages(createTestArgs(conv.id), null);
204
+ const response = handleListMessages(createTestArgs(conv.id));
201
205
  const body = response as { messages: MessagePayload[] };
202
206
 
203
- // Orphan tool_result is preserved (not suppressed) to avoid data loss
207
+ // Both messages exist user message preserved at pagination boundary,
208
+ // but the orphan tool_result is dropped (no phantom toolCalls)
204
209
  expect(body.messages).toHaveLength(2);
205
210
  expect(body.messages[0].role).toBe("user");
206
- // The preserved message must retain the actual tool_result payload
207
- const orphanToolCalls = body.messages[0].toolCalls;
208
- expect(orphanToolCalls).toBeDefined();
209
- expect(orphanToolCalls).toHaveLength(1);
210
- expect(orphanToolCalls![0].result).toBe("stale result");
211
+ expect(body.messages[0].toolCalls).toBeUndefined();
211
212
  expect(body.messages[1].role).toBe("assistant");
212
213
  expect(body.messages[1].content).toBe("response");
213
214
  });
@@ -262,7 +263,7 @@ describe("handleListMessages tool_result merging", () => {
262
263
  JSON.stringify([{ type: "text", text: "thanks" }]),
263
264
  );
264
265
 
265
- const response = handleListMessages(createTestArgs(conv.id), null);
266
+ const response = handleListMessages(createTestArgs(conv.id));
266
267
  const body = response as { messages: MessagePayload[] };
267
268
 
268
269
  // Consecutive assistant messages are merged at query time so the client
@@ -312,7 +313,7 @@ describe("handleListMessages tool_result merging", () => {
312
313
  ]),
313
314
  );
314
315
 
315
- const response = handleListMessages(createTestArgs(conv.id), null);
316
+ const response = handleListMessages(createTestArgs(conv.id));
316
317
  const body = response as { messages: MessagePayload[] };
317
318
 
318
319
  expect(body.messages).toHaveLength(2);