@circuitwall/jarela 2.3.0 → 2.5.0

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 (290) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +5 -5
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +4 -4
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.html +14 -2
  13. package/.next/standalone/.next/server/app/_not-found.rsc +16 -4
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -4
  15. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +16 -4
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/api/v1/access/[identity]/route.js +7 -0
  19. package/.next/standalone/.next/server/app/api/v1/access/route.js +7 -0
  20. package/.next/standalone/.next/server/app/api/v1/agents/[id]/compact/route.js +7 -0
  21. package/.next/standalone/.next/server/app/api/v1/agents/[id]/display-filters/route.js +17 -5
  22. package/.next/standalone/.next/server/app/api/v1/agents/[id]/route.js +7 -0
  23. package/.next/standalone/.next/server/app/api/v1/agents/[id]/thread/route.js +17 -5
  24. package/.next/standalone/.next/server/app/api/v1/agents/route.js +7 -0
  25. package/.next/standalone/.next/server/app/api/v1/allowed-sites/[hostname]/route.js +7 -0
  26. package/.next/standalone/.next/server/app/api/v1/allowed-sites/route.js +7 -0
  27. package/.next/standalone/.next/server/app/api/v1/artifacts/lifecycle/route.js +7 -0
  28. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +26 -32
  29. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/[remote_jid]/route.js +18 -7
  31. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/ignores/route.js +18 -7
  32. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +26 -32
  33. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +27 -33
  35. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +26 -32
  37. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/[route_id]/route.js +28 -12
  39. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/routes/route.js +28 -12
  40. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +26 -32
  41. package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js +9 -2
  43. package/.next/standalone/.next/server/app/api/v1/bridges/kinds/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/v1/bridges/route.js +19 -7
  45. package/.next/standalone/.next/server/app/api/v1/bridges/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/v1/dashboard/metrics/route.js +17 -5
  47. package/.next/standalone/.next/server/app/api/v1/dashboard/pricing/route.js +7 -0
  48. package/.next/standalone/.next/server/app/api/v1/dashboard/pricing-task/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/v1/documents/search/route.js +7 -0
  50. package/.next/standalone/.next/server/app/api/v1/documents/settings/route.js +7 -0
  51. package/.next/standalone/.next/server/app/api/v1/documents/sources/[id]/route.js +1 -1
  52. package/.next/standalone/.next/server/app/api/v1/documents/sources/route.js +1 -1
  53. package/.next/standalone/.next/server/app/api/v1/env-sync/allowlist/route.js +7 -0
  54. package/.next/standalone/.next/server/app/api/v1/env-sync/route.js +7 -0
  55. package/.next/standalone/.next/server/app/api/v1/events/route.js +26 -32
  56. package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/v1/extension/browser/activate/route.js +7 -0
  59. package/.next/standalone/.next/server/app/api/v1/extension/browser/foreground/route.js +7 -0
  60. package/.next/standalone/.next/server/app/api/v1/extension/browser/history/route.js +7 -0
  61. package/.next/standalone/.next/server/app/api/v1/extension/browser/poll/route.js +7 -0
  62. package/.next/standalone/.next/server/app/api/v1/extension/browser/progress/route.js +7 -0
  63. package/.next/standalone/.next/server/app/api/v1/extension/browser/result/route.js +7 -0
  64. package/.next/standalone/.next/server/app/api/v1/extension/browser/retry/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/v1/extension/browser/status/route.js +7 -0
  66. package/.next/standalone/.next/server/app/api/v1/extension/browser/tabs/route.js +7 -0
  67. package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js +1 -1
  68. package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js +1 -1
  69. package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js +1 -1
  70. package/.next/standalone/.next/server/app/api/v1/extensions/route.js +1 -1
  71. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/config/route.js +1 -1
  72. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/route.js +1 -1
  73. package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +1 -1
  74. package/.next/standalone/.next/server/app/api/v1/harnesses/[id]/route.js +7 -0
  75. package/.next/standalone/.next/server/app/api/v1/harnesses/default/route.js +7 -0
  76. package/.next/standalone/.next/server/app/api/v1/harnesses/route.js +7 -0
  77. package/.next/standalone/.next/server/app/api/v1/health/route.js +17 -5
  78. package/.next/standalone/.next/server/app/api/v1/integrations/[name]/route.js +7 -0
  79. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/callback/route.js +7 -0
  80. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/start/route.js +7 -0
  81. package/.next/standalone/.next/server/app/api/v1/integrations/gmail/oauth/status/route.js +7 -0
  82. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/callback/route.js +7 -0
  83. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/start/route.js +7 -0
  84. package/.next/standalone/.next/server/app/api/v1/integrations/outlook/oauth/status/route.js +7 -0
  85. package/.next/standalone/.next/server/app/api/v1/integrations/route.js +7 -0
  86. package/.next/standalone/.next/server/app/api/v1/lifecycle/adoption/route.js +17 -5
  87. package/.next/standalone/.next/server/app/api/v1/maps/embed/route.js +7 -0
  88. package/.next/standalone/.next/server/app/api/v1/mcp-servers/[name]/route.js +17 -5
  89. package/.next/standalone/.next/server/app/api/v1/mcp-servers/route.js +17 -5
  90. package/.next/standalone/.next/server/app/api/v1/memory/[namespace]/[key]/route.js +7 -0
  91. package/.next/standalone/.next/server/app/api/v1/memory/policy/route.js +7 -0
  92. package/.next/standalone/.next/server/app/api/v1/memory/route.js +7 -0
  93. package/.next/standalone/.next/server/app/api/v1/memory/search/route.js +7 -0
  94. package/.next/standalone/.next/server/app/api/v1/models/[name]/compact-threads/route.js +7 -0
  95. package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +7 -0
  96. package/.next/standalone/.next/server/app/api/v1/models/route.js +7 -0
  97. package/.next/standalone/.next/server/app/api/v1/packages/defaults/[id]/route.js +28 -4
  98. package/.next/standalone/.next/server/app/api/v1/packages/reload/route.js +28 -4
  99. package/.next/standalone/.next/server/app/api/v1/packages/route.js +28 -4
  100. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +1 -1
  101. package/.next/standalone/.next/server/app/api/v1/pending-actions/[id]/approve/route.js +27 -12
  102. package/.next/standalone/.next/server/app/api/v1/profile/location/route.js +7 -0
  103. package/.next/standalone/.next/server/app/api/v1/profile/route.js +7 -0
  104. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +585 -58
  105. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +7 -0
  106. package/.next/standalone/.next/server/app/api/v1/providers/github-copilot/auth/route.js +7 -0
  107. package/.next/standalone/.next/server/app/api/v1/providers/route.js +7 -0
  108. package/.next/standalone/.next/server/app/api/v1/proxy-config/route.js +7 -0
  109. package/.next/standalone/.next/server/app/api/v1/redaction/init/route.js +7 -0
  110. package/.next/standalone/.next/server/app/api/v1/redaction/route.js +7 -0
  111. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/route.js +17 -5
  112. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/[id]/run/route.js +1 -1
  113. package/.next/standalone/.next/server/app/api/v1/scheduled-tasks/route.js +7 -0
  114. package/.next/standalone/.next/server/app/api/v1/security/idle-timeout/route.js +7 -0
  115. package/.next/standalone/.next/server/app/api/v1/security/pin/route.js +7 -0
  116. package/.next/standalone/.next/server/app/api/v1/security/state/route.js +7 -0
  117. package/.next/standalone/.next/server/app/api/v1/security/unlock/route.js +7 -0
  118. package/.next/standalone/.next/server/app/api/v1/security/verify-pin/route.js +7 -0
  119. package/.next/standalone/.next/server/app/api/v1/skills/[id]/route.js +7 -0
  120. package/.next/standalone/.next/server/app/api/v1/skills/repos/[id]/route.js +7 -0
  121. package/.next/standalone/.next/server/app/api/v1/skills/repos/route.js +7 -0
  122. package/.next/standalone/.next/server/app/api/v1/skills/route.js +7 -0
  123. package/.next/standalone/.next/server/app/api/v1/tasks/[agent_id]/route.js +7 -0
  124. package/.next/standalone/.next/server/app/api/v1/tasks/route.js +7 -0
  125. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/activity/route.js +7 -0
  126. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/context-pin/route.js +12 -6
  127. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/route.js +125 -26
  128. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +1 -2
  129. package/.next/standalone/.next/server/app/api/v1/threads/route.js +17 -5
  130. package/.next/standalone/.next/server/app/api/v1/tools/route.js +1 -1
  131. package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/v1/voice/transcribe/route.js +17 -5
  133. package/.next/standalone/.next/server/app/api/v1/voice/tts/route.js +17 -5
  134. package/.next/standalone/.next/server/app/api/v1/watchers/[id]/route.js +17 -5
  135. package/.next/standalone/.next/server/app/api/v1/watchers/[id]/run/route.js +1 -1
  136. package/.next/standalone/.next/server/app/api/v1/watchers/route.js +1 -1
  137. package/.next/standalone/.next/server/app/page.js +27323 -27034
  138. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  139. package/.next/standalone/.next/server/app/setup/page.js +8 -0
  140. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  141. package/.next/standalone/.next/server/app-paths-manifest.json +5 -5
  142. package/.next/standalone/.next/server/chunks/1388.js +106 -36
  143. package/.next/standalone/.next/server/chunks/2068.js +47 -10
  144. package/.next/standalone/.next/server/chunks/210.js +6 -6
  145. package/.next/standalone/.next/server/chunks/2447.js +10 -5
  146. package/.next/standalone/.next/server/chunks/2653.js +80 -1
  147. package/.next/standalone/.next/server/chunks/3042.js +10 -8
  148. package/.next/standalone/.next/server/chunks/{4540.js → 309.js} +104 -38
  149. package/.next/standalone/.next/server/chunks/{2350.js → 3272.js} +104 -37
  150. package/.next/standalone/.next/server/chunks/3524.js +150 -67
  151. package/.next/standalone/.next/server/chunks/3566.js +1 -1
  152. package/.next/standalone/.next/server/chunks/4189.js +1 -1
  153. package/.next/standalone/.next/server/chunks/4596.js +28 -4
  154. package/.next/standalone/.next/server/chunks/46.js +91 -31
  155. package/.next/standalone/.next/server/chunks/466.js +53 -7
  156. package/.next/standalone/.next/server/chunks/4955.js +30 -3
  157. package/.next/standalone/.next/server/chunks/5585.js +28 -4
  158. package/.next/standalone/.next/server/chunks/5908.js +63 -42
  159. package/.next/standalone/.next/server/chunks/6142.js +10 -2
  160. package/.next/standalone/.next/server/chunks/8461.js +10 -8
  161. package/.next/standalone/.next/server/chunks/9032.js +5 -5
  162. package/.next/standalone/.next/server/chunks/9164.js +487 -177
  163. package/.next/standalone/.next/server/chunks/9304.js +51 -12
  164. package/.next/standalone/.next/server/chunks/{423.js → 9590.js} +84 -24
  165. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  166. package/.next/standalone/.next/server/middleware.js +89 -21
  167. package/.next/standalone/.next/server/pages/404.html +14 -2
  168. package/.next/standalone/.next/server/pages/500.html +1 -1
  169. package/.next/standalone/.next/server/preview-props.json +3 -3
  170. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  171. package/.next/standalone/.next/static/chunks/{1395-e4a99912b8f83044.js → 1395-bfe1f3ef760c0f50.js} +54 -8
  172. package/.next/standalone/.next/static/chunks/app/{layout-9bc0b10127cb7d42.js → layout-7b24ad13faa3944b.js} +69 -2
  173. package/.next/standalone/.next/static/chunks/app/{page-213a623b37226452.js → page-2ef8d75d40eb4311.js} +10643 -10295
  174. package/.next/standalone/.next/static/css/c160ea862133acd2.css +5 -0
  175. package/.next/standalone/docs/adoption/changesets.json +2805 -0
  176. package/.next/standalone/package.json +1 -1
  177. package/CHANGELOG.md +52 -0
  178. package/api/client.models.test.ts +10 -0
  179. package/api/client.startup.test.ts +129 -0
  180. package/api/client.ts +51 -9
  181. package/api/thread-metadata.test.ts +72 -0
  182. package/api/types.ts +6 -3
  183. package/app/api/v1/providers/[provider]/models/route.test.ts +216 -6
  184. package/app/api/v1/providers/[provider]/models/route.ts +259 -40
  185. package/app/api/v1/threads/[thread_id]/route.ts +26 -23
  186. package/app/api/v1/threads/[thread_id]/run/route.ts +0 -1
  187. package/app/globals.css +119 -29
  188. package/app/layout.tsx +12 -0
  189. package/components/agents/AgentsPanel.tsx +5 -13
  190. package/components/chat/ChatView.tsx +9 -18
  191. package/components/chat/InputBar.tsx +1 -1
  192. package/components/chat/MessageBubble.tsx +13 -1
  193. package/components/chat/MessageList.test.tsx +45 -4
  194. package/components/chat/MessageList.tsx +123 -89
  195. package/components/chat/ToolList.tsx +88 -84
  196. package/components/chat/useThreadActivity.test.tsx +2 -2
  197. package/components/chat/useThreadActivity.ts +2 -2
  198. package/components/chat/useThreadData.ts +1 -30
  199. package/components/chat/useThreadMetadataPolling.test.tsx +128 -0
  200. package/components/chat/useThreadMetadataPolling.ts +50 -0
  201. package/components/chat/useUserProfileAndAgent.test.tsx +31 -0
  202. package/components/chat/useUserProfileAndAgent.ts +13 -2
  203. package/components/dashboard/DashboardPanel.tsx +1 -1
  204. package/components/dashboard/Series.tsx +34 -21
  205. package/components/dashboard/SummaryCards.tsx +1 -1
  206. package/components/integrations/NetworkSection.tsx +2 -2
  207. package/components/layout/AppShell.tsx +22 -7
  208. package/components/layout/MenuPanel.tsx +14 -24
  209. package/components/profile/ProfilePanel.tsx +2 -2
  210. package/components/settings/AppearancePanel.tsx +70 -3
  211. package/components/settings/SettingsPanel.tsx +2 -2
  212. package/components/tools/ToolsPanel.tsx +2 -7
  213. package/components/ui/AppearancePreferences.test.tsx +68 -0
  214. package/components/ui/BootScreen.test.tsx +44 -0
  215. package/components/ui/BootScreen.tsx +24 -11
  216. package/components/ui/NotificationStatus.test.tsx +2 -2
  217. package/components/ui/NotificationStatus.tsx +5 -5
  218. package/components/ui/SubTabBar.tsx +2 -3
  219. package/contexts/ThemeContext.tsx +84 -1
  220. package/hooks/useAgentSession.test.ts +12 -1
  221. package/hooks/useAgentSession.ts +6 -4
  222. package/hooks/useUrlSync.test.ts +13 -0
  223. package/hooks/useUrlSync.ts +0 -4
  224. package/lib/agents/citation-checker.test.ts +40 -2
  225. package/lib/agents/citation-checker.ts +56 -11
  226. package/lib/agents/context-boundary.ts +5 -6
  227. package/lib/agents/instruction-edits.test.ts +120 -0
  228. package/lib/agents/instruction-edits.ts +65 -42
  229. package/lib/agents/prepare/request.ts +0 -2
  230. package/lib/agents/prepare/system-prompt.test.ts +10 -0
  231. package/lib/agents/prepare/system-prompt.ts +8 -2
  232. package/lib/agents/proposals.test.ts +76 -0
  233. package/lib/agents/proposals.ts +13 -5
  234. package/lib/agents/run-thread.auto-boundary.test.ts +125 -5
  235. package/lib/agents/run-thread.ts +24 -9
  236. package/lib/agents/sqlite-checkpoint-saver.test.ts +91 -0
  237. package/lib/agents/warm-summary-background.ts +22 -2
  238. package/lib/agents/warm-topics.test.ts +155 -1
  239. package/lib/agents/warm-topics.ts +27 -7
  240. package/lib/bridges/dispatcher.ts +1 -0
  241. package/lib/bridges/message-role.test.ts +81 -0
  242. package/lib/bridges/message-role.ts +23 -10
  243. package/lib/bridges/types.ts +6 -0
  244. package/lib/bridges/whatsapp-link.test.ts +32 -0
  245. package/lib/bridges/whatsapp-link.ts +21 -0
  246. package/lib/bridges/whatsapp.inbound.test.ts +239 -0
  247. package/lib/bridges/whatsapp.sender.test.ts +80 -1
  248. package/lib/bridges/whatsapp.ts +76 -38
  249. package/lib/db/migrations.test.ts +61 -0
  250. package/lib/db/migrations.ts +13 -0
  251. package/lib/embeddings/performance.test.ts +114 -0
  252. package/lib/pricing/snapshot.test.ts +70 -0
  253. package/lib/pricing/snapshot.ts +10 -2
  254. package/lib/proxy/dispatcher.test.ts +119 -1
  255. package/lib/proxy/dispatcher.ts +22 -9
  256. package/lib/stores/agent-configs.ts +10 -5
  257. package/lib/stores/bridges.test.ts +81 -0
  258. package/lib/stores/bridges.ts +9 -8
  259. package/lib/stores/dashboard-metrics-cache.test.ts +99 -0
  260. package/lib/stores/dashboard-metrics.ts +30 -4
  261. package/lib/stores/message-embedding-jobs.test.ts +27 -0
  262. package/lib/stores/message-embedding-jobs.ts +5 -3
  263. package/lib/stores/message-usage.test.ts +21 -1
  264. package/lib/stores/pricing-cache.test.ts +31 -0
  265. package/lib/stores/pricing.ts +3 -0
  266. package/lib/stores/proxy-config.test.ts +120 -0
  267. package/lib/stores/proxy-config.ts +22 -4
  268. package/lib/stores/threads.ts +12 -8
  269. package/lib/tools/communications/calendar.test.ts +43 -2
  270. package/lib/tools/communications/calendar.ts +2 -0
  271. package/lib/tools/communications/gmail.test.ts +51 -1
  272. package/lib/tools/communications/gmail.ts +44 -2
  273. package/lib/tools/communications/outlook-calendar.ts +2 -0
  274. package/lib/tools/communications/outlook.test.ts +33 -2
  275. package/lib/tools/communications/outlook.ts +2 -0
  276. package/lib/tools/filesystem/edit-match.test.ts +133 -0
  277. package/lib/tools/filesystem/edit-match.ts +7 -2
  278. package/lib/tools/packages/default-packages.test.ts +21 -0
  279. package/lib/tools/packages/optional-package.test.ts +72 -0
  280. package/lib/tools/packages/optional-package.ts +33 -4
  281. package/lib/tools/support/result-refs.test.ts +131 -0
  282. package/lib/tools/support/result-refs.ts +41 -9
  283. package/lib/ui/navigate.test.ts +13 -0
  284. package/lib/ui/navigate.ts +10 -2
  285. package/lib/utils/source-link.test.ts +51 -0
  286. package/lib/utils/source-link.ts +45 -0
  287. package/package.json +1 -1
  288. package/.next/standalone/.next/static/css/312542a5725ba0d5.css +0 -5
  289. /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → 544OFJV-q0RC6Gjgje4LT}/_buildManifest.js +0 -0
  290. /package/.next/standalone/.next/static/{bMRK5y3ruT7c3WtBfCQOG → 544OFJV-q0RC6Gjgje4LT}/_ssgManifest.js +0 -0
@@ -76,8 +76,9 @@ const ALL_FILTERS_SHOWN: Record<MessageFilterKey, boolean> = Object.fromEntries(
76
76
  MESSAGE_FILTER_KEYS.map((k) => [k, true]),
77
77
  ) as Record<MessageFilterKey, boolean>;
78
78
  const NO_CHANNEL_SUMMARIES: WarmChannelSummary[] = [];
79
+ const NOOP = () => {};
79
80
 
80
- export function MessageList({ threadId, messages, steeredSegments, agentConfig, userProfile, streamingContent, thinkingContent, toolEvents, hasMore, loadingMore, onLoadMore, queuedMessages, onRemoveQueued, hotSinceSeq, warmSummary, warmSummaryBeforeSeq, warmSummaryComputedAt, warmSummarySourceMessages, warmSummarySourceChars, warmSummaryTopics, warmChannelSummaries = NO_CHANNEL_SUMMARIES, warmSummaryPending = false, compactionPending = false, onSetContextPin, streaming, contextWindowTokens, onRetryMessage, filters = ALL_FILTERS_SHOWN, onToggleFilter: toggle = () => {}, onResetFilters: reset = () => {} }: Props) {
81
+ export function MessageList({ threadId, messages, steeredSegments, agentConfig, userProfile, streamingContent, thinkingContent, toolEvents, hasMore, loadingMore, onLoadMore, queuedMessages, onRemoveQueued, hotSinceSeq, warmSummary, warmSummaryBeforeSeq, warmSummaryComputedAt, warmSummarySourceMessages, warmSummarySourceChars, warmSummaryTopics, warmChannelSummaries = NO_CHANNEL_SUMMARIES, warmSummaryPending = false, compactionPending = false, onSetContextPin, streaming, contextWindowTokens, onRetryMessage, filters = ALL_FILTERS_SHOWN, onToggleFilter: toggle = NOOP, onResetFilters: reset = NOOP }: Props) {
81
82
  const hostRef = useRef<HTMLDivElement>(null);
82
83
  const maskRegionRef = useRef<HTMLDivElement>(null);
83
84
  const scrollRef = useRef<HTMLDivElement>(null);
@@ -102,7 +103,11 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
102
103
  const [pendingHotSinceSeq, setPendingHotSinceSeq] = useState<number | null>(null);
103
104
  const [topControlsOpen, setTopControlsOpen] = useState(false);
104
105
  const [summaryPopoverOpen, setSummaryPopoverOpen] = useState(false);
105
-
106
+ const hasStreamingContent = !!streamingContent;
107
+ const hasThinkingContent = !!thinkingContent;
108
+ const hasLiveToolEvents = !!toolEvents?.length;
109
+ const hidePersistedDraft = !!streaming || hasStreamingContent;
110
+
106
111
  const retryWorkflowStep = useMemo(
107
112
  () => onRetryMessage
108
113
  ? (request: WorkflowStepRetryRequest) => onRetryMessage(workflowStepRetryMessage(request), [])
@@ -116,7 +121,7 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
116
121
  // they're handled below by gating the inline ToolList / ThinkingLine.
117
122
  const visibleMessages = useMemo(() => {
118
123
  return messages.filter((m) => {
119
- if (m.role === "assistant" && m.transcript_status === "in_progress" && (streaming || !!streamingContent)) return false;
124
+ if (m.role === "assistant" && m.transcript_status === "in_progress" && hidePersistedDraft) return false;
120
125
  const cat = m.category;
121
126
  if (!cat) return true;
122
127
  if (cat === "scheduled_task") return filters.scheduled_task;
@@ -129,7 +134,53 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
129
134
  // never silently drop content the server thinks should be visible.
130
135
  return true;
131
136
  });
132
- }, [messages, streaming, streamingContent, filters.scheduled_task, filters.watcher, filters.bridge, filters.extension, filters.page_capture, filters.synthetic]);
137
+ }, [messages, hidePersistedDraft, filters.scheduled_task, filters.watcher, filters.bridge, filters.extension, filters.page_capture, filters.synthetic]);
138
+
139
+ const historyRows = useMemo(() => {
140
+ const attachedActivities = new Map<string, AutomationActivityMetadata>();
141
+ const hiddenActivityIds = new Set<string>();
142
+ for (let i = 0; i < visibleMessages.length - 1; i++) {
143
+ const activityMessage = visibleMessages[i];
144
+ const activity = activityMessage.metadata?.automation_activity;
145
+ if (!activity) continue;
146
+ const related = visibleMessages[i + 1];
147
+ const matches = !related.metadata?.automation_activity && (
148
+ activity.source_kind === "bridge"
149
+ ? related.role === "user"
150
+ && related.metadata?.bridge_conversation?.key === activity.source_id
151
+ : related.role === "assistant" && related.category === activity.source_kind
152
+ );
153
+ if (!matches) continue;
154
+ attachedActivities.set(related.id, activity);
155
+ hiddenActivityIds.add(activityMessage.id);
156
+ }
157
+ const segmentsById = new Map(steeredSegments?.map((segment) => [segment.id, segment]));
158
+ let previousRole: Message["role"] | null = null;
159
+ return visibleMessages.map((msg, i) => {
160
+ if (hiddenActivityIds.has(msg.id)) return null;
161
+ const startsTurn = previousRole == null || previousRole !== msg.role;
162
+ previousRole = msg.role;
163
+ const steeredSegment = segmentsById.get(msg.id);
164
+ return (
165
+ <HistoryRow
166
+ key={msg.id}
167
+ message={msg}
168
+ steeredSegment={steeredSegment}
169
+ segmentShowAvatar={i === 0 || visibleMessages[i - 1].role !== "assistant"}
170
+ attachedActivity={attachedActivities.get(msg.id)}
171
+ startsTurn={startsTurn}
172
+ separated={startsTurn && i > 0}
173
+ threadId={threadId ?? null}
174
+ agentConfig={agentConfig}
175
+ userProfile={userProfile}
176
+ showToolEvents={filters.tool_use}
177
+ contextWindowTokens={contextWindowTokens ?? null}
178
+ isLatest={i === visibleMessages.length - 1 && !hasStreamingContent}
179
+ onRetry={onRetryMessage}
180
+ />
181
+ );
182
+ });
183
+ }, [visibleMessages, steeredSegments, threadId, agentConfig, userProfile, filters.tool_use, contextWindowTokens, hasStreamingContent, onRetryMessage]);
133
184
 
134
185
  const hiddenCount = messages.length - visibleMessages.length;
135
186
  const effectiveHotSinceSeq = hotSinceSeq ?? null;
@@ -536,7 +587,7 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
536
587
  // perceive this as "chat is empty". Auto-reset once per thread snapshot so
537
588
  // content remains visible without requiring manual recovery.
538
589
  useEffect(() => {
539
- if (messages.length === 0 || streamingContent) return;
590
+ if (messages.length === 0 || hasStreamingContent) return;
540
591
  if (visibleMessages.length > 0) return;
541
592
  if (hiddenCount !== messages.length) return;
542
593
  const lastId = messages[messages.length - 1]?.id ?? "none";
@@ -544,7 +595,7 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
544
595
  if (autoRecoveredRef.current === key) return;
545
596
  autoRecoveredRef.current = key;
546
597
  reset();
547
- }, [threadId, messages, visibleMessages.length, hiddenCount, streamingContent, reset]);
598
+ }, [threadId, messages, visibleMessages.length, hiddenCount, hasStreamingContent, reset]);
548
599
 
549
600
  // Surface a category chip in the toolbar only if it can actually do
550
601
  // something useful right now: scheduled_task/bridge/synthetic chips
@@ -563,10 +614,10 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
563
614
  else if (m.category === "synthetic") set.add("synthetic");
564
615
  if (m.tool_events && m.tool_events.length > 0) set.add("tool_use");
565
616
  }
566
- if (thinkingContent) set.add("thinking");
567
- if (toolEvents && toolEvents.length > 0) set.add("tool_use");
617
+ if (hasThinkingContent) set.add("thinking");
618
+ if (hasLiveToolEvents) set.add("tool_use");
568
619
  return MESSAGE_FILTER_KEYS.filter((k) => set.has(k));
569
- }, [messages, thinkingContent, toolEvents]);
620
+ }, [messages, hasThinkingContent, hasLiveToolEvents]);
570
621
 
571
622
  // Tracks whether the user was at the bottom on the most recent scroll event.
572
623
  // After every render, if true, we snap to bottom — which means: while the
@@ -920,30 +971,6 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
920
971
  // "+ N more we don't know about yet" — older pages are likely unloaded.
921
972
  const olderCountLabel = olderInVisible + (hasMore ? 1 : 0);
922
973
 
923
- const attachedActivities = new Map<string, AutomationActivityMetadata>();
924
- const hiddenActivityIds = new Set<string>();
925
- for (let i = 0; i < visibleMessages.length - 1; i++) {
926
- const activityMessage = visibleMessages[i];
927
- const activity = activityMessage.metadata?.automation_activity;
928
- if (!activity) continue;
929
- const related = visibleMessages[i + 1];
930
- const matches = !related.metadata?.automation_activity && (
931
- activity.source_kind === "bridge"
932
- ? related.role === "user"
933
- && related.metadata?.bridge_conversation?.key === activity.source_id
934
- : related.role === "assistant" && related.category === activity.source_kind
935
- );
936
- if (!matches) continue;
937
- attachedActivities.set(related.id, activity);
938
- hiddenActivityIds.add(activityMessage.id);
939
- }
940
- const previousVisibleRoles = new Map<string, Message["role"] | null>();
941
- let previousVisibleRole: Message["role"] | null = null;
942
- for (const message of visibleMessages) {
943
- previousVisibleRoles.set(message.id, previousVisibleRole);
944
- if (!hiddenActivityIds.has(message.id)) previousVisibleRole = message.role;
945
- }
946
-
947
974
  const renderBoundary = (key: string) => (
948
975
  <div
949
976
  key={key}
@@ -969,54 +996,12 @@ export function MessageList({ threadId, messages, steeredSegments, agentConfig,
969
996
  </div>
970
997
  );
971
998
 
972
- const nodes = visibleMessages.flatMap((msg, i) => {
973
- const out = [] as React.ReactNode[];
974
- if (hasBoundary && !pinAfterAll && i === boundaryIndex) {
975
- out.push(renderBoundary(`boundary-${msg.id}`));
976
- }
977
- if (hiddenActivityIds.has(msg.id)) return out;
978
- const previousRole = previousVisibleRoles.get(msg.id);
979
- const startsTurn = previousRole == null || previousRole !== msg.role;
980
- const steeredSegment = steeredSegments?.find((segment) => segment.id === msg.id);
981
- if (steeredSegment) {
982
- out.push(
983
- <StreamingBubble
984
- key={`steered-${steeredSegment.id}`}
985
- content={steeredSegment.content}
986
- threadId={threadId ?? null}
987
- agentConfig={agentConfig ?? null}
988
- showAvatar={i === 0 || visibleMessages[i - 1].role !== "assistant"}
989
- />,
990
- );
991
- }
992
- out.push(
993
- <div
994
- key={msg.id}
995
- id={typeof msg.seq === "number" ? String(msg.seq) : `msg-${msg.id}`}
996
- data-message-id={msg.id}
997
- data-hot-candidate="1"
998
- data-message-seq={msg.seq}
999
- data-created-at={msg.created_at}
1000
- className={startsTurn && i > 0 ? "mt-3" : undefined}
1001
- >
1002
- <MessageBubble
1003
- message={msg}
1004
- attachedActivity={attachedActivities.get(msg.id)}
1005
- threadId={threadId ?? null}
1006
- agentConfig={agentConfig}
1007
- userProfile={userProfile}
1008
- showAvatar={startsTurn}
1009
- showToolEvents={filters.tool_use}
1010
- contextWindowTokens={contextWindowTokens ?? null}
1011
- isLatest={i === visibleMessages.length - 1 && !streamingContent}
1012
- onRetry={onRetryMessage}
1013
- />
1014
- </div>,
1015
- );
1016
- return out;
1017
- });
1018
- if (hasBoundary && pinAfterAll) nodes.push(renderBoundary("boundary-tail"));
1019
- return nodes;
999
+ if (!hasBoundary) return historyRows;
1000
+ return [
1001
+ ...historyRows.slice(0, boundaryIndex),
1002
+ renderBoundary(pinAfterAll ? "boundary-tail" : `boundary-${visibleMessages[boundaryIndex].id}`),
1003
+ ...historyRows.slice(boundaryIndex),
1004
+ ];
1020
1005
  })()}
1021
1006
  <LiveTurnActivity
1022
1007
  thinkingContent={thinkingContent}
@@ -1225,8 +1210,8 @@ function LiveTurnActivity({
1225
1210
  if (!showThinking && !streamingContent && !showTools && !showDots) return null;
1226
1211
  return (
1227
1212
  <div data-testid="live-turn-activity" className="mt-1 flex w-full min-w-0 flex-col gap-1">
1228
- {showDots && <ThinkingDots />}
1229
- {showThinking && <ThinkingLine text={thinkingContent} />}
1213
+ {showDots && <div className="min-w-0 pl-9"><ThinkingDots /></div>}
1214
+ {showThinking && <div className="min-w-0 pl-9"><ThinkingLine text={thinkingContent} /></div>}
1230
1215
  {streamingContent && (
1231
1216
  <StreamingBubble
1232
1217
  content={streamingContent}
@@ -1235,16 +1220,65 @@ function LiveTurnActivity({
1235
1220
  showAvatar={showStreamingAvatar}
1236
1221
  />
1237
1222
  )}
1238
- {showTools && <ToolList events={toolEvents} />}
1223
+ {showTools && <div className="min-w-0 pl-9"><ToolList events={toolEvents} /></div>}
1239
1224
  </div>
1240
1225
  );
1241
1226
  }
1242
1227
 
1243
- // Streaming-bubble wrapper. Wraps the in-flight assistant bubble so the
1244
- // `message` object passed to MessageBubble keeps a stable identity across
1245
- // renders that don't actually change the streaming text — without this
1246
- // the inline `{ role, content, streaming: true }` literal in the parent
1247
- // JSX would defeat MessageBubble's React.memo on every parent re-render.
1228
+ const HistoryRow = memo(function HistoryRow({
1229
+ message, steeredSegment, segmentShowAvatar, attachedActivity, startsTurn, separated,
1230
+ threadId, agentConfig, userProfile, showToolEvents, contextWindowTokens, isLatest, onRetry,
1231
+ }: {
1232
+ message: Message;
1233
+ steeredSegment?: SteeredSegment;
1234
+ segmentShowAvatar: boolean;
1235
+ attachedActivity?: AutomationActivityMetadata;
1236
+ startsTurn: boolean;
1237
+ separated: boolean;
1238
+ threadId: string | null;
1239
+ agentConfig?: AgentConfig | null;
1240
+ userProfile?: UserProfile | null;
1241
+ showToolEvents: boolean;
1242
+ contextWindowTokens: number | null;
1243
+ isLatest: boolean;
1244
+ onRetry?: Props["onRetryMessage"];
1245
+ }) {
1246
+ return (
1247
+ <>
1248
+ {steeredSegment && (
1249
+ <StreamingBubble
1250
+ content={steeredSegment.content}
1251
+ threadId={threadId}
1252
+ agentConfig={agentConfig ?? null}
1253
+ showAvatar={segmentShowAvatar}
1254
+ />
1255
+ )}
1256
+ <div
1257
+ id={typeof message.seq === "number" ? String(message.seq) : `msg-${message.id}`}
1258
+ data-message-id={message.id}
1259
+ data-hot-candidate="1"
1260
+ data-message-seq={message.seq}
1261
+ data-created-at={message.created_at}
1262
+ className={separated ? "mt-3" : undefined}
1263
+ >
1264
+ <MessageBubble
1265
+ message={message}
1266
+ attachedActivity={attachedActivity}
1267
+ threadId={threadId}
1268
+ agentConfig={agentConfig}
1269
+ userProfile={userProfile}
1270
+ showAvatar={startsTurn}
1271
+ showToolEvents={showToolEvents}
1272
+ contextWindowTokens={contextWindowTokens}
1273
+ isLatest={isLatest}
1274
+ onRetry={onRetry}
1275
+ />
1276
+ </div>
1277
+ </>
1278
+ );
1279
+ });
1280
+
1281
+ // Keep the synthetic message identity stable when only the parent updates.
1248
1282
  const StreamingBubble = memo(function StreamingBubble({
1249
1283
  content,
1250
1284
  threadId,
@@ -434,83 +434,87 @@ function ToolCallCard({ group, startedAt }: { group: ToolCallGroup; startedAt: n
434
434
  const transcriptSteps = group.steps.length > 0 ? group.steps : delegateTranscript?.steps ?? stepsFromResult(displayedResult);
435
435
  return (
436
436
  <div className="min-w-0 max-w-full">
437
- <MetaRow fullWidth onClick={() => setOpen((v) => !v)} expanded={open}>
438
- <CollapseChevron open={open} size={9} className="text-fg-faint/70" />
439
- <ToolIcon toolName={group.name} />
440
- <span className="font-medium text-fg-muted shrink-0">{group.name}</span>
441
- <span
442
- className="relative truncate text-fg-faint font-normal flex-1 min-w-0"
443
- title={summaryTitle || undefined}
444
- style={{
445
- // Fade the tail of the args summary into the surface so the
446
- // status pills + progress bar on the right stay legible even
447
- // when the args fill the available space.
448
- WebkitMaskImage:
449
- "linear-gradient(to right, black 0, black calc(100% - 28px), transparent 100%)",
450
- maskImage:
451
- "linear-gradient(to right, black 0, black calc(100% - 28px), transparent 100%)",
452
- }}
453
- >
454
- {summary}
455
- </span>
456
- {group.status === "async" && (
457
- <Badge
458
- tone="info"
459
- className="uppercase tracking-wide"
460
- title="running in the background"
461
- >
462
- bg
463
- </Badge>
464
- )}
465
- {codexJob?.status === "running" && (
466
- <Badge tone="info" className="uppercase tracking-wide" title="Codex job is updating live">
467
- live
468
- </Badge>
469
- )}
470
- {codexJob?.status === "running" && (
471
- <button
472
- type="button"
473
- onClick={(event) => { event.stopPropagation(); void cancelCodexJob(); }}
474
- className="control-tap shrink-0 text-fg-faint hover:text-rose-600 dark:hover:text-rose-300"
475
- title="Cancel Codex task"
476
- aria-label="Cancel Codex task"
477
- >
478
- <X size={12} />
479
- </button>
480
- )}
481
- {group.readByAgent && (
482
- <Badge
483
- tone="neutral"
484
- className="uppercase tracking-wide"
485
- title="agent read the background result"
486
- >
487
- read
488
- </Badge>
489
- )}
490
- {isToolResultRefEnvelope(group.result) && (
491
- <Badge
492
- tone="info"
493
- className="uppercase tracking-wide"
494
- title="large result is shown as a preview with a file reference"
495
- >
496
- preview
497
- </Badge>
498
- )}
499
- {delegateTranscript?.awaitingUserAnswers && (
500
- <Badge
501
- tone="warning"
502
- className="uppercase tracking-wide"
503
- title="Claude asked design questions and is waiting for the user"
437
+ <MetaRow fullWidth onClick={() => setOpen((v) => !v)} expanded={open} className="flex-wrap">
438
+ <span className="flex min-w-0 flex-1 basis-40 items-center gap-1.5">
439
+ <CollapseChevron open={open} size={9} className="text-fg-faint/70" />
440
+ <ToolIcon toolName={group.name} />
441
+ <span className="min-w-0 max-w-[50%] truncate font-medium text-fg-muted" title={group.name}>{group.name}</span>
442
+ <span
443
+ className="relative truncate text-fg-faint font-normal flex-1 min-w-0"
444
+ title={summaryTitle || undefined}
445
+ style={{
446
+ // Fade the tail of the args summary into the surface so the
447
+ // status pills + progress bar on the right stay legible even
448
+ // when the args fill the available space.
449
+ WebkitMaskImage:
450
+ "linear-gradient(to right, black 0, black calc(100% - 28px), transparent 100%)",
451
+ maskImage:
452
+ "linear-gradient(to right, black 0, black calc(100% - 28px), transparent 100%)",
453
+ }}
504
454
  >
505
- asks
506
- </Badge>
507
- )}
508
- <StatusIndicator
509
- status={group.status}
510
- deadlineMs={group.deadlineMs}
511
- startedAt={startedAt}
512
- stepCount={transcriptSteps.length}
513
- />
455
+ {summary}
456
+ </span>
457
+ </span>
458
+ <span className="ml-auto flex max-w-full flex-wrap items-center gap-1.5">
459
+ {group.status === "async" && (
460
+ <Badge
461
+ tone="info"
462
+ className="uppercase tracking-wide"
463
+ title="running in the background"
464
+ >
465
+ bg
466
+ </Badge>
467
+ )}
468
+ {codexJob?.status === "running" && (
469
+ <Badge tone="info" className="uppercase tracking-wide" title="Codex job is updating live">
470
+ live
471
+ </Badge>
472
+ )}
473
+ {codexJob?.status === "running" && (
474
+ <button
475
+ type="button"
476
+ onClick={(event) => { event.stopPropagation(); void cancelCodexJob(); }}
477
+ className="control-tap shrink-0 text-fg-faint hover:text-rose-600 dark:hover:text-rose-300"
478
+ title="Cancel Codex task"
479
+ aria-label="Cancel Codex task"
480
+ >
481
+ <X size={12} />
482
+ </button>
483
+ )}
484
+ {group.readByAgent && (
485
+ <Badge
486
+ tone="neutral"
487
+ className="uppercase tracking-wide"
488
+ title="agent read the background result"
489
+ >
490
+ read
491
+ </Badge>
492
+ )}
493
+ {isToolResultRefEnvelope(group.result) && (
494
+ <Badge
495
+ tone="info"
496
+ className="uppercase tracking-wide"
497
+ title="large result is shown as a preview with a file reference"
498
+ >
499
+ preview
500
+ </Badge>
501
+ )}
502
+ {delegateTranscript?.awaitingUserAnswers && (
503
+ <Badge
504
+ tone="warning"
505
+ className="uppercase tracking-wide"
506
+ title="Claude asked design questions and is waiting for the user"
507
+ >
508
+ asks
509
+ </Badge>
510
+ )}
511
+ <StatusIndicator
512
+ status={group.status}
513
+ deadlineMs={group.deadlineMs}
514
+ startedAt={startedAt}
515
+ stepCount={transcriptSteps.length}
516
+ />
517
+ </span>
514
518
  </MetaRow>
515
519
  {delegateTranscript ? (
516
520
  <DelegateTurnCards
@@ -526,7 +530,7 @@ function ToolCallCard({ group, startedAt }: { group: ToolCallGroup; startedAt: n
526
530
  <LiveTranscript steps={transcriptSteps} />
527
531
  )}
528
532
  {open && (
529
- <div className="mt-0.5 rounded border border-border/40 bg-surface-2/30 px-2 py-1.5 space-y-1.5 text-[10px]">
533
+ <div className="mt-0.5 min-w-0 rounded border border-border/40 bg-surface-2/30 px-2 py-1.5 space-y-1.5 text-[10px] [overflow-wrap:anywhere]">
530
534
  <WorkflowProgressDetails group={group} />
531
535
  {!hasWorkflowView(group) && (<>
532
536
  {hasVisibleArgs(group.args) ? (
@@ -1073,16 +1077,16 @@ function DetailSection({
1073
1077
  <div className="min-w-0">
1074
1078
  <div className="text-[10px] uppercase tracking-wide text-fg-faint mb-0.5">{label}</div>
1075
1079
  {kvEntries ? (
1076
- <dl className={`grid grid-cols-[auto_minmax(0,1fr)] gap-x-2 gap-y-0.5 text-[11px] font-mono ${toneClass}`}>
1080
+ <dl className={`grid grid-cols-[fit-content(35%)_minmax(0,1fr)] gap-x-2 gap-y-0.5 text-[11px] font-mono ${toneClass}`}>
1077
1081
  {kvEntries.map(([k, v]) => (
1078
1082
  <div key={k} className="contents">
1079
- <dt className="text-fg-faint">{k}</dt>
1080
- <dd className="min-w-0 break-words whitespace-pre-wrap">{renderKvValue(v)}</dd>
1083
+ <dt className="min-w-0 text-fg-faint">{k}</dt>
1084
+ <dd className="min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere]">{renderKvValue(v)}</dd>
1081
1085
  </div>
1082
1086
  ))}
1083
1087
  </dl>
1084
1088
  ) : (
1085
- <pre className={`text-[11px] font-mono whitespace-pre-wrap break-words max-w-full ${toneClass}`}>
1089
+ <pre className={`text-[11px] font-mono whitespace-pre-wrap [overflow-wrap:anywhere] max-w-full ${toneClass}`}>
1086
1090
  {formatPayload(value)}
1087
1091
  </pre>
1088
1092
  )}
@@ -1198,11 +1202,11 @@ function renderKvValue(v: unknown): ReactNode {
1198
1202
  const entries = Object.entries(obj).filter(([k]) => !HIDDEN_ARG_KEYS.has(k));
1199
1203
  if (entries.length === 0) return <span className="text-fg-faint">{"{}"}</span>;
1200
1204
  return (
1201
- <dl className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-2 gap-y-0.5">
1205
+ <dl className="grid grid-cols-[fit-content(35%)_minmax(0,1fr)] gap-x-2 gap-y-0.5">
1202
1206
  {entries.map(([k, vv]) => (
1203
1207
  <div key={k} className="contents">
1204
- <dt className="text-fg-faint">{k}</dt>
1205
- <dd className="min-w-0 break-words whitespace-pre-wrap">{renderKvValue(vv)}</dd>
1208
+ <dt className="min-w-0 text-fg-faint">{k}</dt>
1209
+ <dd className="min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere]">{renderKvValue(vv)}</dd>
1206
1210
  </div>
1207
1211
  ))}
1208
1212
  </dl>
@@ -9,7 +9,7 @@ vi.mock("@/api/client", () => ({
9
9
  api: {
10
10
  threads: {
11
11
  activity: (...args: unknown[]) => mocks.activity(...args),
12
- get: (...args: unknown[]) => mocks.getThread(...args),
12
+ metadata: (...args: unknown[]) => mocks.getThread(...args),
13
13
  },
14
14
  },
15
15
  }));
@@ -87,7 +87,7 @@ describe("useThreadActivity", () => {
87
87
  expect(meta.setCompactionPending).toHaveBeenCalledWith(true);
88
88
  expect(meta.setManualCompactionPending).toHaveBeenCalledWith(true);
89
89
  await tick(1_500);
90
- expect(mocks.getThread).toHaveBeenCalledWith("t1", { limit: 1 });
90
+ expect(mocks.getThread).toHaveBeenCalledWith("t1");
91
91
  expect(meta.setManualCompactionPending).toHaveBeenLastCalledWith(false);
92
92
  });
93
93
 
@@ -73,9 +73,9 @@ export function useThreadActivity({ threadId, attach, isLive, lastSettledAt, met
73
73
  // pull the new boundary and recap.
74
74
  d.metaApplier.setCompactionPending?.(false);
75
75
  d.metaApplier.setManualCompactionPending?.(false);
76
- api.threads.get(threadId, { limit: 1 })
76
+ api.threads.metadata(threadId)
77
77
  .then((meta) => { if (!cancelled) applyThreadMeta(deps.current.metaApplier, meta); })
78
- .catch(() => { /* next poll retries */ });
78
+ .catch((err) => { if (!cancelled) console.error("activity metadata refresh failed", err); });
79
79
  }
80
80
 
81
81
  schedule(next.busy ? BUSY_POLL_MS : IDLE_POLL_MS);
@@ -110,7 +110,7 @@ export function useThreadData({ threadId, attach }: Params): ThreadDataApi {
110
110
  setCompactionPending(false);
111
111
  setManualCompactionPending(false);
112
112
  setContextWindowTokens(null);
113
- api.threads.get(threadId).then((d) => {
113
+ api.threads.get(threadId, { startup: true }).then((d) => {
114
114
  if (cancelled) return;
115
115
  setMessages(d.messages);
116
116
  setHasMore(d.has_more);
@@ -163,35 +163,6 @@ export function useThreadData({ threadId, attach }: Params): ThreadDataApi {
163
163
  }
164
164
  }, [threadId]);
165
165
 
166
- useEffect(() => {
167
- if (!threadId) return;
168
- const summaryFresh = hotSinceSeq !== null && warmSummaryBeforeSeq === hotSinceSeq;
169
- const summaryStale = warmSummaryPending && hotSinceSeq !== null && !summaryFresh;
170
- if (!summaryStale && !compactionPending && !manualCompactionPending) {
171
- if (warmSummaryPending && hotSinceSeq !== null && summaryFresh) setWarmSummaryPending(false);
172
- return;
173
- }
174
- let cancelled = false;
175
- const timer = window.setInterval(() => {
176
- void api.threads.get(threadId).then((d) => {
177
- if (cancelled) return;
178
- applyThreadMeta(metaApplier, d);
179
- const refreshedSummaryIsFresh = d.hot_since_seq != null
180
- && d.warm_summary_before_seq === d.hot_since_seq;
181
- if (!d.hot_since_seq || refreshedSummaryIsFresh) {
182
- setWarmSummaryPending(false);
183
- }
184
- }).catch((err) => {
185
- if (!cancelled) console.error("warm summary refresh poll failed", err);
186
- });
187
- }, 1600);
188
- return () => {
189
- cancelled = true;
190
- window.clearInterval(timer);
191
- };
192
- // eslint-disable-next-line react-hooks/exhaustive-deps
193
- }, [threadId, warmSummaryPending, compactionPending, manualCompactionPending, hotSince, hotSinceSeq, warmSummaryBefore, warmSummaryBeforeSeq]);
194
-
195
166
  const loadOlder = useCallback(async () => {
196
167
  if (!threadId || loadingMore || !hasMore || messages.length === 0) return;
197
168
  setLoadingMore(true);