@pheem49/mint 1.17.0 → 1.18.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 (421) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +2 -2
  3. package/README.md +8 -2
  4. package/Release_Note.md +222 -71
  5. package/crates/mint-cli/src/agent/diff_render.rs +66 -44
  6. package/crates/mint-cli/src/agent/live_status.rs +21 -4
  7. package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
  8. package/crates/mint-cli/src/agent.rs +25 -10
  9. package/crates/mint-cli/src/commands/agent.rs +4 -92
  10. package/crates/mint-cli/src/commands/browser.rs +2 -15
  11. package/crates/mint-cli/src/commands/integrations.rs +3 -3
  12. package/crates/mint-cli/src/commands/mod.rs +0 -3
  13. package/crates/mint-cli/src/commands/tailnet_web.rs +1 -0
  14. package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
  15. package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
  16. package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
  17. package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
  18. package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
  19. package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
  20. package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
  21. package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
  22. package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
  23. package/crates/mint-cli/src/interactive/command_ui.rs +53 -5
  24. package/crates/mint-cli/src/interactive/composer.rs +186 -0
  25. package/crates/mint-cli/src/interactive/format.rs +78 -3
  26. package/crates/mint-cli/src/interactive/input_box.rs +61 -1
  27. package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
  28. package/crates/mint-cli/src/interactive/slash_commands.rs +648 -371
  29. package/crates/mint-cli/src/interactive/tool_activity.rs +93 -20
  30. package/crates/mint-cli/src/interactive.rs +37 -0
  31. package/crates/mint-cli/src/main.rs +6 -3
  32. package/crates/mint-cli/src/markdown.rs +2 -37
  33. package/crates/mint-cli/src/onboard.rs +700 -282
  34. package/crates/mint-cli/src/setup.rs +1342 -624
  35. package/crates/mint-cli/src/terminal_theme.rs +159 -35
  36. package/crates/mint-cli/tests/library_intake.rs +1 -0
  37. package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
  38. package/crates/mint-core/src/agent/chat/custom.rs +149 -0
  39. package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
  40. package/crates/mint-core/src/agent/chat/mod.rs +857 -0
  41. package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
  42. package/crates/mint-core/src/agent/chat/openai.rs +582 -0
  43. package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
  44. package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
  45. package/crates/mint-core/src/agent/memory.rs +56 -2
  46. package/crates/mint-core/src/agent/tasks.rs +2 -2
  47. package/crates/mint-core/src/api_server/routes/chat.rs +24 -2
  48. package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
  49. package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
  50. package/crates/mint-core/src/api_server/routes/sessions.rs +21 -0
  51. package/crates/mint-core/src/avatar_bridge.rs +0 -22
  52. package/crates/mint-core/src/browser/cdp.rs +10 -4
  53. package/crates/mint-core/src/browser/headless.rs +582 -0
  54. package/crates/mint-core/src/browser/input.rs +7 -62
  55. package/crates/mint-core/src/browser/interact.rs +0 -14
  56. package/crates/mint-core/src/browser/mod.rs +8 -29
  57. package/crates/mint-core/src/browser/navigate.rs +2 -36
  58. package/crates/mint-core/src/browser/runtime.rs +2 -4
  59. package/crates/mint-core/src/browser/session.rs +50 -466
  60. package/crates/mint-core/src/browser/workflow.rs +142 -32
  61. package/crates/mint-core/src/cron/store.rs +2 -2
  62. package/crates/mint-core/src/integrations/mcp.rs +6 -8
  63. package/crates/mint-core/src/lib.rs +0 -5
  64. package/crates/mint-core/src/media/gemini_live.rs +636 -86
  65. package/crates/mint-core/src/media/library/migration.rs +26 -0
  66. package/crates/mint-core/src/media/library/tests.rs +1 -1
  67. package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
  68. package/crates/mint-core/src/media/pictures.rs +1 -1
  69. package/crates/mint-core/src/media/tool_images.rs +92 -1
  70. package/crates/mint-core/src/mods/manager.rs +101 -35
  71. package/crates/mint-core/src/orchestration/context_render.rs +441 -24
  72. package/crates/mint-core/src/orchestration/decision_parsing.rs +531 -16
  73. package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
  74. package/crates/mint-core/src/orchestration/loop_guard.rs +145 -0
  75. package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
  76. package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
  77. package/crates/mint-core/src/orchestration/mod.rs +320 -4249
  78. package/crates/mint-core/src/orchestration/reliability.rs +67 -27
  79. package/crates/mint-core/src/orchestration/review_regressions.rs +5 -0
  80. package/crates/mint-core/src/orchestration/tests.rs +1665 -0
  81. package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
  82. package/crates/mint-core/src/orchestration/tools/files.rs +1 -0
  83. package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
  84. package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
  85. package/crates/mint-core/src/orchestration/tools/shell.rs +129 -58
  86. package/crates/mint-core/src/orchestration/truncation.rs +170 -13
  87. package/crates/mint-core/src/orchestration/types.rs +648 -0
  88. package/crates/mint-core/src/orchestration/verification.rs +0 -3
  89. package/crates/mint-core/src/orchestration/workspace_helpers.rs +24 -6
  90. package/crates/mint-core/src/prompts/agent.rs +52 -76
  91. package/crates/mint-core/src/prompts/tool_catalog.rs +8 -151
  92. package/crates/mint-core/src/search/files.rs +20 -0
  93. package/crates/mint-core/src/slash/mod.rs +228 -265
  94. package/crates/mint-core/src/slash/models.rs +6 -1
  95. package/crates/mint-core/src/system/bg_shell.rs +1 -0
  96. package/crates/mint-core/src/system/config.rs +176 -34
  97. package/crates/mint-mod-runtime/src/lib.rs +37 -23
  98. package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
  99. package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
  100. package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
  101. package/out/web/assets/{CliSessionsView-MI8kXMAb.js → CliSessionsView-z5i6qGyb.js} +1 -1
  102. package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
  103. package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
  104. package/out/web/assets/{CommandPalette-BD8SlTE5.js → CommandPalette-DaKlzbKs.js} +1 -1
  105. package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
  106. package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
  107. package/out/web/assets/{ImageStudioPanel-BP7N4rLr.js → ImageStudioPanel-B5N7TjeB.js} +1 -1
  108. package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
  109. package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
  110. package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
  111. package/out/web/assets/{McpRegistryPicker-CoS83vtc.js → McpRegistryPicker-DnXPlzI7.js} +1 -1
  112. package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
  113. package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
  114. package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
  115. package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
  116. package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
  117. package/out/web/assets/{PluginsView-jBYmcKh4.js → PluginsView-DoTg9MVa.js} +1 -1
  118. package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
  119. package/out/web/assets/{ScheduledTasksView-5uw9-V9H.js → ScheduledTasksView-BD5DVPkZ.js} +1 -1
  120. package/out/web/assets/{SelectField-DXBQtrUK.js → SelectField-Cy6CghrU.js} +1 -1
  121. package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
  122. package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
  123. package/out/web/assets/{SkillsView-C-gp9J7m.js → SkillsView-CIeWwq5z.js} +1 -1
  124. package/out/web/assets/StockCard-DxUciKyp.js +1 -0
  125. package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
  126. package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
  127. package/out/web/assets/{UiFeatureCard-6BwxTOCe.js → UiFeatureCard-CdY0hGWy.js} +1 -1
  128. package/out/web/assets/{UiGridCard-DvZcLNxb.js → UiGridCard-BBK3I5hC.js} +1 -1
  129. package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
  130. package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css → VeoStudioPanel-9kU9YLEN.css} +1 -1
  131. package/out/web/assets/{VeoStudioPanel-DfsOJKv0.js → VeoStudioPanel-Co6-wEWM.js} +2 -2
  132. package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
  133. package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
  134. package/out/web/assets/{abnfDiagram-VCTEODGH-BOo3Ckng.js → abnfDiagram-VCTEODGH-CMfR95N2.js} +1 -1
  135. package/out/web/assets/{architecture-7GRP2DOG-ZOwheW3n.js → architecture-7GRP2DOG-DhAM4HyO.js} +1 -1
  136. package/out/web/assets/{architectureDiagram-5GKGNRK7-DC2FDzVd.js → architectureDiagram-5GKGNRK7-Yk3TUJ_F.js} +1 -1
  137. package/out/web/assets/{blockDiagram-I7D4REHJ-BrKFWdAH.js → blockDiagram-I7D4REHJ-X9fBgJik.js} +1 -1
  138. package/out/web/assets/{c4Diagram-7LVT6UL2-BHTHK1Yd.js → c4Diagram-7LVT6UL2-Bk9x7N-8.js} +1 -1
  139. package/out/web/assets/{chunk-4HAMMTFA-BYFkZwNj.js → chunk-4HAMMTFA-Dx5fk1_0.js} +1 -1
  140. package/out/web/assets/{chunk-75Z2AOVW-BSKfSKSA.js → chunk-75Z2AOVW-BszTQoM4.js} +1 -1
  141. package/out/web/assets/{chunk-DU6HZSFF-vIhsiW6n.js → chunk-DU6HZSFF-DK-As3rk.js} +1 -1
  142. package/out/web/assets/{chunk-F27PBJKO-BlYpW9xq.js → chunk-F27PBJKO-LmncZZIS.js} +1 -1
  143. package/out/web/assets/{chunk-GMAD6QVW-BIBz_Ug5.js → chunk-GMAD6QVW-DSQ_sDRJ.js} +1 -1
  144. package/out/web/assets/{chunk-GVQU2GXP-NPSHxeqC.js → chunk-GVQU2GXP-Cno3ulje.js} +1 -1
  145. package/out/web/assets/{chunk-IMKFNOWR-YlJK8_Fp.js → chunk-IMKFNOWR-bIzfY9Jc.js} +1 -1
  146. package/out/web/assets/{chunk-L3NEJ4N5-DMYi4-af.js → chunk-L3NEJ4N5-Dl6eOa0D.js} +1 -1
  147. package/out/web/assets/{chunk-OSK3NFVY-Begd3-jA.js → chunk-OSK3NFVY-BaUGJ-cM.js} +1 -1
  148. package/out/web/assets/{chunk-P2QGCYS3-D1I4xFHN.js → chunk-P2QGCYS3-_066ndhH.js} +1 -1
  149. package/out/web/assets/{chunk-POPQ4Y6H-CafZZzNE.js → chunk-POPQ4Y6H-YT1AKhTv.js} +1 -1
  150. package/out/web/assets/{chunk-PWAF6VOD-Dwh6_Lnt.js → chunk-PWAF6VOD-CrEgDsxh.js} +1 -1
  151. package/out/web/assets/{chunk-SHT3W25Y-B0XrCSEb.js → chunk-SHT3W25Y-_KwaRLOl.js} +1 -1
  152. package/out/web/assets/{chunk-SVP7TREG-CxLsddxC.js → chunk-SVP7TREG-BQ1qb32L.js} +1 -1
  153. package/out/web/assets/{chunk-TICWLB2K-y156QInb.js → chunk-TICWLB2K-BDyehCtg.js} +1 -1
  154. package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
  155. package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
  156. package/out/web/assets/{config-DqXJXeZM.css → config-2suVFPZg.css} +1 -1
  157. package/out/web/assets/config-CkRCdkxn.js +2 -0
  158. package/out/web/assets/{cynefin-OW5HDTMX-SMVvrLf-.js → cynefin-OW5HDTMX-Dl_31aaN.js} +1 -1
  159. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-rZznN24T.js → cynefinDiagram-5FMLGOSQ-Bpir8jha.js} +1 -1
  160. package/out/web/assets/{dagre-GXQ25YYZ-BOToydFu.js → dagre-GXQ25YYZ-D9xu3nsq.js} +1 -1
  161. package/out/web/assets/{diagram-S7CK7UJ4-TMVaGQEJ.js → diagram-S7CK7UJ4-DeveoEeB.js} +1 -1
  162. package/out/web/assets/{diagram-UQ7AKVKN-C2JK-GP2.js → diagram-UQ7AKVKN-p4xWRESV.js} +1 -1
  163. package/out/web/assets/{diagram-VSXAHHWV-Cw8VSi2K.js → diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
  164. package/out/web/assets/{diagram-VX7I27RA-DXp4fKk9.js → diagram-VX7I27RA-DRiCa3Vd.js} +1 -1
  165. package/out/web/assets/{diagram-Z3DM3KII-CzSicYiW.js → diagram-Z3DM3KII-Bt7zduQX.js} +1 -1
  166. package/out/web/assets/{ebnfDiagram-PWID7BFC-Dqj-zqPu.js → ebnfDiagram-PWID7BFC-DV9WpwdR.js} +1 -1
  167. package/out/web/assets/{erDiagram-RLTQ6QDP-DZwFZAuD.js → erDiagram-RLTQ6QDP-BJ_nwG2m.js} +1 -1
  168. package/out/web/assets/{eventmodeling-NTZA5JFV-C0O-Lvgp.js → eventmodeling-NTZA5JFV-BTBMrfVa.js} +1 -1
  169. package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
  170. package/out/web/assets/{ganttDiagram-EL5Y4UJY-Da4ULXTl.js → ganttDiagram-EL5Y4UJY-DQLdsXrC.js} +1 -1
  171. package/out/web/assets/{gitGraph-4MIJSDKK-B9wNFZIP.js → gitGraph-4MIJSDKK-CL1OEOQ_.js} +1 -1
  172. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-RjrhB0MP.js → gitGraphDiagram-WWUBYQGX-8cSi42H7.js} +1 -1
  173. package/out/web/assets/index-BocxnrDG.js +168 -0
  174. package/out/web/assets/index-yeh_r55a.css +1 -0
  175. package/out/web/assets/{info-A6RAGUB7-B3dfCpeY.js → info-A6RAGUB7-C1zsX5sc.js} +1 -1
  176. package/out/web/assets/{infoDiagram-27XIBGKW-QVWkLVx7.js → infoDiagram-27XIBGKW-DsRYtIyI.js} +1 -1
  177. package/out/web/assets/{ishikawaDiagram-5VMMS53U-CLFNIRI9.js → ishikawaDiagram-5VMMS53U-B2crwIXj.js} +1 -1
  178. package/out/web/assets/{journeyDiagram-3NMN7TZE-fgOQadYF.js → journeyDiagram-3NMN7TZE-BTH-GyVR.js} +1 -1
  179. package/out/web/assets/{kanban-definition-UXKFOSKX-G3Q_9g9O.js → kanban-definition-UXKFOSKX-BeRfCnuA.js} +1 -1
  180. package/out/web/assets/{mermaid-parser.core-v3Jm7nco.js → mermaid-parser.core-BJwgCBBc.js} +3 -3
  181. package/out/web/assets/{mermaid.core-DTEjLUA1.js → mermaid.core-CsDDodN8.js} +4 -4
  182. package/out/web/assets/{mindmap-definition-YA3MSWOX-C_D42_fF.js → mindmap-definition-YA3MSWOX-B42efOQ1.js} +1 -1
  183. package/out/web/assets/{modelManager-BPLycQrN.js → modelManager-B3ZFMh8v.js} +2 -2
  184. package/out/web/assets/{oauthManager-BUK7ic7q.js → oauthManager-Ch5Du2fF.js} +1 -1
  185. package/out/web/assets/{packet-AYTQ26CC-B-R-SHlF.js → packet-AYTQ26CC-B6xUyE6r.js} +1 -1
  186. package/out/web/assets/{pegDiagram-XKGWAZYB-Da2q_giz.js → pegDiagram-XKGWAZYB-1LabVycL.js} +1 -1
  187. package/out/web/assets/{pie-WAS4IAKB-BC8F0N7-.js → pie-WAS4IAKB-DJwo-nf_.js} +1 -1
  188. package/out/web/assets/{pieDiagram-E7YTZNPT-Iy9REJNC.js → pieDiagram-E7YTZNPT-icmAqzy4.js} +1 -1
  189. package/out/web/assets/{quadrantDiagram-AXDQQJYC-ztsiYw7h.js → quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +1 -1
  190. package/out/web/assets/{radar-RG4KPBEZ-B_1P8Z_z.js → radar-RG4KPBEZ-D7d-Xriw.js} +1 -1
  191. package/out/web/assets/{railroad-74A4TZTK-DuTqczSo.js → railroad-74A4TZTK-viuhdYbs.js} +1 -1
  192. package/out/web/assets/{railroad-abnf-HS5TGJTU-Df29WLmp.js → railroad-abnf-HS5TGJTU-DNXiCNea.js} +1 -1
  193. package/out/web/assets/{railroad-ebnf-LZEXJU2U-ePFMo-qp.js → railroad-ebnf-LZEXJU2U-nkSu1-0C.js} +1 -1
  194. package/out/web/assets/{railroad-peg-WCYAUIDC-DYXLW4_E.js → railroad-peg-WCYAUIDC-BhbMeQzz.js} +1 -1
  195. package/out/web/assets/{railroadDiagram-O6MQD6OU-LStnZf2S.js → railroadDiagram-O6MQD6OU-CLpG1jUX.js} +1 -1
  196. package/out/web/assets/{requirementDiagram-BXWQKSXE-DmzonMH-.js → requirementDiagram-BXWQKSXE-kRDrvWrc.js} +1 -1
  197. package/out/web/assets/{sankeyDiagram-P5KCCOFB-B8vjmUU1.js → sankeyDiagram-P5KCCOFB-DxAwmq4x.js} +1 -1
  198. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-CTnplOAX.js → sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js} +1 -1
  199. package/out/web/assets/{stateDiagram-D77RDMKH-BFrIaLdI.js → stateDiagram-D77RDMKH-D5QWfOhp.js} +1 -1
  200. package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
  201. package/out/web/assets/{swimlanes-42K2YHIH-BXGngUNU.js → swimlanes-42K2YHIH-CRs5UyA-.js} +1 -1
  202. package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
  203. package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
  204. package/out/web/assets/{timeline-definition-24CTP7MA-CegH7n43.js → timeline-definition-24CTP7MA-Denqd9qb.js} +1 -1
  205. package/out/web/assets/{treeView-Q6P3EWNA-DTpJv2Tw.js → treeView-Q6P3EWNA-0my3Z3s4.js} +1 -1
  206. package/out/web/assets/{treemap-WGGIJYW6-i8M9iJU6.js → treemap-WGGIJYW6-B_EjkLYq.js} +1 -1
  207. package/out/web/assets/{vennDiagram-4TSXK5OY-Dpzo1S8U.js → vennDiagram-4TSXK5OY-DxVg3Ewr.js} +1 -1
  208. package/out/web/assets/{wardley-WFR3VGLG-BrNsgtfw.js → wardley-WFR3VGLG-CGD-Tj4p.js} +1 -1
  209. package/out/web/assets/{wardleyDiagram-VM6X3IG4-lQ92LFE1.js → wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +1 -1
  210. package/out/web/assets/{xychartDiagram-S5SC5T6Z-BaOaEoBd.js → xychartDiagram-S5SC5T6Z-BVM9bKis.js} +1 -1
  211. package/out/web/index-web.html +2 -2
  212. package/out/web/index.html +2 -2
  213. package/package.json +2 -2
  214. package/shared-modules/CODEOWNERS +3 -3
  215. package/shared-modules/SDL2/SDL2_mixer.json +27 -0
  216. package/shared-modules/appstream/README.md +13 -0
  217. package/shared-modules/appstream/appstream-compose.json +111 -0
  218. package/shared-modules/dbus-glib/dbus-glib.json +2 -2
  219. package/shared-modules/libportal/libportal.json +23 -0
  220. package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
  221. package/shared-modules/linux-audio/fluidsynth2.json +2 -2
  222. package/shared-modules/linux-audio/libinstpatch.json +2 -2
  223. package/shared-modules/linux-audio/liblo-static.json +3 -0
  224. package/shared-modules/luajit/luajit.json +2 -2
  225. package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
  226. package/slash-commands.json +7 -2
  227. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  228. package/src/renderer/shared/agentProgress.ts +51 -3
  229. package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -56
  230. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +15 -1
  231. package/src/renderer/shared/components/Settings/GeneralTab.tsx +25 -12
  232. package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
  233. package/src/renderer/shared/components/Settings/ThemeTab.tsx +106 -3
  234. package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +67 -17
  235. package/src/renderer/shared/components/activity/AgentActivityTable.tsx +376 -134
  236. package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
  237. package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
  238. package/src/renderer/shared/components/activity/McpToolResults.tsx +39 -2
  239. package/src/renderer/shared/components/activity/SubagentDagView.tsx +7 -7
  240. package/src/renderer/shared/components/activity/ViewedImages.tsx +41 -2
  241. package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +3 -3
  242. package/src/renderer/shared/components/app/CommandPalette.tsx +7 -7
  243. package/src/renderer/shared/components/app/DesktopTitlebar.tsx +2 -2
  244. package/src/renderer/shared/components/app/MintDashboard.tsx +27 -17
  245. package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +3 -3
  246. package/src/renderer/shared/components/cards/CalculationCard.tsx +3 -3
  247. package/src/renderer/shared/components/cards/ImageGenCard.tsx +2 -2
  248. package/src/renderer/shared/components/cards/ImageSearchCard.tsx +2 -2
  249. package/src/renderer/shared/components/cards/MermaidCard.tsx +1 -1
  250. package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
  251. package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
  252. package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
  253. package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
  254. package/src/renderer/shared/components/cards/StockCard.tsx +7 -7
  255. package/src/renderer/shared/components/cards/ToolResultCards.tsx +11 -3
  256. package/src/renderer/shared/components/cards/UiMockupWidget.tsx +4 -4
  257. package/src/renderer/shared/components/cards/WeatherCard.tsx +5 -5
  258. package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +11 -0
  259. package/src/renderer/shared/components/chat/ApprovalCard.tsx +21 -2
  260. package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
  261. package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
  262. package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
  263. package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
  264. package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
  265. package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +15 -1
  266. package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +40 -2
  267. package/src/renderer/shared/components/chat/ChatMessageItem.tsx +180 -34
  268. package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
  269. package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
  270. package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
  271. package/src/renderer/shared/components/chat/ChatPanel.test.tsx +22 -0
  272. package/src/renderer/shared/components/chat/ChatPanel.tsx +227 -53
  273. package/src/renderer/shared/components/chat/DiffReviewModal.tsx +6 -6
  274. package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +142 -1
  275. package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +45 -3
  276. package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +42 -14
  277. package/src/renderer/shared/components/chat/RewindModal.tsx +10 -10
  278. package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +14 -0
  279. package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
  280. package/src/renderer/shared/components/chat/SourcesBlock.tsx +236 -90
  281. package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +45 -1
  282. package/src/renderer/shared/components/chat/TurnFileChanges.tsx +202 -169
  283. package/src/renderer/shared/components/extensions/CliSessionsView.tsx +5 -4
  284. package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +3 -3
  285. package/src/renderer/shared/components/studio/LibraryPage.tsx +48 -5
  286. package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +125 -13
  287. package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
  288. package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +77 -16
  289. package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +78 -22
  290. package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +13 -0
  291. package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +48 -28
  292. package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +266 -47
  293. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +18 -6
  294. package/src/renderer/shared/components/workspace/background-terminals.css +11 -11
  295. package/src/renderer/shared/constants/config.ts +1 -5
  296. package/src/renderer/shared/constants/models.test.ts +51 -0
  297. package/src/renderer/shared/constants/models.ts +139 -32
  298. package/src/renderer/shared/conversation/lifecycle.test.ts +91 -3
  299. package/src/renderer/shared/conversation/lifecycle.ts +104 -5
  300. package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
  301. package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
  302. package/src/renderer/shared/css/agent-observability.css +133 -0
  303. package/src/renderer/shared/css/chat-ui-cards.css +13 -13
  304. package/src/renderer/shared/css/code-review.css +63 -34
  305. package/src/renderer/shared/css/code-sessions.css +47 -18
  306. package/src/renderer/shared/css/collapsed-paste.css +1 -1
  307. package/src/renderer/shared/css/error-notice.css +1 -1
  308. package/src/renderer/shared/css/file-changes.css +165 -80
  309. package/src/renderer/shared/css/library.css +105 -10
  310. package/src/renderer/shared/css/management-views.css +1 -1
  311. package/src/renderer/shared/css/mcp-results.css +5 -5
  312. package/src/renderer/shared/css/model-selector.css +18 -18
  313. package/src/renderer/shared/css/mods.css +7 -7
  314. package/src/renderer/shared/css/notification-notice.css +2 -2
  315. package/src/renderer/shared/css/queued-messages.css +5 -5
  316. package/src/renderer/shared/css/settings/general.css +18 -13
  317. package/src/renderer/shared/css/settings/profile.css +5 -5
  318. package/src/renderer/shared/css/settings/theme.css +5 -5
  319. package/src/renderer/shared/css/slash-choice.css +2 -2
  320. package/src/renderer/shared/css/slash-suggestions.css +3 -3
  321. package/src/renderer/shared/css/sources-block.css +41 -2
  322. package/src/renderer/shared/css/tools-panel.css +2 -2
  323. package/src/renderer/shared/css/ui-tailwind.css +8 -0
  324. package/src/renderer/shared/css/veo-studio.css +2 -2
  325. package/src/renderer/shared/css/viewed-images.css +7 -7
  326. package/src/renderer/shared/platform.ts +2 -1
  327. package/src/renderer/shared/theme/themeManager.test.ts +17 -0
  328. package/src/renderer/shared/theme/themeManager.ts +21 -4
  329. package/src/renderer/shared/theme/themes.ts +54 -0
  330. package/src/renderer/shared/theme/tokens.css +6 -4
  331. package/src/renderer/shared/types.ts +1 -1
  332. package/src/renderer/shared/utils/agentActivity.test.ts +468 -2
  333. package/src/renderer/shared/utils/agentActivity.ts +602 -35
  334. package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
  335. package/src/renderer/shared/utils/artifactBrowser.ts +17 -19
  336. package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
  337. package/src/renderer/shared/utils/attachedDocument.ts +49 -0
  338. package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
  339. package/src/renderer/shared/utils/fileIcons.ts +149 -16
  340. package/src/renderer/shared/utils/markdown.tsx +72 -19
  341. package/src/renderer/shared/utils/markdown.webImages.test.tsx +16 -0
  342. package/src/renderer/shared/utils/mcpResults.test.tsx +29 -0
  343. package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
  344. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
  345. package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
  346. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
  347. package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
  348. package/src/renderer/shared/utils/workspaceDrag.ts +142 -0
  349. package/src/renderer/shared/utils/workspaceMedia.ts +10 -2
  350. package/src/renderer/src/App.tsx +14 -8
  351. package/src/renderer/src/agentProgress.ts +1 -0
  352. package/src/renderer/src/components/ScreenPicker.css +1 -1
  353. package/src/renderer/src/components/WorkspacePanel.tsx +6 -5
  354. package/src/renderer/src/css/app-shell.css +4 -4
  355. package/src/renderer/src/css/chat.css +1261 -241
  356. package/src/renderer/src/css/desktop-tools.css +11 -11
  357. package/src/renderer/src/css/sidebar-workspace.css +2 -2
  358. package/src/renderer/src/css/tool-surfaces.css +12 -12
  359. package/src/renderer/src/index.css +38 -20
  360. package/src/renderer/src/tauri.ts +22 -2
  361. package/src/renderer/src-web/App.tsx +14 -0
  362. package/src/renderer/src-web/agentProgress.ts +1 -0
  363. package/src/renderer/src-web/css/app-shell.css +4 -4
  364. package/src/renderer/src-web/css/chat.css +1278 -190
  365. package/src/renderer/src-web/css/mobile.css +29 -13
  366. package/src/renderer/src-web/css/settings/mobile.css +2 -2
  367. package/src/renderer/src-web/css/sidebar-workspace.css +1 -1
  368. package/src/renderer/src-web/index.css +38 -20
  369. package/src/renderer/src-web/tauri.ts +22 -2
  370. package/src/renderer/tests/browserArtifacts.browser.tsx +1 -1
  371. package/src/renderer/tests/library.browser.tsx +3 -0
  372. package/src/renderer/tests/liveVoice.browser.html +1 -0
  373. package/src/renderer/tests/workspaceDrop.browser.tsx +1 -1
  374. package/src-tauri/src/app/lifecycle.rs +1 -14
  375. package/src-tauri/src/background/headless.rs +14 -8
  376. package/src-tauri/src/commands/conversation.rs +38 -0
  377. package/src-tauri/src/native/mod.rs +0 -1
  378. package/src-tauri/src/native/system.rs +4 -47
  379. package/crates/mint-cli/src/interactive/chat_tui.rs +0 -8855
  380. package/crates/mint-core/src/agent/chat.rs +0 -4394
  381. package/crates/mint-core/src/browser/lifecycle.rs +0 -291
  382. package/crates/mint-core/src/browser/logging.rs +0 -20
  383. package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
  384. package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +0 -2
  385. package/out/web/assets/CalculationCard-CRjP3E1j.js +0 -1
  386. package/out/web/assets/CliSessionsView-C3Br2v5Y.css +0 -1
  387. package/out/web/assets/CodeReviewPage-CUJceQme.css +0 -1
  388. package/out/web/assets/CodeReviewPage-VCUgPla_.js +0 -2
  389. package/out/web/assets/ImageGenCard-BCXxkVJX.js +0 -1
  390. package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +0 -1
  391. package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
  392. package/out/web/assets/LibraryPage-WlviRdR8.js +0 -2
  393. package/out/web/assets/LinkedFoldersView-CKRgroWK.js +0 -1
  394. package/out/web/assets/McpServersView-D3wHnuHg.js +0 -1
  395. package/out/web/assets/MermaidCard-NqrHdyJW.js +0 -2
  396. package/out/web/assets/MintDashboard-B2DJsDDO.css +0 -1
  397. package/out/web/assets/MintDashboard-COw_xol7.js +0 -63
  398. package/out/web/assets/RewindModal-DempuDod.js +0 -1
  399. package/out/web/assets/SettingsWindow-2YP3yj7s.js +0 -4
  400. package/out/web/assets/SettingsWindow-Dz8LQVgD.css +0 -1
  401. package/out/web/assets/StockCard-BDMCe7mx.js +0 -1
  402. package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +0 -38
  403. package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +0 -1
  404. package/out/web/assets/UiMockupWidget-DD73QWg1.js +0 -1
  405. package/out/web/assets/WeatherCard-CPucIfq8.js +0 -1
  406. package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +0 -2
  407. package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +0 -1
  408. package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +0 -1
  409. package/out/web/assets/config-CvIO5hkn.js +0 -2
  410. package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +0 -1
  411. package/out/web/assets/index-BmpulIkg.js +0 -168
  412. package/out/web/assets/index-CkvBwhX_.css +0 -1
  413. package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +0 -1
  414. package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +0 -8
  415. package/shared-modules/libsecret/libsecret.json +0 -23
  416. package/shared-modules/libusb/libusb.json +0 -27
  417. package/shared-modules/lzo/lzo.json +0 -27
  418. package/src/renderer/src/components/BrowserShell.tsx +0 -108
  419. package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
  420. package/src/renderer/src/components/browser-shell.css +0 -185
  421. package/src-tauri/src/native/browser.rs +0 -252
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ getModelMetadata,
4
+ hasNativeThinkingSupport,
5
+ isNonTextModel,
6
+ } from './models'
7
+
8
+ describe('models thinking classification', () => {
9
+ it('excludes only non-text media and embedding models from thinking', () => {
10
+ expect(isNonTextModel('nano-banana-pro-preview')).toBe(true)
11
+ expect(isNonTextModel('gemini-nano-banana-2.1')).toBe(true)
12
+ expect(isNonTextModel('gemini-3.1-flash-image')).toBe(true)
13
+ expect(isNonTextModel('dall-e-3')).toBe(true)
14
+ expect(isNonTextModel('text-embedding-3-large')).toBe(true)
15
+ expect(isNonTextModel('whisper-1')).toBe(true)
16
+ expect(isNonTextModel('gemini-2.5-flash-native-audio-preview-12-2025')).toBe(true)
17
+
18
+ expect(getModelMetadata('nano-banana-pro-preview', 'gemini').supportsThinking).toBe(false)
19
+ expect(getModelMetadata('gemini-nano-banana-2.1', 'gemini').supportsThinking).toBe(false)
20
+ })
21
+
22
+ it('allows thinking on all text and chat models across providers', () => {
23
+ expect(getModelMetadata('deepseek-flash', 'deepseek').supportsThinking).toBe(true)
24
+ expect(getModelMetadata('deepseek-v4-pro', 'deepseek').supportsThinking).toBe(true)
25
+ expect(getModelMetadata('deepseek-chat', 'deepseek').supportsThinking).toBe(true)
26
+ expect(getModelMetadata('gemini-2.5-flash', 'gemini').supportsThinking).toBe(true)
27
+ expect(getModelMetadata('gemini-1.5-flash', 'gemini').supportsThinking).toBe(true)
28
+ expect(getModelMetadata('gpt-4o', 'openai').supportsThinking).toBe(true)
29
+ expect(getModelMetadata('claude-haiku-4.5', 'anthropic').supportsThinking).toBe(true)
30
+ expect(getModelMetadata('meta-llama/llama-3.3-70b-instruct', 'openrouter').supportsThinking).toBe(true)
31
+ })
32
+
33
+ it('distinguishes native reasoning API models from prompted CoT models', () => {
34
+ expect(hasNativeThinkingSupport('deepseek-flash', 'deepseek')).toBe(true)
35
+ expect(hasNativeThinkingSupport('deepseek-v4-pro', 'deepseek')).toBe(true)
36
+ expect(hasNativeThinkingSupport('deepseek-reasoner', 'deepseek')).toBe(true)
37
+ expect(hasNativeThinkingSupport('deepseek-chat', 'deepseek')).toBe(false)
38
+
39
+ expect(hasNativeThinkingSupport('gemini-2.5-flash', 'gemini')).toBe(true)
40
+ expect(hasNativeThinkingSupport('gemini-3.6-flash', 'gemini')).toBe(true)
41
+ expect(hasNativeThinkingSupport('gemini-1.5-flash', 'gemini')).toBe(false)
42
+
43
+ expect(hasNativeThinkingSupport('gpt-5.6-terra', 'openai')).toBe(true)
44
+ expect(hasNativeThinkingSupport('o3-mini', 'openai')).toBe(true)
45
+ expect(hasNativeThinkingSupport('gpt-4o', 'openai')).toBe(false)
46
+
47
+ expect(hasNativeThinkingSupport('claude-sonnet-5', 'anthropic')).toBe(true)
48
+ expect(hasNativeThinkingSupport('claude-haiku-4.5', 'anthropic')).toBe(true)
49
+ expect(hasNativeThinkingSupport('claude-3-5-sonnet', 'anthropic')).toBe(false)
50
+ })
51
+ })
@@ -82,6 +82,8 @@ export function isFreeModel(modelId: string): boolean {
82
82
  }
83
83
 
84
84
  export const DEEPSEEK_MODELS = [
85
+ 'deepseek-flash',
86
+ 'deepseek-v4-pro',
85
87
  'deepseek-chat',
86
88
  'deepseek-reasoner',
87
89
  ] as const
@@ -227,6 +229,18 @@ export const MODEL_METADATA: Record<string, ModelMeta> = {
227
229
  supportsThinking: true,
228
230
  },
229
231
 
232
+ // Gemini Image Generation (Nano Banana)
233
+ 'nano-banana-pro-preview': {
234
+ description: 'Google Gemini Nano Banana Pro model for AI image generation and photo editing.',
235
+ contextWindow: '32K Context',
236
+ supportsThinking: false,
237
+ },
238
+ 'gemini-nano-banana-2.1': {
239
+ description: 'Google Gemini Nano Banana 2.1 model for fast image generation and visual editing.',
240
+ contextWindow: '32K Context',
241
+ supportsThinking: false,
242
+ },
243
+
230
244
  // Anthropic Claude
231
245
  'claude-sonnet-5': {
232
246
  description: 'Anthropic flagship hybrid model with extended thinking and deep coding comprehension.',
@@ -251,7 +265,7 @@ export const MODEL_METADATA: Record<string, ModelMeta> = {
251
265
  'claude-haiku-4.5': {
252
266
  description: 'Fast and responsive Claude model for lightweight conversational turns.',
253
267
  contextWindow: '200K Context',
254
- supportsThinking: false,
268
+ supportsThinking: true,
255
269
  },
256
270
 
257
271
  // OpenAI
@@ -283,37 +297,139 @@ export const MODEL_METADATA: Record<string, ModelMeta> = {
283
297
  'gpt-4o': {
284
298
  description: 'OpenAI versatile multimodal model for text, vision, and high-speed chat.',
285
299
  contextWindow: '128K Context',
286
- supportsThinking: false,
300
+ supportsThinking: true,
287
301
  },
288
302
  'gpt-4o-mini': {
289
303
  description: 'Compact and fast OpenAI model for everyday conversational tasks.',
290
304
  contextWindow: '128K Context',
291
- supportsThinking: false,
305
+ supportsThinking: true,
292
306
  },
293
307
 
294
308
  // DeepSeek
309
+ 'deepseek-flash': {
310
+ description: 'DeepSeek V4.1 Flash (1M context) supporting both thinking and fast non-thinking modes.',
311
+ contextWindow: '1M Context',
312
+ supportsThinking: true,
313
+ },
295
314
  'deepseek-v4-flash': {
296
- description: 'High-throughput DeepSeek model built for swift responses and programming.',
297
- contextWindow: '128K Context',
298
- supportsThinking: false,
315
+ description: 'High-throughput DeepSeek V4 Flash model supporting both thinking and non-thinking modes.',
316
+ contextWindow: '1M Context',
317
+ supportsThinking: true,
299
318
  },
300
319
  'deepseek-v4-pro': {
301
- description: 'Advanced DeepSeek model featuring deep multilingual and coding reasoning.',
302
- contextWindow: '128K Context',
320
+ description: 'Advanced DeepSeek V4 Pro model (1M context) featuring deep multilingual and coding reasoning.',
321
+ contextWindow: '1M Context',
303
322
  supportsThinking: true,
304
323
  },
305
324
  'deepseek-reasoner': {
306
- description: 'DeepSeek native reasoning model (R1 architecture) with comprehensive chain-of-thought.',
307
- contextWindow: '64K Context',
325
+ description: 'DeepSeek native reasoning model (R1 / V3.2 thinking) with comprehensive chain-of-thought.',
326
+ contextWindow: '128K Context',
308
327
  supportsThinking: true,
309
328
  },
310
329
  'deepseek-chat': {
311
- description: 'General-purpose DeepSeek V3 conversational and coding model.',
312
- contextWindow: '64K Context',
313
- supportsThinking: false,
330
+ description: 'General-purpose DeepSeek conversational and coding model.',
331
+ contextWindow: '128K Context',
332
+ supportsThinking: true,
314
333
  },
315
334
  }
316
335
 
336
+ /**
337
+ * Returns true if the model is strictly a non-text/media/embedding model
338
+ * (image generation, TTS, audio transcription, embeddings) that cannot use
339
+ * Chain-of-Thought reasoning.
340
+ */
341
+ export function isNonTextModel(modelId: string): boolean {
342
+ const m = modelId.trim().toLowerCase()
343
+ const clean = m.split('/').pop() || m
344
+ return (
345
+ clean.includes('banana') ||
346
+ clean.includes('imagen') ||
347
+ clean.includes('image') ||
348
+ clean.includes('dall-e') ||
349
+ clean.includes('flux') ||
350
+ clean.includes('sdxl') ||
351
+ clean.includes('sd3') ||
352
+ clean.includes('ideogram') ||
353
+ clean.includes('veo-') ||
354
+ clean.includes('tts') ||
355
+ clean.includes('whisper') ||
356
+ clean.includes('embed') ||
357
+ clean.includes('aqa') ||
358
+ clean.includes('moderation') ||
359
+ clean.includes('native-audio') ||
360
+ clean.includes('-live-') ||
361
+ clean.endsWith('-live')
362
+ )
363
+ }
364
+
365
+ /**
366
+ * Returns true if the provider & model expose a native API parameter for visible
367
+ * reasoning tokens (e.g. Gemini 2.5+/3 thinkingConfig, DeepSeek V4/R1 thinking,
368
+ * OpenAI o1/o3/o4/gpt-5 reasoning_effort, Claude 3.7/4/5 extended thinking).
369
+ * Models where this returns false can still use Thinking via Prompt-based CoT (<think>).
370
+ */
371
+ export function hasNativeThinkingSupport(modelId: string, provider?: string): boolean {
372
+ if (isNonTextModel(modelId)) return false
373
+ const p = (provider || '').trim().toLowerCase()
374
+ const m = modelId.trim().toLowerCase()
375
+ const clean = m.split('/').pop() || m
376
+
377
+ if (clean.includes('gemini') || p === 'gemini') {
378
+ return (
379
+ clean.startsWith('gemini-2.5') ||
380
+ clean.startsWith('gemini-3') ||
381
+ clean.includes('thinking')
382
+ )
383
+ }
384
+
385
+ if (clean.includes('claude') || p === 'anthropic') {
386
+ return (
387
+ clean.includes('3-7') ||
388
+ clean.includes('3.7') ||
389
+ clean.includes('sonnet-4') ||
390
+ clean.includes('opus-4') ||
391
+ clean.includes('haiku-4') ||
392
+ clean.includes('claude-4') ||
393
+ clean.includes('sonnet-5') ||
394
+ clean.includes('opus-5') ||
395
+ clean.includes('haiku-5') ||
396
+ clean.includes('thinking')
397
+ )
398
+ }
399
+
400
+ if (clean.includes('deepseek') || p === 'deepseek') {
401
+ return (
402
+ clean.includes('reasoner') ||
403
+ clean.includes('r1') ||
404
+ clean.includes('v4') ||
405
+ clean.includes('flash') ||
406
+ clean.includes('pro') ||
407
+ clean.includes('thinking')
408
+ )
409
+ }
410
+
411
+ if (
412
+ clean.startsWith('o1') ||
413
+ clean.startsWith('o3') ||
414
+ clean.startsWith('o4') ||
415
+ clean.startsWith('gpt-5')
416
+ ) {
417
+ return true
418
+ }
419
+
420
+ return (
421
+ clean.includes('thinking') ||
422
+ clean.includes('reason') ||
423
+ clean.includes('qwq') ||
424
+ clean.includes('r1') ||
425
+ clean.includes('qwen3') ||
426
+ clean.includes('glm-5') ||
427
+ clean.includes('glm-4.5') ||
428
+ clean.includes('grok-3-mini') ||
429
+ clean.includes('grok-4')
430
+ )
431
+ }
432
+
317
433
  export function getModelMetadata(modelId: string, provider?: string): ModelMeta {
318
434
  const clean = modelId.trim()
319
435
  const lower = clean.toLowerCase()
@@ -331,29 +447,20 @@ export function getModelMetadata(modelId: string, provider?: string): ModelMeta
331
447
  contextWindow = lower.includes('pro') ? '2M Context' : '1M Context'
332
448
  } else if (lower.includes('claude') || provider === 'anthropic') {
333
449
  contextWindow = '200K Context'
450
+ } else if (lower.includes('deepseek-flash') || lower.includes('deepseek-v4')) {
451
+ contextWindow = '1M Context'
334
452
  } else if (lower.includes('llama-3') || lower.includes('qwen')) {
335
453
  contextWindow = '128K Context'
336
454
  }
337
455
 
338
- const supportsThinking =
339
- lower.includes('reason') ||
340
- lower.includes('thinking') ||
341
- lower.includes('r1') ||
342
- lower.includes('o1') ||
343
- lower.includes('o3') ||
344
- lower.includes('qwq') ||
345
- lower.includes('sonnet-5') ||
346
- lower.includes('opus-5') ||
347
- lower.includes('3-7') ||
348
- lower.includes('2.5') ||
349
- lower.includes('glm') ||
350
- lower.includes('deepseek') ||
351
- lower.includes('qwen') ||
352
- (provider === 'gemini')
353
-
354
- const description = supportsThinking
355
- ? `${clean} supports advanced multi-step reasoning and dynamic thinking effort.`
356
- : `${clean} is an optimized model for responsive conversational interactions.`
456
+ const supportsThinking = !isNonTextModel(clean)
457
+ const hasNative = hasNativeThinkingSupport(clean, provider)
458
+
459
+ const description = !supportsThinking
460
+ ? `${clean} is a specialized media or embedding model.`
461
+ : hasNative
462
+ ? `${clean} supports native multi-step reasoning and dynamic thinking effort.`
463
+ : `${clean} is an optimized conversational model supporting prompted chain-of-thought reasoning.`
357
464
 
358
465
  return { description, contextWindow, supportsThinking }
359
466
  }
@@ -18,7 +18,7 @@ const reply: ChatResponse = { provider: 'system', model: 'test', text: 'complete
18
18
  const activity: AgentProgress = { type: 'ToolStart', data: { action: 'read_file', input: { path: 'file.txt' } } }
19
19
 
20
20
  function setup(overrides: Partial<ConversationTransport> = {}, callbacks: ConversationEffects = {}, drafts?: { read(id: string): string; write(id: string, text: string): void }) {
21
- const streams: Array<{ chatId: string; onProgress: (p: AgentProgress) => void; onApproval: (p: PendingApproval) => void; onStarted: (id: number) => void; result: ReturnType<typeof deferred<ChatResponse>> }> = []
21
+ const streams: Array<{ chatId: string; onChunk: (chunk: string) => void; onProgress: (p: AgentProgress) => void; onApproval: (p: PendingApproval) => void; onStarted: (id: number) => void; result: ReturnType<typeof deferred<ChatResponse>> }> = []
22
22
  const saved: Array<{ id: number; progress: AgentProgress[] }> = []
23
23
  const submitted: Array<{ token: string; approved: boolean; answer?: string }> = []
24
24
  const transport: ConversationTransport = {
@@ -28,10 +28,11 @@ function setup(overrides: Partial<ConversationTransport> = {}, callbacks: Conver
28
28
  saveInteractionAgentActivity: async (id: number, progress: AgentProgress[]) => { saved.push({ id, progress }) },
29
29
  cancelChatMessage: async () => {},
30
30
  clearChatHistory: async () => 1,
31
+ truncateInteractionsFrom: async () => 1,
31
32
  submitToolApproval: async (token: string, approved: boolean, answer?: string) => { submitted.push({ token, approved, answer }) },
32
- streamChatMessage: (_message: string, _chunk: unknown, _image: unknown, _audio: unknown, _video: unknown, _instruction: unknown, onProgress: (progress: AgentProgress) => void, _document: unknown, _workspace: unknown, chatId: string, _agent: unknown, _plan: unknown, _mcp: unknown, onApproval: (approval: PendingApproval) => void, onStarted: (id: number) => void) => {
33
+ streamChatMessage: (_message: string, onChunk: (chunk: string) => void, _image: unknown, _audio: unknown, _video: unknown, _instruction: unknown, onProgress: (progress: AgentProgress) => void, _document: unknown, _workspace: unknown, chatId: string, _agent: unknown, _plan: unknown, _mcp: unknown, onApproval: (approval: PendingApproval) => void, onStarted: (id: number) => void) => {
33
34
  const result = deferred<ChatResponse>()
34
- streams.push({ chatId, onProgress, onApproval, onStarted, result })
35
+ streams.push({ chatId, onChunk, onProgress, onApproval, onStarted, result })
35
36
  return result.promise
36
37
  },
37
38
  ...overrides,
@@ -521,4 +522,91 @@ describe('Queued messages lifecycle and steering', () => {
521
522
  expect(lifecycle.getSnapshot().sendingMessage).toBe('Important Urgent Turn')
522
523
  expect(streams).toHaveLength(2)
523
524
  })
525
+
526
+ it('progressively reveals streamed lines while holding incomplete tables and unclosed UI cards until completed', async () => {
527
+ const { lifecycle, streams } = setup()
528
+ const run = lifecycle.sendPrompt('Show card and table')
529
+
530
+ streams[0].onChunk('Line 1\n```ui-card\n[{"title":"Box"}]\n')
531
+ expect(lifecycle.getSnapshot().streamedReply).toBe('Line 1')
532
+
533
+ streams[0].onChunk('```\n| A | B |\n| - | - |\n')
534
+ expect(lifecycle.getSnapshot().streamedReply).toBe('Line 1\n```ui-card\n[{"title":"Box"}]\n```')
535
+
536
+ streams[0].onChunk('| 1 | 2 |\nAfter table\n')
537
+ expect(lifecycle.getSnapshot().streamedReply).toBe(
538
+ 'Line 1\n```ui-card\n[{"title":"Box"}]\n```\n| A | B |\n| - | - |\n| 1 | 2 |\nAfter table',
539
+ )
540
+
541
+ streams[0].result.resolve({
542
+ provider: 'system',
543
+ model: 'test',
544
+ text: 'Line 1\n```ui-card\n[{"title":"Box"}]\n```\n| A | B |\n| - | - |\n| 1 | 2 |\nAfter table',
545
+ })
546
+ await run
547
+ })
548
+
549
+ it('drains a single-burst DirectText response progressively across multiple frames instead of dumping all at once', async () => {
550
+ const { lifecycle, streams } = setup()
551
+ const observedReplies: string[] = []
552
+ const unsubscribe = lifecycle.subscribe(() => {
553
+ const current = lifecycle.getSnapshot().streamedReply
554
+ if (current && observedReplies[observedReplies.length - 1] !== current) {
555
+ observedReplies.push(current)
556
+ }
557
+ })
558
+
559
+ const fullText = [
560
+ 'Line 1',
561
+ 'Line 2',
562
+ '| H1 | H2 |',
563
+ '| - | - |',
564
+ '| V1 | V2 |',
565
+ '```ui-card',
566
+ '[{"title":"Card"}]',
567
+ '```',
568
+ 'Final line',
569
+ ].join('\n')
570
+
571
+ const run = lifecycle.sendPrompt('Direct text burst')
572
+ streams[0].onChunk(fullText)
573
+ streams[0].result.resolve({ provider: 'system', model: 'test', text: fullText })
574
+ await run
575
+ unsubscribe()
576
+
577
+ expect(observedReplies).toEqual([
578
+ 'Line 1\nLine 2',
579
+ 'Line 1\nLine 2\n| H1 | H2 |\n| - | - |\n| V1 | V2 |',
580
+ 'Line 1\nLine 2\n| H1 | H2 |\n| - | - |\n| V1 | V2 |\n```ui-card\n[{"title":"Card"}]\n```',
581
+ fullText,
582
+ ])
583
+ })
584
+
585
+ it('truncates interactions from a given id and resends prompt', async () => {
586
+ let truncatedFrom: { chatId: string; fromId: number } | null = null
587
+ const { lifecycle, streams } = setup({
588
+ truncateInteractionsFrom: async (chatId, fromId) => {
589
+ truncatedFrom = { chatId, fromId }
590
+ return 1
591
+ },
592
+ })
593
+
594
+ lifecycle.appendInteraction({ id: 1, userText: 'first', aiText: 'reply 1', createdAt: '2026-01-01T00:00:00Z', provider: 'test', model: 'test' })
595
+ lifecycle.appendInteraction({ id: 2, userText: 'second', aiText: 'reply 2', createdAt: '2026-01-01T00:01:00Z', provider: 'test', model: 'test' })
596
+ lifecycle.appendInteraction({ id: 3, userText: 'third', aiText: 'reply 3', createdAt: '2026-01-01T00:02:00Z', provider: 'test', model: 'test' })
597
+
598
+ expect(lifecycle.getSnapshot().interactions).toHaveLength(3)
599
+
600
+ const resendPromise = lifecycle.resendFromInteraction(2, 'edited second')
601
+ expect(truncatedFrom).toEqual({ chatId: 'A', fromId: 2 })
602
+ expect(lifecycle.getSnapshot().interactions).toEqual([{ id: 1, userText: 'first', aiText: 'reply 1', createdAt: '2026-01-01T00:00:00Z', provider: 'test', model: 'test', chatId: 'A', status: 'completed' }])
603
+
604
+ await Promise.resolve()
605
+ await Promise.resolve()
606
+
607
+ streams[0].onChunk('new reply')
608
+ streams[0].result.resolve({ provider: 'test', model: 'test', text: 'new reply' })
609
+ await resendPromise
610
+ expect(lifecycle.getSnapshot().sending).toBe(false)
611
+ })
524
612
  })
@@ -2,13 +2,14 @@ import { initialConversationState, transitionConversation, type ConversationEven
2
2
  import { compactAgentProgressForPersistence, mergeActivitySnapshots } from '../agentProgress'
3
3
  import { requiresExplicitApproval } from '../utils/approval'
4
4
  import { navigateToProject } from './projectNavigation'
5
+ import { createProgressiveStreamPacer, type ProgressiveStreamPacer } from './progressiveStream'
5
6
  import type { MintPlatformApi } from '../platform'
6
7
  import type { AgentProgress, ChatResponse, ChatSession, InteractionMemory, QueuedPrompt } from '../types'
7
8
 
8
9
  export type ConversationTransport = Pick<MintPlatformApi,
9
10
  | 'getConversationSnapshot' | 'getConversationChanges' | 'listChatSessions'
10
11
  | 'streamChatMessage' | 'cancelChatMessage' | 'submitToolApproval'
11
- | 'saveInteractionAgentActivity' | 'clearChatHistory'>
12
+ | 'saveInteractionAgentActivity' | 'clearChatHistory' | 'truncateInteractionsFrom'>
12
13
 
13
14
  export interface PendingApproval { token: string; approval: Record<string, unknown> }
14
15
  export interface ConversationView extends ConversationState {
@@ -53,7 +54,9 @@ interface Run {
53
54
  autoApprove: boolean
54
55
  progress: AgentProgress[]
55
56
  queuedProgress: AgentProgress[]
57
+ pacer: ProgressiveStreamPacer
56
58
  timer?: ReturnType<typeof setTimeout>
59
+ streamTimer?: ReturnType<typeof setTimeout>
57
60
  }
58
61
  export interface ConversationBrowser {
59
62
  window: Pick<Window, 'addEventListener' | 'removeEventListener' | 'setInterval' | 'clearInterval'>
@@ -100,6 +103,21 @@ export function createConversationLifecycle(
100
103
  if (run.timer !== undefined) clearTimeout(run.timer)
101
104
  run.timer = undefined
102
105
  }
106
+ const clearStreamTimer = (run: Run) => {
107
+ if (run.streamTimer !== undefined) clearTimeout(run.streamTimer)
108
+ run.streamTimer = undefined
109
+ }
110
+ const stepStreamPacer = (run: Run) => {
111
+ clearStreamTimer(run)
112
+ if (!currentRun(run)) return
113
+ const revealed = run.pacer.step()
114
+ if (revealed !== null) {
115
+ update({ streamedReply: revealed })
116
+ if (!run.pacer.isDone()) {
117
+ run.streamTimer = setTimeout(() => stepStreamPacer(run), 18)
118
+ }
119
+ }
120
+ }
103
121
  const flushProgress = (run: Run) => {
104
122
  clearProgressTimer(run)
105
123
  const batch = run.queuedProgress
@@ -256,6 +274,7 @@ export function createConversationLifecycle(
256
274
  if (!state.sending || !run || !currentRun(run)) return
257
275
  runs.delete(origin.chatId)
258
276
  clearProgressTimer(run)
277
+ clearStreamTimer(run)
259
278
  const approvalToken = state.pendingApprovals[origin.chatId]?.token
260
279
  try {
261
280
  await transport.cancelChatMessage(origin.chatId)
@@ -275,7 +294,7 @@ export function createConversationLifecycle(
275
294
  async function sendPrompt(prompt: string, options: PromptOptions = {}) {
276
295
  if (state.sending) return
277
296
  const origin = session
278
- const run: Run = { session: origin, turnId: null, autoApprove: false, progress: [], queuedProgress: [] }
297
+ const run: Run = { session: origin, turnId: null, autoApprove: false, progress: [], queuedProgress: [], pacer: createProgressiveStreamPacer() }
279
298
  runs.set(origin.chatId, run)
280
299
  const images = options.imageAttachments ?? []
281
300
  const videos = options.videoAttachments ?? []
@@ -305,7 +324,27 @@ export function createConversationLifecycle(
305
324
  }
306
325
  try {
307
326
  const response = await transport.streamChatMessage(
308
- agent ? prompt : `/chat ${prompt}`, () => {}, images.map(img => img.dataUri).join(' '),
327
+ agent ? prompt : `/chat ${prompt}`,
328
+ chunk => {
329
+ if (runs.get(origin.chatId) !== run) return
330
+ run.pacer.pushDelta(chunk)
331
+ if (!currentRun(run)) return
332
+ // Immediately reveal up to 2 ready units (e.g. a completed table + following line);
333
+ // if a larger multi-line burst arrived at once, pace the remaining units smoothly.
334
+ let latest: string | null = null
335
+ for (let i = 0; i < 2; i++) {
336
+ const next = run.pacer.step()
337
+ if (next === null) break
338
+ latest = next
339
+ }
340
+ if (latest !== null) {
341
+ update({ streamedReply: latest })
342
+ if (run.streamTimer === undefined) {
343
+ run.streamTimer = setTimeout(() => stepStreamPacer(run), 18)
344
+ }
345
+ }
346
+ },
347
+ images.map(img => img.dataUri).join(' '),
309
348
  options.audioDataUri ?? null, videos.map(video => video.dataUri).join(' '), options.systemInstruction ?? '',
310
349
  progress => {
311
350
  if (runs.get(origin.chatId) !== run) return
@@ -325,8 +364,36 @@ export function createConversationLifecycle(
325
364
  )
326
365
  if (response.provider !== 'error') accepted()
327
366
  flushProgress(run)
328
- // Reveal only the authoritative completed reply, as on CLI/TUI.
329
- if (currentRun(run)) update({ streamedReply: response.text, streamedResponse: response })
367
+ clearStreamTimer(run)
368
+ run.pacer.finish(response.text)
369
+ if (currentRun(run)) {
370
+ const firstStep = run.pacer.step()
371
+ if (firstStep !== null) {
372
+ update({ streamedReply: firstStep, streamedResponse: response })
373
+ }
374
+ const usingFakeTimers = typeof (globalThis as any).vi?.isFakeTimers === 'function'
375
+ && (globalThis as any).vi.isFakeTimers()
376
+ if (!usingFakeTimers) {
377
+ while (!run.pacer.isDone() && runs.get(origin.chatId) === run && currentRun(run)) {
378
+ await new Promise<void>(resolve => {
379
+ run.streamTimer = setTimeout(() => {
380
+ run.streamTimer = undefined
381
+ resolve()
382
+ }, 18)
383
+ })
384
+ if (runs.get(origin.chatId) !== run || !currentRun(run)) break
385
+ const next = run.pacer.step()
386
+ if (next !== null) {
387
+ update({ streamedReply: next, streamedResponse: response })
388
+ } else {
389
+ break
390
+ }
391
+ }
392
+ }
393
+ if (runs.get(origin.chatId) === run && currentRun(run)) {
394
+ update({ streamedReply: run.pacer.flushAll(), streamedResponse: response })
395
+ }
396
+ }
330
397
  await effects.onReply?.(response, origin.chatId)
331
398
  const persisted = compactAgentProgressForPersistence(run.progress)
332
399
  if (persisted.length && run.turnId !== null) {
@@ -349,6 +416,7 @@ export function createConversationLifecycle(
349
416
  if (currentRun(run)) effects.onError?.(error)
350
417
  } finally {
351
418
  flushProgress(run)
419
+ clearStreamTimer(run)
352
420
  if (runs.get(origin.chatId) === run) {
353
421
  const isCurrent = currentRun(run)
354
422
  removeApproval(origin.chatId)
@@ -399,6 +467,36 @@ export function createConversationLifecycle(
399
467
  }
400
468
  }
401
469
 
470
+ async function truncateFrom(fromId: number) {
471
+ const origin = session
472
+ try {
473
+ if (state.sending && runs.has(origin.chatId)) {
474
+ await cancel()
475
+ }
476
+ // Optimistically update local state immediately so UI updates with zero latency
477
+ const filtered = state.interactions.filter(i => i.id < fromId)
478
+ const snapshots = { ...state.agentActivitySnapshots }
479
+ for (const interaction of state.interactions) {
480
+ if (interaction.id >= fromId) {
481
+ delete snapshots[String(interaction.id)]
482
+ }
483
+ }
484
+ update({
485
+ interactions: filtered,
486
+ agentActivitySnapshots: snapshots,
487
+ activeTurnId: null,
488
+ })
489
+ await transport.truncateInteractionsFrom(origin.chatId, fromId)
490
+ } catch (error) {
491
+ if (origin === session) throw error
492
+ }
493
+ }
494
+
495
+ async function resendFromInteraction(fromId: number, promptText: string, options: PromptOptions = {}) {
496
+ await truncateFrom(fromId)
497
+ await sendPrompt(promptText, options)
498
+ }
499
+
402
500
  async function selectProject(
403
501
  choosePath: () => Promise<string | null>,
404
502
  navigation: {
@@ -560,6 +658,7 @@ export function createConversationLifecycle(
560
658
  subscribe: (notify: () => void) => { subscribers.add(notify); return () => { subscribers.delete(notify) } },
561
659
  activate, refreshHistory, refreshSessions, syncHistory, loadOlder, sendPrompt, cancel,
562
660
  receiveApproval, resolveApproval, clearHistory, selectProject, watch,
661
+ truncateFrom, resendFromInteraction,
563
662
  queuePrompt, removeQueuedPrompt, editQueuedPrompt, clearQueuedPrompts, setQueuePaused, resumeQueue, steerQueuedPrompt,
564
663
  appendInteraction: (row: Omit<InteractionMemory, 'chatId' | 'status'> & Partial<Pick<InteractionMemory, 'chatId' | 'status'>>) =>
565
664
  update({ interactions: [...state.interactions, { chatId: session.chatId, status: 'completed', ...row }] }),
@@ -0,0 +1,83 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ extractRenderableStreamPrefix,
4
+ splitProgressiveStreamUnits,
5
+ createProgressiveStreamPacer,
6
+ } from './progressiveStream'
7
+
8
+ describe('progressiveStream (Line-by-Line + Block-Aware)', () => {
9
+ it('releases normal paragraphs and regular code blocks line by line while holding incomplete tables and unclosed UI cards', () => {
10
+ const partialTable = 'Intro paragraph\n| Name | Role |\n| --- | --- |\n| Alice | Admin |\n'
11
+ expect(extractRenderableStreamPrefix(partialTable, false)).toBe('Intro paragraph')
12
+ expect(extractRenderableStreamPrefix(partialTable, true)).toBe(partialTable.trimEnd())
13
+
14
+ const completedTable = `${partialTable}Summary after table\n`
15
+ expect(extractRenderableStreamPrefix(completedTable, false)).toBe(completedTable.trimEnd())
16
+
17
+ const partialUiCard = 'Before card\n```ui-card\n[{"title": "Fast", "subtitle": "Card"}]\n'
18
+ expect(extractRenderableStreamPrefix(partialUiCard, false)).toBe('Before card')
19
+
20
+ const closedUiCard = `${partialUiCard}\`\`\`\nAfter card\n`
21
+ expect(extractRenderableStreamPrefix(closedUiCard, false)).toBe(closedUiCard.trimEnd())
22
+
23
+ const bareFence = 'Before code\n```ts\n'
24
+ expect(extractRenderableStreamPrefix(bareFence, false)).toBe('Before code')
25
+
26
+ const partialCode = 'Before code\n```ts\nconst x = 1\n'
27
+ expect(extractRenderableStreamPrefix(partialCode, false)).toBe('Before code\n```ts\nconst x = 1')
28
+
29
+ const unlabeledUiCard = 'Before card\n```\nui-card [\n {"title": "Fast"}\n]\n'
30
+ expect(extractRenderableStreamPrefix(unlabeledUiCard, false)).toBe('Before card')
31
+ expect(extractRenderableStreamPrefix(`${unlabeledUiCard}\`\`\`\n`, false)).toBe(
32
+ `${unlabeledUiCard}\`\`\``,
33
+ )
34
+ })
35
+
36
+ it('splits complete markdown into line units for text/code and atomic block units for tables and JSON/Mermaid cards', () => {
37
+ const doc = [
38
+ '# Title',
39
+ 'First line',
40
+ '| A | B |',
41
+ '| - | - |',
42
+ '| 1 | 2 |',
43
+ '```python',
44
+ 'print(42)',
45
+ '```',
46
+ '```ui-grid',
47
+ '[{"title": "Item"}]',
48
+ '```',
49
+ 'Done',
50
+ ].join('\n')
51
+
52
+ expect(splitProgressiveStreamUnits(doc, true)).toEqual([
53
+ '# Title',
54
+ 'First line',
55
+ '| A | B |\n| - | - |\n| 1 | 2 |',
56
+ '```python\nprint(42)',
57
+ '```',
58
+ '```ui-grid\n[{"title": "Item"}]\n```',
59
+ 'Done',
60
+ ])
61
+ })
62
+
63
+ it('paces deltas and authoritative final text smoothly in order without leaking raw JSON mid-card', () => {
64
+ const revealed: string[] = []
65
+ const pacer = createProgressiveStreamPacer((text) => revealed.push(text))
66
+
67
+ pacer.pushDelta('Line 1\n```ui-card\n[{"title":"Box"}]\n')
68
+ expect(pacer.step()).toBe('Line 1')
69
+ expect(pacer.step()).toBeNull()
70
+
71
+ pacer.pushDelta('```\nLine 2\n')
72
+ expect(pacer.step()).toBe('Line 1\n```ui-card\n[{"title":"Box"}]\n```')
73
+ expect(pacer.step()).toBe('Line 1\n```ui-card\n[{"title":"Box"}]\n```\nLine 2')
74
+ expect(pacer.isDone()).toBe(false)
75
+
76
+ pacer.finish('Line 1\n```ui-card\n[{"title":"Box"}]\n```\nLine 2\nFinal tail')
77
+ expect(pacer.flushAll()).toBe('Line 1\n```ui-card\n[{"title":"Box"}]\n```\nLine 2\nFinal tail')
78
+ expect(pacer.isDone()).toBe(true)
79
+ expect(revealed[revealed.length - 1]).toBe(
80
+ 'Line 1\n```ui-card\n[{"title":"Box"}]\n```\nLine 2\nFinal tail',
81
+ )
82
+ })
83
+ })