@adecore/agents-react 0.0.1 → 0.17.0-beta.1

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 (513) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +53 -0
  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/ModelOptionControl.d.ts +15 -0
  8. package/dist/agents/ModelOptionControl.js +33 -0
  9. package/dist/agents/ProviderLogo.d.ts +8 -0
  10. package/dist/agents/ProviderLogo.js +8 -0
  11. package/dist/agents/account-limits.d.ts +7 -0
  12. package/dist/agents/account-limits.js +40 -0
  13. package/dist/agents/accounts.d.ts +22 -0
  14. package/dist/agents/accounts.js +113 -0
  15. package/dist/agents/model-name.d.ts +12 -0
  16. package/dist/agents/model-name.js +56 -0
  17. package/dist/agents/model-options.d.ts +5 -0
  18. package/dist/agents/model-options.js +15 -0
  19. package/dist/agents/provider-paths.d.ts +2 -0
  20. package/dist/agents/provider-paths.js +8 -0
  21. package/dist/agents/status-look.d.ts +13 -0
  22. package/dist/agents/status-look.js +24 -0
  23. package/dist/chat/account-choice.d.ts +11 -0
  24. package/dist/chat/account-choice.js +30 -0
  25. package/dist/chat/actions.d.ts +6 -0
  26. package/dist/chat/actions.js +68 -0
  27. package/dist/chat/activity.d.ts +5 -0
  28. package/dist/chat/activity.js +6 -0
  29. package/dist/chat/attachments.d.ts +23 -0
  30. package/dist/chat/attachments.js +86 -0
  31. package/dist/chat/bookmarks.d.ts +14 -0
  32. package/dist/chat/bookmarks.js +73 -0
  33. package/dist/chat/chat-client.d.ts +55 -0
  34. package/dist/chat/chat-client.js +279 -0
  35. package/dist/chat/chat-references.d.ts +5 -0
  36. package/dist/chat/chat-references.js +5 -0
  37. package/dist/chat/drafts.d.ts +29 -0
  38. package/dist/chat/drafts.js +116 -0
  39. package/dist/chat/forks.d.ts +2 -0
  40. package/dist/chat/forks.js +28 -0
  41. package/dist/chat/guards.d.ts +12 -0
  42. package/dist/chat/guards.js +69 -0
  43. package/dist/chat/logic/ansi.d.ts +13 -0
  44. package/dist/chat/logic/ansi.js +185 -0
  45. package/dist/chat/logic/answers.d.ts +1 -0
  46. package/dist/chat/logic/answers.js +8 -0
  47. package/dist/chat/logic/bookmarks.d.ts +5 -0
  48. package/dist/chat/logic/bookmarks.js +33 -0
  49. package/dist/chat/logic/context-usage.d.ts +12 -0
  50. package/dist/chat/logic/context-usage.js +21 -0
  51. package/dist/chat/logic/fork.d.ts +45 -0
  52. package/dist/chat/logic/fork.js +148 -0
  53. package/dist/chat/logic/handback.d.ts +2 -0
  54. package/dist/chat/logic/handback.js +8 -0
  55. package/dist/chat/logic/json.d.ts +1 -0
  56. package/dist/chat/logic/json.js +4 -0
  57. package/dist/chat/logic/limit.d.ts +7 -0
  58. package/dist/chat/logic/limit.js +34 -0
  59. package/dist/chat/logic/resume-compaction.d.ts +14 -0
  60. package/dist/chat/logic/resume-compaction.js +47 -0
  61. package/dist/chat/logic/scrubber.d.ts +50 -0
  62. package/dist/chat/logic/scrubber.js +220 -0
  63. package/dist/chat/logic/thread-cards.d.ts +3 -0
  64. package/dist/chat/logic/thread-cards.js +59 -0
  65. package/dist/chat/logic/timeline-copy.d.ts +4 -0
  66. package/dist/chat/logic/timeline-copy.js +59 -0
  67. package/dist/chat/logic/timeline-target.d.ts +12 -0
  68. package/dist/chat/logic/timeline-target.js +34 -0
  69. package/dist/chat/logic/timeline.d.ts +115 -0
  70. package/dist/chat/logic/timeline.js +343 -0
  71. package/dist/chat/logic/tool-catalog.d.ts +10 -0
  72. package/dist/chat/logic/tool-catalog.js +44 -0
  73. package/dist/chat/logic/tools.d.ts +15 -0
  74. package/dist/chat/logic/tools.js +82 -0
  75. package/dist/chat/logic/welcome.d.ts +2 -0
  76. package/dist/chat/logic/welcome.js +10 -0
  77. package/dist/chat/logic/workflow.d.ts +14 -0
  78. package/dist/chat/logic/workflow.js +47 -0
  79. package/dist/chat/mentions.d.ts +65 -0
  80. package/dist/chat/mentions.js +184 -0
  81. package/dist/chat/persisted-json.d.ts +5 -0
  82. package/dist/chat/persisted-json.js +58 -0
  83. package/dist/chat/preferences.d.ts +27 -0
  84. package/dist/chat/preferences.js +139 -0
  85. package/dist/chat/quote.d.ts +2 -0
  86. package/dist/chat/quote.js +25 -0
  87. package/dist/chat/recent-messages.d.ts +17 -0
  88. package/dist/chat/recent-messages.js +36 -0
  89. package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
  90. package/dist/chat/resume-compaction-dismissals.js +14 -0
  91. package/dist/chat/runtime-modes.d.ts +4 -0
  92. package/dist/chat/runtime-modes.js +15 -0
  93. package/dist/chat/stash.d.ts +24 -0
  94. package/dist/chat/stash.js +91 -0
  95. package/dist/chat/subagent-conversation.d.ts +41 -0
  96. package/dist/chat/subagent-conversation.js +167 -0
  97. package/dist/chat/subagent-list.d.ts +19 -0
  98. package/dist/chat/subagent-list.js +106 -0
  99. package/dist/chat/subagent-support.d.ts +6 -0
  100. package/dist/chat/subagent-support.js +14 -0
  101. package/dist/chat/subagent-view.d.ts +35 -0
  102. package/dist/chat/subagent-view.js +96 -0
  103. package/dist/chat/thumbnails.d.ts +10 -0
  104. package/dist/chat/thumbnails.js +89 -0
  105. package/dist/chat/timeline-flash.d.ts +12 -0
  106. package/dist/chat/timeline-flash.js +16 -0
  107. package/dist/chat/timeline-scroll.d.ts +27 -0
  108. package/dist/chat/timeline-scroll.js +152 -0
  109. package/dist/chat/ui/AccountPill.d.ts +3 -0
  110. package/dist/chat/ui/AccountPill.js +23 -0
  111. package/dist/chat/ui/AnsiOutput.d.ts +5 -0
  112. package/dist/chat/ui/AnsiOutput.js +9 -0
  113. package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
  114. package/dist/chat/ui/BookmarkMarker.js +57 -0
  115. package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
  116. package/dist/chat/ui/BookmarkSubmenu.js +33 -0
  117. package/dist/chat/ui/ChatActivity.d.ts +7 -0
  118. package/dist/chat/ui/ChatActivity.js +84 -0
  119. package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
  120. package/dist/chat/ui/ChatReferenceChip.js +16 -0
  121. package/dist/chat/ui/CodeBlock.d.ts +4 -0
  122. package/dist/chat/ui/CodeBlock.js +132 -0
  123. package/dist/chat/ui/Composer.d.ts +15 -0
  124. package/dist/chat/ui/Composer.js +856 -0
  125. package/dist/chat/ui/ComposerInput.d.ts +29 -0
  126. package/dist/chat/ui/ComposerInput.js +127 -0
  127. package/dist/chat/ui/DiffPool.d.ts +5 -0
  128. package/dist/chat/ui/DiffPool.js +26 -0
  129. package/dist/chat/ui/EditDiff.d.ts +4 -0
  130. package/dist/chat/ui/EditDiff.js +21 -0
  131. package/dist/chat/ui/FadingWords.d.ts +3 -0
  132. package/dist/chat/ui/FadingWords.js +5 -0
  133. package/dist/chat/ui/ImageView.d.ts +6 -0
  134. package/dist/chat/ui/ImageView.js +81 -0
  135. package/dist/chat/ui/LimitState.d.ts +8 -0
  136. package/dist/chat/ui/LimitState.js +112 -0
  137. package/dist/chat/ui/Markdown.d.ts +18 -0
  138. package/dist/chat/ui/Markdown.js +159 -0
  139. package/dist/chat/ui/MessageActions.d.ts +9 -0
  140. package/dist/chat/ui/MessageActions.js +53 -0
  141. package/dist/chat/ui/Pickers.d.ts +21 -0
  142. package/dist/chat/ui/Pickers.js +111 -0
  143. package/dist/chat/ui/PromptComposer.d.ts +13 -0
  144. package/dist/chat/ui/PromptComposer.js +56 -0
  145. package/dist/chat/ui/QuoteButton.d.ts +7 -0
  146. package/dist/chat/ui/QuoteButton.js +74 -0
  147. package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
  148. package/dist/chat/ui/ResumeCompactionDock.js +14 -0
  149. package/dist/chat/ui/RunSettings.d.ts +28 -0
  150. package/dist/chat/ui/RunSettings.js +153 -0
  151. package/dist/chat/ui/Scrubber.d.ts +17 -0
  152. package/dist/chat/ui/Scrubber.js +118 -0
  153. package/dist/chat/ui/SubagentControls.d.ts +11 -0
  154. package/dist/chat/ui/SubagentControls.js +36 -0
  155. package/dist/chat/ui/SubagentInfo.d.ts +4 -0
  156. package/dist/chat/ui/SubagentInfo.js +34 -0
  157. package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
  158. package/dist/chat/ui/SubagentStopButton.js +34 -0
  159. package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
  160. package/dist/chat/ui/SubagentTimeline.js +107 -0
  161. package/dist/chat/ui/Timeline.d.ts +6 -0
  162. package/dist/chat/ui/Timeline.js +424 -0
  163. package/dist/chat/ui/TimelineMenu.d.ts +12 -0
  164. package/dist/chat/ui/TimelineMenu.js +54 -0
  165. package/dist/chat/ui/UnifiedDiff.d.ts +11 -0
  166. package/dist/chat/ui/UnifiedDiff.js +66 -0
  167. package/dist/chat/ui/UploadThumb.d.ts +4 -0
  168. package/dist/chat/ui/UploadThumb.js +11 -0
  169. package/dist/chat/ui/Welcome.d.ts +4 -0
  170. package/dist/chat/ui/Welcome.js +16 -0
  171. package/dist/chat/ui/chips.d.ts +4 -0
  172. package/dist/chat/ui/chips.js +11 -0
  173. package/dist/chat/ui/code-lines.d.ts +18 -0
  174. package/dist/chat/ui/code-lines.js +46 -0
  175. package/dist/chat/ui/code-streaming.d.ts +1 -0
  176. package/dist/chat/ui/code-streaming.js +3 -0
  177. package/dist/chat/ui/code-theme.d.ts +3 -0
  178. package/dist/chat/ui/code-theme.js +12 -0
  179. package/dist/chat/ui/composer/chips.d.ts +6 -0
  180. package/dist/chat/ui/composer/chips.js +31 -0
  181. package/dist/chat/ui/composer/editor.d.ts +11 -0
  182. package/dist/chat/ui/composer/editor.js +98 -0
  183. package/dist/chat/ui/composer/insert.d.ts +2 -0
  184. package/dist/chat/ui/composer/insert.js +19 -0
  185. package/dist/chat/ui/composer/keys.d.ts +30 -0
  186. package/dist/chat/ui/composer/keys.js +100 -0
  187. package/dist/chat/ui/diff-theme.d.ts +2 -0
  188. package/dist/chat/ui/diff-theme.js +20 -0
  189. package/dist/chat/ui/file-links.d.ts +5 -0
  190. package/dist/chat/ui/file-links.js +20 -0
  191. package/dist/chat/ui/find-reveal.d.ts +7 -0
  192. package/dist/chat/ui/find-reveal.js +13 -0
  193. package/dist/chat/ui/full-diff.d.ts +6 -0
  194. package/dist/chat/ui/full-diff.js +19 -0
  195. package/dist/chat/ui/icons.d.ts +4 -0
  196. package/dist/chat/ui/icons.js +20 -0
  197. package/dist/chat/ui/markdown-blocks.d.ts +6 -0
  198. package/dist/chat/ui/markdown-blocks.js +103 -0
  199. package/dist/chat/ui/quote-selection.d.ts +3 -0
  200. package/dist/chat/ui/quote-selection.js +46 -0
  201. package/dist/chat/ui/rehype-chips.d.ts +23 -0
  202. package/dist/chat/ui/rehype-chips.js +32 -0
  203. package/dist/chat/ui/rehype-fade.d.ts +23 -0
  204. package/dist/chat/ui/rehype-fade.js +36 -0
  205. package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
  206. package/dist/chat/ui/remark-html-as-text.js +26 -0
  207. package/dist/chat/ui/reply-context.d.ts +5 -0
  208. package/dist/chat/ui/reply-context.js +2 -0
  209. package/dist/chat/ui/reveal.d.ts +13 -0
  210. package/dist/chat/ui/reveal.js +114 -0
  211. package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
  212. package/dist/chat/ui/rows/ForksRow.js +38 -0
  213. package/dist/chat/ui/rows/MessageRows.d.ts +40 -0
  214. package/dist/chat/ui/rows/MessageRows.js +204 -0
  215. package/dist/chat/ui/rows/Rows.d.ts +12 -0
  216. package/dist/chat/ui/rows/Rows.js +61 -0
  217. package/dist/chat/ui/rows/SubagentRow.d.ts +17 -0
  218. package/dist/chat/ui/rows/SubagentRow.js +101 -0
  219. package/dist/chat/ui/rows/TaskRow.d.ts +8 -0
  220. package/dist/chat/ui/rows/TaskRow.js +63 -0
  221. package/dist/chat/ui/rows/WorkRows.d.ts +47 -0
  222. package/dist/chat/ui/rows/WorkRows.js +168 -0
  223. package/dist/chat/ui/rows/WorkflowRow.d.ts +8 -0
  224. package/dist/chat/ui/rows/WorkflowRow.js +48 -0
  225. package/dist/chat/ui/rows/row-rhythm.d.ts +6 -0
  226. package/dist/chat/ui/rows/row-rhythm.js +34 -0
  227. package/dist/chat/ui/useToggleSet.d.ts +6 -0
  228. package/dist/chat/ui/useToggleSet.js +25 -0
  229. package/dist/host.d.ts +195 -0
  230. package/dist/host.js +55 -0
  231. package/dist/lazy.d.ts +6 -0
  232. package/dist/lazy.js +57 -0
  233. package/dist/locales/en/agent-chat.json +446 -0
  234. package/dist/locales/en/agent-prompts.json +42 -0
  235. package/dist/locales/en/agent-providers.json +131 -0
  236. package/dist/locales/en/agent-usage.json +131 -0
  237. package/dist/locales/nl/agent-chat.json +446 -0
  238. package/dist/locales/nl/agent-prompts.json +42 -0
  239. package/dist/locales/nl/agent-providers.json +131 -0
  240. package/dist/locales/nl/agent-usage.json +131 -0
  241. package/dist/locales.d.ts +5 -0
  242. package/dist/locales.js +20 -0
  243. package/dist/mounted-registry.d.ts +7 -0
  244. package/dist/mounted-registry.js +39 -0
  245. package/dist/port-transport.d.ts +6 -0
  246. package/dist/port-transport.js +129 -0
  247. package/dist/prompts/logic/focus.d.ts +2 -0
  248. package/dist/prompts/logic/focus.js +23 -0
  249. package/dist/prompts/logic/keys.d.ts +42 -0
  250. package/dist/prompts/logic/keys.js +94 -0
  251. package/dist/prompts/logic/prompts.d.ts +34 -0
  252. package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
  253. package/dist/prompts/logic/prompts.fixtures.js +79 -0
  254. package/dist/prompts/logic/prompts.js +32 -0
  255. package/dist/prompts/logic/subjects.d.ts +50 -0
  256. package/dist/prompts/logic/subjects.js +63 -0
  257. package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
  258. package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
  259. package/dist/prompts/logic/usePromptSession.d.ts +24 -0
  260. package/dist/prompts/logic/usePromptSession.js +87 -0
  261. package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
  262. package/dist/prompts/ui/ApprovalBody.js +34 -0
  263. package/dist/prompts/ui/PromptActions.d.ts +27 -0
  264. package/dist/prompts/ui/PromptActions.js +18 -0
  265. package/dist/prompts/ui/PromptCard.d.ts +16 -0
  266. package/dist/prompts/ui/PromptCard.js +50 -0
  267. package/dist/prompts/ui/PromptView.d.ts +2 -0
  268. package/dist/prompts/ui/PromptView.js +47 -0
  269. package/dist/prompts/ui/QuestionBody.d.ts +9 -0
  270. package/dist/prompts/ui/QuestionBody.js +87 -0
  271. package/dist/providers/AccountColors.d.ts +5 -0
  272. package/dist/providers/AccountColors.js +8 -0
  273. package/dist/providers/AccountDetail.d.ts +12 -0
  274. package/dist/providers/AccountDetail.js +69 -0
  275. package/dist/providers/AccountVariables.d.ts +8 -0
  276. package/dist/providers/AccountVariables.js +43 -0
  277. package/dist/providers/AddAccountForm.d.ts +10 -0
  278. package/dist/providers/AddAccountForm.js +54 -0
  279. package/dist/providers/CliDetail.d.ts +12 -0
  280. package/dist/providers/CliDetail.js +54 -0
  281. package/dist/providers/ProvidersPane.d.ts +9 -0
  282. package/dist/providers/ProvidersPane.js +86 -0
  283. package/dist/providers/account-actions.d.ts +7 -0
  284. package/dist/providers/account-actions.js +57 -0
  285. package/dist/providers/parts.d.ts +9 -0
  286. package/dist/providers/parts.js +18 -0
  287. package/dist/providers/provider-abilities.d.ts +2 -0
  288. package/dist/providers/provider-abilities.js +7 -0
  289. package/dist/providers/variables.d.ts +13 -0
  290. package/dist/providers/variables.js +63 -0
  291. package/dist/scope.d.ts +11 -0
  292. package/dist/scope.js +10 -0
  293. package/dist/settings/sections.d.ts +13 -0
  294. package/dist/settings/sections.js +32 -0
  295. package/dist/state/chats.d.ts +44 -0
  296. package/dist/state/chats.js +199 -0
  297. package/dist/state/provider-accounts.d.ts +17 -0
  298. package/dist/state/provider-accounts.js +67 -0
  299. package/dist/state/providers.d.ts +17 -0
  300. package/dist/state/providers.js +33 -0
  301. package/dist/state/usage.d.ts +44 -0
  302. package/dist/state/usage.js +121 -0
  303. package/dist/storage.d.ts +20 -0
  304. package/dist/storage.js +89 -0
  305. package/dist/theme.css +196 -0
  306. package/dist/transport.d.ts +24 -0
  307. package/dist/transport.js +18 -0
  308. package/dist/ui/PlainTextarea.d.ts +2 -0
  309. package/dist/ui/PlainTextarea.js +5 -0
  310. package/dist/usage/LimitsList.d.ts +18 -0
  311. package/dist/usage/LimitsList.js +73 -0
  312. package/dist/usage/UsageBreakdown.d.ts +9 -0
  313. package/dist/usage/UsageBreakdown.js +67 -0
  314. package/dist/usage/UsageChart.d.ts +10 -0
  315. package/dist/usage/UsageChart.js +47 -0
  316. package/dist/usage/UsageDialog.d.ts +6 -0
  317. package/dist/usage/UsageDialog.js +12 -0
  318. package/dist/usage/UsageLimits.d.ts +1 -0
  319. package/dist/usage/UsageLimits.js +57 -0
  320. package/dist/usage/UsageLimitsCard.d.ts +4 -0
  321. package/dist/usage/UsageLimitsCard.js +26 -0
  322. package/dist/usage/UsagePage.d.ts +7 -0
  323. package/dist/usage/UsagePage.js +154 -0
  324. package/dist/usage/UsagePane.d.ts +3 -0
  325. package/dist/usage/UsagePane.js +21 -0
  326. package/dist/usage/UsageSummary.d.ts +12 -0
  327. package/dist/usage/UsageSummary.js +15 -0
  328. package/dist/usage/UsageTiles.d.ts +7 -0
  329. package/dist/usage/UsageTiles.js +19 -0
  330. package/dist/usage/format.d.ts +13 -0
  331. package/dist/usage/format.js +55 -0
  332. package/dist/usage/limit-groups.d.ts +22 -0
  333. package/dist/usage/limit-groups.js +86 -0
  334. package/dist/usage/limits.d.ts +4 -0
  335. package/dist/usage/limits.js +58 -0
  336. package/dist/usage/money.d.ts +1 -0
  337. package/dist/usage/money.js +13 -0
  338. package/dist/usage/summary.d.ts +34 -0
  339. package/dist/usage/summary.js +94 -0
  340. package/package.json +895 -3
  341. package/src/agents/AccountDot.tsx +14 -0
  342. package/src/agents/AgentIcon.tsx +26 -0
  343. package/src/agents/ModelOptionControl.tsx +68 -0
  344. package/src/agents/ProviderLogo.tsx +20 -0
  345. package/src/agents/account-limits.ts +61 -0
  346. package/src/agents/accounts.ts +137 -0
  347. package/src/agents/model-name.ts +70 -0
  348. package/src/agents/model-options.ts +21 -0
  349. package/src/agents/provider-paths.ts +10 -0
  350. package/src/agents/status-look.ts +43 -0
  351. package/src/chat/account-choice.ts +44 -0
  352. package/src/chat/actions.ts +72 -0
  353. package/src/chat/activity.ts +8 -0
  354. package/src/chat/attachments.ts +118 -0
  355. package/src/chat/bookmarks.ts +87 -0
  356. package/src/chat/chat-client.ts +345 -0
  357. package/src/chat/chat-references.ts +11 -0
  358. package/src/chat/drafts.ts +165 -0
  359. package/src/chat/forks.ts +33 -0
  360. package/src/chat/guards.ts +84 -0
  361. package/src/chat/logic/ansi.ts +206 -0
  362. package/src/chat/logic/answers.ts +8 -0
  363. package/src/chat/logic/bookmarks.ts +42 -0
  364. package/src/chat/logic/context-usage.ts +36 -0
  365. package/src/chat/logic/fork.ts +200 -0
  366. package/src/chat/logic/handback.ts +10 -0
  367. package/src/chat/logic/json.ts +4 -0
  368. package/src/chat/logic/limit.ts +46 -0
  369. package/src/chat/logic/resume-compaction.ts +71 -0
  370. package/src/chat/logic/scrubber.ts +289 -0
  371. package/src/chat/logic/thread-cards.ts +64 -0
  372. package/src/chat/logic/timeline-copy.ts +65 -0
  373. package/src/chat/logic/timeline-target.ts +52 -0
  374. package/src/chat/logic/timeline.ts +419 -0
  375. package/src/chat/logic/tool-catalog.ts +62 -0
  376. package/src/chat/logic/tools.ts +102 -0
  377. package/src/chat/logic/welcome.ts +13 -0
  378. package/src/chat/logic/workflow.ts +66 -0
  379. package/src/chat/mentions.ts +232 -0
  380. package/src/chat/persisted-json.ts +67 -0
  381. package/src/chat/preferences.ts +179 -0
  382. package/src/chat/quote.ts +26 -0
  383. package/src/chat/recent-messages.ts +59 -0
  384. package/src/chat/resume-compaction-dismissals.ts +17 -0
  385. package/src/chat/runtime-modes.ts +18 -0
  386. package/src/chat/stash.ts +122 -0
  387. package/src/chat/subagent-conversation.ts +203 -0
  388. package/src/chat/subagent-list.ts +141 -0
  389. package/src/chat/subagent-support.ts +21 -0
  390. package/src/chat/subagent-view.ts +137 -0
  391. package/src/chat/thumbnails.ts +108 -0
  392. package/src/chat/timeline-flash.ts +33 -0
  393. package/src/chat/timeline-scroll.ts +199 -0
  394. package/src/chat/ui/AccountPill.tsx +31 -0
  395. package/src/chat/ui/AnsiOutput.tsx +18 -0
  396. package/src/chat/ui/BookmarkMarker.tsx +108 -0
  397. package/src/chat/ui/BookmarkSubmenu.tsx +53 -0
  398. package/src/chat/ui/ChatActivity.tsx +198 -0
  399. package/src/chat/ui/ChatReferenceChip.tsx +28 -0
  400. package/src/chat/ui/CodeBlock.tsx +175 -0
  401. package/src/chat/ui/Composer.tsx +1326 -0
  402. package/src/chat/ui/ComposerInput.tsx +187 -0
  403. package/src/chat/ui/DiffPool.tsx +36 -0
  404. package/src/chat/ui/EditDiff.tsx +30 -0
  405. package/src/chat/ui/FadingWords.tsx +18 -0
  406. package/src/chat/ui/ImageView.tsx +159 -0
  407. package/src/chat/ui/LimitState.tsx +165 -0
  408. package/src/chat/ui/Markdown.tsx +258 -0
  409. package/src/chat/ui/MessageActions.tsx +88 -0
  410. package/src/chat/ui/Pickers.tsx +272 -0
  411. package/src/chat/ui/PromptComposer.tsx +119 -0
  412. package/src/chat/ui/QuoteButton.tsx +103 -0
  413. package/src/chat/ui/ResumeCompactionDock.tsx +28 -0
  414. package/src/chat/ui/RunSettings.tsx +546 -0
  415. package/src/chat/ui/Scrubber.tsx +265 -0
  416. package/src/chat/ui/SubagentControls.tsx +66 -0
  417. package/src/chat/ui/SubagentInfo.tsx +67 -0
  418. package/src/chat/ui/SubagentStopButton.tsx +45 -0
  419. package/src/chat/ui/SubagentTimeline.tsx +170 -0
  420. package/src/chat/ui/Timeline.tsx +628 -0
  421. package/src/chat/ui/TimelineMenu.tsx +124 -0
  422. package/src/chat/ui/UnifiedDiff.tsx +99 -0
  423. package/src/chat/ui/UploadThumb.tsx +13 -0
  424. package/src/chat/ui/Welcome.tsx +22 -0
  425. package/src/chat/ui/chips.ts +15 -0
  426. package/src/chat/ui/code-lines.ts +60 -0
  427. package/src/chat/ui/code-streaming.ts +4 -0
  428. package/src/chat/ui/code-theme.ts +15 -0
  429. package/src/chat/ui/composer/chips.ts +43 -0
  430. package/src/chat/ui/composer/editor.ts +118 -0
  431. package/src/chat/ui/composer/insert.ts +22 -0
  432. package/src/chat/ui/composer/keys.ts +144 -0
  433. package/src/chat/ui/diff-theme.ts +23 -0
  434. package/src/chat/ui/file-links.ts +24 -0
  435. package/src/chat/ui/find-reveal.ts +23 -0
  436. package/src/chat/ui/full-diff.ts +25 -0
  437. package/src/chat/ui/icons.tsx +24 -0
  438. package/src/chat/ui/markdown-blocks.ts +123 -0
  439. package/src/chat/ui/quote-selection.ts +53 -0
  440. package/src/chat/ui/rehype-chips.ts +62 -0
  441. package/src/chat/ui/rehype-fade.ts +64 -0
  442. package/src/chat/ui/remark-html-as-text.ts +36 -0
  443. package/src/chat/ui/reply-context.ts +4 -0
  444. package/src/chat/ui/reveal.ts +135 -0
  445. package/src/chat/ui/rows/ForksRow.tsx +79 -0
  446. package/src/chat/ui/rows/MessageRows.tsx +415 -0
  447. package/src/chat/ui/rows/Rows.tsx +81 -0
  448. package/src/chat/ui/rows/SubagentRow.tsx +209 -0
  449. package/src/chat/ui/rows/TaskRow.tsx +91 -0
  450. package/src/chat/ui/rows/WorkRows.tsx +445 -0
  451. package/src/chat/ui/rows/WorkflowRow.tsx +120 -0
  452. package/src/chat/ui/rows/row-rhythm.ts +37 -0
  453. package/src/chat/ui/useToggleSet.ts +39 -0
  454. package/src/host.ts +324 -0
  455. package/src/lazy.tsx +64 -0
  456. package/src/locales/en/agent-chat.json +446 -0
  457. package/src/locales/en/agent-prompts.json +42 -0
  458. package/src/locales/en/agent-providers.json +131 -0
  459. package/src/locales/en/agent-usage.json +131 -0
  460. package/src/locales/nl/agent-chat.json +446 -0
  461. package/src/locales/nl/agent-prompts.json +42 -0
  462. package/src/locales/nl/agent-providers.json +131 -0
  463. package/src/locales/nl/agent-usage.json +131 -0
  464. package/src/locales.ts +33 -0
  465. package/src/mounted-registry.ts +45 -0
  466. package/src/port-transport.ts +146 -0
  467. package/src/prompts/logic/focus.ts +25 -0
  468. package/src/prompts/logic/keys.ts +116 -0
  469. package/src/prompts/logic/prompts.fixtures.ts +84 -0
  470. package/src/prompts/logic/prompts.ts +58 -0
  471. package/src/prompts/logic/subjects.ts +122 -0
  472. package/src/prompts/logic/useFocusAfterAnswer.ts +38 -0
  473. package/src/prompts/logic/usePromptSession.ts +114 -0
  474. package/src/prompts/ui/ApprovalBody.tsx +109 -0
  475. package/src/prompts/ui/PromptActions.tsx +109 -0
  476. package/src/prompts/ui/PromptCard.tsx +101 -0
  477. package/src/prompts/ui/PromptView.tsx +111 -0
  478. package/src/prompts/ui/QuestionBody.tsx +174 -0
  479. package/src/providers/AccountColors.tsx +8 -0
  480. package/src/providers/AccountDetail.tsx +209 -0
  481. package/src/providers/AccountVariables.tsx +126 -0
  482. package/src/providers/AddAccountForm.tsx +121 -0
  483. package/src/providers/CliDetail.tsx +157 -0
  484. package/src/providers/ProvidersPane.tsx +236 -0
  485. package/src/providers/account-actions.ts +67 -0
  486. package/src/providers/parts.tsx +25 -0
  487. package/src/providers/provider-abilities.ts +9 -0
  488. package/src/providers/variables.ts +87 -0
  489. package/src/scope.ts +30 -0
  490. package/src/settings/sections.ts +47 -0
  491. package/src/state/chats.ts +279 -0
  492. package/src/state/provider-accounts.ts +87 -0
  493. package/src/state/providers.ts +50 -0
  494. package/src/state/usage.ts +196 -0
  495. package/src/storage.ts +113 -0
  496. package/src/theme.css +196 -0
  497. package/src/transport.ts +50 -0
  498. package/src/ui/PlainTextarea.tsx +6 -0
  499. package/src/usage/LimitsList.tsx +232 -0
  500. package/src/usage/UsageBreakdown.tsx +210 -0
  501. package/src/usage/UsageChart.tsx +113 -0
  502. package/src/usage/UsageDialog.tsx +20 -0
  503. package/src/usage/UsageLimits.tsx +145 -0
  504. package/src/usage/UsageLimitsCard.tsx +36 -0
  505. package/src/usage/UsagePage.tsx +276 -0
  506. package/src/usage/UsagePane.tsx +52 -0
  507. package/src/usage/UsageSummary.tsx +50 -0
  508. package/src/usage/UsageTiles.tsx +34 -0
  509. package/src/usage/format.ts +71 -0
  510. package/src/usage/limit-groups.ts +119 -0
  511. package/src/usage/limits.ts +71 -0
  512. package/src/usage/money.ts +14 -0
  513. package/src/usage/summary.ts +137 -0
@@ -0,0 +1,118 @@
1
+ import i18next from 'i18next';
2
+ import {
3
+ attachmentBytes,
4
+ attachmentImageMime,
5
+ CHAT_ATTACHMENTS_MAX_BYTES,
6
+ CHAT_ATTACHMENTS_MAX_COUNT,
7
+ CHAT_ATTACHMENT_MAX_BYTES,
8
+ type ChatAttachment,
9
+ type ChatAttachmentUpload
10
+ } from '@adecore/agent-contracts';
11
+ import { formatBytes as bytesOf } from '@adecore/ui/format';
12
+
13
+ interface IncomingFile {
14
+ name: string;
15
+ mime: string;
16
+ // Decoded size; the base64 the wire carries is a third larger.
17
+ bytes: number;
18
+ }
19
+
20
+ interface AttachmentCheck<T extends IncomingFile> {
21
+ accepted: T[];
22
+ rejected: Array<{ name: string; reason: string }>;
23
+ }
24
+
25
+ export function isImageAttachment(mime: string): boolean {
26
+ return mime.startsWith('image/');
27
+ }
28
+
29
+ /* The type on a file card: the extension, as long as it is short enough to read as one. */
30
+ export function fileBadge(name: string): string | null {
31
+ const dot = name.lastIndexOf('.');
32
+ if (dot <= 0 || dot === name.length - 1) {
33
+ return null;
34
+ }
35
+ const extension = name.slice(dot + 1);
36
+ return extension.length <= 5 ? extension.toUpperCase() : null;
37
+ }
38
+
39
+ /* Whole kilobytes, which is all a row under a file name has room for, and a decimal once it runs into megabytes. */
40
+ export function formatBytes(bytes: number): string {
41
+ return bytesOf(bytes, bytes < 1024 * 1024);
42
+ }
43
+
44
+ /* Which of the incoming files fit next to what the composer already holds, and why the rest do not. */
45
+ export function checkAttachmentLimits<T extends IncomingFile>(current: number, incoming: T[], currentBytes = 0): AttachmentCheck<T> {
46
+ const accepted: T[] = [];
47
+ const rejected: Array<{ name: string; reason: string }> = [];
48
+ let bytes = currentBytes;
49
+ for (const file of incoming) {
50
+ if (file.bytes === 0) {
51
+ rejected.push({ name: file.name, reason: i18next.t('agent-chat:attachments.rejected.empty') });
52
+ } else if (file.bytes > CHAT_ATTACHMENT_MAX_BYTES) {
53
+ rejected.push({ name: file.name, reason: i18next.t('agent-chat:attachments.rejected.tooLarge', { size: formatBytes(CHAT_ATTACHMENT_MAX_BYTES) }) });
54
+ } else if (current + accepted.length >= CHAT_ATTACHMENTS_MAX_COUNT) {
55
+ rejected.push({ name: file.name, reason: i18next.t('agent-chat:attachments.rejected.tooMany', { count: CHAT_ATTACHMENTS_MAX_COUNT }) });
56
+ } else if (bytes + file.bytes > CHAT_ATTACHMENTS_MAX_BYTES) {
57
+ rejected.push({
58
+ name: file.name,
59
+ reason: i18next.t('agent-chat:attachments.rejected.tooHeavy', { size: formatBytes(CHAT_ATTACHMENTS_MAX_BYTES) })
60
+ });
61
+ } else {
62
+ accepted.push(file);
63
+ bytes += file.bytes;
64
+ }
65
+ }
66
+ return { accepted, rejected };
67
+ }
68
+
69
+ /* The files in a paste or a drop; a paste of plain text gives none. */
70
+ export function filesOf(transfer: DataTransfer | null): File[] {
71
+ return transfer ? [...transfer.files] : [];
72
+ }
73
+
74
+ function readAsBase64(file: File): Promise<string> {
75
+ return new Promise((resolve, reject) => {
76
+ const reader = new FileReader();
77
+ reader.onerror = () => reject(reader.error ?? new Error(i18next.t('agent-chat:attachments.unreadable', { name: file.name })));
78
+ reader.onload = () => {
79
+ const url = String(reader.result);
80
+ resolve(url.slice(url.indexOf(',') + 1));
81
+ };
82
+ reader.readAsDataURL(file);
83
+ });
84
+ }
85
+
86
+ // A pasted screenshot has no name; the clock gives it one so the row and the agent can tell them apart.
87
+ function nameFor(file: File, index: number): string {
88
+ return file.name || `pasted-${Date.now()}-${index + 1}.${file.type.split('/')[1] ?? 'bin'}`;
89
+ }
90
+
91
+ export async function readAttachments(files: File[]): Promise<ChatAttachmentUpload[]> {
92
+ return Promise.all(
93
+ files.map(async (file, index) => ({
94
+ name: nameFor(file, index),
95
+ mime: attachmentImageMime({ name: file.name, mime: file.type }) ?? (file.type || 'application/octet-stream'),
96
+ data: await readAsBase64(file)
97
+ }))
98
+ );
99
+ }
100
+
101
+ /* Files the host already stored for a chat, back as uploads the composer can hold again. */
102
+ export async function readStoredAttachments(
103
+ read: (attachment: ChatAttachment) => Promise<Blob>,
104
+ stored: readonly ChatAttachment[]
105
+ ): Promise<ChatAttachmentUpload[]> {
106
+ const files = await Promise.all(stored.map(async (attachment) => new File([await read(attachment)], attachment.name, { type: attachment.mime })));
107
+ return readAttachments(files);
108
+ }
109
+
110
+ /* A preview of a file the composer still holds; the bytes have not reached the host yet. */
111
+ export function uploadPreviewUrl(upload: ChatAttachmentUpload): string {
112
+ return `data:${upload.mime};base64,${upload.data}`;
113
+ }
114
+
115
+ /* What the file weighs, from the base64 the composer is holding: three bytes per four characters. */
116
+ export function uploadBytes(upload: ChatAttachmentUpload): number {
117
+ return attachmentBytes(upload.data);
118
+ }
@@ -0,0 +1,87 @@
1
+ import i18next from 'i18next';
2
+ import { create } from 'zustand';
3
+ import type { ChatBookmark } from '@adecore/agent-contracts';
4
+ import { chatHost } from '../host';
5
+ import type { ChatScope } from '../scope';
6
+ import { jumpToTimelineItem } from './timeline-scroll';
7
+
8
+ /* The one message in the window whose bookmark has its name field open, by the scope's key of its chat. */
9
+ interface BookmarkNaming {
10
+ chatKey: string | null;
11
+ itemId: string | null;
12
+ open(chatKey: string, itemId: string): void;
13
+ close(): void;
14
+ }
15
+
16
+ export const useBookmarkNaming = create<BookmarkNaming>((set) => ({
17
+ chatKey: null,
18
+ itemId: null,
19
+ open: (chatKey, itemId) => set({ chatKey, itemId }),
20
+ close: () => set({ chatKey: null, itemId: null })
21
+ }));
22
+
23
+ function failed(title: string, e: unknown): void {
24
+ chatHost().notify({ title, description: e instanceof Error ? e.message : String(e), kind: 'error' });
25
+ }
26
+
27
+ async function add(scope: ChatScope, chatId: string, itemId: string, name?: string): Promise<boolean> {
28
+ try {
29
+ await scope.chats.addBookmark(chatId, itemId, name);
30
+ return true;
31
+ } catch (e) {
32
+ failed(i18next.t('agent-chat:bookmarks.addFailed'), e);
33
+ return false;
34
+ }
35
+ }
36
+
37
+ /* Marks a message and opens the field for its name at once, before the host answered. */
38
+ export async function placeBookmark(scope: ChatScope, chatId: string, itemId: string): Promise<void> {
39
+ useBookmarkNaming.getState().open(scope.keyOf(chatId), itemId);
40
+ if (!(await add(scope, chatId, itemId))) {
41
+ useBookmarkNaming.getState().close();
42
+ }
43
+ }
44
+
45
+ /*
46
+ * The name a field settles on. A name goes through `addBookmark`, which also names a bookmark that
47
+ * already stands, so a name typed before the mark itself landed is not refused.
48
+ */
49
+ export async function nameBookmark(scope: ChatScope, chatId: string, itemId: string, name: string, previous: string | undefined): Promise<void> {
50
+ const next = name.trim();
51
+ if (next === (previous ?? '')) {
52
+ return;
53
+ }
54
+ if (next !== '') {
55
+ await add(scope, chatId, itemId, next);
56
+ return;
57
+ }
58
+ try {
59
+ await scope.chats.renameBookmark(chatId, itemId, '');
60
+ } catch (e) {
61
+ failed(i18next.t('agent-chat:bookmarks.renameFailed'), e);
62
+ }
63
+ }
64
+
65
+ /* Takes a bookmark away at once; the toast puts it back, name and all. */
66
+ export async function removeBookmark(scope: ChatScope, chatId: string, bookmark: ChatBookmark): Promise<void> {
67
+ try {
68
+ await scope.chats.removeBookmark(chatId, bookmark.itemId);
69
+ } catch (e) {
70
+ failed(i18next.t('agent-chat:bookmarks.removeFailed'), e);
71
+ return;
72
+ }
73
+ chatHost().notify({
74
+ title: i18next.t('agent-chat:bookmarks.removed'),
75
+ kind: 'deleted',
76
+ action: { label: i18next.t('agent-chat:common.action.undo'), run: () => void add(scope, chatId, bookmark.itemId, bookmark.name) }
77
+ });
78
+ }
79
+
80
+ /* Scrolls the chat's thread to the message, and opens its name field for a rename. False when no thread of it is on screen yet. */
81
+ export function goToBookmark(scope: ChatScope, chatId: string, itemId: string, rename = false): boolean {
82
+ const key = scope.keyOf(chatId);
83
+ if (rename) {
84
+ useBookmarkNaming.getState().open(key, itemId);
85
+ }
86
+ return jumpToTimelineItem(key, itemId);
87
+ }
@@ -0,0 +1,345 @@
1
+ import type {
2
+ AgentKind,
3
+ ChatAttachmentUpload,
4
+ ChatAttachResult,
5
+ ChatBookmark,
6
+ ChatPreferencesPayload,
7
+ ChatSkill,
8
+ ModelSelection,
9
+ ProviderInfo,
10
+ RuntimeMode
11
+ } from '@adecore/agent-contracts';
12
+ import { MountedRegistry, type MountedEntry } from '../mounted-registry';
13
+ import type { ChatSink } from '../state/chats';
14
+ import { errorCode, isConnectionError, type ChatTransport, type ChatTransportStatus } from '../transport';
15
+
16
+ interface ChatOpenOptions {
17
+ /* Which agent CLI answers; a chat that exists on the host keeps its own. */
18
+ provider?: AgentKind;
19
+ /* The account of that CLI; a chat that exists on the host keeps its own. */
20
+ account?: string;
21
+ cwd?: string;
22
+ /* A CLI session to continue, for a chat opened from a terminal that ran the agent. */
23
+ resume?: string;
24
+ /* What a fresh chat starts with; an existing chat keeps what it has. */
25
+ selection?: ModelSelection;
26
+ runtimeMode?: RuntimeMode;
27
+ }
28
+
29
+ export interface ChatSendExtras {
30
+ /* Paths picked with `@`; they also sit in the text, this is what the timeline highlights. */
31
+ mentions?: string[];
32
+ /* Skills picked with `$`; they also sit in the text, this is what the timeline chips. */
33
+ skills?: string[];
34
+ /* Chats of the project picked with `@`; only their ids travel, the agent reads them itself. */
35
+ chats?: string[];
36
+ attachments?: ChatAttachmentUpload[];
37
+ }
38
+
39
+ interface Mounted extends ChatOpenOptions, MountedEntry {
40
+ // The last place in the chat's stream the store holds, so a reattach asks only for what came after.
41
+ seq?: number;
42
+ }
43
+
44
+ /* How many items a chat reads at once, on attach and for every page before it. */
45
+ const HISTORY_PAGE = 60;
46
+
47
+ interface ProviderSink {
48
+ setProviders(providers: ProviderInfo[]): void;
49
+ }
50
+
51
+ /*
52
+ * One chat per chat id on one host. A chat opens on mount and detaches on unmount, and every mounted
53
+ * chat is attached again when the transport comes back. The provider list is fetched once per
54
+ * connection and handed to its own store. The transport it is given never changes hosts, so a
55
+ * reattach stays on the host these chats run on.
56
+ */
57
+ export class ChatClient {
58
+ private readonly transport: ChatTransport;
59
+ private readonly sink: ChatSink;
60
+ private readonly providers: ProviderSink | null;
61
+ private readonly mounted = new MountedRegistry<Mounted>();
62
+ private readonly unsubscribe: Array<() => void> = [];
63
+ private readonly earlier = new Map<string, Promise<void>>();
64
+ private preferences: ChatPreferencesPayload | null = null;
65
+
66
+ constructor(transport: ChatTransport, sink: ChatSink, providers: ProviderSink | null = null) {
67
+ this.transport = transport;
68
+ this.sink = sink;
69
+ this.providers = providers;
70
+ this.unsubscribe.push(
71
+ transport.on('chat.event', ({ chatId, event, seq }) => {
72
+ this.sink.apply(chatId, event);
73
+ const entry = this.mounted.get(chatId);
74
+ if (entry && seq !== undefined) {
75
+ entry.seq = seq;
76
+ }
77
+ }),
78
+ // Every chat on the machine, attached or not, so a node waiting on a person says so on a view nobody has open.
79
+ transport.on('chat.status', ({ chatId, info }) => this.sink.status(chatId, info)),
80
+ transport.on('chat.bookmarks', ({ chatId, bookmarks }) => this.sink.bookmarks(chatId, bookmarks)),
81
+ transport.subscribeStatus((status) => this.onStatus(status))
82
+ );
83
+ if (transport.status === 'open') {
84
+ void this.loadProviders();
85
+ void this.loadStatuses();
86
+ }
87
+ }
88
+
89
+ private readonly inspections = new Map<string, Promise<ChatAttachResult>>();
90
+
91
+ inspect(chatId: string): Promise<ChatAttachResult> {
92
+ const pending = this.inspections.get(chatId);
93
+ if (pending) {
94
+ return pending;
95
+ }
96
+ const read = this.transport.request('chat.attach', { chatId, historyLimit: 100 }).finally(async () => {
97
+ this.inspections.delete(chatId);
98
+ // A visible chat owns its attachment; an inspection must never release it.
99
+ if (!this.mounted.get(chatId)) {
100
+ await this.transport.request('chat.detach', { chatId }).catch(() => undefined);
101
+ }
102
+ });
103
+ this.inspections.set(chatId, read);
104
+ return read;
105
+ }
106
+
107
+ /* Answers false when the transport is not connected; the chat opens once it is. */
108
+ async open(chatId: string, options: ChatOpenOptions): Promise<boolean> {
109
+ this.mounted.set(chatId, { ...options, attached: false });
110
+ try {
111
+ await this.attach(chatId);
112
+ return true;
113
+ } catch (e) {
114
+ if (isConnectionError(e)) {
115
+ return false;
116
+ }
117
+ throw e;
118
+ }
119
+ }
120
+
121
+ /*
122
+ * Points a chat that has not spoken yet at another CLI. The host fixes a chat's provider when
123
+ * it registers the chat, so the only way over is to drop the empty one and register it again.
124
+ * The row stays until the attach of the new one replaces it, so a view keeps what it drew.
125
+ */
126
+ async retarget(chatId: string, provider: AgentKind, selection: ModelSelection): Promise<void> {
127
+ const entry = this.mounted.get(chatId);
128
+ if (!entry) {
129
+ return;
130
+ }
131
+ // The account belonged to the CLI the chat leaves, and the seq to its stream: the new chat's
132
+ // stream starts over, so offering it the old seq would read as nothing missed and reset nothing.
133
+ const { attached: _attached, account: _account, seq: _seq, ...options } = entry;
134
+ this.mounted.delete(chatId);
135
+ await this.transport.request('chat.kill', { chatId });
136
+ try {
137
+ await this.open(chatId, { ...options, provider, selection });
138
+ } catch (e) {
139
+ this.sink.forget(chatId);
140
+ throw e;
141
+ }
142
+ }
143
+
144
+ /*
145
+ * Tells the host what a chat it starts on its own is made with while this socket is connected,
146
+ * since no client mounting that chat sends a composer preference. Said again on every fresh socket.
147
+ */
148
+ setPreferences(preferences: ChatPreferencesPayload): void {
149
+ this.preferences = preferences;
150
+ this.sendPreferences();
151
+ }
152
+
153
+ async detach(chatId: string): Promise<void> {
154
+ const entry = this.mounted.get(chatId);
155
+ this.mounted.delete(chatId);
156
+ if (!entry?.attached) {
157
+ return;
158
+ }
159
+ try {
160
+ await this.transport.request('chat.detach', { chatId });
161
+ } catch {
162
+ // The socket closing detaches every chat server-side anyway.
163
+ }
164
+ }
165
+
166
+ /* Current hosts return the stable turn id; older compatible ones only report whether the message queued. */
167
+ async send(chatId: string, text: string, extras: ChatSendExtras = {}): Promise<{ queued: boolean; turnId?: string }> {
168
+ return this.transport.request('chat.send', {
169
+ chatId,
170
+ text,
171
+ mentions: extras.mentions,
172
+ skills: extras.skills,
173
+ chats: extras.chats,
174
+ attachments: extras.attachments
175
+ });
176
+ }
177
+
178
+ /* Marks a message; the list every client of the chat holds comes back as `chat.bookmarks`. */
179
+ async addBookmark(chatId: string, itemId: string, name?: string): Promise<ChatBookmark[]> {
180
+ const { bookmarks } = await this.transport.request('chat.addBookmark', { chatId, itemId, ...(name === undefined ? {} : { name }) });
181
+ return bookmarks;
182
+ }
183
+
184
+ /* An empty name takes the name away. */
185
+ async renameBookmark(chatId: string, itemId: string, name: string): Promise<ChatBookmark[]> {
186
+ const { bookmarks } = await this.transport.request('chat.renameBookmark', { chatId, itemId, name });
187
+ return bookmarks;
188
+ }
189
+
190
+ async removeBookmark(chatId: string, itemId: string): Promise<ChatBookmark[]> {
191
+ const { bookmarks } = await this.transport.request('chat.removeBookmark', { chatId, itemId });
192
+ return bookmarks;
193
+ }
194
+
195
+ /* What this chat's CLI would run as a skill, for the composer's `$` picker. */
196
+ async listSkills(chatId: string): Promise<ChatSkill[]> {
197
+ const { skills } = await this.transport.request('skills.list', { chatId });
198
+ return skills;
199
+ }
200
+
201
+ /* Starts the chat over; refused with `chat-busy` while a turn runs, unless `force` stops that turn. */
202
+ async clear(chatId: string, force = false): Promise<void> {
203
+ await this.transport.request('chat.clear', { chatId, ...(force ? { force: true } : {}) });
204
+ }
205
+
206
+ async kill(chatId: string): Promise<void> {
207
+ this.mounted.delete(chatId);
208
+ this.sink.forget(chatId);
209
+ await this.transport.request('chat.kill', { chatId });
210
+ }
211
+
212
+ /*
213
+ * What every chat on this machine is doing right now. `chat.status` only carries a change, so a
214
+ * window that just opened or came back from a lost socket asks for the standing answer.
215
+ */
216
+ async loadStatuses(): Promise<void> {
217
+ try {
218
+ const { chats } = await this.transport.request('chat.list', {});
219
+ for (const info of chats) {
220
+ this.sink.status(info.chatId, info);
221
+ }
222
+ } catch {
223
+ // The next status event says it instead; an older host never answers this at all.
224
+ }
225
+ }
226
+
227
+ async loadProviders(): Promise<void> {
228
+ if (!this.providers) {
229
+ return;
230
+ }
231
+ try {
232
+ const { providers } = await this.transport.request('provider.list', {});
233
+ this.providers.setProviders(providers);
234
+ } catch {
235
+ // The list comes with the next connection; pickers show what they had.
236
+ }
237
+ }
238
+
239
+ /*
240
+ * The page before the oldest one this chat holds, one request per chat at a time. A cursor the
241
+ * host no longer knows means the thread changed under it, so the chat is read again from its end.
242
+ */
243
+ loadEarlier(chatId: string, cursor: string): Promise<void> {
244
+ const running = this.earlier.get(chatId);
245
+ if (running) {
246
+ return running;
247
+ }
248
+ const read = this.transport
249
+ .request('chat.history', { chatId, cursor, limit: HISTORY_PAGE })
250
+ .then((page) => this.sink.prepend(chatId, cursor, page))
251
+ .catch(async (e: unknown) => {
252
+ const entry = this.mounted.get(chatId);
253
+ if (errorCode(e) !== 'history-expired' || !entry) {
254
+ throw e;
255
+ }
256
+ delete entry.seq;
257
+ await this.attach(chatId);
258
+ })
259
+ .finally(() => this.earlier.delete(chatId));
260
+ this.earlier.set(chatId, read);
261
+ return read;
262
+ }
263
+
264
+ isMounted(chatId: string): boolean {
265
+ return this.mounted.has(chatId);
266
+ }
267
+
268
+ /* Lets go of the machine; the chats keep running there, the host only stops streaming them here. */
269
+ dispose(): void {
270
+ for (const off of this.unsubscribe) {
271
+ off();
272
+ }
273
+ this.unsubscribe.length = 0;
274
+ for (const chatId of [...this.mounted.keys()]) {
275
+ void this.detach(chatId);
276
+ }
277
+ }
278
+
279
+ /*
280
+ * A reattach offers the last seq the store holds. The host answers only what came after it when
281
+ * it still has all of that, and the newest page of the thread otherwise, so a short drop costs a
282
+ * few events.
283
+ */
284
+ private async attach(chatId: string): Promise<void> {
285
+ const entry = this.mounted.get(chatId);
286
+ if (!entry) {
287
+ return;
288
+ }
289
+ await this.transport.request('chat.create', {
290
+ chatId,
291
+ provider: entry.provider,
292
+ account: entry.account,
293
+ cwd: entry.cwd,
294
+ resume: entry.resume,
295
+ selection: entry.selection,
296
+ runtimeMode: entry.runtimeMode
297
+ });
298
+ // Closed or opened again in the meantime: an opening that is still there attaches itself.
299
+ if (this.mounted.get(chatId) !== entry) {
300
+ return;
301
+ }
302
+ const since = entry.seq;
303
+ const result = await this.transport.request('chat.attach', { chatId, historyLimit: HISTORY_PAGE, ...(since === undefined ? {} : { since }) });
304
+ const current = this.mounted.get(chatId);
305
+ if (current !== entry) {
306
+ // Its detach found nothing attached yet, and the host would stream this chat here until the socket closes.
307
+ if (!current) {
308
+ await this.transport.request('chat.detach', { chatId }).catch(() => undefined);
309
+ }
310
+ return;
311
+ }
312
+ entry.attached = true;
313
+ if (result.seq !== undefined) {
314
+ entry.seq = result.seq;
315
+ }
316
+ if (result.events && since !== undefined) {
317
+ for (const event of result.events) {
318
+ this.sink.apply(chatId, event);
319
+ }
320
+ } else {
321
+ const page = result.history === undefined ? undefined : { history: result.history, pending: result.pending ?? [] };
322
+ this.sink.reset(chatId, result.info, result.items, page);
323
+ }
324
+ // A host without bookmarks sends none, and then this chat has none.
325
+ this.sink.bookmarks(chatId, result.bookmarks ?? []);
326
+ }
327
+
328
+ private sendPreferences(): void {
329
+ if (this.preferences === null) {
330
+ return;
331
+ }
332
+ void this.transport.request('chat.setPreferences', this.preferences).catch(() => undefined);
333
+ }
334
+
335
+ private onStatus(status: ChatTransportStatus): void {
336
+ if (status === 'open') {
337
+ this.sendPreferences();
338
+ void this.loadProviders();
339
+ void this.loadStatuses();
340
+ void this.mounted.reattachAll((chatId) => this.attach(chatId).then(() => undefined));
341
+ return;
342
+ }
343
+ this.mounted.detachAll();
344
+ }
345
+ }
@@ -0,0 +1,11 @@
1
+ /* Another chat a message may point at, under the name it has right now. */
2
+ export interface ProjectChat {
3
+ id: string;
4
+ title: string;
5
+ }
6
+
7
+ /* The chats an `@query` offers: never the chat typing it or one already attached. */
8
+ export function chatSuggestions(chats: readonly ProjectChat[], query: string, exclude: readonly string[], limit: number): ProjectChat[] {
9
+ const needle = query.toLowerCase();
10
+ return chats.filter((chat) => !exclude.includes(chat.id) && chat.title.toLowerCase().includes(needle)).slice(0, limit);
11
+ }