@pheem49/mint 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CLAUDE.md +1 -1
  2. package/Cargo.lock +291 -11
  3. package/Cargo.toml +1 -1
  4. package/README.md +71 -9
  5. package/ROADMAP.md +30 -0
  6. package/Release_Note.md +579 -465
  7. package/crates/mint-cli/Cargo.toml +3 -1
  8. package/crates/mint-cli/src/agent/approval_prompts.rs +16 -3
  9. package/crates/mint-cli/src/agent/live_status.rs +367 -118
  10. package/crates/mint-cli/src/agent/markdown_render.rs +601 -61
  11. package/crates/mint-cli/src/agent.rs +452 -215
  12. package/crates/mint-cli/src/commands/agent.rs +16 -21
  13. package/crates/mint-cli/src/commands/eval.rs +9 -5
  14. package/crates/mint-cli/src/commands/git.rs +204 -0
  15. package/crates/mint-cli/src/commands/integrations.rs +28 -0
  16. package/crates/mint-cli/src/commands/knowledge.rs +1 -0
  17. package/crates/mint-cli/src/commands/mod.rs +37 -2
  18. package/crates/mint-cli/src/commands/tailnet_web.rs +235 -0
  19. package/crates/mint-cli/src/image.rs +26 -3
  20. package/crates/mint-cli/src/interactive/chat_tui.rs +4742 -0
  21. package/crates/mint-cli/src/interactive/command_ui.rs +477 -0
  22. package/crates/mint-cli/src/interactive/format.rs +8 -0
  23. package/crates/mint-cli/src/interactive/input_box.rs +38 -2
  24. package/crates/mint-cli/src/interactive/picker.rs +180 -37
  25. package/crates/mint-cli/src/interactive/resume_picker.rs +686 -0
  26. package/crates/mint-cli/src/interactive/slash_commands.rs +868 -450
  27. package/crates/mint-cli/src/interactive/thought_viewer.rs +286 -0
  28. package/crates/mint-cli/src/interactive.rs +400 -136
  29. package/crates/mint-cli/src/main.rs +84 -23
  30. package/crates/mint-cli/src/markdown.rs +12 -5
  31. package/crates/mint-cli/src/onboard.rs +32 -30
  32. package/crates/mint-cli/src/plugins_cli.rs +8 -5
  33. package/crates/mint-cli/src/setup.rs +10 -8
  34. package/crates/mint-cli/src/terminal_theme.rs +187 -0
  35. package/crates/mint-cli/src/updater.rs +56 -21
  36. package/crates/mint-core/src/agent/agent_loop.rs +1 -0
  37. package/crates/mint-core/src/agent/chat.rs +843 -177
  38. package/crates/mint-core/src/agent/code_tools.rs +83 -2
  39. package/crates/mint-core/src/agent/memory.rs +658 -20
  40. package/crates/mint-core/src/agent/skills.rs +1 -1
  41. package/crates/mint-core/src/api_server/routes/chat.rs +31 -5
  42. package/crates/mint-core/src/api_server/routes/linked_folders.rs +118 -3
  43. package/crates/mint-core/src/api_server/routes/misc.rs +10 -14
  44. package/crates/mint-core/src/api_server/routes/sessions.rs +44 -0
  45. package/crates/mint-core/src/api_server.rs +22 -3
  46. package/crates/mint-core/src/avatar_bridge.rs +2 -0
  47. package/crates/mint-core/src/browser/cdp.rs +74 -83
  48. package/crates/mint-core/src/browser/evidence.rs +260 -0
  49. package/crates/mint-core/src/browser/input.rs +1 -1
  50. package/crates/mint-core/src/browser/interact.rs +98 -124
  51. package/crates/mint-core/src/browser/lifecycle.rs +75 -4
  52. package/crates/mint-core/src/browser/mod.rs +16 -2
  53. package/crates/mint-core/src/browser/navigate.rs +27 -19
  54. package/crates/mint-core/src/browser/observe.js +40 -0
  55. package/crates/mint-core/src/browser/session.rs +1558 -0
  56. package/crates/mint-core/src/git/branches.rs +348 -0
  57. package/crates/mint-core/src/git/checkpoint.rs +40 -0
  58. package/crates/mint-core/src/git/diff.rs +206 -0
  59. package/crates/mint-core/src/git/mod.rs +10 -2
  60. package/crates/mint-core/src/integrations/channels.rs +1 -0
  61. package/crates/mint-core/src/lib.rs +22 -14
  62. package/crates/mint-core/src/live_sync.rs +28 -9
  63. package/crates/mint-core/src/media/auto_shorts.rs +1 -0
  64. package/crates/mint-core/src/media/gemini_live.rs +12 -1
  65. package/crates/mint-core/src/media/mic_transcribe.rs +1 -0
  66. package/crates/mint-core/src/media/subtitle.rs +1 -0
  67. package/crates/mint-core/src/orchestration/context_render.rs +1 -0
  68. package/crates/mint-core/src/orchestration/memory_skill.rs +6 -1
  69. package/crates/mint-core/src/orchestration/mod.rs +1024 -271
  70. package/crates/mint-core/src/orchestration/tools/browser.rs +16 -139
  71. package/crates/mint-core/src/orchestration/tools/misc.rs +2 -1
  72. package/crates/mint-core/src/orchestration/tools/web.rs +1 -1
  73. package/crates/mint-core/src/prompts/agent.rs +58 -29
  74. package/crates/mint-core/src/prompts/chat.rs +1 -1
  75. package/crates/mint-core/src/prompts/persona.rs +1 -1
  76. package/crates/mint-core/src/prompts/tool_catalog.rs +93 -25
  77. package/crates/mint-core/src/search/linked_folders/index.rs +732 -0
  78. package/crates/mint-core/src/search/linked_folders.rs +391 -130
  79. package/crates/mint-core/src/slash/mod.rs +470 -22
  80. package/crates/mint-core/src/slash/model_fetcher.rs +12 -3
  81. package/crates/mint-core/src/slash/models.rs +66 -10
  82. package/crates/mint-core/src/system/config.rs +230 -0
  83. package/crates/mint-core/src/system/workspace.rs +228 -0
  84. package/crates/mint-core/src/system.rs +1 -0
  85. package/crates/mint-core/tests/memory_persistence.rs +39 -0
  86. package/mint-presentation-demo.mp4 +0 -0
  87. package/out/web/assets/CliSessionsView-BxUVWEEL.js +1 -0
  88. package/out/web/assets/CliSessionsView-CBDTXuQv.css +1 -0
  89. package/out/web/assets/CodeReviewPage-DkMgfcos.css +1 -0
  90. package/out/web/assets/CodeReviewPage-jKlmLJwT.js +3 -0
  91. package/out/web/assets/CommandPalette-DnwPA5hy.js +1 -0
  92. package/out/web/assets/ImageStudioPanel-BR1XFwhR.js +1 -0
  93. package/out/web/assets/ImageStudioPanel-D0lnhQV5.css +1 -0
  94. package/out/web/assets/LinkedFoldersView-bi-CWQ-I.js +1 -0
  95. package/out/web/assets/McpRegistryPicker-D5tEFnyq.js +1 -0
  96. package/out/web/assets/McpServersView-BGG1dhFM.js +1 -0
  97. package/out/web/assets/MintDashboard-BIglDEWX.js +20 -0
  98. package/out/web/assets/MintDashboard-DSMywSgs.css +1 -0
  99. package/out/web/assets/ModelPanel-CbJ1PFoZ.js +1 -0
  100. package/out/web/assets/PicturesLibrary-CQP0fLP1.css +1 -0
  101. package/out/web/assets/PicturesLibrary-DoYN7T1p.js +1 -0
  102. package/out/web/assets/PluginsView-CU1HWXGk.js +1 -0
  103. package/out/web/assets/ScheduledTasksView-Bnl36y2N.js +1 -0
  104. package/out/web/assets/SettingsWindow-DhWLAn9T.css +1 -0
  105. package/out/web/assets/SettingsWindow-Dw8L01pw.js +2 -0
  106. package/out/web/assets/SkillsView-cnl8aY5p.js +2 -0
  107. package/out/web/assets/ToolSurfacePage-CgsQw6l5.css +1 -0
  108. package/out/web/assets/ToolSurfacePage-Cz2VJAPZ.js +37 -0
  109. package/out/web/assets/VeoStudioPanel-D4JnERWR.js +4 -0
  110. package/out/web/assets/VeoStudioPanel-DOj7_WR3.css +1 -0
  111. package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +1 -0
  112. package/out/web/assets/{abnfDiagram-VCTEODGH-DAOBA7ZG.js → abnfDiagram-VCTEODGH-ByMyfanq.js} +1 -1
  113. package/out/web/assets/{architecture-7GRP2DOG-CgSwQOPQ.js → architecture-7GRP2DOG-CLq2kEOB.js} +1 -1
  114. package/out/web/assets/{architectureDiagram-5GKGNRK7-CYp3yupP.js → architectureDiagram-5GKGNRK7-6PoVSmNG.js} +1 -1
  115. package/out/web/assets/{blockDiagram-NRAW4CY4-CyWybDWr.js → blockDiagram-NRAW4CY4-cKw9mAKi.js} +1 -1
  116. package/out/web/assets/{c4Diagram-UCG6FXSJ-BvY3U709.js → c4Diagram-UCG6FXSJ-Cqy8N5Ge.js} +1 -1
  117. package/out/web/assets/{chunk-75Z2AOVW-TLXCoZYv.js → chunk-75Z2AOVW-3qybijF5.js} +1 -1
  118. package/out/web/assets/chunk-CKBBP62Z-cIrXq6jN.js +2 -0
  119. package/out/web/assets/{chunk-CLGD4ZFX-xjsKtNS7.js → chunk-CLGD4ZFX-CndDQkww.js} +1 -1
  120. package/out/web/assets/chunk-DSNCTWBM-Cc4NxvgD.js +2 -0
  121. package/out/web/assets/chunk-DU6HZSFF-drzh6GsU.js +128 -0
  122. package/out/web/assets/{chunk-F27PBJKO-BFpgF55A.js → chunk-F27PBJKO-FiY35l4v.js} +1 -1
  123. package/out/web/assets/{chunk-G27WJ6UU-DfF7qW-C.js → chunk-G27WJ6UU-Bjt9Il4A.js} +1 -1
  124. package/out/web/assets/{chunk-GMAD6QVW-C-558kh_.js → chunk-GMAD6QVW-Bppi3Ctd.js} +1 -1
  125. package/out/web/assets/{chunk-JQ64N6SF-wJmeGKAw.js → chunk-JQ64N6SF-DGg5kcEV.js} +1 -1
  126. package/out/web/assets/{chunk-LCL6LL3I-C1wHD8Ie.js → chunk-LCL6LL3I-CCYcjd7p.js} +1 -1
  127. package/out/web/assets/{chunk-OBVCFTLP-BsRTPBqs.js → chunk-OBVCFTLP-DJeNil5M.js} +1 -1
  128. package/out/web/assets/{chunk-P2QGCYS3-HZIiM7Ni.js → chunk-P2QGCYS3-lRzkRjBB.js} +1 -1
  129. package/out/web/assets/{chunk-POPQ4Y6H-Dk9wvVb0.js → chunk-POPQ4Y6H-DouASMBW.js} +1 -1
  130. package/out/web/assets/{chunk-PWAF6VOD-BmjYLNbq.js → chunk-PWAF6VOD-DMmD1vVf.js} +1 -1
  131. package/out/web/assets/{chunk-R7TYR2AO-CaHIf-lK.js → chunk-R7TYR2AO-CYLA5UC5.js} +1 -1
  132. package/out/web/assets/{chunk-RHFEMEQ7-_T561hOU.js → chunk-RHFEMEQ7-BiXwEcUT.js} +1 -1
  133. package/out/web/assets/{chunk-SVP7TREG-CM-GGYoY.js → chunk-SVP7TREG-BV-zPslF.js} +1 -1
  134. package/out/web/assets/{chunk-TEH6E4GO-K2A8Vg2C.js → chunk-TEH6E4GO-B2RGEohZ.js} +1 -1
  135. package/out/web/assets/classDiagram-DTDB5LWJ-Dq0smRWk.js +1 -0
  136. package/out/web/assets/classDiagram-v2-JRS7N3AN-Dq0smRWk.js +1 -0
  137. package/out/web/assets/config-C-ZFtipU.css +1 -0
  138. package/out/web/assets/config-eEDngJXO.js +1 -0
  139. package/out/web/assets/core-hQ2VA9Ti.js +1 -0
  140. package/out/web/assets/{cynefin-OW5HDTMX-ClzaB6cc.js → cynefin-OW5HDTMX-Cas4csHM.js} +1 -1
  141. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-D_JzZBzf.js → cynefinDiagram-5FMLGOSQ-Bbpj66Y3.js} +1 -1
  142. package/out/web/assets/{dagre-3AP2YEHR-BDuUeMHj.js → dagre-3AP2YEHR-B0-2RDEd.js} +1 -1
  143. package/out/web/assets/{diagram-S7CK7UJ4-DgtcfE8P.js → diagram-S7CK7UJ4-BdzEjUyH.js} +1 -1
  144. package/out/web/assets/{diagram-UQ7AKVKN-DTLoEYti.js → diagram-UQ7AKVKN-CJDwyPxF.js} +1 -1
  145. package/out/web/assets/{diagram-VSXAHHWV-ChGl9pXz.js → diagram-VSXAHHWV-BxaG1r_B.js} +1 -1
  146. package/out/web/assets/{diagram-VX7I27RA-Be8DWz0S.js → diagram-VX7I27RA-BMmUqm8f.js} +1 -1
  147. package/out/web/assets/{diagram-Z3DM3KII-BMXjRTOF.js → diagram-Z3DM3KII-CFtd03eE.js} +1 -1
  148. package/out/web/assets/dist-js-Bf2bhoku.js +1 -0
  149. package/out/web/assets/{ebnfDiagram-PWID7BFC-8Q9pl1CJ.js → ebnfDiagram-PWID7BFC-DwAen1hO.js} +1 -1
  150. package/out/web/assets/{erDiagram-SSCWMZ5O-DjH8Lnkn.js → erDiagram-SSCWMZ5O-BCZSHACj.js} +1 -1
  151. package/out/web/assets/event-ChBVpRy0.js +1 -0
  152. package/out/web/assets/{eventmodeling-NTZA5JFV-BKUH_DOp.js → eventmodeling-NTZA5JFV-XHStE4E_.js} +1 -1
  153. package/out/web/assets/fira-code-latin-600-normal-BmSSHshu.woff +0 -0
  154. package/out/web/assets/fira-code-latin-600-normal-CvYZpPHg.woff2 +0 -0
  155. package/out/web/assets/fira-code-symbols2-600-normal-Dvq6IQE5.woff +0 -0
  156. package/out/web/assets/flowDiagram-A5DVABFB-BLg7nEcd.js +1 -0
  157. package/out/web/assets/{ganttDiagram-EL5Y4UJY-D7fZKJNM.js → ganttDiagram-EL5Y4UJY-BAEu1Cql.js} +1 -1
  158. package/out/web/assets/{gitGraph-4MIJSDKK-BypWaaBU.js → gitGraph-4MIJSDKK-r32Vyper.js} +1 -1
  159. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-DWiw_3bq.js → gitGraphDiagram-WWUBYQGX-z9o9qt6V.js} +1 -1
  160. package/out/web/assets/index-Btsf4L8M.css +1 -0
  161. package/out/web/assets/index-D8D9MeRA.js +11 -0
  162. package/out/web/assets/{info-A6RAGUB7-KkAUxT4j.js → info-A6RAGUB7-BvLaC2cw.js} +1 -1
  163. package/out/web/assets/{infoDiagram-RXCK75RN-BNb6E48y.js → infoDiagram-RXCK75RN-Be6WAXXw.js} +1 -1
  164. package/out/web/assets/{ishikawaDiagram-5VMMS53U-Caa-JoAK.js → ishikawaDiagram-5VMMS53U-DZjoHJHC.js} +1 -1
  165. package/out/web/assets/{journeyDiagram-EYS64GPL-DOrmFGz6.js → journeyDiagram-EYS64GPL-CtVkjkGm.js} +1 -1
  166. package/out/web/assets/{kanban-definition-3QL26DDD-BOWCYcK0.js → kanban-definition-3QL26DDD-CW5FMv1w.js} +1 -1
  167. package/out/web/assets/lucide-react-Ip3L6Jtn.js +1 -0
  168. package/out/web/assets/markdown-BSeeUbJH.js +38 -0
  169. package/out/web/assets/{mermaid-parser.core-BQqjCkS_.js → mermaid-parser.core-aUgCqsZ5.js} +3 -3
  170. package/out/web/assets/mermaid.core-D57sEiSL.js +11 -0
  171. package/out/web/assets/{mindmap-definition-FBJOCRG2-jTceWpr0.js → mindmap-definition-FBJOCRG2-DAmSpM3a.js} +1 -1
  172. package/out/web/assets/modelManager-tMVmJVAW.js +2 -0
  173. package/out/web/assets/oauthManager-9jxicDWJ.js +1 -0
  174. package/out/web/assets/{packet-AYTQ26CC-CJ5RXgol.js → packet-AYTQ26CC-DJ27iIoJ.js} +1 -1
  175. package/out/web/assets/{pegDiagram-XKGWAZYB-y8wMtd0y.js → pegDiagram-XKGWAZYB-Bj3ii-gS.js} +1 -1
  176. package/out/web/assets/{pie-WAS4IAKB-72fTBbl4.js → pie-WAS4IAKB-DG5mCUbc.js} +1 -1
  177. package/out/web/assets/{pieDiagram-E7YTZNPT-e0O02qWq.js → pieDiagram-E7YTZNPT-BrJLPnDA.js} +1 -1
  178. package/out/web/assets/prompt-latin-700-normal-BfbftDSq.woff +0 -0
  179. package/out/web/assets/prompt-latin-700-normal-I2gc831J.woff2 +0 -0
  180. package/out/web/assets/prompt-thai-700-normal-Cb5ilR-c.woff +0 -0
  181. package/out/web/assets/prompt-thai-700-normal-Cg4aQ0Nn.woff2 +0 -0
  182. package/out/web/assets/{quadrantDiagram-AXDQQJYC-C9phYLom.js → quadrantDiagram-AXDQQJYC-3x_TmF3j.js} +1 -1
  183. package/out/web/assets/{radar-RG4KPBEZ-BAU2hzUT.js → radar-RG4KPBEZ-CrXaTTuV.js} +1 -1
  184. package/out/web/assets/{railroad-74A4TZTK-B0bCAVHC.js → railroad-74A4TZTK-BpqMH9BE.js} +1 -1
  185. package/out/web/assets/{railroad-abnf-HS5TGJTU-WhxO7ndE.js → railroad-abnf-HS5TGJTU-DHI0uLlV.js} +1 -1
  186. package/out/web/assets/{railroad-ebnf-LZEXJU2U-BkA0m7-F.js → railroad-ebnf-LZEXJU2U-CQCb316y.js} +1 -1
  187. package/out/web/assets/{railroad-peg-WCYAUIDC-BsVpHIaj.js → railroad-peg-WCYAUIDC-Ph1giKkH.js} +1 -1
  188. package/out/web/assets/{railroadDiagram-O6MQD6OU-CyOLN65Q.js → railroadDiagram-O6MQD6OU-BRq6wIxx.js} +1 -1
  189. package/out/web/assets/{requirementDiagram-EFPCY7ZU-DQXJ7Dud.js → requirementDiagram-EFPCY7ZU-DIJj6G1g.js} +1 -1
  190. package/out/web/assets/{sankeyDiagram-P5KCCOFB-B2XLyIIc.js → sankeyDiagram-P5KCCOFB-DZajSryq.js} +1 -1
  191. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-DGQY4wwm.js → sequenceDiagram-WJ2MYXX4-DxJCWyZS.js} +1 -1
  192. package/out/web/assets/{stateDiagram-HBIQ2CUA-DHZpDwNy.js → stateDiagram-HBIQ2CUA-k6voi8Vf.js} +1 -1
  193. package/out/web/assets/stateDiagram-v2-4QOOHH4V-DeEc0lGZ.js +1 -0
  194. package/out/web/assets/studio-B1mk40wD.js +1 -0
  195. package/out/web/assets/{swimlanes-XN3QIQJK-Bn_jcZz-.js → swimlanes-XN3QIQJK-igYhTjXB.js} +1 -1
  196. package/out/web/assets/swimlanesDiagram-VK2B7HYN-DpVik9v7.js +8 -0
  197. package/out/web/assets/{timeline-definition-24CTP7MA-CdTERuJZ.js → timeline-definition-24CTP7MA-Dke2p93p.js} +1 -1
  198. package/out/web/assets/{treeView-Q6P3EWNA-TKBKQG6R.js → treeView-Q6P3EWNA-hNELnXUs.js} +1 -1
  199. package/out/web/assets/{treemap-WGGIJYW6-BP7gQoiC.js → treemap-WGGIJYW6-D2B6wDsx.js} +1 -1
  200. package/out/web/assets/{vennDiagram-4TSXK5OY-BHUmxDuK.js → vennDiagram-4TSXK5OY-CDkW7rUP.js} +1 -1
  201. package/out/web/assets/{wardley-WFR3VGLG-9bct5AAN.js → wardley-WFR3VGLG-BECdXETi.js} +1 -1
  202. package/out/web/assets/{wardleyDiagram-VM6X3IG4-BfJwDDso.js → wardleyDiagram-VM6X3IG4-B5BycpQ4.js} +1 -1
  203. package/out/web/assets/window-5qYXKoW7.js +1 -0
  204. package/out/web/assets/{xychartDiagram-S5SC5T6Z-Dqd9Mvpo.js → xychartDiagram-S5SC5T6Z-CtJS3b--.js} +1 -1
  205. package/out/web/index-web.html +3 -4
  206. package/out/web/index.html +3 -4
  207. package/package.json +17 -6
  208. package/skills-lock.json +36 -0
  209. package/slash-commands.json +9 -0
  210. package/src/renderer/index-web.html +1 -0
  211. package/src/renderer/index.html +1 -0
  212. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  213. package/src/renderer/shared/agentProgress.ts +168 -3
  214. package/src/renderer/shared/components/AgentActivityDrawer.tsx +6 -6
  215. package/src/renderer/shared/components/AgentActivityTable.tsx +192 -132
  216. package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +4 -4
  217. package/src/renderer/shared/components/AuthGate.tsx +4 -9
  218. package/src/renderer/shared/components/CalculationCard.tsx +41 -15
  219. package/src/renderer/shared/components/ChatMessageItem.tsx +20 -21
  220. package/src/renderer/shared/components/ChatPanel.tsx +432 -193
  221. package/src/renderer/shared/components/ChunkErrorBoundary.tsx +198 -49
  222. package/src/renderer/shared/components/CliSessionsView.tsx +495 -0
  223. package/src/renderer/shared/components/CodeReviewPage.tsx +565 -0
  224. package/src/renderer/shared/components/CommandPalette.tsx +8 -8
  225. package/src/renderer/shared/components/DashboardSidebar.tsx +862 -125
  226. package/src/renderer/shared/components/DesktopTitlebar.tsx +345 -0
  227. package/src/renderer/shared/components/DiffReviewModal.tsx +3 -3
  228. package/src/renderer/shared/components/GitBranchSelector.tsx +442 -0
  229. package/src/renderer/shared/components/ImageGenCard.tsx +31 -12
  230. package/src/renderer/shared/components/ImageSearchCard.tsx +59 -38
  231. package/src/renderer/shared/components/ImageStudioPanel.tsx +40 -41
  232. package/src/renderer/shared/components/InlineThoughtNote.tsx +27 -0
  233. package/src/renderer/shared/components/LinkedFoldersView.tsx +365 -46
  234. package/src/renderer/shared/components/McpRegistryPicker.tsx +1 -1
  235. package/src/renderer/shared/components/McpServersView.tsx +24 -22
  236. package/src/renderer/shared/components/MermaidCard.tsx +14 -8
  237. package/src/renderer/shared/components/MintDashboard.tsx +1141 -508
  238. package/src/renderer/shared/components/ModelSelectorPopover.tsx +535 -214
  239. package/src/renderer/shared/components/PlanChecklistWidget.tsx +1 -1
  240. package/src/renderer/shared/components/PluginsView.tsx +1 -1
  241. package/src/renderer/shared/components/RewindModal.tsx +6 -6
  242. package/src/renderer/shared/components/RunSummaryDashboard.tsx +3 -3
  243. package/src/renderer/shared/components/ScheduledTasksView.tsx +4 -4
  244. package/src/renderer/shared/components/ScreenCaptureDialog.css +221 -0
  245. package/src/renderer/shared/components/ScreenCaptureDialog.tsx +199 -0
  246. package/src/renderer/shared/components/Settings/AgentsTab.tsx +34 -39
  247. package/src/renderer/shared/components/Settings/AudioTab.tsx +8 -8
  248. package/src/renderer/shared/components/Settings/AutomationTab.tsx +8 -58
  249. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +368 -0
  250. package/src/renderer/shared/components/Settings/GeneralTab.tsx +335 -81
  251. package/src/renderer/shared/components/Settings/MemoryTab.tsx +3 -3
  252. package/src/renderer/shared/components/Settings/PluginsTab.tsx +20 -12
  253. package/src/renderer/shared/components/Settings/ProfileTab.tsx +29 -5
  254. package/src/renderer/shared/components/Settings/SearchableModelCombobox.tsx +411 -0
  255. package/src/renderer/shared/components/Settings/ThemeTab.tsx +401 -158
  256. package/src/renderer/shared/components/SkillsView.tsx +41 -16
  257. package/src/renderer/shared/components/SlashSuggestions.tsx +3 -2
  258. package/src/renderer/shared/components/SourcesBlock.tsx +44 -32
  259. package/src/renderer/shared/components/StockCard.tsx +58 -24
  260. package/src/renderer/shared/components/SubagentDagView.tsx +4 -4
  261. package/src/renderer/shared/components/TerminalDock.tsx +241 -0
  262. package/src/renderer/shared/components/ThinkingBlock.tsx +14 -8
  263. package/src/renderer/shared/components/ToolSurfacePage.tsx +520 -0
  264. package/src/renderer/shared/components/UiFeatureCard.tsx +267 -0
  265. package/src/renderer/shared/components/UiGridCard.tsx +188 -0
  266. package/src/renderer/shared/components/UiMockupWidget.tsx +149 -0
  267. package/src/renderer/shared/components/VeoStudioPanel.tsx +13 -29
  268. package/src/renderer/shared/components/WeatherCard.tsx +3 -3
  269. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +133 -0
  270. package/src/renderer/shared/components/WorkspaceSelector.tsx +149 -0
  271. package/src/renderer/shared/constants/config.ts +28 -3
  272. package/src/renderer/shared/constants/models.ts +189 -2
  273. package/src/renderer/shared/conversation/coordinator.test.ts +27 -0
  274. package/src/renderer/shared/conversation/coordinator.ts +82 -0
  275. package/src/renderer/shared/conversation/syncView.test.ts +46 -0
  276. package/src/renderer/shared/conversation/syncView.ts +43 -0
  277. package/src/renderer/shared/conversation/useConversationCoordinator.ts +106 -0
  278. package/src/renderer/shared/css/agent-observability.css +35 -3
  279. package/src/renderer/shared/css/auth-gate.css +2 -2
  280. package/src/renderer/shared/css/chat-ui-cards.css +495 -0
  281. package/src/renderer/shared/css/code-review.css +823 -0
  282. package/src/renderer/shared/css/code-sessions.css +452 -0
  283. package/src/renderer/shared/css/file-changes.css +132 -0
  284. package/src/renderer/shared/css/image-studio.css +16 -16
  285. package/src/renderer/shared/css/management-views.css +277 -27
  286. package/src/renderer/shared/css/model-selector.css +442 -42
  287. package/src/renderer/shared/css/settings/agents.css +1 -1
  288. package/src/renderer/shared/css/settings/automation.css +13 -13
  289. package/src/renderer/shared/css/settings/general.css +526 -5
  290. package/src/renderer/shared/css/settings/plugins.css +5 -5
  291. package/src/renderer/shared/css/settings/profile.css +331 -1
  292. package/src/renderer/shared/css/settings/theme.css +651 -3
  293. package/src/renderer/shared/css/veo-studio.css +16 -15
  294. package/src/renderer/shared/fonts.ts +19 -37
  295. package/src/renderer/shared/platform.test.ts +34 -0
  296. package/src/renderer/shared/platform.ts +173 -6
  297. package/src/renderer/shared/theme/themeManager.ts +144 -0
  298. package/src/renderer/shared/theme/themes.ts +116 -0
  299. package/src/renderer/shared/theme/tokens.css +318 -0
  300. package/src/renderer/shared/types.ts +82 -1
  301. package/src/renderer/shared/utils/agentActivity.ts +126 -5
  302. package/src/renderer/shared/utils/cardJson.test.ts +35 -0
  303. package/src/renderer/shared/utils/cardJson.ts +30 -0
  304. package/src/renderer/shared/utils/fileIcons.ts +1 -1
  305. package/src/renderer/shared/utils/markdown.tsx +329 -80
  306. package/src/renderer/shared/utils/markdown.webImages.test.tsx +40 -0
  307. package/src/renderer/shared/utils/providers.ts +8 -3
  308. package/src/renderer/shared/utils/screenCapture.ts +43 -0
  309. package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
  310. package/src/renderer/shared/utils/ui.ts +4 -64
  311. package/src/renderer/src/App.tsx +7 -0
  312. package/src/renderer/src/components/BrowserShell.tsx +108 -0
  313. package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +91 -0
  314. package/src/renderer/src/components/Live2DStage.tsx +32 -9
  315. package/src/renderer/src/components/MintDashboard.tsx +4 -2
  316. package/src/renderer/src/components/ModelPanel.tsx +2 -1
  317. package/src/renderer/src/components/PicturesLibrary.tsx +3 -2
  318. package/src/renderer/src/components/ScreenPicker.css +190 -0
  319. package/src/renderer/src/components/ScreenPicker.tsx +185 -540
  320. package/src/renderer/src/components/SettingsWindow.tsx +40 -2
  321. package/src/renderer/src/components/SpotlightWindow.tsx +1 -0
  322. package/src/renderer/src/components/WidgetWindow.tsx +27 -3
  323. package/src/renderer/src/components/WorkspacePanel.tsx +61 -43
  324. package/src/renderer/src/components/browser-shell.css +185 -0
  325. package/src/renderer/src/components/browser-suggestions-popup.css +55 -0
  326. package/src/renderer/src/components/liveTranslateFrames.test.ts +23 -0
  327. package/src/renderer/src/components/liveTranslateFrames.ts +34 -0
  328. package/src/renderer/src/css/app-shell.css +394 -113
  329. package/src/renderer/src/css/chat.css +1926 -380
  330. package/src/renderer/src/css/dashboard.css +11 -0
  331. package/src/renderer/src/css/desktop-tools.css +730 -0
  332. package/src/renderer/src/css/misc.css +19 -22
  333. package/src/renderer/src/css/model.css +49 -48
  334. package/src/renderer/src/css/pictures.css +144 -136
  335. package/src/renderer/src/css/settings/base.css +357 -123
  336. package/src/renderer/src/css/sidebar-workspace.css +930 -291
  337. package/src/renderer/src/css/spotlight.css +15 -26
  338. package/src/renderer/src/css/tool-surfaces.css +728 -0
  339. package/src/renderer/src/css/widget.css +35 -7
  340. package/src/renderer/src/env.d.ts +3 -0
  341. package/src/renderer/src/hooks/useProviderModels.ts +2 -9
  342. package/src/renderer/src/index.css +53 -46
  343. package/src/renderer/src/main.tsx +11 -0
  344. package/src/renderer/src/tauri.ts +201 -30
  345. package/src/renderer/src-web/App.tsx +40 -0
  346. package/src/renderer/src-web/components/MintDashboard.tsx +4 -2
  347. package/src/renderer/src-web/components/PicturesLibrary.tsx +3 -2
  348. package/src/renderer/src-web/components/PullToRefresh.tsx +107 -0
  349. package/src/renderer/src-web/components/SettingsWindow.tsx +28 -2
  350. package/src/renderer/src-web/css/app-shell.css +174 -96
  351. package/src/renderer/src-web/css/chat.css +1519 -316
  352. package/src/renderer/src-web/css/dashboard.css +11 -0
  353. package/src/renderer/src-web/css/misc.css +17 -17
  354. package/src/renderer/src-web/css/mobile.css +678 -0
  355. package/src/renderer/src-web/css/model.css +46 -46
  356. package/src/renderer/src-web/css/pictures.css +81 -34
  357. package/src/renderer/src-web/css/pull-to-refresh.css +54 -0
  358. package/src/renderer/src-web/css/settings/base.css +316 -100
  359. package/src/renderer/src-web/css/settings/mobile.css +320 -0
  360. package/src/renderer/src-web/css/sidebar-workspace.css +545 -100
  361. package/src/renderer/src-web/css/spotlight.css +9 -26
  362. package/src/renderer/src-web/css/widget.css +0 -5
  363. package/src/renderer/src-web/env.d.ts +3 -0
  364. package/src/renderer/src-web/hooks/useProviderModels.ts +2 -9
  365. package/src/renderer/src-web/index.css +53 -96
  366. package/src/renderer/src-web/main.tsx +11 -0
  367. package/src/renderer/src-web/tauri.ts +200 -30
  368. package/vite.config.web.ts +1 -0
  369. package/out/web/assets/MintDashboard-DWSqucgb.js +0 -57
  370. package/out/web/assets/SettingsWindow-DFM2aj5B.js +0 -2
  371. package/out/web/assets/chunk-CKBBP62Z-nr1uKPj9.js +0 -2
  372. package/out/web/assets/chunk-DSNCTWBM-Dtj8Rqsd.js +0 -2
  373. package/out/web/assets/chunk-DU6HZSFF-2tv5K1wT.js +0 -128
  374. package/out/web/assets/classDiagram-DTDB5LWJ-BdSFArgw.js +0 -1
  375. package/out/web/assets/classDiagram-v2-JRS7N3AN-BdSFArgw.js +0 -1
  376. package/out/web/assets/core-DImejIUG.js +0 -1
  377. package/out/web/assets/dist-js-BxuKEVWM.js +0 -1
  378. package/out/web/assets/event-BNl8Zahw.js +0 -1
  379. package/out/web/assets/fira-code-cyrillic-400-normal-UC0NFL4U.woff2 +0 -0
  380. package/out/web/assets/fira-code-cyrillic-400-normal-dc1q3VD7.woff +0 -0
  381. package/out/web/assets/fira-code-cyrillic-500-normal-Bu7VCL72.woff +0 -0
  382. package/out/web/assets/fira-code-cyrillic-500-normal-CX35h3Mg.woff2 +0 -0
  383. package/out/web/assets/fira-code-cyrillic-ext-400-normal-Buh61xzB.woff +0 -0
  384. package/out/web/assets/fira-code-cyrillic-ext-400-normal-txZ9Fk_1.woff2 +0 -0
  385. package/out/web/assets/fira-code-cyrillic-ext-500-normal-CYRspHgm.woff2 +0 -0
  386. package/out/web/assets/fira-code-cyrillic-ext-500-normal-CwtLmSFD.woff +0 -0
  387. package/out/web/assets/fira-code-greek-400-normal-B2Gh_Y8s.woff2 +0 -0
  388. package/out/web/assets/fira-code-greek-400-normal-DobhaxvF.woff +0 -0
  389. package/out/web/assets/fira-code-greek-500-normal-CVO0IRNt.woff2 +0 -0
  390. package/out/web/assets/fira-code-greek-500-normal-DEXIWiey.woff +0 -0
  391. package/out/web/assets/fira-code-greek-ext-400-normal-DR7mBgIM.woff2 +0 -0
  392. package/out/web/assets/fira-code-greek-ext-400-normal-DSPUheWg.woff +0 -0
  393. package/out/web/assets/fira-code-greek-ext-500-normal-BO3iq16n.woff2 +0 -0
  394. package/out/web/assets/fira-code-greek-ext-500-normal-CMiNLXER.woff +0 -0
  395. package/out/web/assets/fira-code-latin-ext-400-normal-DG73JjOf.woff +0 -0
  396. package/out/web/assets/fira-code-latin-ext-400-normal-KSMg0QLl.woff2 +0 -0
  397. package/out/web/assets/fira-code-latin-ext-500-normal-CaANqXjI.woff +0 -0
  398. package/out/web/assets/fira-code-latin-ext-500-normal-Crd9e_oI.woff2 +0 -0
  399. package/out/web/assets/flowDiagram-A5DVABFB-BpnHUSU7.js +0 -1
  400. package/out/web/assets/index-CVRg270z.css +0 -1
  401. package/out/web/assets/index-DxZQr9yD.js +0 -10
  402. package/out/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  403. package/out/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  404. package/out/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  405. package/out/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  406. package/out/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  407. package/out/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  408. package/out/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  409. package/out/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  410. package/out/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  411. package/out/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  412. package/out/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  413. package/out/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  414. package/out/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  415. package/out/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  416. package/out/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  417. package/out/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  418. package/out/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  419. package/out/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  420. package/out/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  421. package/out/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  422. package/out/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  423. package/out/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  424. package/out/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  425. package/out/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  426. package/out/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  427. package/out/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  428. package/out/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  429. package/out/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  430. package/out/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  431. package/out/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  432. package/out/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  433. package/out/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  434. package/out/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  435. package/out/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  436. package/out/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  437. package/out/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  438. package/out/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  439. package/out/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  440. package/out/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  441. package/out/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  442. package/out/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  443. package/out/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  444. package/out/web/assets/kanit-latin-400-normal-BUlsQXH3.woff2 +0 -0
  445. package/out/web/assets/kanit-latin-400-normal-HVZWrfev.woff +0 -0
  446. package/out/web/assets/kanit-latin-500-normal-CX_4XcKi.woff2 +0 -0
  447. package/out/web/assets/kanit-latin-500-normal-qBha86O4.woff +0 -0
  448. package/out/web/assets/kanit-latin-600-normal-0lS78yAd.woff2 +0 -0
  449. package/out/web/assets/kanit-latin-600-normal-DlacpeTg.woff +0 -0
  450. package/out/web/assets/kanit-latin-ext-400-normal-B2xPf58u.woff2 +0 -0
  451. package/out/web/assets/kanit-latin-ext-400-normal-CHvNKHHD.woff +0 -0
  452. package/out/web/assets/kanit-latin-ext-500-normal-BFdWrG22.woff +0 -0
  453. package/out/web/assets/kanit-latin-ext-500-normal-EHs3suD6.woff2 +0 -0
  454. package/out/web/assets/kanit-latin-ext-600-normal-BBDzoLaj.woff +0 -0
  455. package/out/web/assets/kanit-latin-ext-600-normal-DAX3wtuq.woff2 +0 -0
  456. package/out/web/assets/kanit-thai-400-normal-D2nliXKa.woff +0 -0
  457. package/out/web/assets/kanit-thai-400-normal-D80Pglro.woff2 +0 -0
  458. package/out/web/assets/kanit-thai-500-normal-BB8gri04.woff2 +0 -0
  459. package/out/web/assets/kanit-thai-500-normal-Iy_w0a80.woff +0 -0
  460. package/out/web/assets/kanit-thai-600-normal-Bj6oiaDc.woff2 +0 -0
  461. package/out/web/assets/kanit-thai-600-normal-wbTlPO7I.woff +0 -0
  462. package/out/web/assets/kanit-vietnamese-400-normal-DRI8YWyb.woff2 +0 -0
  463. package/out/web/assets/kanit-vietnamese-400-normal-dow6Bnto.woff +0 -0
  464. package/out/web/assets/kanit-vietnamese-500-normal-BzTrpCM3.woff2 +0 -0
  465. package/out/web/assets/kanit-vietnamese-500-normal-T1ndT7MF.woff +0 -0
  466. package/out/web/assets/kanit-vietnamese-600-normal-Bi7fw3I4.woff2 +0 -0
  467. package/out/web/assets/kanit-vietnamese-600-normal-DXCRAa_8.woff +0 -0
  468. package/out/web/assets/mali-latin-400-normal-ByPcZ8Gx.woff +0 -0
  469. package/out/web/assets/mali-latin-400-normal-Cuc6Ykl8.woff2 +0 -0
  470. package/out/web/assets/mali-latin-500-normal-B01M-LP6.woff2 +0 -0
  471. package/out/web/assets/mali-latin-500-normal-Thgqw_9f.woff +0 -0
  472. package/out/web/assets/mali-latin-600-normal-B9TiUNpT.woff2 +0 -0
  473. package/out/web/assets/mali-latin-600-normal-CycpQCwU.woff +0 -0
  474. package/out/web/assets/mali-latin-ext-400-normal-DUVdJQO4.woff +0 -0
  475. package/out/web/assets/mali-latin-ext-400-normal-iCG1ALuJ.woff2 +0 -0
  476. package/out/web/assets/mali-latin-ext-500-normal-BfJE4UMZ.woff2 +0 -0
  477. package/out/web/assets/mali-latin-ext-500-normal-FoLEyOKS.woff +0 -0
  478. package/out/web/assets/mali-latin-ext-600-normal-BTvIfiCp.woff2 +0 -0
  479. package/out/web/assets/mali-latin-ext-600-normal-DAQcLVmD.woff +0 -0
  480. package/out/web/assets/mali-thai-400-normal-BO7A3bLZ.woff2 +0 -0
  481. package/out/web/assets/mali-thai-400-normal-CX9VrjTn.woff +0 -0
  482. package/out/web/assets/mali-thai-500-normal-Cph2qha9.woff +0 -0
  483. package/out/web/assets/mali-thai-500-normal-D89rMT77.woff2 +0 -0
  484. package/out/web/assets/mali-thai-600-normal-DPgXT6rZ.woff +0 -0
  485. package/out/web/assets/mali-thai-600-normal-DlTx3OsB.woff2 +0 -0
  486. package/out/web/assets/mali-vietnamese-400-normal-DUKxzXaz.woff2 +0 -0
  487. package/out/web/assets/mali-vietnamese-400-normal-gFDLvsOK.woff +0 -0
  488. package/out/web/assets/mali-vietnamese-500-normal--OY07q2c.woff +0 -0
  489. package/out/web/assets/mali-vietnamese-500-normal-KDrNRwfW.woff2 +0 -0
  490. package/out/web/assets/mali-vietnamese-600-normal-CtTMUU7T.woff +0 -0
  491. package/out/web/assets/mali-vietnamese-600-normal-DFlJr66u.woff2 +0 -0
  492. package/out/web/assets/mermaid.core-DYGGlqOf.js +0 -11
  493. package/out/web/assets/mitr-latin-400-normal-Cb18vPmZ.woff +0 -0
  494. package/out/web/assets/mitr-latin-400-normal-CwbkLQ4a.woff2 +0 -0
  495. package/out/web/assets/mitr-latin-500-normal-B4JOhzAi.woff +0 -0
  496. package/out/web/assets/mitr-latin-500-normal-BmClOow2.woff2 +0 -0
  497. package/out/web/assets/mitr-latin-ext-400-normal-B3CPG7Yo.woff2 +0 -0
  498. package/out/web/assets/mitr-latin-ext-400-normal-DTx65hiC.woff +0 -0
  499. package/out/web/assets/mitr-latin-ext-500-normal-B_Diajz1.woff2 +0 -0
  500. package/out/web/assets/mitr-latin-ext-500-normal-DYOsVaVT.woff +0 -0
  501. package/out/web/assets/mitr-thai-400-normal-CS4Do6aM.woff +0 -0
  502. package/out/web/assets/mitr-thai-400-normal-CXWaAdVj.woff2 +0 -0
  503. package/out/web/assets/mitr-thai-500-normal-CVdBrEVH.woff2 +0 -0
  504. package/out/web/assets/mitr-thai-500-normal-CW_B5D-i.woff +0 -0
  505. package/out/web/assets/mitr-vietnamese-400-normal-CPMGWyoC.woff2 +0 -0
  506. package/out/web/assets/mitr-vietnamese-400-normal-Z6XhIpdY.woff +0 -0
  507. package/out/web/assets/mitr-vietnamese-500-normal-CDrv0585.woff2 +0 -0
  508. package/out/web/assets/mitr-vietnamese-500-normal-CMQWyq-Z.woff +0 -0
  509. package/out/web/assets/noto-sans-thai-latin-400-normal-BaWBrqFP.woff2 +0 -0
  510. package/out/web/assets/noto-sans-thai-latin-400-normal-Dh_FJjOB.woff +0 -0
  511. package/out/web/assets/noto-sans-thai-latin-500-normal-BC04jsmm.woff +0 -0
  512. package/out/web/assets/noto-sans-thai-latin-500-normal-fyBKClF1.woff2 +0 -0
  513. package/out/web/assets/noto-sans-thai-latin-600-normal-CemcjnVR.woff2 +0 -0
  514. package/out/web/assets/noto-sans-thai-latin-600-normal-Dbc-dNO6.woff +0 -0
  515. package/out/web/assets/noto-sans-thai-latin-ext-400-normal-B-1H0YkH.woff +0 -0
  516. package/out/web/assets/noto-sans-thai-latin-ext-400-normal-nBP59pVw.woff2 +0 -0
  517. package/out/web/assets/noto-sans-thai-latin-ext-500-normal-B-ZWb8SZ.woff2 +0 -0
  518. package/out/web/assets/noto-sans-thai-latin-ext-500-normal-CrEHO7C8.woff +0 -0
  519. package/out/web/assets/noto-sans-thai-latin-ext-600-normal-PkMZo0xa.woff +0 -0
  520. package/out/web/assets/noto-sans-thai-latin-ext-600-normal-maHHBsez.woff2 +0 -0
  521. package/out/web/assets/noto-sans-thai-thai-400-normal-BEOmKAI1.woff2 +0 -0
  522. package/out/web/assets/noto-sans-thai-thai-400-normal-Dxb_unuU.woff +0 -0
  523. package/out/web/assets/noto-sans-thai-thai-500-normal-BOGXO2tw.woff +0 -0
  524. package/out/web/assets/noto-sans-thai-thai-500-normal-CU_X86GQ.woff2 +0 -0
  525. package/out/web/assets/noto-sans-thai-thai-600-normal-CmgB8_JK.woff +0 -0
  526. package/out/web/assets/noto-sans-thai-thai-600-normal-Cs73sVjl.woff2 +0 -0
  527. package/out/web/assets/oauthManager-DNiwM8Rn.js +0 -2
  528. package/out/web/assets/oauthManager-DuamS1Av.css +0 -1
  529. package/out/web/assets/outfit-latin-300-normal-AWLfuCom.woff +0 -0
  530. package/out/web/assets/outfit-latin-300-normal-CRYTwY6m.woff2 +0 -0
  531. package/out/web/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
  532. package/out/web/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
  533. package/out/web/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
  534. package/out/web/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
  535. package/out/web/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
  536. package/out/web/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
  537. package/out/web/assets/outfit-latin-ext-300-normal-BYS6Dpvq.woff2 +0 -0
  538. package/out/web/assets/outfit-latin-ext-300-normal-CMIMshEd.woff +0 -0
  539. package/out/web/assets/outfit-latin-ext-400-normal-5tcqmc2S.woff2 +0 -0
  540. package/out/web/assets/outfit-latin-ext-400-normal-DHm7mdGe.woff +0 -0
  541. package/out/web/assets/outfit-latin-ext-500-normal-DrCvqoFD.woff +0 -0
  542. package/out/web/assets/outfit-latin-ext-500-normal-zeox_O30.woff2 +0 -0
  543. package/out/web/assets/outfit-latin-ext-600-normal-B85nYjL1.woff2 +0 -0
  544. package/out/web/assets/outfit-latin-ext-600-normal-CWJcPgd7.woff +0 -0
  545. package/out/web/assets/preload-helper-D0uHx7FM.js +0 -1
  546. package/out/web/assets/prompt-latin-ext-400-normal-1pHbN9uy.woff +0 -0
  547. package/out/web/assets/prompt-latin-ext-400-normal-DdSafGZ9.woff2 +0 -0
  548. package/out/web/assets/prompt-latin-ext-500-normal--EZ1um7s.woff2 +0 -0
  549. package/out/web/assets/prompt-latin-ext-500-normal-D-lI1EVT.woff +0 -0
  550. package/out/web/assets/prompt-latin-ext-600-normal-C8b50x67.woff +0 -0
  551. package/out/web/assets/prompt-latin-ext-600-normal-Cg9L7iJU.woff2 +0 -0
  552. package/out/web/assets/prompt-vietnamese-400-normal-BCPzsgPT.woff2 +0 -0
  553. package/out/web/assets/prompt-vietnamese-400-normal-D4pLyeNK.woff +0 -0
  554. package/out/web/assets/prompt-vietnamese-500-normal-DmzxmPwa.woff2 +0 -0
  555. package/out/web/assets/prompt-vietnamese-500-normal-w_sS_j2F.woff +0 -0
  556. package/out/web/assets/prompt-vietnamese-600-normal-1dyohtNh.woff +0 -0
  557. package/out/web/assets/prompt-vietnamese-600-normal-7QWjJBsF.woff2 +0 -0
  558. package/out/web/assets/sarabun-latin-400-normal-DmguIS-7.woff +0 -0
  559. package/out/web/assets/sarabun-latin-400-normal-URPBxl-K.woff2 +0 -0
  560. package/out/web/assets/sarabun-latin-500-normal-BjUTcdxu.woff2 +0 -0
  561. package/out/web/assets/sarabun-latin-500-normal-CJexTAnl.woff +0 -0
  562. package/out/web/assets/sarabun-latin-600-normal-9FonlkKx.woff +0 -0
  563. package/out/web/assets/sarabun-latin-600-normal-DMD3TROr.woff2 +0 -0
  564. package/out/web/assets/sarabun-latin-ext-400-normal-BbP7GpLL.woff2 +0 -0
  565. package/out/web/assets/sarabun-latin-ext-400-normal-BsmTn0SS.woff +0 -0
  566. package/out/web/assets/sarabun-latin-ext-500-normal-Cot16Bd2.woff +0 -0
  567. package/out/web/assets/sarabun-latin-ext-500-normal-DE1LysqI.woff2 +0 -0
  568. package/out/web/assets/sarabun-latin-ext-600-normal-0tXu9x_i.woff +0 -0
  569. package/out/web/assets/sarabun-latin-ext-600-normal-BRxjKUQ6.woff2 +0 -0
  570. package/out/web/assets/sarabun-thai-400-normal-C2DaJlKK.woff2 +0 -0
  571. package/out/web/assets/sarabun-thai-400-normal-CH-UbWgg.woff +0 -0
  572. package/out/web/assets/sarabun-thai-500-normal-BVssjame.woff2 +0 -0
  573. package/out/web/assets/sarabun-thai-500-normal-C3iDLEzi.woff +0 -0
  574. package/out/web/assets/sarabun-thai-600-normal-3dIAQ-_s.woff2 +0 -0
  575. package/out/web/assets/sarabun-thai-600-normal-DWKeTe6a.woff +0 -0
  576. package/out/web/assets/sarabun-vietnamese-400-normal-DPfkSCMy.woff +0 -0
  577. package/out/web/assets/sarabun-vietnamese-400-normal-igdrzAGY.woff2 +0 -0
  578. package/out/web/assets/sarabun-vietnamese-500-normal-CpRVvGRU.woff +0 -0
  579. package/out/web/assets/sarabun-vietnamese-500-normal-Dpe7Br9m.woff2 +0 -0
  580. package/out/web/assets/sarabun-vietnamese-600-normal-BMbOgAW-.woff +0 -0
  581. package/out/web/assets/sarabun-vietnamese-600-normal-DkxrOlLB.woff2 +0 -0
  582. package/out/web/assets/stateDiagram-v2-4QOOHH4V-s6P9g2_b.js +0 -1
  583. package/out/web/assets/swimlanesDiagram-VK2B7HYN-CV9fOLrV.js +0 -8
  584. package/out/web/assets/tauri-CNrHQ2Gb.js +0 -3
  585. package/out/web/assets/window-DkIhgI7B.js +0 -1
@@ -7,11 +7,13 @@ use std::future::Future;
7
7
  use std::path::{Path, PathBuf};
8
8
  use std::pin::Pin;
9
9
  use std::process::Command;
10
+ use std::sync::Arc;
10
11
  use std::time::Instant;
11
12
  use thiserror::Error;
12
13
 
13
14
  use crate::chat::{
14
- ChatMessage, ChatRole, ContentBlock, send_chat_with_fallback, stream_chat_with_fallback,
15
+ ChatMessage, ChatRole, ChatStreamEvent, ContentBlock, send_chat_with_fallback,
16
+ stream_chat_events_with_fallback, stream_chat_with_fallback,
15
17
  };
16
18
  use crate::code_tools::{
17
19
  CodeEdit, CodePatchHunk, apply_code_edits, build_code_patch, list_code_files,
@@ -41,6 +43,135 @@ const CONTEXT_LIMIT: usize = 3;
41
43
  /// continuity, not to re-litigate a whole previous answer.
42
44
  const MAX_CONTEXT_MESSAGE_CHARS: usize = 200;
43
45
 
46
+ struct TurnStartListener {
47
+ chat_id: String,
48
+ callback: Arc<dyn Fn(i64) + Send + Sync>,
49
+ }
50
+
51
+ tokio::task_local! {
52
+ static TURN_START_LISTENER: TurnStartListener;
53
+ }
54
+
55
+ /// Reports the persisted turn ID to the initiating transport without changing
56
+ /// the agent/chat interfaces used by CLI, jobs, and background integrations.
57
+ pub async fn with_turn_start_listener<F, R>(
58
+ chat_id: String,
59
+ callback: impl Fn(i64) + Send + Sync + 'static,
60
+ future: F,
61
+ ) -> R
62
+ where
63
+ F: Future<Output = R>,
64
+ {
65
+ TURN_START_LISTENER
66
+ .scope(
67
+ TurnStartListener {
68
+ chat_id,
69
+ callback: Arc::new(callback),
70
+ },
71
+ future,
72
+ )
73
+ .await
74
+ }
75
+
76
+ /// Owns one persisted turn, including its cross-process queue lease. Dropping a
77
+ /// cancelled task marks its prompt interrupted instead of leaving it running.
78
+ struct TurnLease {
79
+ memory: MemoryStore,
80
+ id: i64,
81
+ heartbeat: tokio::task::JoinHandle<()>,
82
+ finished: bool,
83
+ activity: Arc<std::sync::Mutex<Vec<AgentProgress>>>,
84
+ }
85
+
86
+ impl TurnLease {
87
+ async fn start(memory: &MemoryStore, chat_id: &str, text: &str) -> Result<Self, MemoryError> {
88
+ let id = memory.start_turn(chat_id, text)?;
89
+ let _ = TURN_START_LISTENER.try_with(|listener| {
90
+ if listener.chat_id == chat_id {
91
+ (listener.callback)(id);
92
+ }
93
+ });
94
+ let heartbeat_memory = memory.clone();
95
+ let heartbeat = tokio::spawn(async move {
96
+ loop {
97
+ tokio::time::sleep(std::time::Duration::from_secs(10)).await;
98
+ if heartbeat_memory.renew_turn(id).is_err() {
99
+ break;
100
+ }
101
+ }
102
+ });
103
+ let mut lease = Self {
104
+ memory: memory.clone(),
105
+ id,
106
+ heartbeat,
107
+ finished: false,
108
+ activity: Arc::new(std::sync::Mutex::new(Vec::new())),
109
+ };
110
+ loop {
111
+ match memory.claim_turn(chat_id, id) {
112
+ Ok(true) => return Ok(lease),
113
+ Ok(false) => tokio::time::sleep(std::time::Duration::from_millis(250)).await,
114
+ Err(error) => {
115
+ lease.fail();
116
+ return Err(error);
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ fn persist_activity(&self) {
123
+ if let Ok(lock) = self.activity.lock() {
124
+ let compacted = compact_agent_progress(&lock);
125
+ if !compacted.is_empty() {
126
+ if let Ok(activity_json) = serde_json::to_string(&compacted) {
127
+ let _ = self
128
+ .memory
129
+ .set_interaction_agent_activity_json(self.id, &activity_json);
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ fn complete_with_summary(
136
+ &mut self,
137
+ summary: &str,
138
+ provider: &str,
139
+ model: &str,
140
+ fallback_provider: Option<&str>,
141
+ ) -> Result<(), MemoryError> {
142
+ self.persist_activity();
143
+ self.memory
144
+ .finish_turn(self.id, summary, provider, model, fallback_provider)?;
145
+ self.finished = true;
146
+ Ok(())
147
+ }
148
+
149
+ fn complete(&mut self, response: &ChatResponse) -> Result<(), MemoryError> {
150
+ self.complete_with_summary(
151
+ &response.text,
152
+ &response.provider,
153
+ &response.model,
154
+ response.fallback_provider.as_deref(),
155
+ )
156
+ }
157
+
158
+ fn fail(&mut self) {
159
+ self.persist_activity();
160
+ let _ = self.memory.end_turn(self.id, "failed");
161
+ self.finished = true;
162
+ }
163
+ }
164
+
165
+ impl Drop for TurnLease {
166
+ fn drop(&mut self) {
167
+ self.heartbeat.abort();
168
+ if !self.finished {
169
+ self.persist_activity();
170
+ let _ = self.memory.end_turn(self.id, "interrupted");
171
+ }
172
+ }
173
+ }
174
+
44
175
  #[derive(Debug, Error)]
45
176
  pub enum OrchestrationError {
46
177
  #[error(transparent)]
@@ -105,19 +236,24 @@ pub async fn orchestrate_chat(
105
236
  config: &MintConfig,
106
237
  request: &ChatRequest,
107
238
  ) -> Result<ChatResponse, OrchestrationError> {
239
+ let memory = MemoryStore::open_default()?;
240
+ let mut turn = TurnLease::start(&memory, &request_chat_id(request), &request.message).await?;
108
241
  let mut resolved_request = request.clone();
109
242
  resolved_request.message = resolve_github_links(&request.message, config).await;
110
- let memory = MemoryStore::open_default()?;
111
243
  let enriched = enrich_request(config, &memory, &resolved_request)?;
112
- let response = send_chat(config, &enriched).await?;
113
- memory.add_interaction_for_chat_with_fallback(
114
- &request_chat_id(request),
115
- &request.message,
116
- &response.text,
117
- &response.provider,
118
- &response.model,
119
- response.fallback_provider.as_deref(),
120
- )?;
244
+ let response = match send_chat(config, &enriched).await {
245
+ Ok(response) => response,
246
+ Err(error) => {
247
+ turn.fail();
248
+ return Err(error.into());
249
+ }
250
+ };
251
+ turn.complete(&response)?;
252
+ if let Some(ref ws) = request.workspace_path {
253
+ if !ws.trim().is_empty() {
254
+ let _ = memory.set_chat_session_workspace(&request_chat_id(request), Some(ws.trim()));
255
+ }
256
+ }
121
257
  spawn_auto_memory_update(
122
258
  config.clone(),
123
259
  request.message.clone(),
@@ -129,6 +265,7 @@ pub async fn orchestrate_chat(
129
265
  config.clone(),
130
266
  request.message.clone(),
131
267
  response.text.clone(),
268
+ turn.id,
132
269
  );
133
270
  Ok(response)
134
271
  }
@@ -141,19 +278,24 @@ pub async fn orchestrate_chat_stream<F>(
141
278
  where
142
279
  F: FnMut(String),
143
280
  {
281
+ let memory = MemoryStore::open_default()?;
282
+ let mut turn = TurnLease::start(&memory, &request_chat_id(request), &request.message).await?;
144
283
  let mut resolved_request = request.clone();
145
284
  resolved_request.message = resolve_github_links(&request.message, config).await;
146
- let memory = MemoryStore::open_default()?;
147
285
  let enriched = enrich_request(config, &memory, &resolved_request)?;
148
- let response = stream_chat(config, &enriched, on_chunk).await?;
149
- memory.add_interaction_for_chat_with_fallback(
150
- &request_chat_id(request),
151
- &request.message,
152
- &response.text,
153
- &response.provider,
154
- &response.model,
155
- response.fallback_provider.as_deref(),
156
- )?;
286
+ let response = match stream_chat(config, &enriched, on_chunk).await {
287
+ Ok(response) => response,
288
+ Err(error) => {
289
+ turn.fail();
290
+ return Err(error.into());
291
+ }
292
+ };
293
+ turn.complete(&response)?;
294
+ if let Some(ref ws) = request.workspace_path {
295
+ if !ws.trim().is_empty() {
296
+ let _ = memory.set_chat_session_workspace(&request_chat_id(request), Some(ws.trim()));
297
+ }
298
+ }
157
299
  spawn_auto_memory_update(
158
300
  config.clone(),
159
301
  request.message.clone(),
@@ -165,6 +307,7 @@ where
165
307
  config.clone(),
166
308
  request.message.clone(),
167
309
  response.text.clone(),
310
+ turn.id,
168
311
  );
169
312
  Ok(response)
170
313
  }
@@ -173,19 +316,19 @@ pub async fn orchestrate_chat_with_fallback(
173
316
  config: &MintConfig,
174
317
  request: &ChatRequest,
175
318
  ) -> Result<(ChatResponse, Option<String>), OrchestrationError> {
319
+ let memory = MemoryStore::open_default()?;
320
+ let mut turn = TurnLease::start(&memory, &request_chat_id(request), &request.message).await?;
176
321
  let mut resolved_request = request.clone();
177
322
  resolved_request.message = resolve_github_links(&request.message, config).await;
178
- let memory = MemoryStore::open_default()?;
179
323
  let enriched = enrich_request(config, &memory, &resolved_request)?;
180
- let (response, fallback) = send_chat_with_fallback(config, &enriched).await?;
181
- memory.add_interaction_for_chat_with_fallback(
182
- &request_chat_id(request),
183
- &request.message,
184
- &response.text,
185
- &response.provider,
186
- &response.model,
187
- response.fallback_provider.as_deref(),
188
- )?;
324
+ let (response, fallback) = match send_chat_with_fallback(config, &enriched).await {
325
+ Ok(result) => result,
326
+ Err(error) => {
327
+ turn.fail();
328
+ return Err(error.into());
329
+ }
330
+ };
331
+ turn.complete(&response)?;
189
332
  spawn_auto_memory_update(
190
333
  config.clone(),
191
334
  request.message.clone(),
@@ -197,6 +340,7 @@ pub async fn orchestrate_chat_with_fallback(
197
340
  config.clone(),
198
341
  request.message.clone(),
199
342
  response.text.clone(),
343
+ turn.id,
200
344
  );
201
345
  Ok((response, fallback))
202
346
  }
@@ -209,19 +353,24 @@ pub async fn orchestrate_chat_stream_with_fallback<F>(
209
353
  where
210
354
  F: FnMut(String),
211
355
  {
356
+ let memory = MemoryStore::open_default()?;
357
+ let mut turn = TurnLease::start(&memory, &request_chat_id(request), &request.message).await?;
212
358
  let mut resolved_request = request.clone();
213
359
  resolved_request.message = resolve_github_links(&request.message, config).await;
214
- let memory = MemoryStore::open_default()?;
215
360
  let enriched = enrich_request(config, &memory, &resolved_request)?;
216
- let (response, fallback) = stream_chat_with_fallback(config, &enriched, on_chunk).await?;
217
- memory.add_interaction_for_chat_with_fallback(
218
- &request_chat_id(request),
219
- &request.message,
220
- &response.text,
221
- &response.provider,
222
- &response.model,
223
- response.fallback_provider.as_deref(),
224
- )?;
361
+ let (response, fallback) = match stream_chat_with_fallback(config, &enriched, on_chunk).await {
362
+ Ok(result) => result,
363
+ Err(error) => {
364
+ turn.fail();
365
+ return Err(error.into());
366
+ }
367
+ };
368
+ turn.complete(&response)?;
369
+ if let Some(ref ws) = request.workspace_path {
370
+ if !ws.trim().is_empty() {
371
+ let _ = memory.set_chat_session_workspace(&request_chat_id(request), Some(ws.trim()));
372
+ }
373
+ }
225
374
  spawn_auto_memory_update(
226
375
  config.clone(),
227
376
  request.message.clone(),
@@ -233,6 +382,7 @@ where
233
382
  config.clone(),
234
383
  request.message.clone(),
235
384
  response.text.clone(),
385
+ turn.id,
236
386
  );
237
387
  Ok((response, fallback))
238
388
  }
@@ -255,7 +405,7 @@ fn enrich_request(
255
405
  request: &ChatRequest,
256
406
  ) -> Result<ChatRequest, MemoryError> {
257
407
  let mut interactions =
258
- memory.recent_interactions_for_chat(&request_chat_id(request), CONTEXT_LIMIT)?;
408
+ memory.recent_completed_interactions_for_chat(&request_chat_id(request), CONTEXT_LIMIT)?;
259
409
  interactions.reverse();
260
410
  let transcript = interactions
261
411
  .into_iter()
@@ -514,6 +664,27 @@ pub enum AgentProgress {
514
664
  Thought {
515
665
  thought: String,
516
666
  },
667
+ /// Incremental model reasoning for the currently active agent step. These
668
+ /// events are live-only; consumers replace them with the matching final
669
+ /// `ExtendedThinking` record instead of persisting every delta.
670
+ ThinkingDelta {
671
+ id: String,
672
+ delta: String,
673
+ elapsed_ms: u64,
674
+ },
675
+ /// Extended reasoning / chain-of-thought from the model's API thinking
676
+ /// tokens (e.g. Claude `<thinking>`, Gemini Thinking, DeepSeek Reasoner,
677
+ /// or `<think>` tags). Semantically distinct from `Thought`, which
678
+ /// carries short, one-line agent notes (fallback warnings, decision
679
+ /// summaries). The CLI renders this as a collapsible block; the web/
680
+ /// desktop UI shows it in a dedicated "Extended Thinking" panel.
681
+ ExtendedThinking {
682
+ #[serde(default, skip_serializing_if = "Option::is_none")]
683
+ id: Option<String>,
684
+ thought: String,
685
+ #[serde(default, skip_serializing_if = "Option::is_none")]
686
+ elapsed_ms: Option<u64>,
687
+ },
517
688
  /// Emitted while retrying a step after every configured provider was
518
689
  /// unreachable (`ChatError::NetworkUnavailable`) — distinct from
519
690
  /// `Thinking` because there's nothing to wait *on* here except the
@@ -549,6 +720,87 @@ pub enum AgentProgress {
549
720
  },
550
721
  }
551
722
 
723
+ /// Determines whether a thought string represents long-form / internal chain-of-thought
724
+ /// (reasoning tokens, <think> tags, or multi-line reasoning) versus a brief agent note.
725
+ pub fn is_internal_cot(text: &str) -> bool {
726
+ let trimmed = text.trim();
727
+ if trimmed.starts_with("<think>") || trimmed.contains("</think>") {
728
+ return true;
729
+ }
730
+ let line_count = trimmed.lines().count();
731
+ let char_count = trimmed.chars().count();
732
+ if trimmed.contains("\n\n") || line_count > 2 || char_count > 180 {
733
+ return true;
734
+ }
735
+ let lower = trimmed.to_lowercase();
736
+ if lower.starts_with("the user ")
737
+ || lower.starts_with("let's think")
738
+ || lower.starts_with("let me analyze")
739
+ || lower.starts_with("per rule")
740
+ || lower.starts_with("note the ")
741
+ || lower.contains("thinking process:")
742
+ {
743
+ return true;
744
+ }
745
+ false
746
+ }
747
+
748
+ /// Compacts an agent progress event stream for storage, removing transport-only
749
+ /// streaming deltas (`ThinkingDelta`) while preserving complete `ExtendedThinking`
750
+ /// blocks, or synthesizing an `ExtendedThinking` block for any interrupted delta sequence.
751
+ pub fn compact_agent_progress(progress: &[AgentProgress]) -> Vec<AgentProgress> {
752
+ let completed_ids: std::collections::HashSet<String> = progress
753
+ .iter()
754
+ .filter_map(|event| match event {
755
+ AgentProgress::ExtendedThinking { id: Some(id), .. } => Some(id.clone()),
756
+ _ => None,
757
+ })
758
+ .collect();
759
+
760
+ let mut result = Vec::new();
761
+ let mut interrupted_order: Vec<String> = Vec::new();
762
+ let mut interrupted: std::collections::HashMap<String, (String, Option<u64>)> =
763
+ std::collections::HashMap::new();
764
+
765
+ for event in progress {
766
+ match event {
767
+ AgentProgress::ThinkingDelta {
768
+ id,
769
+ delta,
770
+ elapsed_ms,
771
+ } => {
772
+ if !completed_ids.contains(id) {
773
+ if !interrupted.contains_key(id) {
774
+ interrupted_order.push(id.clone());
775
+ }
776
+ interrupted
777
+ .entry(id.clone())
778
+ .and_modify(|(text, elapsed)| {
779
+ text.push_str(delta);
780
+ *elapsed = Some(*elapsed_ms);
781
+ })
782
+ .or_insert_with(|| (delta.clone(), Some(*elapsed_ms)));
783
+ }
784
+ }
785
+ _ => {
786
+ result.push(event.clone());
787
+ }
788
+ }
789
+ }
790
+
791
+ for id in interrupted_order {
792
+ if let Some((thought, elapsed_ms)) = interrupted.remove(&id) {
793
+ result.push(AgentProgress::ExtendedThinking {
794
+ id: Some(id),
795
+ thought,
796
+ elapsed_ms,
797
+ });
798
+ }
799
+ }
800
+
801
+ result
802
+ }
803
+
552
804
  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
553
805
  #[serde(rename_all = "camelCase")]
554
806
  pub struct ToolExecutionRecord {
@@ -598,6 +850,8 @@ pub struct AgentResult {
598
850
  pub summary: String,
599
851
  pub verification: String,
600
852
  pub fallback: Option<String>,
853
+ #[serde(default)]
854
+ pub fallback_reason: Option<String>,
601
855
  /// Sum of `total_tokens` across every step's API response this turn —
602
856
  /// see the doc comment on `turn_total_tokens` where it's accumulated.
603
857
  pub total_tokens: u64,
@@ -722,6 +976,24 @@ struct AgentInput {
722
976
  button: String,
723
977
  #[serde(default)]
724
978
  key: String,
979
+ #[serde(default)]
980
+ element_ref: String,
981
+ #[serde(default)]
982
+ tab_id: String,
983
+ #[serde(default)]
984
+ operation: String,
985
+ #[serde(default)]
986
+ condition: String,
987
+ #[serde(default)]
988
+ value: String,
989
+ #[serde(default)]
990
+ timeout_ms: Option<u64>,
991
+ #[serde(default)]
992
+ text_offset: Option<u64>,
993
+ #[serde(default)]
994
+ element_offset: Option<u64>,
995
+ #[serde(default)]
996
+ takeover_complete: bool,
725
997
  // Video tools input fields
726
998
  #[serde(default)]
727
999
  input: String,
@@ -915,22 +1187,56 @@ fn resolve_agent_config(
915
1187
  )
916
1188
  }
917
1189
 
918
- /// Calls `send_chat_with_fallback`, retrying up to `NETWORK_RETRY_ATTEMPTS`
919
- /// times (waiting `NETWORK_RETRY_DELAY` between each) when every configured
920
- /// provider comes back `ChatError::NetworkUnavailable` — swapping providers
921
- /// is pointless when the network itself is down, so this waits for it to
922
- /// come back instead of burning through the provider list, keeping the user
923
- /// informed via `AgentProgress::WaitingForNetwork` in the meantime. Any other
924
- /// error (including `NetworkUnavailable` after the last attempt) is returned
925
- /// immediately, unchanged.
926
- async fn send_chat_with_network_retry(
1190
+ enum AgentStreamChunk {
1191
+ DirectText(String),
1192
+ FinalSummary(String),
1193
+ }
1194
+
1195
+ async fn stream_chat_with_network_retry(
927
1196
  config: &MintConfig,
928
1197
  request: &ChatRequest,
1198
+ thought_id: &str,
1199
+ thought_started: Instant,
1200
+ fast_mode: bool,
929
1201
  progress: &mut (dyn FnMut(AgentProgress) + Send),
1202
+ stream_json_prompt: bool,
1203
+ allow_final_stream: bool,
1204
+ on_stream_chunk: &mut (dyn FnMut(AgentStreamChunk) + Send),
930
1205
  ) -> Result<(ChatResponse, Option<String>), ChatError> {
931
1206
  let mut attempt = 0;
1207
+ let mut summary_stream = FinishSummaryStream::default();
932
1208
  loop {
933
- match send_chat_with_fallback(config, request).await {
1209
+ let result = stream_chat_events_with_fallback(config, request, |event| match event {
1210
+ ChatStreamEvent::ReasoningDelta { delta } if !fast_mode && !delta.is_empty() => {
1211
+ progress(AgentProgress::ThinkingDelta {
1212
+ id: thought_id.to_owned(),
1213
+ delta,
1214
+ elapsed_ms: thought_started.elapsed().as_millis() as u64,
1215
+ });
1216
+ }
1217
+ ChatStreamEvent::TextDelta { delta } if allow_final_stream => {
1218
+ if stream_json_prompt {
1219
+ summary_stream.push_json_prompt(&delta, &mut |chunk| {
1220
+ on_stream_chunk(AgentStreamChunk::FinalSummary(chunk));
1221
+ });
1222
+ }
1223
+ }
1224
+ ChatStreamEvent::ToolCallDelta {
1225
+ index,
1226
+ name,
1227
+ arguments,
1228
+ input,
1229
+ } if allow_final_stream => summary_stream.push_tool_call(
1230
+ index,
1231
+ name.as_deref(),
1232
+ arguments.as_deref(),
1233
+ input.as_ref(),
1234
+ &mut |chunk| on_stream_chunk(AgentStreamChunk::FinalSummary(chunk)),
1235
+ ),
1236
+ _ => {}
1237
+ })
1238
+ .await;
1239
+ match result {
934
1240
  Err(ChatError::NetworkUnavailable) if attempt < NETWORK_RETRY_ATTEMPTS => {
935
1241
  attempt += 1;
936
1242
  progress(AgentProgress::WaitingForNetwork {
@@ -939,11 +1245,183 @@ async fn send_chat_with_network_retry(
939
1245
  });
940
1246
  tokio::time::sleep(NETWORK_RETRY_DELAY).await;
941
1247
  }
1248
+ Ok((response, fallback)) => {
1249
+ if !stream_json_prompt
1250
+ && allow_final_stream
1251
+ && response
1252
+ .tool_calls
1253
+ .as_ref()
1254
+ .is_none_or(|calls| calls.is_empty())
1255
+ && !response.text.is_empty()
1256
+ {
1257
+ // Plain text from a native tool-calling response is only a final
1258
+ // answer if the completed model turn contains no tool calls. Buffer
1259
+ // it until then so an assistant preamble doesn't appear under
1260
+ // "Mint:" while the requested tools are still about to run.
1261
+ on_stream_chunk(AgentStreamChunk::DirectText(response.text.clone()));
1262
+ }
1263
+ return Ok((response, fallback));
1264
+ }
942
1265
  other => return other,
943
1266
  }
944
1267
  }
945
1268
  }
946
1269
 
1270
+ #[derive(Default)]
1271
+ struct FinishSummaryStream {
1272
+ tool_names: std::collections::HashMap<usize, String>,
1273
+ tool_arguments: std::collections::HashMap<usize, String>,
1274
+ json_prompt: String,
1275
+ emitted: String,
1276
+ }
1277
+
1278
+ impl FinishSummaryStream {
1279
+ fn push_tool_call(
1280
+ &mut self,
1281
+ index: usize,
1282
+ name_delta: Option<&str>,
1283
+ arguments_delta: Option<&str>,
1284
+ input: Option<&serde_json::Value>,
1285
+ on_chunk: &mut (dyn FnMut(String) + Send),
1286
+ ) {
1287
+ if let Some(name) = name_delta {
1288
+ self.tool_names.insert(index, name.to_owned());
1289
+ }
1290
+ if let Some(arguments) = arguments_delta {
1291
+ self.tool_arguments
1292
+ .entry(index)
1293
+ .or_default()
1294
+ .push_str(arguments);
1295
+ }
1296
+ if let Some(input) = input {
1297
+ self.tool_arguments.insert(index, input.to_string());
1298
+ }
1299
+ if self
1300
+ .tool_names
1301
+ .get(&index)
1302
+ .is_some_and(|name| name == "finish")
1303
+ && let Some(arguments) = self.tool_arguments.get(&index)
1304
+ && let Some(summary) = json_string_field_prefix(arguments, "summary")
1305
+ {
1306
+ self.emit_new_text(&summary, on_chunk);
1307
+ }
1308
+ }
1309
+
1310
+ fn push_json_prompt(&mut self, delta: &str, on_chunk: &mut (dyn FnMut(String) + Send)) {
1311
+ self.json_prompt.push_str(delta);
1312
+ if json_string_field_prefix(&self.json_prompt, "action").as_deref() == Some("finish")
1313
+ && let Some(summary) = json_string_field_prefix(&self.json_prompt, "summary")
1314
+ {
1315
+ self.emit_new_text(&summary, on_chunk);
1316
+ }
1317
+ }
1318
+
1319
+ fn emit_new_text(&mut self, text: &str, on_chunk: &mut (dyn FnMut(String) + Send)) {
1320
+ if !text.starts_with(&self.emitted) || text.len() == self.emitted.len() {
1321
+ return;
1322
+ }
1323
+ let delta = text[self.emitted.len()..].to_owned();
1324
+ self.emitted.push_str(&delta);
1325
+ on_chunk(delta);
1326
+ }
1327
+ }
1328
+
1329
+ fn unstreamed_summary_remainder<'a>(
1330
+ summary: &'a str,
1331
+ streamed_finish: &str,
1332
+ streamed_direct: &str,
1333
+ ) -> &'a str {
1334
+ let streamed_finish = streamed_finish.trim();
1335
+ if !streamed_finish.is_empty()
1336
+ && let Some(remainder) = summary.strip_prefix(streamed_finish)
1337
+ {
1338
+ return remainder;
1339
+ }
1340
+
1341
+ let streamed_direct = streamed_direct.trim();
1342
+ if !streamed_direct.is_empty()
1343
+ && let Some(remainder) = summary.strip_prefix(streamed_direct)
1344
+ {
1345
+ return remainder;
1346
+ }
1347
+
1348
+ summary
1349
+ }
1350
+
1351
+ /// Reads a JSON string-valued property even while its value is still arriving.
1352
+ /// It skips quoted values and only recognizes actual object keys, so a mention
1353
+ /// of `"summary"` inside a thought or another string cannot leak to the chat.
1354
+ fn json_string_field_prefix(input: &str, field: &str) -> Option<String> {
1355
+ let bytes = input.as_bytes();
1356
+ let mut cursor = 0;
1357
+ while cursor < bytes.len() {
1358
+ if bytes[cursor] != b'"' {
1359
+ cursor += 1;
1360
+ continue;
1361
+ }
1362
+ let start = cursor;
1363
+ cursor += 1;
1364
+ let mut escaped = false;
1365
+ let mut end = None;
1366
+ while cursor < bytes.len() {
1367
+ match (bytes[cursor], escaped) {
1368
+ (b'"', false) => {
1369
+ end = Some(cursor);
1370
+ cursor += 1;
1371
+ break;
1372
+ }
1373
+ (b'\\', false) => escaped = true,
1374
+ (_, true) => escaped = false,
1375
+ _ => {}
1376
+ }
1377
+ cursor += 1;
1378
+ }
1379
+ if end.is_none() {
1380
+ break;
1381
+ }
1382
+ let Ok(key) = serde_json::from_str::<String>(&input[start..cursor]) else {
1383
+ continue;
1384
+ };
1385
+ if key != field {
1386
+ continue;
1387
+ }
1388
+ let mut value_start = cursor;
1389
+ while value_start < bytes.len() && bytes[value_start].is_ascii_whitespace() {
1390
+ value_start += 1;
1391
+ }
1392
+ if value_start >= bytes.len() || bytes[value_start] != b':' {
1393
+ continue;
1394
+ }
1395
+ value_start += 1;
1396
+ while value_start < bytes.len() && bytes[value_start].is_ascii_whitespace() {
1397
+ value_start += 1;
1398
+ }
1399
+ if value_start >= bytes.len() || bytes[value_start] != b'"' {
1400
+ continue;
1401
+ }
1402
+ let value_content_start = value_start + 1;
1403
+ let mut value_cursor = value_content_start;
1404
+ let mut value_escaped = false;
1405
+ while value_cursor < bytes.len() {
1406
+ match (bytes[value_cursor], value_escaped) {
1407
+ (b'"', false) => {
1408
+ return serde_json::from_str::<String>(&input[value_start..value_cursor + 1])
1409
+ .ok();
1410
+ }
1411
+ (b'\\', false) => value_escaped = true,
1412
+ (_, true) => value_escaped = false,
1413
+ _ => {}
1414
+ }
1415
+ value_cursor += 1;
1416
+ }
1417
+ // Close a temporary copy of an incomplete string. Invalid partial
1418
+ // escapes (e.g. half a `\\uXXXX`) are held until the next provider delta.
1419
+ let partial = format!("\"{}\"", &input[value_content_start..]);
1420
+ return serde_json::from_str::<String>(&partial).ok();
1421
+ }
1422
+ None
1423
+ }
1424
+
947
1425
  /// Returns a boxed `dyn Future` trait object rather than being a plain
948
1426
  /// `async fn` (whose return type would otherwise be an opaque, compiler-
949
1427
  /// inferred type). This is required, not just a style choice: `execute_tool`
@@ -987,7 +1465,6 @@ where
987
1465
  e
988
1466
  ))
989
1467
  })?;
990
- let resolved_task = resolve_github_links(task, config).await;
991
1468
  let chat_id = chat_id
992
1469
  .map(str::trim)
993
1470
  .filter(|chat_id| !chat_id.is_empty())
@@ -1000,6 +1477,17 @@ where
1000
1477
  // are already scoped or aren't "cli" at all (see `scoped_chat_id`).
1001
1478
  let chat_id = crate::agent::memory::scoped_chat_id(chat_id, Some(&root.to_string_lossy()));
1002
1479
  let chat_id = chat_id.as_str();
1480
+ let memory = MemoryStore::open_default()?;
1481
+ let mut turn = TurnLease::start(&memory, chat_id, task).await?;
1482
+ let record_activity = Arc::clone(&turn.activity);
1483
+ let mut progress = move |event: AgentProgress| {
1484
+ if let Ok(mut lock) = record_activity.lock() {
1485
+ lock.push(event.clone());
1486
+ }
1487
+ progress(event);
1488
+ };
1489
+ let resolved_task = resolve_github_links(task, config).await;
1490
+ let agent_result = crate::browser::run_session(async {
1003
1491
  // Subagent runs use a synthetic `{parent_chat_id}::subagent::{name}` chat id
1004
1492
  // (see the `dispatch_subagent` arm in `execute_tool`) so their own memory
1005
1493
  // interaction doesn't leak into the parent conversation's history. That
@@ -1065,7 +1553,10 @@ where
1065
1553
  #[allow(unused_assignments)]
1066
1554
  let mut final_model = "".to_string();
1067
1555
  let mut final_fallback = None;
1556
+ let mut final_fallback_reason = None;
1068
1557
  let mut action_counts = BTreeMap::<String, usize>::new();
1558
+ let mut streamed_finish_summary = String::new();
1559
+ let mut streamed_direct_text = String::new();
1069
1560
  // Track the most recent step (if any) that successfully modified a file
1070
1561
  // (`apply_patch`/`write_file`) and the most recent step that ran `verify`,
1071
1562
  // so `finish` can be rejected when code was changed but never checked —
@@ -1119,6 +1610,12 @@ where
1119
1610
  std::collections::BTreeSet::new();
1120
1611
 
1121
1612
  'steps: for step in 1..=MAX_STEPS {
1613
+ let thought_id = if let Some(name) = chat_id.split("::subagent::").nth(1) {
1614
+ format!("subagent-{name}-step-{step}")
1615
+ } else {
1616
+ format!("root-step-{step}")
1617
+ };
1618
+ let thought_started = Instant::now();
1122
1619
  let (active_config, agent_instruction, active_agent_name, active_model_name) =
1123
1620
  resolve_agent_config(config, agent_id, &trajectory);
1124
1621
 
@@ -1150,6 +1647,12 @@ where
1150
1647
  }
1151
1648
 
1152
1649
  let tool_mode = active_config.tool_calling_mode();
1650
+ // A finish can still be rejected after the provider finishes
1651
+ // streaming when edited files have not been verified. Don't show
1652
+ // that provisional summary to the user as if the run were done.
1653
+ let allow_final_stream =
1654
+ !unverified_modification(last_modify_step, last_verify_step, "")
1655
+ && last_verify_failed != Some(true);
1153
1656
 
1154
1657
  if tool_mode == ToolCallingMode::JsonPrompt
1155
1658
  && active_config.ai_provider == "ollama"
@@ -1204,54 +1707,92 @@ where
1204
1707
  content,
1205
1708
  });
1206
1709
  }
1207
- send_chat_with_network_retry(
1208
- &active_config,
1209
- &ChatRequest {
1210
- message: String::new(),
1211
- system_instruction: active_system_prompt.clone(),
1212
- chat_id: Some(chat_id.to_owned()),
1213
- image_data_uri: None,
1214
- audio_data_uri: None,
1215
- video_data_uri: None,
1216
- document_attachment: None,
1217
- workspace_path: None,
1218
- agent_id: None,
1219
- plan_mode: false,
1220
- pinned_mcp_server: None,
1221
- messages: Some(native_messages.clone()),
1222
- tools: Some(tool_catalog(
1223
- &active_config,
1224
- plan_mode,
1225
- &root,
1226
- allow_subagent_dispatch,
1227
- )),
1228
- temperature: active_config.temperature,
1229
- },
1230
- &mut progress,
1231
- )
1232
- .await?
1710
+ {
1711
+ let mut emit_stream_chunk = |chunk| match chunk {
1712
+ AgentStreamChunk::DirectText(delta) => {
1713
+ streamed_direct_text.push_str(&delta);
1714
+ on_chunk(delta);
1715
+ }
1716
+ AgentStreamChunk::FinalSummary(delta) => {
1717
+ streamed_finish_summary.push_str(&delta);
1718
+ on_chunk(delta);
1719
+ }
1720
+ };
1721
+ stream_chat_with_network_retry(
1722
+ &active_config,
1723
+ &ChatRequest {
1724
+ message: String::new(),
1725
+ system_instruction: active_system_prompt.clone(),
1726
+ chat_id: Some(chat_id.to_owned()),
1727
+ image_data_uri: None,
1728
+ audio_data_uri: None,
1729
+ video_data_uri: None,
1730
+ document_attachment: None,
1731
+ workspace_path: None,
1732
+ agent_id: None,
1733
+ plan_mode: false,
1734
+ pinned_mcp_server: None,
1735
+ messages: Some(native_messages.clone()),
1736
+ tools: Some(tool_catalog(
1737
+ &active_config,
1738
+ plan_mode,
1739
+ &root,
1740
+ allow_subagent_dispatch,
1741
+ )),
1742
+ temperature: active_config.temperature,
1743
+ ..Default::default()
1744
+ },
1745
+ &thought_id,
1746
+ thought_started,
1747
+ fast_mode,
1748
+ &mut progress,
1749
+ false,
1750
+ allow_final_stream,
1751
+ &mut emit_stream_chunk,
1752
+ )
1753
+ .await?
1754
+ }
1233
1755
  } else {
1234
- send_chat_with_network_retry(
1235
- &active_config,
1236
- &ChatRequest {
1237
- message: observation.clone(),
1238
- system_instruction: active_system_prompt.clone(),
1239
- chat_id: Some(chat_id.to_owned()),
1240
- image_data_uri: pending_image.take(),
1241
- audio_data_uri: pending_audio.take(),
1242
- video_data_uri: pending_video.take(),
1243
- document_attachment: None,
1244
- workspace_path: None,
1245
- agent_id: None,
1246
- plan_mode: false,
1247
- pinned_mcp_server: None,
1248
- messages: None,
1249
- tools: None,
1250
- temperature: active_config.temperature,
1251
- },
1252
- &mut progress,
1253
- )
1254
- .await?
1756
+ {
1757
+ let mut emit_stream_chunk = |chunk| match chunk {
1758
+ AgentStreamChunk::DirectText(delta) => {
1759
+ streamed_direct_text.push_str(&delta);
1760
+ on_chunk(delta);
1761
+ }
1762
+ AgentStreamChunk::FinalSummary(delta) => {
1763
+ streamed_finish_summary.push_str(&delta);
1764
+ on_chunk(delta);
1765
+ }
1766
+ };
1767
+ stream_chat_with_network_retry(
1768
+ &active_config,
1769
+ &ChatRequest {
1770
+ message: observation.clone(),
1771
+ system_instruction: active_system_prompt.clone(),
1772
+ chat_id: Some(chat_id.to_owned()),
1773
+ image_data_uri: pending_image.take(),
1774
+ audio_data_uri: pending_audio.take(),
1775
+ video_data_uri: pending_video.take(),
1776
+ document_attachment: None,
1777
+ workspace_path: None,
1778
+ agent_id: None,
1779
+ plan_mode: false,
1780
+ pinned_mcp_server: None,
1781
+ messages: None,
1782
+ tools: None,
1783
+ temperature: active_config.temperature,
1784
+ ..Default::default()
1785
+ },
1786
+ &thought_id,
1787
+ thought_started,
1788
+ fast_mode,
1789
+ &mut progress,
1790
+ true,
1791
+ allow_final_stream,
1792
+ &mut emit_stream_chunk,
1793
+ )
1794
+ .await?
1795
+ }
1255
1796
  };
1256
1797
 
1257
1798
  final_provider = response.provider.clone();
@@ -1262,14 +1803,10 @@ where
1262
1803
  last_input_tokens = input as u64;
1263
1804
  }
1264
1805
  if fallback.is_some() {
1265
- // `fallback` (this function's own return value) is the provider
1266
- // that actually served this response; `response.fallback_provider`
1267
- // is a same-shaped but differently-populated field that
1268
- // `send_chat_with_fallback` sets to the *original* provider that
1269
- // failed over — using it here showed e.g. "gemini → fallback:
1270
- // gemini • Qwen..." in the CLI badge instead of "gemini →
1271
- // fallback: huggingface • Qwen...".
1272
- final_fallback = fallback.clone();
1806
+ // Track the original provider that failed over (held in `response.fallback_provider`),
1807
+ // so UI and logs correctly show `<original> unavailable, fell back to <current>`.
1808
+ final_fallback = response.fallback_provider.clone();
1809
+ final_fallback_reason = response.fallback_reason.clone();
1273
1810
  if let Some(reason) = &response.fallback_reason {
1274
1811
  progress(AgentProgress::Thought {
1275
1812
  thought: format!(
@@ -1300,10 +1837,7 @@ where
1300
1837
  .enumerate()
1301
1838
  .map(|(index, call)| {
1302
1839
  let thought = if index == 0 {
1303
- match &response.thought {
1304
- Some(t) if !t.trim().is_empty() => t.trim().to_string(),
1305
- _ => response.text.trim().to_string(),
1306
- }
1840
+ response.text.trim().to_string()
1307
1841
  } else {
1308
1842
  String::new()
1309
1843
  };
@@ -1377,6 +1911,7 @@ where
1377
1911
  messages: None,
1378
1912
  tools: None,
1379
1913
  temperature: active_config.temperature,
1914
+ ..Default::default()
1380
1915
  },
1381
1916
  )
1382
1917
  .await?;
@@ -1388,6 +1923,13 @@ where
1388
1923
  })?
1389
1924
  }
1390
1925
  };
1926
+ // If the model's inline JSON `thought` is empty but the API
1927
+ // sent separate thinking tokens (reasoning models), emit them
1928
+ // as `ExtendedThinking` so the UI can display them in a
1929
+ // dedicated collapsible block instead of the short-thought
1930
+ // timeline. Still copy into `decision.thought` so the text
1931
+ // is available downstream, but the *progress event* uses the
1932
+ // correct variant.
1391
1933
  if decision.thought.trim().is_empty() {
1392
1934
  if let Some(t) = &response.thought {
1393
1935
  decision.thought = t.trim().to_string();
@@ -1396,6 +1938,19 @@ where
1396
1938
  vec![(format!("call_{step}"), decision)]
1397
1939
  };
1398
1940
 
1941
+ let mut extended_emitted = false;
1942
+ if !fast_mode
1943
+ && let Some(t) = &response.thought
1944
+ && !t.trim().is_empty()
1945
+ {
1946
+ progress(AgentProgress::ExtendedThinking {
1947
+ id: Some(thought_id.clone()),
1948
+ thought: t.trim().to_owned(),
1949
+ elapsed_ms: Some(thought_started.elapsed().as_millis() as u64),
1950
+ });
1951
+ extended_emitted = true;
1952
+ }
1953
+
1399
1954
  let mut step_tool_results: Vec<(String, String, Value, String)> = Vec::new();
1400
1955
  // Screenshots (and any future binary/image tool result) are kept out of
1401
1956
  // `step_tool_results`'s plain-text `final_result` — that string is what
@@ -1440,114 +1995,84 @@ where
1440
1995
  .await;
1441
1996
  } else {
1442
1997
  for (call_id, decision) in decisions {
1443
- if !fast_mode && !decision.thought.trim().is_empty() {
1444
- progress(AgentProgress::Thought {
1445
- thought: decision.thought.trim().to_owned(),
1446
- });
1998
+ let d_thought = decision.thought.trim();
1999
+ if !fast_mode && !d_thought.is_empty() {
2000
+ let already_extended = extended_emitted
2001
+ && response.thought.as_deref().map(str::trim) == Some(d_thought);
2002
+ if !already_extended {
2003
+ if is_internal_cot(d_thought) {
2004
+ progress(AgentProgress::ExtendedThinking {
2005
+ id: Some(thought_id.clone()),
2006
+ thought: d_thought.to_owned(),
2007
+ elapsed_ms: Some(thought_started.elapsed().as_millis() as u64),
2008
+ });
2009
+ } else {
2010
+ progress(AgentProgress::Thought {
2011
+ thought: d_thought.to_owned(),
2012
+ });
2013
+ }
2014
+ }
1447
2015
  }
1448
2016
 
1449
2017
  if decision.action == "finish" {
1450
2018
  let mut summary = decision.input.summary.trim().to_owned();
1451
- let is_thai_task =
1452
- task.chars().any(|c| ('\u{0e00}'..='\u{0e7f}').contains(&c));
1453
- if let Some(err_line) = observation
1454
- .lines()
1455
- .find(|l| l.contains("Web search error:"))
1456
- {
1457
- let clean_err = err_line
1458
- .replace("Web search error: ", "")
1459
- .replace("Web search is currently unavailable.", "")
1460
- .trim()
1461
- .to_string();
1462
- if summary.is_empty() {
1463
- if is_thai_task {
1464
- summary = format!(
1465
- "การค้นหาข้อมูลจากเว็บล้มเหลวเนื่องจากข้อผิดพลาด: {}\nมิ้นท์ขออภัยด้วยนะคะที่ไม่สามารถค้นหาข้อมูลเรียลไทม์ให้ได้ในขณะนี้ค่ะ",
1466
- clean_err
1467
- );
1468
- } else {
1469
- summary = format!(
1470
- "Web search failed due to error: {}\nI apologize, but I cannot retrieve real-time information at the moment.",
1471
- clean_err
1472
- );
1473
- }
1474
- } else {
1475
- let err_lower = clean_err.to_lowercase();
1476
- let summary_lower = summary.to_lowercase();
1477
- let already_mentions_error = if is_thai_task {
1478
- summary_lower.contains("ล้มเหลว")
1479
- || summary_lower.contains("ข้อผิดพลาด")
1480
- || summary_lower.contains(&err_lower)
1481
- } else {
1482
- summary_lower.contains("fail")
1483
- || summary_lower.contains("error")
1484
- || summary_lower.contains(&err_lower)
1485
- };
1486
- if !already_mentions_error {
1487
- if is_thai_task {
1488
- summary.push_str(&format!(
1489
- "\n\n(การค้นหาเว็บล้มเหลวเนื่องจากข้อผิดพลาด: {})",
1490
- clean_err
1491
- ));
1492
- } else {
1493
- summary.push_str(&format!(
1494
- "\n\n(Web search failed due to error: {})",
1495
- clean_err
1496
- ));
1497
- }
1498
- }
1499
- }
1500
- } else {
1501
- if summary.is_empty() {
1502
- let err_msg = "Error: Your finish action summary was empty. \
2019
+ if summary.is_empty() {
2020
+ let err_msg = "Error: Your finish action summary was empty. \
1503
2021
  You MUST provide a final answer, explanation, or response to the user's query \
1504
2022
  in the 'summary' field of the 'finish' action input. Do not leave it empty.";
1505
- trajectory.push(format_trajectory_step(
1506
- step,
1507
- &decision.thought,
1508
- &decision.action,
1509
- err_msg,
1510
- ));
1511
- rebuild_observation(task, &root, &trajectory, &mut observation);
1512
- reject_native_finish(
1513
- tool_mode,
1514
- &mut native_messages,
1515
- &response.text,
1516
- err_msg,
1517
- );
1518
- continue 'steps;
1519
- }
1520
- if unverified_modification(
1521
- last_modify_step,
1522
- last_verify_step,
1523
- &decision.input.verification,
1524
- ) {
1525
- let err_msg = "Error: You modified a file (apply_patch/write_file) in this \
2023
+ trajectory.push(format_trajectory_step(
2024
+ step,
2025
+ &decision.thought,
2026
+ &decision.action,
2027
+ err_msg,
2028
+ ));
2029
+ rebuild_observation(task, &root, &trajectory, &mut observation);
2030
+ reject_native_finish(
2031
+ tool_mode,
2032
+ &mut native_messages,
2033
+ &response.text,
2034
+ err_msg,
2035
+ );
2036
+ continue 'steps;
2037
+ }
2038
+ if crate::browser::session_finish_evidence().await && meaningful_verification(&decision.input.verification).is_empty() {
2039
+ let error = "Browser tasks require observed evidence in finish.verification. Describe the observed outcome, or explicitly state that completion is unverified or blocked.";
2040
+ trajectory.push(format_trajectory_step(step, &decision.thought, &decision.action, error));
2041
+ rebuild_observation(task, &root, &trajectory, &mut observation);
2042
+ reject_native_finish(tool_mode, &mut native_messages, &response.text, error);
2043
+ continue 'steps;
2044
+ }
2045
+ if unverified_modification(
2046
+ last_modify_step,
2047
+ last_verify_step,
2048
+ &decision.input.verification,
2049
+ ) {
2050
+ let err_msg = "Error: You modified a file (apply_patch/write_file) in this \
1526
2051
  run but finished without verifying it. Call the verify tool \
1527
2052
  with build/test/lint commands appropriate for this project \
1528
2053
  before finishing. If no check genuinely applies (e.g. no test \
1529
2054
  suite, documentation-only change), say so explicitly in the \
1530
2055
  finish action's 'verification' field and finish again.";
1531
- trajectory.push(format_trajectory_step(
1532
- step,
1533
- &decision.thought,
1534
- &decision.action,
1535
- err_msg,
1536
- ));
1537
- rebuild_observation(task, &root, &trajectory, &mut observation);
1538
- reject_native_finish(
1539
- tool_mode,
1540
- &mut native_messages,
1541
- &response.text,
1542
- err_msg,
1543
- );
1544
- continue 'steps;
1545
- }
1546
- if unacknowledged_verify_failure(
1547
- last_verify_failed,
1548
- &decision.input.verification,
1549
- ) {
1550
- let err_msg = "Error: Your last verify call reported a failure \
2056
+ trajectory.push(format_trajectory_step(
2057
+ step,
2058
+ &decision.thought,
2059
+ &decision.action,
2060
+ err_msg,
2061
+ ));
2062
+ rebuild_observation(task, &root, &trajectory, &mut observation);
2063
+ reject_native_finish(
2064
+ tool_mode,
2065
+ &mut native_messages,
2066
+ &response.text,
2067
+ err_msg,
2068
+ );
2069
+ continue 'steps;
2070
+ }
2071
+ if unacknowledged_verify_failure(
2072
+ last_verify_failed,
2073
+ &decision.input.verification,
2074
+ ) {
2075
+ let err_msg = "Error: Your last verify call reported a failure \
1551
2076
  (non-zero exit code), but you're finishing without \
1552
2077
  addressing it. Read the stdout/stderr from that verify \
1553
2078
  call, fix the actual problem, and run verify again until \
@@ -1556,49 +2081,21 @@ where
1556
2081
  unrelated to your change (e.g. pre-existing), say so \
1557
2082
  explicitly in the finish action's 'verification' field and \
1558
2083
  finish again.";
1559
- trajectory.push(format_trajectory_step(
1560
- step,
1561
- &decision.thought,
1562
- &decision.action,
1563
- err_msg,
1564
- ));
1565
- rebuild_observation(task, &root, &trajectory, &mut observation);
1566
- reject_native_finish(
1567
- tool_mode,
1568
- &mut native_messages,
1569
- &response.text,
1570
- err_msg,
1571
- );
1572
- continue 'steps;
1573
- }
1574
- let mut provider_used = None;
1575
- for line in observation.lines() {
1576
- if line.contains("Web search succeeded using Google Search") {
1577
- provider_used = Some("Google");
1578
- } else if line.contains("Web search succeeded using Brave Search") {
1579
- provider_used = Some("Brave");
1580
- }
1581
- }
1582
- if let Some(prov) = provider_used {
1583
- let summary_lower = summary.to_lowercase();
1584
- if !summary_lower.contains("google")
1585
- && !summary_lower.contains("brave")
1586
- {
1587
- if is_thai_task {
1588
- summary.push_str(&format!(
1589
- "\n\n(มิ้นท์หาข้อมูลนี้มาจาก {} Search นะคะ 💖)",
1590
- prov
1591
- ));
1592
- } else {
1593
- summary.push_str(&format!(
1594
- "\n\n(Information retrieved via {} Search 💖)",
1595
- prov
1596
- ));
1597
- }
1598
- }
1599
- }
2084
+ trajectory.push(format_trajectory_step(
2085
+ step,
2086
+ &decision.thought,
2087
+ &decision.action,
2088
+ err_msg,
2089
+ ));
2090
+ rebuild_observation(task, &root, &trajectory, &mut observation);
2091
+ reject_native_finish(
2092
+ tool_mode,
2093
+ &mut native_messages,
2094
+ &response.text,
2095
+ err_msg,
2096
+ );
2097
+ continue 'steps;
1600
2098
  }
1601
-
1602
2099
  // Auto-append generated media (image/video) and model feedback to summary if LLM omitted it
1603
2100
  let mut media_blocks = Vec::new();
1604
2101
  for step in trajectory.iter() {
@@ -1621,17 +2118,15 @@ where
1621
2118
  let verification =
1622
2119
  meaningful_verification(&decision.input.verification).to_owned();
1623
2120
 
1624
- on_chunk(summary.clone());
1625
-
1626
- let memory = MemoryStore::open_default()?;
1627
- memory.add_interaction_for_chat_with_fallback(
1628
- chat_id,
1629
- task,
2121
+ let remainder = unstreamed_summary_remainder(
1630
2122
  &summary,
1631
- &final_provider,
1632
- &final_model,
1633
- final_fallback.as_deref(),
1634
- )?;
2123
+ &streamed_finish_summary,
2124
+ &streamed_direct_text,
2125
+ );
2126
+ if !remainder.is_empty() {
2127
+ on_chunk(remainder.to_owned());
2128
+ }
2129
+
1635
2130
  memory.save_workspace_session(
1636
2131
  &root.to_string_lossy(),
1637
2132
  &summary,
@@ -1648,6 +2143,7 @@ where
1648
2143
  config.clone(),
1649
2144
  task.to_string(),
1650
2145
  summary.clone(),
2146
+ turn.id,
1651
2147
  );
1652
2148
  if config.auto_skill_writing && looks_skill_worthy(step, &action_counts) {
1653
2149
  spawn_auto_skill_write(
@@ -1678,12 +2174,20 @@ where
1678
2174
  summary: run_summary,
1679
2175
  });
1680
2176
 
2177
+ turn.complete_with_summary(
2178
+ &summary,
2179
+ &final_provider,
2180
+ &final_model,
2181
+ final_fallback.as_deref(),
2182
+ )?;
2183
+
1681
2184
  return Ok(AgentResult {
1682
2185
  provider: final_provider,
1683
2186
  model: final_model,
1684
2187
  summary,
1685
2188
  verification,
1686
2189
  fallback: final_fallback,
2190
+ fallback_reason: final_fallback_reason,
1687
2191
  total_tokens: turn_total_tokens,
1688
2192
  input_tokens: last_input_tokens,
1689
2193
  generated_tokens: turn_generated_tokens,
@@ -1960,7 +2464,7 @@ where
1960
2464
  _ => "[Screenshot captured — see attached image]".to_string(),
1961
2465
  }
1962
2466
  } else {
1963
- truncate(&result)
2467
+ if decision.action.starts_with("browser_") {result.clone()} else {truncate(&result)}
1964
2468
  };
1965
2469
  if matches!(
1966
2470
  decision.action.as_str(),
@@ -1986,7 +2490,7 @@ where
1986
2490
  concise verification read.]",
1987
2491
  );
1988
2492
  }
1989
- if action_count >= 3 {
2493
+ if action_count >= 3 && !decision.action.starts_with("browser_") {
1990
2494
  final_result.push_str(
1991
2495
  "\n\n[System Tip: You repeated the same tool action three or more times. \
1992
2496
  Stop repeating it. If you already have enough information or the requested edit is done, \
@@ -2116,10 +2620,16 @@ where
2116
2620
  summary: run_summary,
2117
2621
  });
2118
2622
 
2623
+ turn.fail();
2119
2624
  Err(OrchestrationError::Agent(format!(
2120
- "code agent reached the limit of {} steps",
2121
- MAX_STEPS
2625
+ "code agent reached the limit of {} steps; task incomplete. Last browser state: {}",
2626
+ MAX_STEPS, crate::browser::session_last_state().await
2122
2627
  )))
2628
+ }).await;
2629
+ if agent_result.is_err() {
2630
+ turn.fail();
2631
+ }
2632
+ agent_result
2123
2633
  })
2124
2634
  }
2125
2635
 
@@ -2427,7 +2937,7 @@ async fn run_parallel_subagent_batch(
2427
2937
  *count
2428
2938
  };
2429
2939
  let mut final_result = truncate(&tool_result);
2430
- if action_count >= 3 {
2940
+ if action_count >= 3 && !action.starts_with("browser_") {
2431
2941
  final_result.push_str(
2432
2942
  "\n\n[System Tip: You repeated the same tool action three or more times. \
2433
2943
  Stop repeating it. If you already have enough information or the requested edit is done, \
@@ -2478,10 +2988,19 @@ async fn run_parallel_read_only_batch(
2478
2988
 
2479
2989
  tasks.push(async move {
2480
2990
  if !fast_mode && !thought.trim().is_empty() {
2991
+ let t = thought.trim();
2481
2992
  let mut guard = progress_mutex.lock().unwrap();
2482
- (*guard)(AgentProgress::Thought {
2483
- thought: thought.trim().to_owned(),
2484
- });
2993
+ if is_internal_cot(t) {
2994
+ (*guard)(AgentProgress::ExtendedThinking {
2995
+ id: None,
2996
+ thought: t.to_owned(),
2997
+ elapsed_ms: None,
2998
+ });
2999
+ } else {
3000
+ (*guard)(AgentProgress::Thought {
3001
+ thought: t.to_owned(),
3002
+ });
3003
+ }
2485
3004
  }
2486
3005
 
2487
3006
  {
@@ -2610,7 +3129,7 @@ async fn run_parallel_read_only_batch(
2610
3129
  truncate(&result)
2611
3130
  };
2612
3131
 
2613
- if action_count >= 3 {
3132
+ if action_count >= 3 && !action.starts_with("browser_") {
2614
3133
  final_result.push_str(
2615
3134
  "\n\n[System Tip: You repeated the same tool action three or more times. \
2616
3135
  Stop repeating it. If you already have enough information or the requested edit is done, \
@@ -2709,7 +3228,13 @@ async fn execute_tool(
2709
3228
  | "browser_mouse_move"
2710
3229
  | "browser_mouse_click"
2711
3230
  | "browser_key_press"
2712
- | "browser_screenshot" => {
3231
+ | "browser_screenshot"
3232
+ | "browser_tabs"
3233
+ | "browser_observe"
3234
+ | "browser_fill"
3235
+ | "browser_select"
3236
+ | "browser_scroll"
3237
+ | "browser_wait" => {
2713
3238
  tools::browser::execute(
2714
3239
  decision.action.as_str(),
2715
3240
  input,
@@ -2861,6 +3386,233 @@ mod tests {
2861
3386
  use super::*;
2862
3387
  use crate::config::AgentConfig;
2863
3388
 
3389
+ #[tokio::test]
3390
+ async fn turn_start_listener_reports_only_the_matching_chat() {
3391
+ let memory = MemoryStore::open(std::env::temp_dir().join(format!(
3392
+ "mint-turn-start-listener-{}.sqlite",
3393
+ uuid::Uuid::new_v4()
3394
+ )));
3395
+ let reported = Arc::new(std::sync::Mutex::new(Vec::new()));
3396
+ let callback_reported = reported.clone();
3397
+ with_turn_start_listener(
3398
+ "cli::one".to_string(),
3399
+ move |id| callback_reported.lock().unwrap().push(id),
3400
+ async {
3401
+ let first = TurnLease::start(&memory, "cli::one", "hello")
3402
+ .await
3403
+ .unwrap();
3404
+ let other = TurnLease::start(&memory, "cli::two", "other")
3405
+ .await
3406
+ .unwrap();
3407
+ assert_eq!(*reported.lock().unwrap(), vec![first.id]);
3408
+ drop(first);
3409
+ drop(other);
3410
+ },
3411
+ )
3412
+ .await;
3413
+ }
3414
+
3415
+ #[test]
3416
+ fn test_compact_agent_progress_cleans_deltas_and_preserves_tools() {
3417
+ let events = vec![
3418
+ AgentProgress::ToolStart {
3419
+ action: "read_file".into(),
3420
+ input: serde_json::json!({ "path": "src/main.rs" }),
3421
+ subagent: None,
3422
+ },
3423
+ AgentProgress::ThinkingDelta {
3424
+ id: "cot-1".into(),
3425
+ delta: "Reading ".into(),
3426
+ elapsed_ms: 100,
3427
+ },
3428
+ AgentProgress::ThinkingDelta {
3429
+ id: "cot-1".into(),
3430
+ delta: "the file...".into(),
3431
+ elapsed_ms: 200,
3432
+ },
3433
+ AgentProgress::ExtendedThinking {
3434
+ id: Some("cot-1".into()),
3435
+ thought: "Reading the file...".into(),
3436
+ elapsed_ms: Some(200),
3437
+ },
3438
+ AgentProgress::ToolEnd {
3439
+ action: "read_file".into(),
3440
+ input: serde_json::json!({ "path": "src/main.rs" }),
3441
+ result: "fn main() {}".into(),
3442
+ subagent: None,
3443
+ },
3444
+ AgentProgress::ThinkingDelta {
3445
+ id: "cot-interrupted".into(),
3446
+ delta: "Let's check ".into(),
3447
+ elapsed_ms: 300,
3448
+ },
3449
+ AgentProgress::ThinkingDelta {
3450
+ id: "cot-interrupted".into(),
3451
+ delta: "more lines".into(),
3452
+ elapsed_ms: 350,
3453
+ },
3454
+ ];
3455
+
3456
+ let compacted = compact_agent_progress(&events);
3457
+ assert!(
3458
+ !compacted
3459
+ .iter()
3460
+ .any(|e| matches!(e, AgentProgress::ThinkingDelta { id, .. } if id == "cot-1"))
3461
+ );
3462
+ assert!(compacted.iter().any(
3463
+ |e| matches!(e, AgentProgress::ToolStart { action, .. } if action == "read_file")
3464
+ ));
3465
+ assert!(
3466
+ compacted.iter().any(
3467
+ |e| matches!(e, AgentProgress::ToolEnd { action, .. } if action == "read_file")
3468
+ )
3469
+ );
3470
+ assert!(compacted.iter().any(|e| matches!(e, AgentProgress::ExtendedThinking { id: Some(id), thought, .. } if id == "cot-1" && thought == "Reading the file...")));
3471
+ assert!(compacted.iter().any(|e| matches!(e, AgentProgress::ExtendedThinking { id: Some(id), thought, .. } if id == "cot-interrupted" && thought == "Let's check more lines")));
3472
+ }
3473
+
3474
+ #[tokio::test]
3475
+ async fn test_turn_lease_persists_agent_activity_on_complete() {
3476
+ let memory = MemoryStore::open(std::env::temp_dir().join(format!(
3477
+ "mint-turn-lease-complete-{}.sqlite",
3478
+ uuid::Uuid::new_v4()
3479
+ )));
3480
+ let mut turn = TurnLease::start(&memory, "cli::test_complete", "hello")
3481
+ .await
3482
+ .unwrap();
3483
+ let turn_id = turn.id;
3484
+ turn.activity
3485
+ .lock()
3486
+ .unwrap()
3487
+ .push(AgentProgress::ToolStart {
3488
+ action: "run_shell".into(),
3489
+ input: serde_json::json!({ "command": "ls -la" }),
3490
+ subagent: None,
3491
+ });
3492
+ turn.activity.lock().unwrap().push(AgentProgress::ToolEnd {
3493
+ action: "run_shell".into(),
3494
+ input: serde_json::json!({ "command": "ls -la" }),
3495
+ result: "file.txt".into(),
3496
+ subagent: None,
3497
+ });
3498
+
3499
+ turn.complete_with_summary("Done listing files", "test_provider", "test_model", None)
3500
+ .unwrap();
3501
+
3502
+ let rows = memory.interactions_for_chat("cli::test_complete").unwrap();
3503
+ assert_eq!(rows.len(), 1);
3504
+ let row = &rows[0];
3505
+ assert_eq!(row.id, turn_id);
3506
+ assert_eq!(row.status, "completed");
3507
+ assert_eq!(row.ai_text, "Done listing files");
3508
+ assert!(row.agent_activity.is_some());
3509
+ let activity_val = row.agent_activity.as_ref().unwrap();
3510
+ let activity_arr = activity_val.as_array().unwrap();
3511
+ assert_eq!(activity_arr.len(), 2);
3512
+ assert_eq!(activity_arr[0]["type"], "ToolStart");
3513
+ assert_eq!(activity_arr[1]["type"], "ToolEnd");
3514
+ }
3515
+
3516
+ #[tokio::test]
3517
+ async fn test_turn_lease_persists_partial_activity_on_interrupt() {
3518
+ let memory = MemoryStore::open(std::env::temp_dir().join(format!(
3519
+ "mint-turn-lease-interrupt-{}.sqlite",
3520
+ uuid::Uuid::new_v4()
3521
+ )));
3522
+ let turn = TurnLease::start(&memory, "cli::test_interrupt", "run something")
3523
+ .await
3524
+ .unwrap();
3525
+ let turn_id = turn.id;
3526
+ turn.activity
3527
+ .lock()
3528
+ .unwrap()
3529
+ .push(AgentProgress::ToolStart {
3530
+ action: "read_file".into(),
3531
+ input: serde_json::json!({ "path": "test.txt" }),
3532
+ subagent: None,
3533
+ });
3534
+ drop(turn);
3535
+
3536
+ let rows = memory.interactions_for_chat("cli::test_interrupt").unwrap();
3537
+ assert_eq!(rows.len(), 1);
3538
+ let row = &rows[0];
3539
+ assert_eq!(row.id, turn_id);
3540
+ assert_eq!(row.status, "interrupted");
3541
+ assert!(row.agent_activity.is_some());
3542
+ let activity_val = row.agent_activity.as_ref().unwrap();
3543
+ let activity_arr = activity_val.as_array().unwrap();
3544
+ assert_eq!(activity_arr.len(), 1);
3545
+ assert_eq!(activity_arr[0]["type"], "ToolStart");
3546
+ }
3547
+
3548
+ #[test]
3549
+ fn extracts_a_summary_incrementally_without_matching_text_inside_other_strings() {
3550
+ let mut stream = FinishSummaryStream::default();
3551
+ let mut output = String::new();
3552
+ let mut emit = |delta: String| output.push_str(&delta);
3553
+
3554
+ stream.push_tool_call(0, Some("read_file"), None, None, &mut emit);
3555
+ stream.push_tool_call(
3556
+ 0,
3557
+ None,
3558
+ Some(r#"{"thought":"the \"summary\" key is data","path":"x"}"#),
3559
+ None,
3560
+ &mut emit,
3561
+ );
3562
+ assert!(stream.emitted.is_empty());
3563
+
3564
+ stream.push_tool_call(1, Some("fin"), None, None, &mut emit);
3565
+ stream.push_tool_call(1, Some("finish"), None, None, &mut emit);
3566
+ stream.push_tool_call(1, None, Some(r#"{"summary":"สวัส"#), None, &mut emit);
3567
+ stream.push_tool_call(
3568
+ 1,
3569
+ None,
3570
+ Some(r#"ดี\nโลก","verification":""}"#),
3571
+ None,
3572
+ &mut emit,
3573
+ );
3574
+
3575
+ assert_eq!(output, "สวัสดี\nโลก");
3576
+ }
3577
+
3578
+ #[test]
3579
+ fn extracts_json_prompt_summary_as_chunks_arrive() {
3580
+ let mut stream = FinishSummaryStream::default();
3581
+ let mut output = String::new();
3582
+ let mut emit = |delta: String| output.push_str(&delta);
3583
+ stream.push_json_prompt(
3584
+ r#"{"thought":"done","action":"finish","input":{"summary":"Hello"#,
3585
+ &mut emit,
3586
+ );
3587
+ assert_eq!(stream.emitted, "Hello");
3588
+ stream.push_json_prompt(r#" world"}}"#, &mut emit);
3589
+ assert_eq!(output, "Hello world");
3590
+ }
3591
+
3592
+ #[test]
3593
+ fn direct_native_text_is_not_sent_again_as_a_completed_summary() {
3594
+ assert_eq!(
3595
+ unstreamed_summary_remainder("A streamed answer.", "", "A streamed answer."),
3596
+ ""
3597
+ );
3598
+ }
3599
+
3600
+ #[test]
3601
+ fn direct_native_text_appends_only_the_unstreamed_suffix() {
3602
+ assert_eq!(
3603
+ unstreamed_summary_remainder("A streamed answer, continued.", "", "A streamed answer"),
3604
+ ", continued."
3605
+ );
3606
+ }
3607
+
3608
+ #[test]
3609
+ fn streamed_finish_summary_takes_precedence_over_direct_text() {
3610
+ assert_eq!(
3611
+ unstreamed_summary_remainder("Final answer.", "Final", "unrelated preamble"),
3612
+ " answer."
3613
+ );
3614
+ }
3615
+
2864
3616
  #[test]
2865
3617
  fn truncate_for_context_leaves_short_text_untouched() {
2866
3618
  assert_eq!(truncate_for_context("hello", 400), "hello");
@@ -3068,6 +3820,7 @@ mod tests {
3068
3820
  messages: None,
3069
3821
  tools: None,
3070
3822
  temperature: None,
3823
+ ..Default::default()
3071
3824
  };
3072
3825
  let config = MintConfig::default();
3073
3826
  assert!(