@ruimte/agents-react 0.0.0 → 0.7.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 (312) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +142 -1
  3. package/dist/agents/AccountDot.d.ts +4 -0
  4. package/dist/agents/AccountDot.js +8 -0
  5. package/dist/agents/AgentIcon.d.ts +6 -0
  6. package/dist/agents/AgentIcon.js +20 -0
  7. package/dist/agents/ProviderLogo.d.ts +8 -0
  8. package/dist/agents/ProviderLogo.js +8 -0
  9. package/dist/agents/account-limits.d.ts +7 -0
  10. package/dist/agents/account-limits.js +26 -0
  11. package/dist/agents/accounts.d.ts +22 -0
  12. package/dist/agents/accounts.js +103 -0
  13. package/dist/agents/model-name.d.ts +6 -0
  14. package/dist/agents/model-name.js +44 -0
  15. package/dist/agents/provider-paths.d.ts +2 -0
  16. package/dist/agents/provider-paths.js +8 -0
  17. package/dist/agents/status-look.d.ts +13 -0
  18. package/dist/agents/status-look.js +22 -0
  19. package/dist/chat/account-choice.d.ts +11 -0
  20. package/dist/chat/account-choice.js +30 -0
  21. package/dist/chat/actions.d.ts +6 -0
  22. package/dist/chat/actions.js +64 -0
  23. package/dist/chat/activity.d.ts +5 -0
  24. package/dist/chat/activity.js +4 -0
  25. package/dist/chat/attachments.d.ts +23 -0
  26. package/dist/chat/attachments.js +70 -0
  27. package/dist/chat/bookmarks.d.ts +14 -0
  28. package/dist/chat/bookmarks.js +73 -0
  29. package/dist/chat/chat-client.d.ts +53 -0
  30. package/dist/chat/chat-client.js +232 -0
  31. package/dist/chat/chat-references.d.ts +5 -0
  32. package/dist/chat/chat-references.js +5 -0
  33. package/dist/chat/drafts.d.ts +29 -0
  34. package/dist/chat/drafts.js +104 -0
  35. package/dist/chat/forks.d.ts +2 -0
  36. package/dist/chat/forks.js +26 -0
  37. package/dist/chat/guards.d.ts +12 -0
  38. package/dist/chat/guards.js +63 -0
  39. package/dist/chat/logic/answers.d.ts +3 -0
  40. package/dist/chat/logic/answers.js +14 -0
  41. package/dist/chat/logic/bookmarks.d.ts +5 -0
  42. package/dist/chat/logic/bookmarks.js +31 -0
  43. package/dist/chat/logic/context-usage.d.ts +12 -0
  44. package/dist/chat/logic/context-usage.js +20 -0
  45. package/dist/chat/logic/fork.d.ts +44 -0
  46. package/dist/chat/logic/fork.js +138 -0
  47. package/dist/chat/logic/handback.d.ts +2 -0
  48. package/dist/chat/logic/handback.js +8 -0
  49. package/dist/chat/logic/json.d.ts +1 -0
  50. package/dist/chat/logic/json.js +2 -0
  51. package/dist/chat/logic/limit.d.ts +7 -0
  52. package/dist/chat/logic/limit.js +34 -0
  53. package/dist/chat/logic/resume-compaction.d.ts +14 -0
  54. package/dist/chat/logic/resume-compaction.js +47 -0
  55. package/dist/chat/logic/scrubber.d.ts +50 -0
  56. package/dist/chat/logic/scrubber.js +216 -0
  57. package/dist/chat/logic/timeline-copy.d.ts +4 -0
  58. package/dist/chat/logic/timeline-copy.js +51 -0
  59. package/dist/chat/logic/timeline-target.d.ts +12 -0
  60. package/dist/chat/logic/timeline-target.js +34 -0
  61. package/dist/chat/logic/timeline.d.ts +110 -0
  62. package/dist/chat/logic/timeline.js +339 -0
  63. package/dist/chat/logic/tool-catalog.d.ts +10 -0
  64. package/dist/chat/logic/tool-catalog.js +40 -0
  65. package/dist/chat/logic/tools.d.ts +15 -0
  66. package/dist/chat/logic/tools.js +79 -0
  67. package/dist/chat/logic/workflow.d.ts +14 -0
  68. package/dist/chat/logic/workflow.js +47 -0
  69. package/dist/chat/mentions.d.ts +46 -0
  70. package/dist/chat/mentions.js +128 -0
  71. package/dist/chat/persisted-json.d.ts +5 -0
  72. package/dist/chat/persisted-json.js +24 -0
  73. package/dist/chat/preferences.d.ts +23 -0
  74. package/dist/chat/preferences.js +110 -0
  75. package/dist/chat/quote.d.ts +2 -0
  76. package/dist/chat/quote.js +25 -0
  77. package/dist/chat/recent-messages.d.ts +17 -0
  78. package/dist/chat/recent-messages.js +28 -0
  79. package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
  80. package/dist/chat/resume-compaction-dismissals.js +14 -0
  81. package/dist/chat/runtime-modes.d.ts +4 -0
  82. package/dist/chat/runtime-modes.js +11 -0
  83. package/dist/chat/stash.d.ts +24 -0
  84. package/dist/chat/stash.js +88 -0
  85. package/dist/chat/subagent-conversation.d.ts +38 -0
  86. package/dist/chat/subagent-conversation.js +150 -0
  87. package/dist/chat/subagent-list.d.ts +16 -0
  88. package/dist/chat/subagent-list.js +76 -0
  89. package/dist/chat/subagent-support.d.ts +6 -0
  90. package/dist/chat/subagent-support.js +14 -0
  91. package/dist/chat/subagent-view.d.ts +33 -0
  92. package/dist/chat/subagent-view.js +65 -0
  93. package/dist/chat/thumbnails.d.ts +10 -0
  94. package/dist/chat/thumbnails.js +87 -0
  95. package/dist/chat/timeline-scroll.d.ts +16 -0
  96. package/dist/chat/timeline-scroll.js +100 -0
  97. package/dist/chat/ui/AccountPill.d.ts +3 -0
  98. package/dist/chat/ui/AccountPill.js +24 -0
  99. package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
  100. package/dist/chat/ui/BookmarkMarker.js +59 -0
  101. package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
  102. package/dist/chat/ui/BookmarkSubmenu.js +44 -0
  103. package/dist/chat/ui/ChatActivity.d.ts +3 -0
  104. package/dist/chat/ui/ChatActivity.js +87 -0
  105. package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
  106. package/dist/chat/ui/ChatReferenceChip.js +17 -0
  107. package/dist/chat/ui/CodeBlock.d.ts +4 -0
  108. package/dist/chat/ui/CodeBlock.js +130 -0
  109. package/dist/chat/ui/Composer.d.ts +14 -0
  110. package/dist/chat/ui/Composer.js +821 -0
  111. package/dist/chat/ui/ComposerInput.d.ts +28 -0
  112. package/dist/chat/ui/ComposerInput.js +118 -0
  113. package/dist/chat/ui/DiffPool.d.ts +5 -0
  114. package/dist/chat/ui/DiffPool.js +26 -0
  115. package/dist/chat/ui/EditDiff.d.ts +4 -0
  116. package/dist/chat/ui/EditDiff.js +21 -0
  117. package/dist/chat/ui/FadingWords.d.ts +3 -0
  118. package/dist/chat/ui/FadingWords.js +5 -0
  119. package/dist/chat/ui/ImageView.d.ts +6 -0
  120. package/dist/chat/ui/ImageView.js +85 -0
  121. package/dist/chat/ui/LimitState.d.ts +8 -0
  122. package/dist/chat/ui/LimitState.js +117 -0
  123. package/dist/chat/ui/Markdown.d.ts +18 -0
  124. package/dist/chat/ui/Markdown.js +158 -0
  125. package/dist/chat/ui/MessageActions.d.ts +9 -0
  126. package/dist/chat/ui/MessageActions.js +56 -0
  127. package/dist/chat/ui/Pickers.d.ts +15 -0
  128. package/dist/chat/ui/Pickers.js +110 -0
  129. package/dist/chat/ui/PromptComposer.d.ts +13 -0
  130. package/dist/chat/ui/PromptComposer.js +48 -0
  131. package/dist/chat/ui/QuoteButton.d.ts +7 -0
  132. package/dist/chat/ui/QuoteButton.js +76 -0
  133. package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
  134. package/dist/chat/ui/ResumeCompactionDock.js +15 -0
  135. package/dist/chat/ui/RunSettings.d.ts +25 -0
  136. package/dist/chat/ui/RunSettings.js +148 -0
  137. package/dist/chat/ui/Scrubber.d.ts +17 -0
  138. package/dist/chat/ui/Scrubber.js +121 -0
  139. package/dist/chat/ui/SubagentControls.d.ts +11 -0
  140. package/dist/chat/ui/SubagentControls.js +37 -0
  141. package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
  142. package/dist/chat/ui/SubagentStopButton.js +36 -0
  143. package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
  144. package/dist/chat/ui/SubagentTimeline.js +92 -0
  145. package/dist/chat/ui/Timeline.d.ts +5 -0
  146. package/dist/chat/ui/Timeline.js +318 -0
  147. package/dist/chat/ui/TimelineMenu.d.ts +12 -0
  148. package/dist/chat/ui/TimelineMenu.js +61 -0
  149. package/dist/chat/ui/UnifiedDiff.d.ts +8 -0
  150. package/dist/chat/ui/UnifiedDiff.js +51 -0
  151. package/dist/chat/ui/UploadThumb.d.ts +4 -0
  152. package/dist/chat/ui/UploadThumb.js +11 -0
  153. package/dist/chat/ui/chips.d.ts +4 -0
  154. package/dist/chat/ui/chips.js +11 -0
  155. package/dist/chat/ui/code-lines.d.ts +18 -0
  156. package/dist/chat/ui/code-lines.js +46 -0
  157. package/dist/chat/ui/code-streaming.d.ts +1 -0
  158. package/dist/chat/ui/code-streaming.js +3 -0
  159. package/dist/chat/ui/code-theme.d.ts +3 -0
  160. package/dist/chat/ui/code-theme.js +10 -0
  161. package/dist/chat/ui/composer/chips.d.ts +6 -0
  162. package/dist/chat/ui/composer/chips.js +29 -0
  163. package/dist/chat/ui/composer/editor.d.ts +11 -0
  164. package/dist/chat/ui/composer/editor.js +94 -0
  165. package/dist/chat/ui/composer/keys.d.ts +30 -0
  166. package/dist/chat/ui/composer/keys.js +98 -0
  167. package/dist/chat/ui/diff-theme.d.ts +2 -0
  168. package/dist/chat/ui/diff-theme.js +20 -0
  169. package/dist/chat/ui/file-links.d.ts +5 -0
  170. package/dist/chat/ui/file-links.js +18 -0
  171. package/dist/chat/ui/find-reveal.d.ts +7 -0
  172. package/dist/chat/ui/find-reveal.js +13 -0
  173. package/dist/chat/ui/icons.d.ts +4 -0
  174. package/dist/chat/ui/icons.js +20 -0
  175. package/dist/chat/ui/markdown-blocks.d.ts +6 -0
  176. package/dist/chat/ui/markdown-blocks.js +101 -0
  177. package/dist/chat/ui/quote-selection.d.ts +3 -0
  178. package/dist/chat/ui/quote-selection.js +44 -0
  179. package/dist/chat/ui/rehype-chips.d.ts +23 -0
  180. package/dist/chat/ui/rehype-chips.js +28 -0
  181. package/dist/chat/ui/rehype-fade.d.ts +23 -0
  182. package/dist/chat/ui/rehype-fade.js +30 -0
  183. package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
  184. package/dist/chat/ui/remark-html-as-text.js +24 -0
  185. package/dist/chat/ui/reveal.d.ts +13 -0
  186. package/dist/chat/ui/reveal.js +112 -0
  187. package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
  188. package/dist/chat/ui/rows/ForksRow.js +40 -0
  189. package/dist/chat/ui/rows/MessageRows.d.ts +36 -0
  190. package/dist/chat/ui/rows/MessageRows.js +190 -0
  191. package/dist/chat/ui/rows/Rows.d.ts +12 -0
  192. package/dist/chat/ui/rows/Rows.js +49 -0
  193. package/dist/chat/ui/rows/SubagentRow.d.ts +15 -0
  194. package/dist/chat/ui/rows/SubagentRow.js +98 -0
  195. package/dist/chat/ui/rows/WorkRows.d.ts +46 -0
  196. package/dist/chat/ui/rows/WorkRows.js +143 -0
  197. package/dist/chat/ui/rows/WorkflowRow.d.ts +7 -0
  198. package/dist/chat/ui/rows/WorkflowRow.js +37 -0
  199. package/dist/chat/ui/rows/row-rhythm.d.ts +3 -0
  200. package/dist/chat/ui/rows/row-rhythm.js +14 -0
  201. package/dist/chat/ui/useToggleSet.d.ts +6 -0
  202. package/dist/chat/ui/useToggleSet.js +25 -0
  203. package/dist/host.d.ts +167 -0
  204. package/dist/host.js +41 -0
  205. package/dist/lazy.d.ts +5 -0
  206. package/dist/lazy.js +35 -0
  207. package/dist/locales/en/agent-chat.json +417 -0
  208. package/dist/locales/en/agent-prompts.json +42 -0
  209. package/dist/locales/en/agent-providers.json +131 -0
  210. package/dist/locales/en/agent-usage.json +131 -0
  211. package/dist/locales/nl/agent-chat.json +417 -0
  212. package/dist/locales/nl/agent-prompts.json +42 -0
  213. package/dist/locales/nl/agent-providers.json +131 -0
  214. package/dist/locales/nl/agent-usage.json +131 -0
  215. package/dist/locales.d.ts +5 -0
  216. package/dist/locales.js +18 -0
  217. package/dist/mounted-registry.d.ts +7 -0
  218. package/dist/mounted-registry.js +39 -0
  219. package/dist/port-transport.d.ts +6 -0
  220. package/dist/port-transport.js +120 -0
  221. package/dist/prompts/logic/focus.d.ts +1 -0
  222. package/dist/prompts/logic/focus.js +17 -0
  223. package/dist/prompts/logic/keys.d.ts +42 -0
  224. package/dist/prompts/logic/keys.js +84 -0
  225. package/dist/prompts/logic/prompts.d.ts +34 -0
  226. package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
  227. package/dist/prompts/logic/prompts.fixtures.js +73 -0
  228. package/dist/prompts/logic/prompts.js +22 -0
  229. package/dist/prompts/logic/subjects.d.ts +49 -0
  230. package/dist/prompts/logic/subjects.js +56 -0
  231. package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
  232. package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
  233. package/dist/prompts/logic/usePromptSession.d.ts +24 -0
  234. package/dist/prompts/logic/usePromptSession.js +87 -0
  235. package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
  236. package/dist/prompts/ui/ApprovalBody.js +35 -0
  237. package/dist/prompts/ui/PromptActions.d.ts +26 -0
  238. package/dist/prompts/ui/PromptActions.js +19 -0
  239. package/dist/prompts/ui/PromptCard.d.ts +16 -0
  240. package/dist/prompts/ui/PromptCard.js +50 -0
  241. package/dist/prompts/ui/PromptView.d.ts +2 -0
  242. package/dist/prompts/ui/PromptView.js +47 -0
  243. package/dist/prompts/ui/QuestionBody.d.ts +9 -0
  244. package/dist/prompts/ui/QuestionBody.js +87 -0
  245. package/dist/providers/AccountColors.d.ts +5 -0
  246. package/dist/providers/AccountColors.js +8 -0
  247. package/dist/providers/AccountDetail.d.ts +12 -0
  248. package/dist/providers/AccountDetail.js +76 -0
  249. package/dist/providers/AccountVariables.d.ts +8 -0
  250. package/dist/providers/AccountVariables.js +48 -0
  251. package/dist/providers/AddAccountForm.d.ts +10 -0
  252. package/dist/providers/AddAccountForm.js +58 -0
  253. package/dist/providers/CliDetail.d.ts +11 -0
  254. package/dist/providers/CliDetail.js +62 -0
  255. package/dist/providers/ProvidersPane.d.ts +8 -0
  256. package/dist/providers/ProvidersPane.js +90 -0
  257. package/dist/providers/account-actions.d.ts +7 -0
  258. package/dist/providers/account-actions.js +53 -0
  259. package/dist/providers/parts.d.ts +9 -0
  260. package/dist/providers/parts.js +18 -0
  261. package/dist/providers/provider-abilities.d.ts +2 -0
  262. package/dist/providers/provider-abilities.js +7 -0
  263. package/dist/providers/variables.d.ts +13 -0
  264. package/dist/providers/variables.js +53 -0
  265. package/dist/scope.d.ts +11 -0
  266. package/dist/scope.js +10 -0
  267. package/dist/settings/sections.d.ts +13 -0
  268. package/dist/settings/sections.js +30 -0
  269. package/dist/state/chats.d.ts +32 -0
  270. package/dist/state/chats.js +124 -0
  271. package/dist/state/provider-accounts.d.ts +17 -0
  272. package/dist/state/provider-accounts.js +63 -0
  273. package/dist/state/providers.d.ts +17 -0
  274. package/dist/state/providers.js +29 -0
  275. package/dist/state/usage.d.ts +41 -0
  276. package/dist/state/usage.js +100 -0
  277. package/dist/theme.css +186 -0
  278. package/dist/transport.d.ts +24 -0
  279. package/dist/transport.js +16 -0
  280. package/dist/ui/PlainTextarea.d.ts +2 -0
  281. package/dist/ui/PlainTextarea.js +5 -0
  282. package/dist/usage/LimitsList.d.ts +18 -0
  283. package/dist/usage/LimitsList.js +69 -0
  284. package/dist/usage/UsageBreakdown.d.ts +9 -0
  285. package/dist/usage/UsageBreakdown.js +69 -0
  286. package/dist/usage/UsageChart.d.ts +10 -0
  287. package/dist/usage/UsageChart.js +49 -0
  288. package/dist/usage/UsageDialog.d.ts +6 -0
  289. package/dist/usage/UsageDialog.js +15 -0
  290. package/dist/usage/UsageLimits.d.ts +1 -0
  291. package/dist/usage/UsageLimits.js +62 -0
  292. package/dist/usage/UsageLimitsCard.d.ts +4 -0
  293. package/dist/usage/UsageLimitsCard.js +27 -0
  294. package/dist/usage/UsagePage.d.ts +7 -0
  295. package/dist/usage/UsagePage.js +161 -0
  296. package/dist/usage/UsagePane.d.ts +3 -0
  297. package/dist/usage/UsagePane.js +24 -0
  298. package/dist/usage/UsageSummary.d.ts +12 -0
  299. package/dist/usage/UsageSummary.js +15 -0
  300. package/dist/usage/UsageTiles.d.ts +7 -0
  301. package/dist/usage/UsageTiles.js +19 -0
  302. package/dist/usage/format.d.ts +14 -0
  303. package/dist/usage/format.js +50 -0
  304. package/dist/usage/limit-groups.d.ts +22 -0
  305. package/dist/usage/limit-groups.js +78 -0
  306. package/dist/usage/limits.d.ts +4 -0
  307. package/dist/usage/limits.js +58 -0
  308. package/dist/usage/money.d.ts +1 -0
  309. package/dist/usage/money.js +13 -0
  310. package/dist/usage/summary.d.ts +34 -0
  311. package/dist/usage/summary.js +90 -0
  312. package/package.json +639 -2
@@ -0,0 +1,112 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ /* How long a word takes to fade in. `.chat-fade` in styles.css carries the same number. */
3
+ export const REVEAL_FADE_MS = 300;
4
+ /* The time constant of the catch-up: after this long about two thirds of what was behind is on screen. */
5
+ export const REVEAL_TAU_MS = 250;
6
+ // Without a floor the last few characters of a gap crawl, since the exponential step shrinks with the gap.
7
+ export const REVEAL_MIN_CPS = 40;
8
+ // Once the item is done nothing more arrives to wait for, so the tail is closed faster.
9
+ export const REVEAL_FINISH_MIN_CPS = 120;
10
+ // A tab that comes back hands one frame the whole time it was away; it would show everything at once.
11
+ export const REVEAL_MAX_FRAME_MS = 100;
12
+ // A word this long (a URL, a line of CJK) would otherwise hold everything behind it until it ends.
13
+ export const REVEAL_MAX_HELD_WORD = 32;
14
+ // The span mounts a commit after the frame that grew the text, so its fade ends a little later than that frame plus the duration.
15
+ const FADE_SETTLE_MS = REVEAL_FADE_MS + 50;
16
+ const isSpace = (text, index) => index >= 0 && index < text.length && /\s/.test(text[index]);
17
+ /*
18
+ * Where the reveal stands `elapsedMs` after it stood at `position`, with `target` characters
19
+ * received. The exponential part composes over frames, so the pace does not depend on the refresh
20
+ * rate of the screen.
21
+ */
22
+ export const advanceReveal = (position, target, elapsedMs, finished) => {
23
+ if (position >= target) {
24
+ return target;
25
+ }
26
+ const elapsed = Math.min(Math.max(elapsedMs, 0), REVEAL_MAX_FRAME_MS);
27
+ const eased = (target - position) * (1 - Math.exp(-elapsed / REVEAL_TAU_MS));
28
+ const floor = ((finished ? REVEAL_FINISH_MIN_CPS : REVEAL_MIN_CPS) * elapsed) / 1000;
29
+ return Math.min(target, position + Math.max(eased, floor));
30
+ };
31
+ /*
32
+ * How much of `text` to draw for a reveal at `position`: back to the last word boundary, because a
33
+ * span that starts with half a word shows the rest of its letters at whatever opacity it already
34
+ * reached. The last word of a text that is still arriving may still grow, so it waits for the
35
+ * whitespace after it.
36
+ */
37
+ export const revealBoundary = (text, position, complete) => {
38
+ const end = Math.min(text.length, Math.max(0, Math.floor(position)));
39
+ if (end === text.length && complete) {
40
+ return end;
41
+ }
42
+ let cut = end;
43
+ while (cut > 0 && !isSpace(text, cut) && !isSpace(text, cut - 1)) {
44
+ cut -= 1;
45
+ }
46
+ if (end - cut <= REVEAL_MAX_HELD_WORD) {
47
+ return cut;
48
+ }
49
+ // Half of a surrogate pair renders as a replacement box.
50
+ const last = text.charCodeAt(end - 1);
51
+ return last >= 0xd800 && last <= 0xdbff ? end - 1 : end;
52
+ };
53
+ /*
54
+ * The part of a streaming text that is on screen, catching up with what arrived over time. It starts
55
+ * at whatever was there on mount, so reopening a thread mid-reply does not replay it, and a text that
56
+ * was already done on mount is shown whole at once.
57
+ */
58
+ export const useRevealedText = (text, streaming) => {
59
+ const [active, setActive] = useState(streaming);
60
+ const [shown, setShown] = useState(() => revealBoundary(text, text.length, !streaming));
61
+ const latest = useRef({ text, streaming });
62
+ const position = useRef(text.length);
63
+ const shownRef = useRef(shown);
64
+ const activeRef = useRef(active);
65
+ useEffect(() => {
66
+ latest.current = { text, streaming };
67
+ });
68
+ // Turning streaming on for a text that stood still (the setting flipped mid-reply) starts from what is there.
69
+ useEffect(() => {
70
+ if (!streaming || activeRef.current) {
71
+ return;
72
+ }
73
+ position.current = latest.current.text.length;
74
+ shownRef.current = revealBoundary(latest.current.text, position.current, false);
75
+ activeRef.current = true;
76
+ setShown(shownRef.current);
77
+ setActive(true);
78
+ }, [streaming]);
79
+ useEffect(() => {
80
+ if (!active) {
81
+ return;
82
+ }
83
+ let frame = 0;
84
+ let previous = null;
85
+ let grewAt = -Infinity;
86
+ const tick = (now) => {
87
+ const elapsed = previous === null ? 0 : now - previous;
88
+ previous = now;
89
+ const current = latest.current;
90
+ const finished = !current.streaming;
91
+ position.current = advanceReveal(position.current, current.text.length, elapsed, finished);
92
+ const length = revealBoundary(current.text, position.current, finished);
93
+ if (length !== shownRef.current) {
94
+ shownRef.current = length;
95
+ grewAt = now;
96
+ setShown(length);
97
+ }
98
+ if (finished && length >= current.text.length && now - grewAt >= FADE_SETTLE_MS) {
99
+ activeRef.current = false;
100
+ setActive(false);
101
+ return;
102
+ }
103
+ frame = requestAnimationFrame(tick);
104
+ };
105
+ frame = requestAnimationFrame(tick);
106
+ return () => cancelAnimationFrame(frame);
107
+ }, [active]);
108
+ if (!active) {
109
+ return { text, active: false };
110
+ }
111
+ return { text: text.slice(0, Math.min(shown, text.length)), active: true };
112
+ };
@@ -0,0 +1,4 @@
1
+ export declare function ForksRow({ chatId, turnId }: {
2
+ chatId: string;
3
+ turnId: string;
4
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,40 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Menu } from '@base-ui-components/react/menu';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ChevronRight, GitFork } from 'lucide-react';
5
+ import { useForkIdsAfter } from '../../forks';
6
+ import { ROW_GUTTER } from '../icons';
7
+ import { chatHost } from '../../../host';
8
+ import { Icon } from '@ruimte/ui/Icon';
9
+ import { MenuPopup } from '@ruimte/ui/MenuPopup';
10
+ const LINE = '-mx-1 mb-0.5 flex h-7 items-center gap-2 rounded-md px-1 text-xs text-text-muted';
11
+ const LINK = `${LINE} hover:bg-surface-hover hover:text-text data-[popup-open]:bg-surface-hover data-[popup-open]:text-text`;
12
+ /*
13
+ * Under a turn that forks went on after, the way over to them: one fork opens at once, more open a
14
+ * menu of them by name.
15
+ */
16
+ export function ForksRow({ chatId, turnId }) {
17
+ const { t } = useTranslation('agent-chat');
18
+ const forks = useForkIdsAfter(chatId, turnId);
19
+ const label = t('work.forksAfter', { count: forks.length });
20
+ if (forks.length === 0) {
21
+ return null;
22
+ }
23
+ if (forks.length === 1) {
24
+ return _jsx(OneFork, { forkId: forks[0], label: label });
25
+ }
26
+ return (_jsx("div", { className: "flex", children: _jsxs(Menu.Root, { children: [_jsx(Menu.Trigger, { className: LINK, children: _jsx(ForksLabel, { label: label }) }), _jsx(MenuPopup, { className: "min-w-48", children: forks.map((forkId) => (_jsx(ForkItem, { forkId: forkId }, forkId))) })] }) }));
27
+ }
28
+ function ForksLabel({ label, link = true }) {
29
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: GitFork, size: 12 }) }), label, link && _jsx(Icon, { icon: ChevronRight, size: 12 })] }));
30
+ }
31
+ // A fork no longer in this project has nowhere to lead, so it only says it is there.
32
+ function OneFork({ forkId, label }) {
33
+ const place = chatHost().useChatPlace(forkId);
34
+ return (_jsx("div", { className: "flex", children: place.title === null ? (_jsx("span", { className: LINE, children: _jsx(ForksLabel, { label: label, link: false }) })) : (_jsx("button", { className: LINK, onClick: place.go, children: _jsx(ForksLabel, { label: label }) })) }));
35
+ }
36
+ function ForkItem({ forkId }) {
37
+ const { t } = useTranslation('agent-chat');
38
+ const place = chatHost().useChatPlace(forkId);
39
+ return (_jsxs(Menu.Item, { className: "menu-item", disabled: place.title === null, onClick: place.go, children: [_jsx(Icon, { icon: GitFork, size: 14 }), " ", _jsx("span", { className: "truncate", children: place.title ?? t('fork.pill.label') })] }));
40
+ }
@@ -0,0 +1,36 @@
1
+ import type { ChatApprovalItem, ChatAssistantItem, ChatQuestionItem, ChatThinkingItem, ChatUserItem } from '@ruimte/agent-contracts';
2
+ export declare function UserRow({ chatId, item }: {
3
+ chatId: string;
4
+ item: ChatUserItem;
5
+ }): import("react").JSX.Element;
6
+ export declare function ReportRow({ id, text }: {
7
+ id: string;
8
+ text: string;
9
+ }): import("react").JSX.Element;
10
+ export declare function AssistantRow({ chatId, item: derived }: {
11
+ chatId: string;
12
+ item: ChatAssistantItem;
13
+ }): import("react").JSX.Element;
14
+ export declare function ThinkingRow({ chatId, item: derived }: {
15
+ chatId: string;
16
+ item: ChatThinkingItem;
17
+ }): import("react").JSX.Element;
18
+ export declare function NoteRow({ id, level, text, from }: {
19
+ id: string;
20
+ level: 'info' | 'warning' | 'error';
21
+ text: string;
22
+ from?: string;
23
+ }): import("react").JSX.Element;
24
+ export declare function AgentTurnRow({ label, onOpen }: {
25
+ label: string;
26
+ onOpen?: () => void;
27
+ }): import("react").JSX.Element;
28
+ export declare function CompactionRow({ preTokens }: {
29
+ preTokens: number | null;
30
+ }): import("react").JSX.Element;
31
+ export declare function ApprovalHistoryRow({ item }: {
32
+ item: ChatApprovalItem;
33
+ }): import("react").JSX.Element | null;
34
+ export declare function QuestionHistoryRow({ item }: {
35
+ item: ChatQuestionItem;
36
+ }): import("react").JSX.Element;
@@ -0,0 +1,190 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Bot, Brain, Check, ChevronDown, CircleAlert, File, Info, MessageCircleQuestionMark, TriangleAlert, X } from 'lucide-react';
6
+ import { fileBadge, formatBytes, isImageAttachment } from '../../attachments';
7
+ import { useChatRow } from '../../../state/chats';
8
+ import { chatHost } from '../../../host';
9
+ import { useChatScope } from '../../../scope';
10
+ import { useProviders } from '../../../state/providers';
11
+ import { ChatReferenceChip } from '../ChatReferenceChip';
12
+ import { ImageThumb } from '../ImageView';
13
+ import { MessageMarkdown, ReplyMarkdown } from '../Markdown';
14
+ import { FadingWords } from '../FadingWords';
15
+ import { settledBlocksText } from '../markdown-blocks';
16
+ import { WHOLE_FADE_CLASS } from '../rehype-fade';
17
+ import { useRevealedText } from '../reveal';
18
+ import { formatElapsedShort } from '@ruimte/ui/format/duration';
19
+ import { formatTokens } from '@ruimte/ui/format/number';
20
+ import { toolSummary } from '../../logic/tools';
21
+ import { ROW_GUTTER } from '../icons';
22
+ import { useOpenForFind } from '../find-reveal';
23
+ import { Icon } from '@ruimte/ui/Icon';
24
+ import { Tooltip } from '@ruimte/ui/Tooltip';
25
+ // A long prompt folds so the answer stays in view; the reader can open it.
26
+ const USER_FOLD_LINES = 8;
27
+ const USER_FOLD_CHARS = 600;
28
+ /* A folded user prompt fades out at the bottom instead of cutting a line in half. */
29
+ const FOLD = 'max-h-[10em] overflow-hidden [mask-image:linear-gradient(to_bottom,black_70%,transparent)]';
30
+ /*
31
+ * A file attached to a message that is not a picture. `download` is what saves a blob URL: the shell
32
+ * hands a link that opens a window to the system browser, which cannot reach a blob of this page.
33
+ */
34
+ function AttachmentLink({ chatId, attachment }) {
35
+ const { id } = useChatScope();
36
+ const { url } = chatHost().attachments.useUrl(id, chatId, attachment.id);
37
+ const badge = fileBadge(attachment.name);
38
+ return (_jsx(Tooltip, { label: attachment.name, children: _jsxs("a", { href: url ?? undefined, download: attachment.name, target: "_blank", rel: "noreferrer", "aria-disabled": url === null, className: "flex h-28 w-36 flex-col rounded-xl border border-border bg-surface-raised bg-clip-padding p-2.5 text-left hover:bg-surface-hover", children: [_jsx("span", { className: "flex h-6 min-w-6 items-center justify-center self-start rounded-md border border-border px-1.5 text-xs font-medium text-text-muted", children: badge ?? _jsx(Icon, { icon: File, size: 12 }) }), _jsx("span", { className: "mt-auto line-clamp-2 text-xs break-all text-text", children: attachment.name }), _jsx("span", { className: "text-xs text-text-faint", children: formatBytes(attachment.size) })] }) }));
39
+ }
40
+ /* A picture attached to a message, which opens large. */
41
+ function AttachmentThumb({ chatId, attachment }) {
42
+ const { id } = useChatScope();
43
+ const source = chatHost().attachments.useUrl(id, chatId, attachment.id);
44
+ return _jsx(ImageThumb, { source: source, alt: attachment.name, className: "max-h-32 max-w-48 object-cover" });
45
+ }
46
+ export function UserRow({ chatId, item }) {
47
+ const { t } = useTranslation('agent-chat');
48
+ const [open, setOpen] = useState(false);
49
+ const long = item.text.split('\n').length > USER_FOLD_LINES || item.text.length > USER_FOLD_CHARS;
50
+ const attachments = item.attachments ?? [];
51
+ const chats = item.chats ?? [];
52
+ useOpenForFind(item.id, 'text', setOpen);
53
+ return (_jsxs("div", { "data-find-item": item.id, className: "flex flex-col items-end", children: [_jsx(MessageHeading, { children: t('rows.user.heading') }), attachments.length > 0 && (_jsx("div", { className: "mb-1.5 flex max-w-[80%] flex-wrap justify-end gap-1.5", children: attachments.map((attachment) => isImageAttachment(attachment.mime) ? (_jsx(AttachmentThumb, { chatId: chatId, attachment: attachment }, attachment.id)) : (_jsx(AttachmentLink, { chatId: chatId, attachment: attachment }, attachment.id))) })), chats.length > 0 && (_jsx("div", { className: "mb-1.5 flex max-w-[80%] flex-wrap justify-end gap-1.5", children: chats.map((id) => (_jsx(ChatReferenceChip, { chatId: id }, id))) })), item.text !== '' && (_jsxs("div", { className: "relative max-w-[80%] rounded-2xl bg-surface-active px-3.5 py-2.5 text-sm text-text select-text", children: [_jsx("div", { "data-find-field": "text", className: clsx(long && !open && FOLD), children: _jsx(MessageMarkdown, { text: item.text, mentions: item.mentions, skills: item.skills }) }), long && (_jsxs("button", { className: "mt-1 flex items-center gap-1 text-xs text-text-muted hover:text-text", onClick: () => setOpen((o) => !o), children: [_jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('transition-transform', open && 'rotate-180') }), ' ', open ? t('rows.user.showLess') : t('rows.user.showAll')] }))] }))] }));
54
+ }
55
+ /*
56
+ * The item as the thread holds it now. The row was derived from the structure, which a delta leaves
57
+ * alone, so only this row renders again when a word arrives.
58
+ */
59
+ const useCurrentItem = (chatId, derived) => useChatRow(chatId, (row) => {
60
+ const item = row?.items[derived.id];
61
+ return item?.kind === derived.kind ? item : derived;
62
+ });
63
+ /*
64
+ * Who wrote a message, for a screen reader only: one heading per message is what lets VoiceOver's
65
+ * rotor walk a thread from message to message. `select-none`, so a selection copied across two
66
+ * messages does not pick the names up.
67
+ */
68
+ function MessageHeading({ children }) {
69
+ return _jsx("h3", { className: "sr-only select-none", children: children });
70
+ }
71
+ /* The report a subagent handed back, drawn as a reply under a label of its own. */
72
+ export function ReportRow({ id, text }) {
73
+ const { t } = useTranslation('agent-chat');
74
+ return (_jsxs("div", { "data-find-item": id, className: "-mx-1 px-1 pb-2", children: [_jsx(MessageHeading, { children: t('rows.report.heading') }), _jsx("div", { "aria-hidden": true, className: "mb-1 text-xs font-medium text-text-faint select-none", children: t('rows.report.heading') }), _jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(ReplyMarkdown, { text: text, streaming: false }) })] }));
75
+ }
76
+ /* The heading of a reply, named after the agent the chat runs. */
77
+ function ReplyHeading({ chatId }) {
78
+ const { t } = useTranslation('agent-chat');
79
+ const kind = useChatRow(chatId, (row) => row?.info.provider);
80
+ const name = useProviders((s) => s.providers.find((provider) => provider.kind === kind)?.name);
81
+ return _jsx(MessageHeading, { children: name ?? t('rows.reply.agent') });
82
+ }
83
+ /* The line that stands where the next words will land; a component, so the word is read at render and not at import. */
84
+ function Writing() {
85
+ const { t } = useTranslation('agent-chat');
86
+ return _jsx("span", { className: "chat-live-text text-sm", children: t('rows.reply.writing') });
87
+ }
88
+ /*
89
+ * A reply. A word at a time, it follows the text as it arrives. A block at a time, the blocks that
90
+ * closed are drawn and fade in as they land, with "Writing…" where the next one will stand. Whole,
91
+ * it says it is being written and fades in once it is done. Only a row that saw the reply being
92
+ * written fades anything: an old reply scrolled back into view just stands there.
93
+ */
94
+ export function AssistantRow({ chatId, item: derived }) {
95
+ const item = useCurrentItem(chatId, derived);
96
+ const mode = chatHost().useStreaming();
97
+ const [sawWriting] = useState(item.streaming);
98
+ const live = mode === 'words' && item.streaming;
99
+ const reveal = useRevealedText(item.text, live);
100
+ if (mode === 'blocks') {
101
+ const settled = item.streaming ? settledBlocksText(item.text) : item.text;
102
+ return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsx(ReplyHeading, { chatId: chatId }), settled !== '' && (_jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(ReplyMarkdown, { text: settled, streaming: false, arriving: sawWriting }) })), item.streaming && _jsx(Writing, {})] }));
103
+ }
104
+ if (mode === 'whole' && item.streaming) {
105
+ return (_jsxs("div", { className: "-mx-1 px-1 pb-2", children: [_jsx(ReplyHeading, { chatId: chatId }), _jsx(Writing, {})] }));
106
+ }
107
+ return (_jsxs("div", { "data-find-item": item.id, className: clsx('-mx-1 px-1 pb-2', mode === 'whole' && sawWriting && WHOLE_FADE_CLASS), children: [_jsx(ReplyHeading, { chatId: chatId }), _jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(ReplyMarkdown, { text: reveal.text, streaming: reveal.active }) }), live && item.text === '' && _jsx("span", { className: "inline-block h-3.5 w-1.5 animate-pulse rounded-sm bg-text-faint align-middle" })] }));
108
+ }
109
+ /*
110
+ * What the model thought before it answered. It shimmers while it streams and folds itself away
111
+ * once the answer starts, because the thought is worth a glance and rarely worth reading twice.
112
+ */
113
+ export function ThinkingRow({ chatId, item: derived }) {
114
+ const { t } = useTranslation('agent-chat');
115
+ const item = useCurrentItem(chatId, derived);
116
+ const mode = chatHost().useStreaming();
117
+ const [open, setOpen] = useState(false);
118
+ const reveal = useRevealedText(item.text, mode === 'words' && item.streaming);
119
+ // A block at a time, the paragraphs of the thought that closed are shown as they land.
120
+ const settled = mode === 'blocks' && item.streaming ? settledBlocksText(item.text) : null;
121
+ // Whole, the thought stays behind "Thinking…" until it is done and then folds like any other.
122
+ const shown = open || reveal.active || (settled !== null && settled !== '');
123
+ const text = settled ?? reveal.text;
124
+ useOpenForFind(item.id, 'text', setOpen);
125
+ return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsxs("button", { className: "flex items-center gap-2 text-xs text-text-muted hover:text-text", disabled: item.streaming, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Brain, size: 12 }) }), item.streaming ? (_jsx("span", { className: "chat-live-text", children: t('rows.thinking.live') })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: t('rows.thinking.thoughtFor', { duration: formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt) }) }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('transition-transform', open && 'rotate-180') })] }))] }), shown && text !== '' && (_jsx("div", { "data-find-field": "text", className: "mt-1 border-l border-border pl-2.5 text-sm whitespace-pre-wrap text-text-faint select-text", children: reveal.active ? _jsx(FadingWords, { text: reveal.text }) : text }))] }));
126
+ }
127
+ const NOTE_ICON = {
128
+ info: _jsx(Icon, { icon: Info, size: 12 }),
129
+ warning: _jsx(Icon, { icon: TriangleAlert, size: 12 }),
130
+ error: _jsx(Icon, { icon: CircleAlert, size: 12 })
131
+ };
132
+ /*
133
+ * A line from the machine in the thread. A note of more than one line (a summary a fork sent back)
134
+ * shows its first line and folds the rest open as markdown; one that came from a fork leads to it.
135
+ */
136
+ export function NoteRow({ id, level, text, from }) {
137
+ const { t } = useTranslation('agent-chat');
138
+ const [open, setOpen] = useState(false);
139
+ const breakAt = text.indexOf('\n');
140
+ const head = breakAt === -1 ? text : text.slice(0, breakAt);
141
+ const rest = breakAt === -1 ? '' : text.slice(breakAt + 1).trim();
142
+ useOpenForFind(id, 'output', setOpen);
143
+ return (_jsxs("div", { "data-find-item": id, className: clsx('-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs', level === 'error' ? 'text-status-error' : level === 'warning' ? 'text-status-needs-you' : 'text-text-faint'), children: [_jsx("span", { className: ROW_GUTTER, children: NOTE_ICON[level] }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2", children: [_jsx("span", { "data-find-field": "summary", className: "select-text", children: head }), rest !== '' && (_jsx("button", { className: "text-text-muted hover:text-text", onClick: () => setOpen(!open), children: open ? t('rows.note.hide') : t('rows.note.show') })), from !== undefined && _jsx(OpenChatButton, { chatId: from })] }), open && rest !== '' && (_jsx("div", { "data-find-field": "output", className: "mt-1 text-text-muted select-text", children: _jsx(MessageMarkdown, { text: rest }) }))] })] }));
144
+ }
145
+ /* "Open fork" on a note a fork sent, while that fork is still in the project. */
146
+ function OpenChatButton({ chatId }) {
147
+ const { t } = useTranslation('agent-chat');
148
+ const place = chatHost().useChatPlace(chatId);
149
+ if (place.title === null) {
150
+ return null;
151
+ }
152
+ return (_jsx("button", { className: "text-text-muted hover:text-text", onClick: place.go, children: t('rows.note.openFork') }));
153
+ }
154
+ /*
155
+ * The header of a turn the agent started itself, in place of the message of the person that is
156
+ * missing. It is one line: what the sub-agent came back with lives on that agent's own row, behind
157
+ * "Show result". When the CLI named the sub-agent it woke up about, the header opens that row.
158
+ */
159
+ export function AgentTurnRow({ label, onOpen }) {
160
+ const line = 'mb-0.5 flex h-7 w-full items-center gap-2 text-left text-xs text-text-muted';
161
+ // A header with nowhere to go is text to read and copy; one that opens a row is a button.
162
+ const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Bot, size: 12 }) }), _jsx("span", { className: clsx('min-w-0 truncate', onOpen === undefined && 'select-text'), children: label })] }));
163
+ if (onOpen === undefined) {
164
+ return _jsx("div", { className: line, children: content });
165
+ }
166
+ return (_jsx("button", { className: clsx(line, 'hover:text-text'), onClick: onOpen, children: content }));
167
+ }
168
+ /* A dashed line, since what stands above it is still there for the reader but only a summary to the agent. */
169
+ export function CompactionRow({ preTokens }) {
170
+ const { t } = useTranslation('agent-chat');
171
+ return (_jsxs("div", { className: "flex items-center gap-3 pb-3 text-xs text-text-faint", children: [_jsx("span", { className: "grow border-t border-dashed border-border" }), _jsx(Tooltip, { label: t('rows.compaction.summary'), children: _jsx("span", { children: preTokens ? t('rows.compaction.from', { tokens: formatTokens(preTokens) }) : t('rows.compaction.plain') }) }), _jsx("span", { className: "grow border-t border-dashed border-border" })] }));
172
+ }
173
+ const DECISION_ICON = {
174
+ allow: _jsx(Icon, { icon: Check, size: 12 }),
175
+ 'allow-always': _jsx(Icon, { icon: Check, size: 12 }),
176
+ deny: _jsx(Icon, { icon: X, size: 12 }),
177
+ cancelled: _jsx(Icon, { icon: X, size: 12 })
178
+ };
179
+ /* The outcome of a permission request, one quiet line; the request itself lived on the composer. */
180
+ export function ApprovalHistoryRow({ item }) {
181
+ const { t } = useTranslation('agent-chat');
182
+ if (item.decision === 'pending') {
183
+ return null;
184
+ }
185
+ return (_jsxs("div", { className: "-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-faint", children: [_jsx("span", { className: clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error'), children: DECISION_ICON[item.decision] }), _jsx("span", { className: clsx(item.decision === 'deny' && 'text-status-error'), children: t(`rows.approval.${item.decision}`) }), _jsx("span", { className: "text-text-muted", children: item.toolName }), _jsx("span", { className: "min-w-0 truncate font-mono", children: toolSummary(item.toolName, item.input) })] }));
186
+ }
187
+ export function QuestionHistoryRow({ item }) {
188
+ const { t } = useTranslation('agent-chat');
189
+ return (_jsx("div", { children: item.questions.map((question) => (_jsxs("div", { className: "-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs text-text-faint", children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: MessageCircleQuestionMark, size: 12 }) }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-text-muted", children: question.question }), item.state === 'answered' && item.answers?.[question.id] !== undefined && (_jsx("span", { className: "ml-1.5 text-text", children: item.answers[question.id] })), item.state === 'cancelled' && _jsx("span", { className: "ml-1.5", children: t('rows.question.notAnswered') }), item.state === 'dismissed' && _jsx("span", { className: "ml-1.5", children: t('rows.question.dismissed') })] })] }, question.id))) }));
190
+ }
@@ -0,0 +1,12 @@
1
+ import type { TimelineRow } from '../../logic/timeline';
2
+ import type { SubagentStep } from '../../subagent-view';
3
+ export interface RowProps {
4
+ row: TimelineRow;
5
+ chatId: string;
6
+ toggleGroup(id: string): void;
7
+ toggleTurn(id: string): void;
8
+ toggleSubagent(id: string): void;
9
+ openSubagent(toolUseId: string): void;
10
+ openConversation?(step: SubagentStep): void;
11
+ }
12
+ export declare function Row({ row, chatId, toggleGroup, toggleTurn, toggleSubagent, openSubagent, openConversation }: RowProps): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AgentTurnRow, ApprovalHistoryRow, AssistantRow, CompactionRow, NoteRow, QuestionHistoryRow, ReportRow, ThinkingRow, UserRow } from './MessageRows';
3
+ import { ForksRow } from './ForksRow';
4
+ import { SubagentBranchRow } from './SubagentRow';
5
+ import { WorkflowRow } from './WorkflowRow';
6
+ import { ChangedFilesRow, TurnFoldRow, WorkGroupRow, WorkLiveRow, WorkRow, WorkingRow } from './WorkRows';
7
+ /* One row of a thread, the chat's own or a subagent's read back, drawn the same way in both. */
8
+ export function Row({ row, chatId, toggleGroup, toggleTurn, toggleSubagent, openSubagent, openConversation }) {
9
+ switch (row.kind) {
10
+ case 'user':
11
+ return _jsx(UserRow, { chatId: chatId, item: row.item });
12
+ case 'turn-start': {
13
+ const toolUseId = row.turn.taskToolUseId;
14
+ return _jsx(AgentTurnRow, { label: row.label, onOpen: toolUseId ? () => openSubagent(toolUseId) : undefined });
15
+ }
16
+ case 'assistant':
17
+ return _jsx(AssistantRow, { chatId: chatId, item: row.item });
18
+ case 'report':
19
+ return _jsx(ReportRow, { id: row.id, text: row.text });
20
+ case 'thinking':
21
+ return _jsx(ThinkingRow, { chatId: chatId, item: row.item });
22
+ case 'work':
23
+ return _jsx(WorkRow, { tool: row.tool });
24
+ case 'work-live':
25
+ return _jsx(WorkLiveRow, { tool: row.tool });
26
+ case 'workflow':
27
+ return _jsx(WorkflowRow, { tool: row.tool, workflow: row.workflow, onOpenAgent: openConversation });
28
+ case 'work-group':
29
+ return _jsx(WorkGroupRow, { tools: row.tools, summary: row.summary, expanded: row.expanded, onToggle: () => toggleGroup(row.id) });
30
+ case 'subagent':
31
+ return _jsx(SubagentBranchRow, { branch: row, onToggle: toggleSubagent, onOpenConversation: openConversation });
32
+ case 'turn-fold':
33
+ return _jsx(TurnFoldRow, { turn: row.turn, label: row.label, work: row.work, expanded: row.expanded, onToggle: () => toggleTurn(row.turn.id) });
34
+ case 'forks':
35
+ return _jsx(ForksRow, { chatId: chatId, turnId: row.turnId });
36
+ case 'changed-files':
37
+ return _jsx(ChangedFilesRow, { chatId: chatId, turnId: row.turnId, tools: row.tools, diff: row.diff, checkpoint: row.checkpoint });
38
+ case 'approval':
39
+ return _jsx(ApprovalHistoryRow, { item: row.item });
40
+ case 'question':
41
+ return _jsx(QuestionHistoryRow, { item: row.item });
42
+ case 'note':
43
+ return _jsx(NoteRow, { id: row.id, level: row.level, text: row.text, from: row.from });
44
+ case 'compaction':
45
+ return _jsx(CompactionRow, { preTokens: row.preTokens });
46
+ case 'working':
47
+ return _jsx(WorkingRow, { startedAt: row.startedAt });
48
+ }
49
+ }
@@ -0,0 +1,15 @@
1
+ import type { ChatItem, ChatSubagentItem } from '@ruimte/agent-contracts';
2
+ import type { SubagentBranch } from '../../logic/timeline';
3
+ import { type SubagentStep } from '../../subagent-view';
4
+ export declare function SubagentRow({ item, work, expanded, onToggle, onOpenConversation }: {
5
+ item: ChatSubagentItem;
6
+ work: ChatItem[];
7
+ expanded: boolean;
8
+ onToggle(): void;
9
+ onOpenConversation?(): void;
10
+ }): import("react").JSX.Element;
11
+ export declare function SubagentBranchRow({ branch, onToggle, onOpenConversation }: {
12
+ branch: SubagentBranch;
13
+ onToggle(id: string): void;
14
+ onOpenConversation?(step: SubagentStep): void;
15
+ }): import("react").JSX.Element;
@@ -0,0 +1,98 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Bot, ChevronDown } from 'lucide-react';
6
+ import { formatElapsedShort } from '@ruimte/ui/format/duration';
7
+ import { useOpenForFind } from '../find-reveal';
8
+ import { Markdown } from '../Markdown';
9
+ import { RunningFor, ToggleLine, WorkLiveRow, WorkRow } from './WorkRows';
10
+ import { entryTimeOf, statusWordOf, taskIdOf } from '../../subagent-list';
11
+ import { useSubagentSupport } from '../../subagent-support';
12
+ import { canOpenSubagent, crumbOf } from '../../subagent-view';
13
+ import { chatHost } from '../../../host';
14
+ import { useChatScope } from '../../../scope';
15
+ import { Icon } from '@ruimte/ui/Icon';
16
+ import { useNow } from '@ruimte/ui/useNow';
17
+ // The work of a long-running agent scrolls inside its row instead of pushing the thread away.
18
+ const CHILDREN_MAX_PX = 320;
19
+ /*
20
+ * One step a sub-agent took, inside its parent's row. The thread's own `Row` draws the same kinds,
21
+ * but off a timeline it has already grouped and folded, and it writes an answer at the size of a
22
+ * message; here every step is a line and the text is the aside under it.
23
+ */
24
+ function ChildRow({ item }) {
25
+ if (item.kind === 'tool') {
26
+ return item.state === 'running' ? _jsx(WorkLiveRow, { tool: item }) : _jsx(WorkRow, { tool: item });
27
+ }
28
+ if (item.kind === 'assistant') {
29
+ return (_jsx("div", { className: "-mx-1 px-1 pb-2 text-xs text-text-muted select-text", children: _jsx(Markdown, { text: item.text }) }));
30
+ }
31
+ return null;
32
+ }
33
+ function StatusPill({ item, task }) {
34
+ const { t } = useTranslation('agent-chat');
35
+ const paused = statusWordOf(item, task) === 'paused';
36
+ const now = useNow(60_000, paused);
37
+ if (paused) {
38
+ return _jsx("span", { className: "shrink-0 text-xs text-text-faint tabular-nums", children: entryTimeOf(item, task, now) });
39
+ }
40
+ if (item.status === 'running') {
41
+ return _jsx(RunningFor, { startedAt: item.startedAt });
42
+ }
43
+ const failed = item.status === 'failed';
44
+ const duration = item.finishedAt === null ? null : formatElapsedShort(item.finishedAt - item.startedAt);
45
+ const outcome = failed ? 'failed' : 'done';
46
+ return (_jsx("span", { className: clsx('shrink-0 text-xs tabular-nums', failed ? 'text-status-error' : 'text-text-faint'), children: duration === null ? t(`common.status.${outcome}`) : t(`rows.subagent.${outcome}In`, { duration }) }));
47
+ }
48
+ /* What the sub-agent did, live: its own tool calls and the text it wrote, as ordinary rows. */
49
+ function SubagentWork({ item, work }) {
50
+ const { t } = useTranslation('agent-chat');
51
+ const scroller = useRef(null);
52
+ const running = item.status === 'running';
53
+ useLayoutEffect(() => {
54
+ // While it works, the newest line is the one worth seeing.
55
+ if (running && scroller.current) {
56
+ scroller.current.scrollTop = scroller.current.scrollHeight;
57
+ }
58
+ }, [work.length, running]);
59
+ if (work.length === 0) {
60
+ // A line inside a chat row, where the centered block of an EmptyState would outweigh the row itself.
61
+ return _jsx("div", { className: "ml-6 pb-1 text-xs text-text-faint", children: t('rows.subagent.nothingYet') });
62
+ }
63
+ return (_jsxs("div", { ref: scroller, className: "ml-6 overflow-y-auto", style: { maxHeight: CHILDREN_MAX_PX }, children: [item.itemsTruncated && _jsx("div", { className: "pb-1 text-xs text-text-faint", children: t('rows.subagent.truncated') }), work.map((child) => (_jsx(ChildRow, { item: child }, child.id)))] }));
64
+ }
65
+ /* The report the sub-agent handed back, behind a fold: the row is about the work, this is the answer. */
66
+ function SubagentResult({ itemId, result }) {
67
+ const { t } = useTranslation('agent-chat');
68
+ const [open, setOpen] = useState(false);
69
+ useOpenForFind(itemId, 'output', setOpen);
70
+ return (_jsxs("div", { className: "ml-6 pb-1", children: [_jsxs("button", { className: "flex items-center gap-1.5 text-xs text-text-muted hover:text-text", onClick: () => setOpen((o) => !o), children: [_jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('transition-transform', open && 'rotate-180') }), open ? t('rows.subagent.hideResult') : t('rows.subagent.showResult')] }), open && (_jsx("div", { "data-find-field": "output", className: "mt-1 rounded-md border border-border bg-surface-raised px-3 py-2 select-text", children: _jsx(Markdown, { text: result }) }))] }));
71
+ }
72
+ /*
73
+ * One agent the agent delegated to. The line says what it is doing and for how long; pressing it
74
+ * opens the whole conversation in the thread's place, and where there is none to open it folds out
75
+ * the work the thread kept and, once it settled, the report it wrote.
76
+ */
77
+ export function SubagentRow({ item, work, expanded, onToggle, onOpenConversation }) {
78
+ const { t } = useTranslation('agent-chat');
79
+ const { id } = useChatScope();
80
+ const taskId = taskIdOf(item);
81
+ const task = chatHost().tasks.useTask(id, taskId);
82
+ // A task paused on its child's limit is still a running row on the wire, but nothing is at work.
83
+ const running = item.status === 'running' && statusWordOf(item, task) !== 'paused';
84
+ const detail = item.description || item.summary || item.subagentType || '';
85
+ // A row that carries no pointer on a host that already said no has nothing to open.
86
+ const refused = useSubagentSupport((s) => s.unsupported[id] === true);
87
+ const press = onOpenConversation !== undefined && canOpenSubagent(item, refused) ? onOpenConversation : onToggle;
88
+ return (_jsxs("div", { "data-find-item": item.id, children: [_jsx(ToggleLine, { icon: _jsx(Icon, { icon: Bot, size: 12 }),
89
+ // A node another agent opened with `--task` reads as the task it is, not as a helper of the CLI's own.
90
+ label: item.origin === 'ruimte' ? t('rows.subagent.task') : t('rows.subagent.label'), detail: detail, open: expanded, onToggle: press, failed: item.status === 'failed', live: running, trailing: _jsxs(_Fragment, { children: [item.background && _jsx("span", { className: "shrink-0 text-xs text-text-faint", children: t('rows.subagent.background') }), running && item.lastTool && _jsx("span", { className: "shrink-0 text-xs text-text-faint", children: item.lastTool }), _jsx(StatusPill, { item: item, task: task })] }) }), expanded && (_jsxs("div", { className: "mb-1", children: [_jsx(SubagentWork, { item: item, work: work }), item.result !== null && _jsx(SubagentResult, { itemId: item.id, result: item.result })] }))] }));
91
+ }
92
+ /*
93
+ * A sub-agent's row with the agents it opened in turn under it. Those stay in sight whether its own
94
+ * work is folded or not, since opening the parent shows its conversation and not theirs.
95
+ */
96
+ export function SubagentBranchRow({ branch, onToggle, onOpenConversation }) {
97
+ return (_jsxs(_Fragment, { children: [_jsx(SubagentRow, { item: branch.item, work: branch.children, expanded: branch.expanded, onToggle: () => onToggle(branch.id), onOpenConversation: onOpenConversation ? () => onOpenConversation(crumbOf(branch.item)) : undefined }), branch.nested.length > 0 && (_jsx("div", { className: "ml-6", children: branch.nested.map((child) => (_jsx(SubagentBranchRow, { branch: child, onToggle: onToggle, onOpenConversation: onOpenConversation }, child.id))) }))] }));
98
+ }
@@ -0,0 +1,46 @@
1
+ import type { ChatCheckpointDiff, ChatToolItem, ChatTurnItem } from '@ruimte/agent-contracts';
2
+ export declare function ToggleLine({ icon, label, detail, open, onToggle, failed, live, trailing, className }: {
3
+ icon: React.ReactNode;
4
+ label: string;
5
+ detail?: string;
6
+ open: boolean;
7
+ onToggle(): void;
8
+ failed?: boolean;
9
+ live?: boolean;
10
+ trailing?: React.ReactNode;
11
+ className?: string;
12
+ }): import("react").JSX.Element;
13
+ export declare function WorkRow({ tool, nested, detail }: {
14
+ tool: ChatToolItem;
15
+ nested?: boolean;
16
+ detail?: string;
17
+ }): import("react").JSX.Element;
18
+ export declare function RunningFor({ startedAt }: {
19
+ startedAt: number;
20
+ }): import("react").JSX.Element;
21
+ export declare function WorkLiveRow({ tool }: {
22
+ tool: ChatToolItem;
23
+ }): import("react").JSX.Element;
24
+ export declare function WorkGroupRow({ tools, summary, expanded, onToggle }: {
25
+ tools: ChatToolItem[];
26
+ summary: string;
27
+ expanded: boolean;
28
+ onToggle(): void;
29
+ }): import("react").JSX.Element;
30
+ export declare function TurnFoldRow({ turn, label, work, expanded, onToggle }: {
31
+ turn: ChatTurnItem;
32
+ label: string;
33
+ work: readonly string[];
34
+ expanded: boolean;
35
+ onToggle(): void;
36
+ }): import("react").JSX.Element;
37
+ export declare function ChangedFilesRow({ tools, diff, checkpoint, chatId, turnId }: {
38
+ tools: ChatToolItem[];
39
+ diff: ChatCheckpointDiff | null;
40
+ checkpoint: boolean;
41
+ chatId: string;
42
+ turnId: string;
43
+ }): import("react").JSX.Element;
44
+ export declare function WorkingRow({ startedAt }: {
45
+ startedAt: number;
46
+ }): import("react").JSX.Element;