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

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 +61 -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 +212 -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 +262 -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 +212 -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,102 @@
1
+ import type { ChatFileChange, ChatToolItem } from '@adecore/agent-contracts';
2
+ import { isRecord } from './json';
3
+ import { toolEntry } from './tool-catalog';
4
+
5
+ export interface FileChange {
6
+ path: string;
7
+ before: string;
8
+ after: string;
9
+ }
10
+
11
+ function str(value: unknown): string | null {
12
+ return typeof value === 'string' ? value : null;
13
+ }
14
+
15
+ /* The one line that says what a tool call is about: the command, the file, the pattern. */
16
+ export function toolSummary(name: string, input: unknown): string {
17
+ if (!isRecord(input)) {
18
+ return '';
19
+ }
20
+ const known = toolEntry(name);
21
+ if (known === undefined) {
22
+ // A tool nobody wrote down, an MCP one above all, gets its best guess from the first string it was given.
23
+ const first = Object.values(input).find((value) => typeof value === 'string');
24
+ return typeof first === 'string' ? first : '';
25
+ }
26
+ for (const key of known.summary) {
27
+ const value = str(input[key]);
28
+ if (value !== null) {
29
+ return value;
30
+ }
31
+ }
32
+ return '';
33
+ }
34
+
35
+ // What `GET /fs/file` will serve; a path with another suffix is not worth asking the host about.
36
+ const IMAGE_SUFFIXES = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];
37
+
38
+ /* The image file a tool call looked at, so the row can draw it; null for every other call. */
39
+ export function readImagePath(name: string, input: unknown): string | null {
40
+ if (toolEntry(name)?.readsImage !== true) {
41
+ return null;
42
+ }
43
+ const path = isRecord(input) ? str(input.file_path) : null;
44
+ if (path === null) {
45
+ return null;
46
+ }
47
+ const lowered = path.toLowerCase();
48
+ return IMAGE_SUFFIXES.some((suffix) => lowered.endsWith(suffix)) ? path : null;
49
+ }
50
+
51
+ /* The before and after text of a file-changing tool call, so the thread can show it as a diff. */
52
+ export function fileChanges(name: string, input: unknown): FileChange[] {
53
+ if (!isRecord(input)) {
54
+ return [];
55
+ }
56
+ const path = str(input.file_path) ?? '';
57
+ if (name === 'Edit') {
58
+ return [{ path, before: str(input.old_string) ?? '', after: str(input.new_string) ?? '' }];
59
+ }
60
+ if (name === 'Write') {
61
+ return [{ path, before: '', after: str(input.content) ?? '' }];
62
+ }
63
+ if (name === 'MultiEdit' && Array.isArray(input.edits)) {
64
+ return input.edits.filter(isRecord).map((edit) => ({ path, before: str(edit.old_string) ?? '', after: str(edit.new_string) ?? '' }));
65
+ }
66
+ return [];
67
+ }
68
+
69
+ export function isFileChange(name: string): boolean {
70
+ return toolEntry(name)?.changesFiles === true;
71
+ }
72
+
73
+ /* The unified diffs a provider put next to a tool call; empty for one that reports before and after. */
74
+ export function unifiedChanges(tool: ChatToolItem): ChatFileChange[] {
75
+ return (tool.changes ?? []).filter((change) => change.diff !== '');
76
+ }
77
+
78
+ /* The same, for the copy an approval carries so the person can read what they are approving. */
79
+ export function approvalChanges(input: unknown): ChatFileChange[] {
80
+ const changes = isRecord(input) ? input.changes : null;
81
+ if (!Array.isArray(changes)) {
82
+ return [];
83
+ }
84
+ return changes.filter(
85
+ (change): change is ChatFileChange => isRecord(change) && typeof change.path === 'string' && typeof change.diff === 'string' && change.diff !== ''
86
+ );
87
+ }
88
+
89
+ /* Whether a settled call is worth a row in the turn's changed files card. */
90
+ export function hasFileChanges(tool: ChatToolItem): boolean {
91
+ return unifiedChanges(tool).length > 0 || fileChanges(tool.name, tool.input).length > 0;
92
+ }
93
+
94
+ /* When a running call started: what the CLI reported, or the moment the call appeared. */
95
+ export function toolStartedAt(tool: ChatToolItem): number {
96
+ return tool.progress?.startedAt ?? tool.createdAt;
97
+ }
98
+
99
+ // Keep the full buffer so the renderer can carry ANSI state into the visible tail.
100
+ export function liveOutput(tool: ChatToolItem): string | null {
101
+ return tool.progress?.output || null;
102
+ }
@@ -0,0 +1,13 @@
1
+ /* The part of the day a greeting names, on the local clock; the night runs until five. */
2
+ export type DayPart = 'morning' | 'afternoon' | 'evening' | 'night';
3
+
4
+ export function dayPartOf(now: Date): DayPart {
5
+ const hour = now.getHours();
6
+ if (hour < 5) {
7
+ return 'night';
8
+ }
9
+ if (hour < 12) {
10
+ return 'morning';
11
+ }
12
+ return hour < 18 ? 'afternoon' : 'evening';
13
+ }
@@ -0,0 +1,66 @@
1
+ import i18next from 'i18next';
2
+ import type { ChatSubagentStatus, ChatWorkflow, ChatWorkflowAgent } from '@adecore/agent-contracts';
3
+ import { formatElapsedShort } from '@adecore/ui/format';
4
+
5
+ /* `waiting` has not started yet; `stopped` was still at work by the last report when the workflow ended. */
6
+ export type WorkflowAgentState = ChatSubagentStatus | 'waiting' | 'stopped';
7
+
8
+ export interface WorkflowAgentView {
9
+ agent: ChatWorkflowAgent;
10
+ state: WorkflowAgentState;
11
+ }
12
+
13
+ export interface WorkflowPhaseView {
14
+ key: string;
15
+ /* The phase's own title; null for agents outside any phase the script announced. */
16
+ title: string | null;
17
+ agents: WorkflowAgentView[];
18
+ }
19
+
20
+ export function workflowAgentState(agent: ChatWorkflowAgent, workflowRunning: boolean): WorkflowAgentState {
21
+ if (agent.status !== 'running') {
22
+ return agent.status;
23
+ }
24
+ if (!workflowRunning) {
25
+ return 'stopped';
26
+ }
27
+ return agent.startedAt === null ? 'waiting' : 'running';
28
+ }
29
+
30
+ /*
31
+ * The phases in the order the script announced them, each with the agents it started in it. A phase
32
+ * announced ahead of its agents is there without any, and agents of no known phase come first, untitled.
33
+ */
34
+ export function workflowPhases(workflow: ChatWorkflow, workflowRunning: boolean): WorkflowPhaseView[] {
35
+ const known = new Map(workflow.phases.map((phase) => [phase.index, phase]));
36
+ const loose: WorkflowAgentView[] = [];
37
+ const byPhase = new Map<number, WorkflowAgentView[]>();
38
+ for (const agent of workflow.agents) {
39
+ const view = { agent, state: workflowAgentState(agent, workflowRunning) };
40
+ if (agent.phaseIndex === null || !known.has(agent.phaseIndex)) {
41
+ loose.push(view);
42
+ continue;
43
+ }
44
+ byPhase.set(agent.phaseIndex, [...(byPhase.get(agent.phaseIndex) ?? []), view]);
45
+ }
46
+ const phases = workflow.phases.map((phase) => ({ key: `phase-${phase.index}`, title: phase.title, agents: byPhase.get(phase.index) ?? [] }));
47
+ return loose.length > 0 ? [{ key: 'loose', title: null, agents: loose }, ...phases] : phases;
48
+ }
49
+
50
+ /* What the line of an agent that is not at work says about its time; one at work counts on by itself. */
51
+ export function workflowAgentTime(view: WorkflowAgentView): string | null {
52
+ const duration = view.agent.durationMs === null ? null : formatElapsedShort(view.agent.durationMs);
53
+ switch (view.state) {
54
+ case 'running':
55
+ return null;
56
+ case 'waiting':
57
+ return i18next.t('agent-chat:rows.workflow.waiting');
58
+ case 'stopped':
59
+ return i18next.t('agent-chat:rows.workflow.stopped');
60
+ case 'done':
61
+ case 'failed':
62
+ return duration === null
63
+ ? i18next.t(`agent-chat:common.status.${view.state}`)
64
+ : i18next.t(`agent-chat:rows.subagent.${view.state}In`, { duration });
65
+ }
66
+ }
@@ -0,0 +1,232 @@
1
+ /* What a file dragged out of an app's own list of files carries: the paths, space separated, relative
2
+ to the folder. The composer takes it next to the image files it already accepts. */
3
+ export const MENTION_DRAG_TYPE = 'application/x-adecore-mention';
4
+ export const LEGACY_MENTION_DRAG_TYPE = 'application/x-ruimte-mention';
5
+ export const MENTION_DRAG_TYPES = [MENTION_DRAG_TYPE, LEGACY_MENTION_DRAG_TYPE] as const;
6
+
7
+ export function carriesMentions(types: readonly string[]): boolean {
8
+ return MENTION_DRAG_TYPES.some((type) => types.includes(type));
9
+ }
10
+
11
+ export function droppedMentions(data: Pick<DataTransfer, 'getData'>): string[] {
12
+ const text = data.getData(MENTION_DRAG_TYPE) || data.getData(LEGACY_MENTION_DRAG_TYPE);
13
+ return text.trim() === '' ? [] : text.trim().split(/\s+/);
14
+ }
15
+
16
+ export function writeMentionDrag(data: Pick<DataTransfer, 'setData'>, paths: readonly string[], legacy = true): void {
17
+ const text = paths.join(' ');
18
+ data.setData(MENTION_DRAG_TYPE, text);
19
+ if (legacy) {
20
+ data.setData(LEGACY_MENTION_DRAG_TYPE, text);
21
+ }
22
+ }
23
+
24
+ export interface MentionQuery {
25
+ // Index of the sigil in the text.
26
+ start: number;
27
+ query: string;
28
+ }
29
+
30
+ export type ChipSegment = { kind: 'text'; text: string } | { kind: 'mention'; path: string } | { kind: 'skill'; name: string };
31
+
32
+ function isBoundary(char: string | undefined): boolean {
33
+ return char === undefined || /\s/.test(char);
34
+ }
35
+
36
+ // A path at the end of a sentence still counts, so "open @a.ts." is a mention of a.ts.
37
+ function isTokenEnd(char: string | undefined): boolean {
38
+ return isBoundary(char) || /[.,;:!?)]/.test(char!);
39
+ }
40
+
41
+ /*
42
+ * The `<sigil>word` the caret sits in, if any. The sigil has to open a word (start of text or after
43
+ * whitespace) so an email address never opens the picker, and the word may not have been
44
+ * closed with whitespace yet.
45
+ */
46
+ function findQuery(text: string, caret: number, sigil: string): MentionQuery | null {
47
+ const before = text.slice(0, caret);
48
+ const at = before.lastIndexOf(sigil);
49
+ if (at < 0 || !isBoundary(before[at - 1])) {
50
+ return null;
51
+ }
52
+ const query = before.slice(at + 1);
53
+ if (/\s/.test(query) || !isBoundary(text[caret])) {
54
+ return null;
55
+ }
56
+ return { start: at, query };
57
+ }
58
+
59
+ export function findMentionQuery(text: string, caret: number): MentionQuery | null {
60
+ return findQuery(text, caret, '@');
61
+ }
62
+
63
+ /* The `$word` the caret sits in. The name has to start with a letter, so `$20` and `$1e6` stay text. */
64
+ export function findSkillQuery(text: string, caret: number): MentionQuery | null {
65
+ const found = findQuery(text, caret, '$');
66
+ return found === null || (found.query !== '' && !/^[A-Za-z]/.test(found.query)) ? null : found;
67
+ }
68
+
69
+ /* Replaces the `<sigil>query` under the caret with the chosen token and a space, and says where the caret goes. */
70
+ export function insertToken(text: string, query: MentionQuery, sigil: string, value: string): { text: string; caret: number } {
71
+ const end = query.start + 1 + query.query.length;
72
+ const token = `${sigil}${value} `;
73
+ return { text: `${text.slice(0, query.start)}${token}${text.slice(end)}`, caret: query.start + token.length };
74
+ }
75
+
76
+ /* Takes the `<sigil>query` under the caret out, for a pick that lands beside the text instead of in it. */
77
+ export function dropQuery(text: string, query: MentionQuery): { text: string; caret: number } {
78
+ return {
79
+ text: `${text.slice(0, query.start)}${text.slice(query.start + 1 + query.query.length)}`,
80
+ caret: query.start
81
+ };
82
+ }
83
+
84
+ export function insertMention(text: string, query: MentionQuery, path: string): { text: string; caret: number } {
85
+ return insertToken(text, query, '@', path);
86
+ }
87
+
88
+ export function insertSkill(text: string, query: MentionQuery, name: string): { text: string; caret: number } {
89
+ return insertToken(text, query, '$', name);
90
+ }
91
+
92
+ /* What a file search answered, with the query it answered, since the query moves on while the answer is on its way. */
93
+ export interface MentionSearch {
94
+ query: string | null;
95
+ files: string[];
96
+ }
97
+
98
+ export const NO_MENTION_SEARCH: MentionSearch = { query: null, files: [] };
99
+
100
+ export type MentionPick<T> = { kind: 'chat'; chat: T } | { kind: 'file'; path: string };
101
+
102
+ /* What Enter or Tab takes from the `@` list at `index`. Files still on screen from an older query are never taken. */
103
+ export function mentionPick<T>(index: number, chats: readonly T[], search: MentionSearch, query: string): MentionPick<T> | null {
104
+ const chat = chats[index];
105
+ if (chat !== undefined) {
106
+ return { kind: 'chat', chat };
107
+ }
108
+ if (search.query !== query) {
109
+ return null;
110
+ }
111
+ const path = search.files[index - chats.length] ?? search.files[0];
112
+ return path === undefined ? null : { kind: 'file', path };
113
+ }
114
+
115
+ function findToken(text: string, sigil: string, value: string, from: number): number {
116
+ const token = `${sigil}${value}`;
117
+ let index = text.indexOf(token, from);
118
+ while (index >= 0) {
119
+ if (isBoundary(text[index - 1]) && isTokenEnd(text[index + token.length])) {
120
+ return index;
121
+ }
122
+ index = text.indexOf(token, index + 1);
123
+ }
124
+ return -1;
125
+ }
126
+
127
+ function present(text: string, sigil: string, chosen: string[]): string[] {
128
+ const found: Array<{ index: number; value: string }> = [];
129
+ for (const value of new Set(chosen)) {
130
+ const index = findToken(text, sigil, value, 0);
131
+ if (index >= 0) {
132
+ found.push({ index, value });
133
+ }
134
+ }
135
+ return found.sort((a, b) => a.index - b.index).map((entry) => entry.value);
136
+ }
137
+
138
+ /* The chosen mentions that still sit in the text as a whole `@path` token, in text order and without doubles. */
139
+ export function presentMentions(text: string, chosen: string[]): string[] {
140
+ return present(text, '@', chosen);
141
+ }
142
+
143
+ /* The chosen skills that still sit in the text as a whole `$name` token, in text order and without doubles. */
144
+ export function presentSkills(text: string, chosen: string[]): string[] {
145
+ return present(text, '$', chosen);
146
+ }
147
+
148
+ /*
149
+ * The characters a segment stands for, sigil included. Joined, the segments spell the text they
150
+ * were cut from.
151
+ */
152
+ export function chipText(segment: ChipSegment): string {
153
+ if (segment.kind === 'mention') {
154
+ return `@${segment.path}`;
155
+ }
156
+ if (segment.kind === 'skill') {
157
+ return `$${segment.name}`;
158
+ }
159
+ return segment.text;
160
+ }
161
+
162
+ /*
163
+ * The paths in a pasted text that is nothing but `@path` tokens, which is what copying mentions from
164
+ * a list of files puts on the clipboard. Any other text, an email address included, is text.
165
+ */
166
+ export function pastedMentions(text: string): string[] {
167
+ const tokens = text.trim().split(/\s+/);
168
+ if (tokens.some((token) => !/^@[^\s@]+$/.test(token))) {
169
+ return [];
170
+ }
171
+ return [...new Set(tokens.map((token) => token.slice(1)))];
172
+ }
173
+
174
+ export interface TextRange {
175
+ from: number;
176
+ to: number;
177
+ }
178
+
179
+ export interface ChipRange extends TextRange {
180
+ kind: 'mention' | 'skill';
181
+ value: string;
182
+ }
183
+
184
+ /*
185
+ * Where the chosen mentions and skills sit in the text, in text order; longer values win when one
186
+ * prefixes another. A token that overlaps an excluded range (a code span, say) is text.
187
+ */
188
+ export function chipRanges(text: string, mentions: string[], skills: string[] = [], excluded: readonly TextRange[] = []): ChipRange[] {
189
+ const tokens = [
190
+ ...[...new Set(mentions)].map((value) => ({ sigil: '@', kind: 'mention' as const, value })),
191
+ ...[...new Set(skills)].map((value) => ({ sigil: '$', kind: 'skill' as const, value }))
192
+ ].sort((a, b) => b.value.length - a.value.length);
193
+ const allowed = (from: number, to: number): boolean => !excluded.some((range) => from < range.to && to > range.from);
194
+ const ranges: ChipRange[] = [];
195
+ let cursor = 0;
196
+ while (cursor < text.length) {
197
+ let best: ChipRange | null = null;
198
+ for (const token of tokens) {
199
+ const length = token.value.length + 1;
200
+ let index = findToken(text, token.sigil, token.value, cursor);
201
+ while (index >= 0 && !allowed(index, index + length)) {
202
+ index = findToken(text, token.sigil, token.value, index + 1);
203
+ }
204
+ if (index >= 0 && (best === null || index < best.from)) {
205
+ best = { from: index, to: index + length, kind: token.kind, value: token.value };
206
+ }
207
+ }
208
+ if (best === null) {
209
+ break;
210
+ }
211
+ ranges.push(best);
212
+ cursor = best.to;
213
+ }
214
+ return ranges;
215
+ }
216
+
217
+ /* Splits the text so a renderer can draw the mentions and skills as chips. */
218
+ export function tokenizeChips(text: string, mentions: string[], skills: string[] = []): ChipSegment[] {
219
+ const segments: ChipSegment[] = [];
220
+ let cursor = 0;
221
+ for (const range of chipRanges(text, mentions, skills)) {
222
+ if (range.from > cursor) {
223
+ segments.push({ kind: 'text', text: text.slice(cursor, range.from) });
224
+ }
225
+ segments.push(range.kind === 'mention' ? { kind: 'mention', path: range.value } : { kind: 'skill', name: range.value });
226
+ cursor = range.to;
227
+ }
228
+ if (cursor < text.length) {
229
+ segments.push({ kind: 'text', text: text.slice(cursor) });
230
+ }
231
+ return segments;
232
+ }
@@ -0,0 +1,67 @@
1
+ import { chatStorageAdapter, hydrateChatStorage } from '../storage';
2
+
3
+ export interface PersistedJson<T> {
4
+ read(): T;
5
+ /* False when storage refused; the value still survives for this session. */
6
+ write(value: T): boolean;
7
+ }
8
+
9
+ export function persistedJson<T>(
10
+ key: string | (() => string),
11
+ parse: (raw: string | null) => T,
12
+ fallback: T,
13
+ legacyKeys: () => readonly string[] = () => []
14
+ ): PersistedJson<T> {
15
+ let memory = fallback;
16
+ let unwritten = false;
17
+ const currentKey = (): string => (typeof key === 'string' ? key : key());
18
+ return {
19
+ read: () => {
20
+ hydrateChatStorage();
21
+ if (unwritten) {
22
+ return memory;
23
+ }
24
+ try {
25
+ const storage = chatStorageAdapter();
26
+ if (!storage) {
27
+ return memory;
28
+ }
29
+ let raw = storage.getItem(currentKey());
30
+ if (raw === null) {
31
+ for (const legacy of legacyKeys()) {
32
+ raw = storage.getItem(legacy);
33
+ if (raw !== null) {
34
+ // Keep the old key for clients still using it, and migrate without changing the record.
35
+ try {
36
+ storage.setItem(currentKey(), raw);
37
+ } catch {
38
+ // A full storage still lets the existing record be read.
39
+ }
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ memory = parse(raw);
45
+ return memory;
46
+ } catch {
47
+ return memory;
48
+ }
49
+ },
50
+ write: (value) => {
51
+ hydrateChatStorage();
52
+ memory = value;
53
+ unwritten = true;
54
+ try {
55
+ const storage = chatStorageAdapter();
56
+ if (!storage) {
57
+ return false;
58
+ }
59
+ storage.setItem(currentKey(), JSON.stringify(value));
60
+ unwritten = false;
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+ };
67
+ }
@@ -0,0 +1,179 @@
1
+ import { chatStorageKey, chatStorageLegacyKeys, createChatStore, onChatStorageConfiguration } from '../storage';
2
+ import type { AgentKind, ChatPreferencesPayload, ModelSelection, ProviderInfo, ProviderAccounts, RuntimeMode } from '@adecore/agent-contracts';
3
+ import { persistedJson } from './persisted-json';
4
+
5
+ const storageKey = (): string => chatStorageKey('preferences');
6
+
7
+ export interface ChatPreferences {
8
+ /* One remembered model per CLI: a slug only means something inside its own catalog. */
9
+ selectionByProvider: Partial<Record<AgentKind, ModelSelection>>;
10
+ /* Whose model was picked last, which is what a chat without a fixed provider opens on. */
11
+ lastProvider: AgentKind | null;
12
+ runtimeMode: RuntimeMode;
13
+ // What an agent started as a terminal node runs in; a chat picks its own mode in the composer.
14
+ terminalRuntimeMode: RuntimeMode;
15
+ /* Per machine, the account a new agent of each CLI starts under; an account id only means something on its own machine. */
16
+ accountByMachine: Record<string, Partial<Record<AgentKind, string>>>;
17
+ /* When a person last changed any of it; what settles whose pick a machine uses when several clients told it one. */
18
+ changedAt: number;
19
+ }
20
+
21
+ export const DEFAULT_CHAT_PREFERENCES: ChatPreferences = {
22
+ selectionByProvider: {},
23
+ lastProvider: null,
24
+ runtimeMode: 'full-access',
25
+ terminalRuntimeMode: 'full-access',
26
+ accountByMachine: {},
27
+ changedAt: 0
28
+ };
29
+
30
+ /*
31
+ * Only the fields we know today, so what an older build wrote (one global `selection`, the plan
32
+ * toggle's `interactionMode`) falls away here instead of riding along in every later write. A
33
+ * global selection cannot be mapped back onto a provider, so it is dropped: one lost pick.
34
+ */
35
+ export function parseChatPreferences(raw: string | null): ChatPreferences {
36
+ if (raw === null) {
37
+ return DEFAULT_CHAT_PREFERENCES;
38
+ }
39
+ try {
40
+ const stored = JSON.parse(raw) as Partial<ChatPreferences>;
41
+ return {
42
+ selectionByProvider: { ...stored.selectionByProvider },
43
+ lastProvider: stored.lastProvider ?? null,
44
+ runtimeMode: stored.runtimeMode ?? DEFAULT_CHAT_PREFERENCES.runtimeMode,
45
+ terminalRuntimeMode: stored.terminalRuntimeMode ?? DEFAULT_CHAT_PREFERENCES.terminalRuntimeMode,
46
+ accountByMachine: { ...stored.accountByMachine },
47
+ changedAt: typeof stored.changedAt === 'number' ? stored.changedAt : 0
48
+ };
49
+ } catch {
50
+ return DEFAULT_CHAT_PREFERENCES;
51
+ }
52
+ }
53
+
54
+ /* Which CLI a chat opens on when its node names none: the last one a model was picked for. */
55
+ export function defaultProvider(preferences: ChatPreferences): AgentKind | null {
56
+ return preferences.lastProvider;
57
+ }
58
+
59
+ /* The model a chat of this provider starts with; null lets the host fall back to the CLI's own default. */
60
+ export function selectionFor(preferences: ChatPreferences, provider: AgentKind | null | undefined): ModelSelection | null {
61
+ const owner = provider ?? preferences.lastProvider;
62
+ if (owner === null) {
63
+ return null;
64
+ }
65
+ return preferences.selectionByProvider[owner] ?? null;
66
+ }
67
+
68
+ export function startingSelection(preferences: ChatPreferences, provider: ProviderInfo): ModelSelection | null {
69
+ return selectionFor(preferences, provider.kind) ?? (provider.defaultModel === null ? null : { model: provider.defaultModel, options: {} });
70
+ }
71
+
72
+ /* The pick lands in its provider's slot and makes that provider the global default. */
73
+ export function withSelection(preferences: ChatPreferences, provider: AgentKind, selection: ModelSelection): ChatPreferences {
74
+ return {
75
+ ...preferences,
76
+ selectionByProvider: { ...preferences.selectionByProvider, [provider]: selection },
77
+ lastProvider: provider
78
+ };
79
+ }
80
+
81
+ /*
82
+ * The account a new agent of this CLI starts under on this machine; null is the CLI's default account.
83
+ * Given the machine's accounts, a pick it turned off or removed is the default account again, so a new
84
+ * chat is never refused over it; `undefined` is a machine that did not say yet, where the pick stands.
85
+ */
86
+ export function accountFor(preferences: ChatPreferences, scopeId: string, provider: AgentKind, accounts?: ProviderAccounts | null): string | null {
87
+ const picked = preferences.accountByMachine[scopeId]?.[provider] ?? null;
88
+ if (picked === null || accounts === undefined) {
89
+ return picked;
90
+ }
91
+ const account = accounts?.accounts[picked];
92
+ return account?.kind === provider && account.enabled !== false ? picked : null;
93
+ }
94
+
95
+ /* The default account is what an absent pick means, so picking it takes the pick away. */
96
+ export function withAccount(preferences: ChatPreferences, scopeId: string, provider: AgentKind, account: string | null): ChatPreferences {
97
+ const { [provider]: _previous, ...others } = preferences.accountByMachine[scopeId] ?? {};
98
+ const picks = account === null || account === provider ? others : { ...others, [provider]: account };
99
+ const { [scopeId]: _machine, ...machines } = preferences.accountByMachine;
100
+ return { ...preferences, accountByMachine: Object.keys(picks).length === 0 ? machines : { ...machines, [scopeId]: picks } };
101
+ }
102
+
103
+ /* What a machine starts a chat with when it starts one with no client mounting it; `accounts` as `accountFor` takes them. */
104
+ export function chatPreferencesPayload(preferences: ChatPreferences, scopeId: string, accounts?: ProviderAccounts | null): ChatPreferencesPayload {
105
+ const picks = Object.keys(preferences.accountByMachine[scopeId] ?? {}).flatMap((provider) => {
106
+ const account = accountFor(preferences, scopeId, provider as AgentKind, accounts);
107
+ return account === null ? [] : [[provider, account] as const];
108
+ });
109
+ return {
110
+ runtimeMode: preferences.runtimeMode,
111
+ terminalRuntimeMode: preferences.terminalRuntimeMode,
112
+ selections: preferences.selectionByProvider,
113
+ ...(picks.length === 0 ? {} : { accounts: Object.fromEntries(picks) }),
114
+ changedAt: preferences.changedAt
115
+ };
116
+ }
117
+
118
+ const storage = persistedJson<ChatPreferences>(storageKey, parseChatPreferences, DEFAULT_CHAT_PREFERENCES, () => chatStorageLegacyKeys('preferences'));
119
+
120
+ /* What a new agent starts with: the last model and modes the person picked, like a remembered default. */
121
+ export const useChatPreferences = createChatStore<ChatPreferences>(
122
+ () => DEFAULT_CHAT_PREFERENCES,
123
+ (store) => store.setState(storage.read(), true)
124
+ );
125
+
126
+ export function readChatPreferences(): ChatPreferences {
127
+ return useChatPreferences.getState();
128
+ }
129
+
130
+ /* Where the remembered default is kept, which an app with more than one window of the same origin listens for. */
131
+ export let CHAT_PREFERENCES_KEY = storageKey();
132
+ onChatStorageConfiguration(() => {
133
+ CHAT_PREFERENCES_KEY = storageKey();
134
+ });
135
+
136
+ export function chatPreferencesKey(): string {
137
+ return storageKey();
138
+ }
139
+
140
+ /* Reads the remembered default again, once another window of the same origin wrote it. */
141
+ export function reloadChatPreferences(): void {
142
+ useChatPreferences.setState(storage.read(), true);
143
+ }
144
+
145
+ function write(preferences: ChatPreferences): void {
146
+ const next = { ...preferences, changedAt: Date.now() };
147
+ useChatPreferences.setState(next);
148
+ storage.write(next);
149
+ }
150
+
151
+ /* The composer writes here on every change and the settings dialog too; both edit the same remembered default. */
152
+ export function rememberChatPreferences(patch: Partial<ChatPreferences>): void {
153
+ write({ ...useChatPreferences.getState(), ...patch });
154
+ }
155
+
156
+ export function rememberChatSelection(provider: AgentKind, selection: ModelSelection): void {
157
+ const current = useChatPreferences.getState();
158
+ const previous = current.selectionByProvider[provider];
159
+ if (
160
+ current.lastProvider === provider &&
161
+ previous?.model === selection.model &&
162
+ Object.keys(previous.options).length === Object.keys(selection.options).length &&
163
+ Object.entries(selection.options).every(([key, value]) => previous.options[key] === value)
164
+ ) {
165
+ return;
166
+ }
167
+ write(withSelection(current, provider, selection));
168
+ }
169
+
170
+ export function rememberChatAccount(scopeId: string, provider: AgentKind, account: string | null): void {
171
+ write(withAccount(useChatPreferences.getState(), scopeId, provider, account));
172
+ }
173
+
174
+ /* Back to the CLI's own default for this provider; the other providers keep what they had. */
175
+ export function forgetChatSelection(provider: AgentKind): void {
176
+ const current = useChatPreferences.getState();
177
+ const { [provider]: _dropped, ...rest } = current.selectionByProvider;
178
+ write({ ...current, selectionByProvider: rest, lastProvider: current.lastProvider === provider ? null : current.lastProvider });
179
+ }