@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
@@ -36,18 +36,20 @@ import { SubagentStatusStrip } from '../activity/SubagentStatusStrip'
36
36
  import { VirtualChatHistory, type VirtualChatHistoryHandle } from './VirtualChatHistory'
37
37
  import type { ArtifactFile } from '../../utils/artifacts'
38
38
  import type { AgentProgress, ChatResponse, DiffHunk, FileChange, GitCheckpoint, QueuedPrompt, RuntimeStatus } from '../../types'
39
- import { shouldShowSessionDivider, formatSessionDividerLabel } from '../../utils/ui'
39
+ import { shouldShowSessionDivider, formatSessionDividerLabel, createTrimmedImagePreview } from '../../utils/ui'
40
40
  import { useVoiceInput } from '@/voiceInput'
41
41
  import VoicePill from './VoicePill'
42
42
  import { useGeminiLiveVoice } from '../../utils/useGeminiLiveVoice'
43
43
  import GeminiLiveOverlay from './GeminiLiveOverlay'
44
44
  import { isSupportedDocument, SUPPORTED_DOCUMENT_ACCEPT } from '../../utils/documentTypes'
45
45
  import ModelSelectorPopover from './ModelSelectorPopover'
46
+ import ChatModeSelector from './ChatModeSelector'
46
47
  import GitBranchSelector from '../workspace/GitBranchSelector'
47
48
  import WorkspaceSelector from '../workspace/WorkspaceSelector'
48
49
  import ToolsPanelToggle from '../workspace/ToolsPanelToggle'
49
50
  import { ModsPanel } from '../extensions/ModsPanel'
50
51
  import type { ToolSurface } from '../workspace/ToolSurfacePage'
52
+ import { WORKSPACE_PATH_MIME, WORKSPACE_FILE_MIME, WORKSPACE_IMAGE_MIME, isImageFileName, resolveImageToDataUri, formatWorkspaceReference } from '../../utils/workspaceDrag'
51
53
  import QueuedMessagesBanner from './QueuedMessagesBanner'
52
54
  import '../../css/thinking-status.css'
53
55
  import '../../css/slash-choice.css'
@@ -78,7 +80,7 @@ export function ThinkingElapsedMeta() {
78
80
  )
79
81
  }
80
82
 
81
- const STREAM_MARKDOWN_UPDATE_MS = 120
83
+ const STREAM_MARKDOWN_UPDATE_MS = 32
82
84
 
83
85
  function useThrottledValue<T>(value: T, intervalMs: number): T {
84
86
  const [throttledValue, setThrottledValue] = useState(value)
@@ -150,6 +152,7 @@ interface ChatPanelContract {
150
152
  onSelectVideo: (event: ChangeEvent<HTMLInputElement>) => void
151
153
  onSelectDocument: (event: ChangeEvent<HTMLInputElement>) => void
152
154
  onPasteImage: (clipboardData: DataTransfer) => boolean
155
+ onAttachImage?: (image: { dataUri: string; previewDataUri?: string; objectUrl?: string; name: string }) => void
153
156
  onSetMessage: (message: string) => void
154
157
  onSendVoiceMessage: (message: string, audioDataUri?: string | null) => Promise<void>
155
158
  onRemoveImage: (idx: number) => void
@@ -191,6 +194,9 @@ interface ChatPanelContract {
191
194
  onClearQueuedPrompts?: () => void
192
195
  onSteerQueuedPrompt?: (id: string) => void
193
196
  onResumeQueue?: () => void
197
+ onResendFrom?: (fromId: number, newPrompt: string) => Promise<void> | void
198
+ onSyncHistory?: () => void
199
+ onReceiveApproval?: (approval: { token: string; approval: Record<string, unknown> }) => void
194
200
  }
195
201
 
196
202
  export type ConversationViewModel = Pick<ChatPanelContract,
@@ -269,6 +275,7 @@ export default function ChatPanel({
269
275
  onSelectVideo,
270
276
  onSelectDocument,
271
277
  onPasteImage,
278
+ onAttachImage,
272
279
  onSetMessage,
273
280
  onSendVoiceMessage,
274
281
  onRemoveImage,
@@ -306,6 +313,9 @@ export default function ChatPanel({
306
313
  onClearQueuedPrompts,
307
314
  onSteerQueuedPrompt,
308
315
  onResumeQueue,
316
+ onResendFrom,
317
+ onSyncHistory,
318
+ onReceiveApproval,
309
319
  } = { ...conversation, ...actions }
310
320
  const message = useComposerDraft(initialMessage, actions.subscribeMessage, actions.getMessage)
311
321
  const onThinkingExpandedChange = useLatestCallback(handleThinkingExpandedChange)
@@ -380,8 +390,12 @@ export default function ChatPanel({
380
390
  })
381
391
 
382
392
  const openReview = useLatestCallback((title: string, changes: FileChange[]) => {
383
- if (onOpenReview) onOpenReview({ id: `review:${Date.now()}`, kind: 'review', title: 'Review', reviewTitle: title, changes })
384
- else setReviewPage({ title, changes })
393
+ if (onOpenReview) {
394
+ setReviewPage(null)
395
+ onOpenReview({ id: `review:${Date.now()}`, kind: 'review', title: 'Review', reviewTitle: title, changes })
396
+ } else {
397
+ setReviewPage({ title, changes })
398
+ }
385
399
  })
386
400
 
387
401
  const openAllConversationReview = useLatestCallback(() => {
@@ -691,6 +705,36 @@ export default function ChatPanel({
691
705
  const dragCounter = useRef(0)
692
706
  const lastDropTimeRef = useRef(0)
693
707
 
708
+ const resizeInput = useCallback((element: HTMLTextAreaElement) => {
709
+ element.style.height = 'auto'
710
+ element.style.height = `${Math.min(element.scrollHeight, 120)}px`
711
+ }, [])
712
+
713
+ // Direct file reference insertion into chat input
714
+ const handleReferenceFile = useCallback((rawPath: string) => {
715
+ const formatted = formatWorkspaceReference(rawPath, workspacePath).trim()
716
+ if (!formatted) return
717
+ const token = `${formatted} `
718
+ const textarea = textareaRef.current
719
+ if (!textarea) {
720
+ onSetMessage(message.trim() ? `${message.trimEnd()} ${token}` : token)
721
+ return
722
+ }
723
+ const start = textarea.selectionStart ?? message.length
724
+ const end = textarea.selectionEnd ?? message.length
725
+ const before = message.slice(0, start)
726
+ const after = message.slice(end)
727
+ const spacingBefore = before && !before.endsWith(' ') && !before.endsWith('\n') ? ' ' : ''
728
+ const updated = `${before}${spacingBefore}${token}${after}`
729
+ onSetMessage(updated)
730
+ const nextCursor = (before + spacingBefore + token).length
731
+ requestAnimationFrame(() => {
732
+ textarea.focus()
733
+ textarea.setSelectionRange(nextCursor, nextCursor)
734
+ resizeInput(textarea)
735
+ })
736
+ }, [message, onSetMessage, workspacePath, resizeInput])
737
+
694
738
  const handleDragEnter = (e: DragEvent<HTMLElement>) => {
695
739
  e.preventDefault()
696
740
  if (e.dataTransfer?.types?.includes('Files')) {
@@ -711,6 +755,14 @@ export default function ChatPanel({
711
755
 
712
756
  const handleDragOver = (e: DragEvent<HTMLElement>) => {
713
757
  e.preventDefault()
758
+ if (
759
+ e.dataTransfer?.types?.includes(WORKSPACE_PATH_MIME) ||
760
+ e.dataTransfer?.types?.includes(WORKSPACE_FILE_MIME) ||
761
+ e.dataTransfer?.types?.includes(WORKSPACE_IMAGE_MIME) ||
762
+ e.dataTransfer?.types?.includes('application/x-mint-workspace-path')
763
+ ) {
764
+ e.dataTransfer.dropEffect = 'copy'
765
+ }
714
766
  }
715
767
 
716
768
  const handleDrop = (e: DragEvent<HTMLElement>) => {
@@ -719,6 +771,27 @@ export default function ChatPanel({
719
771
  dragCounter.current = 0
720
772
  setIsDragging(false)
721
773
 
774
+ const wsPath =
775
+ e.dataTransfer?.getData(WORKSPACE_IMAGE_MIME) ||
776
+ e.dataTransfer?.getData(WORKSPACE_PATH_MIME) ||
777
+ e.dataTransfer?.getData('application/x-mint-workspace-path')
778
+ const plainRef = e.dataTransfer?.getData('text/plain')?.trim() || ''
779
+ const fileRef =
780
+ e.dataTransfer?.getData(WORKSPACE_PATH_MIME) ||
781
+ e.dataTransfer?.getData('application/x-mint-workspace-path') ||
782
+ (plainRef.startsWith('@') ? plainRef : '')
783
+ if (wsPath || fileRef) {
784
+ void tryAttachDroppedImage(e.dataTransfer).then((attached) => {
785
+ if (!attached) {
786
+ const raw = fileRef || wsPath
787
+ if (raw) {
788
+ handleReferenceFile(raw)
789
+ }
790
+ }
791
+ })
792
+ return
793
+ }
794
+
722
795
  const now = Date.now()
723
796
  if (now - lastDropTimeRef.current < 250) {
724
797
  return
@@ -792,9 +865,31 @@ export default function ChatPanel({
792
865
  chatId,
793
866
  startSession: startGeminiLiveSession,
794
867
  sendAudioChunk: sendGeminiLiveAudioChunk,
795
- stopSession: stopGeminiLiveSession
868
+ stopSession: stopGeminiLiveSession,
869
+ onTurnSaved: () => {
870
+ onSyncHistory?.()
871
+ },
872
+ onToolActivity: () => {
873
+ onWorkspaceChanged?.()
874
+ },
875
+ onApprovalRequested: (approval) => {
876
+ onReceiveApproval?.(approval)
877
+ },
796
878
  })
797
879
 
880
+ useEffect(() => {
881
+ const onTriggerVoice = () => {
882
+ if (geminiLiveEnabled) {
883
+ geminiLive.setVoiceMode(true)
884
+ } else {
885
+ setVoiceMode(true)
886
+ void startRecognition()
887
+ }
888
+ }
889
+ window.addEventListener('mint:trigger-voice', onTriggerVoice)
890
+ return () => window.removeEventListener('mint:trigger-voice', onTriggerVoice)
891
+ }, [geminiLiveEnabled, geminiLive, setVoiceMode, startRecognition])
892
+
798
893
  const canSubmit = Boolean(message.trim() || imageAttachments.length > 0 || videoAttachments.length > 0 || documentName)
799
894
  const sendingImageMarkers = Array.from({ length: sendingImageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
800
895
  const sendingVideoMarkers = Array.from({ length: sendingVideoCount || 0 }, (_, index) => `[Video #${index + 1}]`).join(' ')
@@ -1006,10 +1101,6 @@ export default function ChatPanel({
1006
1101
  historyReadyRef.current = false; submittedDuringSessionRef.current = false; lastAutoSpokenIdRef.current = null
1007
1102
  }, [chatId])
1008
1103
 
1009
- const resizeInput = (element: HTMLTextAreaElement) => {
1010
- element.style.height = 'auto'
1011
- element.style.height = `${Math.min(element.scrollHeight, 120)}px`
1012
- }
1013
1104
  useEffect(() => {
1014
1105
  return () => {
1015
1106
  void speechPlayerRef.current?.dispose()
@@ -1113,13 +1204,77 @@ export default function ChatPanel({
1113
1204
  if (!trimmed) return
1114
1205
  onSetMessage(message.trim() ? `${message.trimEnd()} ${trimmed} ` : `${trimmed} `)
1115
1206
  }
1207
+
1208
+ const tryAttachDroppedImage = async (dataTransfer: DataTransfer): Promise<boolean> => {
1209
+ if (!onAttachImage) return false
1210
+
1211
+ const rawImage = dataTransfer.getData(WORKSPACE_IMAGE_MIME)
1212
+ let imagePayload: { path?: string; root?: string; url?: string; name?: string } | null = null
1213
+ if (rawImage) {
1214
+ try {
1215
+ imagePayload = JSON.parse(rawImage)
1216
+ } catch {
1217
+ // ignore
1218
+ }
1219
+ }
1220
+
1221
+ const wsPath =
1222
+ imagePayload?.path ||
1223
+ dataTransfer.getData(WORKSPACE_PATH_MIME) ||
1224
+ dataTransfer.getData('application/x-mint-workspace-path')
1225
+ const plainText = dataTransfer.getData('text/plain')
1226
+ const uriList = dataTransfer.getData('text/uri-list')
1227
+ const candidatePath = (wsPath || plainText || uriList || '').replace(/^@/, '').trim()
1228
+
1229
+ const isImg =
1230
+ Boolean(imagePayload) ||
1231
+ isImageFileName(candidatePath) ||
1232
+ candidatePath.startsWith('blob:') ||
1233
+ candidatePath.startsWith('data:image/') ||
1234
+ Boolean(imagePayload?.url)
1235
+ if (!isImg) return false
1236
+
1237
+ const name = imagePayload?.name || candidatePath.split(/[\\/]/).pop()?.split('?')[0] || 'image.png'
1238
+ const root = imagePayload?.root || workspacePath || ''
1239
+ const url =
1240
+ imagePayload?.url ||
1241
+ (candidatePath.startsWith('http') || candidatePath.startsWith('blob:') || candidatePath.startsWith('data:image/')
1242
+ ? candidatePath
1243
+ : '')
1244
+
1245
+ let dataUri = ''
1246
+ if (url) {
1247
+ dataUri = await resolveImageToDataUri(url, root, (p, r) => runtimePlatform.readWorkspaceImage(p, r))
1248
+ }
1249
+ if (!dataUri && candidatePath) {
1250
+ dataUri = await resolveImageToDataUri(candidatePath, root, (p, r) => runtimePlatform.readWorkspaceImage(p, r))
1251
+ }
1252
+
1253
+ if (dataUri) {
1254
+ const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
1255
+ onAttachImage({ dataUri, previewDataUri, name })
1256
+ textareaRef.current?.focus()
1257
+ return true
1258
+ }
1259
+ return false
1260
+ }
1261
+
1116
1262
  const handleWorkspaceDrop = (event: DragEvent<HTMLElement>) => {
1117
- const reference =
1118
- event.dataTransfer.getData('application/x-mint-workspace-path') ||
1119
- event.dataTransfer.getData('text/plain')
1120
- if (!reference.trim().startsWith('@')) return
1121
1263
  event.preventDefault()
1122
- appendWorkspaceReference(reference)
1264
+ const wsPath =
1265
+ event.dataTransfer.getData(WORKSPACE_PATH_MIME) ||
1266
+ event.dataTransfer.getData('application/x-mint-workspace-path')
1267
+ const plainText = event.dataTransfer.getData('text/plain')
1268
+ const reference = wsPath || plainText
1269
+
1270
+ void tryAttachDroppedImage(event.dataTransfer).then((attached) => {
1271
+ if (attached) return
1272
+ if (!reference) return
1273
+ const trimmed = reference.trim()
1274
+ if (wsPath || trimmed.startsWith('@')) {
1275
+ handleReferenceFile(trimmed)
1276
+ }
1277
+ })
1123
1278
  }
1124
1279
  const isEmptyChat = interactions.length === 0 && !sending && !pendingApproval
1125
1280
 
@@ -1157,12 +1312,16 @@ export default function ChatPanel({
1157
1312
  onToggle={toggleActivity}
1158
1313
  pendingApproval={!!pendingApproval}
1159
1314
  chatId={chatId}
1315
+ workspacePath={workspacePath}
1316
+ onReferenceFile={handleReferenceFile}
1160
1317
  onOpenArtifact={openArtifact}
1161
1318
  />
1162
1319
  )}
1163
1320
  <TurnFileChanges
1164
1321
  changes={composerChanges}
1165
1322
  isLive
1323
+ workspacePath={workspacePath}
1324
+ onReferenceFile={handleReferenceFile}
1166
1325
  onOpenArtifact={openArtifact}
1167
1326
  onOpenReview={openAllConversationReview}
1168
1327
  />
@@ -1188,7 +1347,7 @@ export default function ChatPanel({
1188
1347
  />
1189
1348
  )}
1190
1349
  <div className="message-bubble">
1191
- <McpToolResults progress={agentProgress} running chatId={chatId} />
1350
+ <McpToolResults progress={agentProgress} running chatId={chatId} mode="artifacts-only" />
1192
1351
  <ViewedImages progress={agentProgress} chatId={chatId} onOpenArtifact={openArtifact} />
1193
1352
  <span>
1194
1353
  {compaction ? (
@@ -1223,7 +1382,7 @@ export default function ChatPanel({
1223
1382
  type="button"
1224
1383
  className={`msg-action-btn copy-btn ${copiedId === 'live' ? 'is-copied' : ''}`}
1225
1384
  onClick={() => handleCopyMessage('live', throttledStreamedReply)}
1226
- title={copiedId === 'live' ? 'คัดลอกแล้ว (Copied!)' : 'คัดลอกข้อความ (Copy message)'}
1385
+ title={copiedId === 'live' ? 'Copied!' : 'Copy message'}
1227
1386
  >
1228
1387
  {renderCopyIcon(copiedId === 'live')}
1229
1388
  </button>
@@ -1281,7 +1440,7 @@ export default function ChatPanel({
1281
1440
  background: 'var(--bg-backdrop)',
1282
1441
  backdropFilter: 'blur(8px)',
1283
1442
  border: '2px dashed var(--accent)',
1284
- borderRadius: '16px',
1443
+ borderRadius: 'var(--radius-xl, 18px)',
1285
1444
  margin: '12px',
1286
1445
  display: 'flex',
1287
1446
  flexDirection: 'column',
@@ -1404,7 +1563,7 @@ export default function ChatPanel({
1404
1563
  <div className="chat-container" ref={chatContainerRef} onScroll={handleChatScroll} style={{ overflowAnchor: 'none' }}>
1405
1564
  {hasOlder && onLoadOlder && (
1406
1565
  <button type="button" className="load-older-messages" disabled={loadingOlder}
1407
- style={{ display: 'block', margin: '0 auto 20px', padding: '7px 14px', borderRadius: 8,
1566
+ style={{ display: 'block', margin: '0 auto 20px', padding: '7px 14px', borderRadius: 'var(--radius-md, 10px)',
1408
1567
  border: '1px solid var(--border-color, #3a3a3a)', background: 'var(--surface, #252525)',
1409
1568
  color: 'var(--text-secondary, #c0c0c0)', cursor: loadingOlder ? 'default' : 'pointer' }}
1410
1569
  onClick={async () => {
@@ -1439,13 +1598,18 @@ export default function ChatPanel({
1439
1598
  interaction={interaction}
1440
1599
  copiedId={copiedId}
1441
1600
  onEditMessage={handleEditMessage}
1601
+ onResendFrom={onResendFrom}
1442
1602
  speakingText={speakingText}
1443
1603
  agentActivitySnapshots={agentActivitySnapshots}
1444
1604
  openActivityIds={openActivityIds}
1605
+ thinkingExpanded={thinkingExpanded}
1606
+ onThinkingExpandedChange={onThinkingExpandedChange}
1445
1607
  handleCopyMessage={handleCopyMessage}
1446
1608
  speak={speak}
1447
1609
  toggleActivity={toggleActivity}
1448
1610
  chatId={chatId}
1611
+ workspacePath={workspacePath}
1612
+ onReferenceFile={handleReferenceFile}
1449
1613
  customProviders={settingsConfig?.customProviders}
1450
1614
  onOpenArtifact={openArtifact}
1451
1615
  onOpenReview={openAllConversationReview}
@@ -1519,24 +1683,6 @@ export default function ChatPanel({
1519
1683
  {workspacePath && <GitBranchSelector workspacePath={workspacePath} disabled={sending} onBranchChanged={onWorkspaceChanged} />}
1520
1684
  </div>
1521
1685
  )}
1522
- <div className="smart-context-bar">
1523
- <label className={`smart-context-option ${agentMode ? 'is-active' : ''}`}>
1524
- <span className="toggle-switch">
1525
- <input type="checkbox" checked={agentMode} onChange={(event) => onSetAgentMode(event.target.checked)} />
1526
- <span className="slider round" />
1527
- </span>
1528
- <span className="smart-context-copy"><span className="smart-context-title">Agent mode</span></span>
1529
- </label>
1530
- {onSetPlanMode && (
1531
- <label className={`smart-context-option ${planMode ? 'is-active' : ''}`} title="Investigate read-only and require plan approval before editing files or running commands">
1532
- <span className="toggle-switch">
1533
- <input type="checkbox" checked={Boolean(planMode)} onChange={(event) => onSetPlanMode(event.target.checked)} />
1534
- <span className="slider round" />
1535
- </span>
1536
- <span className="smart-context-copy"><span className="smart-context-title">Plan mode</span></span>
1537
- </label>
1538
- )}
1539
- </div>
1540
1686
  {queuedPrompts && queuedPrompts.length > 0 && (
1541
1687
  <QueuedMessagesBanner
1542
1688
  queuedPrompts={queuedPrompts}
@@ -1559,7 +1705,12 @@ export default function ChatPanel({
1559
1705
  onSubmit(event)
1560
1706
  }}
1561
1707
  onDragOver={(event) => {
1562
- if (event.dataTransfer.types.includes('application/x-mint-workspace-path')) {
1708
+ if (
1709
+ event.dataTransfer.types.includes(WORKSPACE_PATH_MIME) ||
1710
+ event.dataTransfer.types.includes(WORKSPACE_FILE_MIME) ||
1711
+ event.dataTransfer.types.includes(WORKSPACE_IMAGE_MIME) ||
1712
+ event.dataTransfer.types.includes('application/x-mint-workspace-path')
1713
+ ) {
1563
1714
  event.preventDefault()
1564
1715
  event.dataTransfer.dropEffect = 'copy'
1565
1716
  }
@@ -1664,6 +1815,18 @@ export default function ChatPanel({
1664
1815
  if (historyIndex !== null) setHistoryIndex(null)
1665
1816
  }}
1666
1817
  onKeyDown={handleInputKeyDown}
1818
+ onDragOver={(event) => {
1819
+ if (
1820
+ event.dataTransfer.types.includes(WORKSPACE_PATH_MIME) ||
1821
+ event.dataTransfer.types.includes(WORKSPACE_FILE_MIME) ||
1822
+ event.dataTransfer.types.includes(WORKSPACE_IMAGE_MIME) ||
1823
+ event.dataTransfer.types.includes('application/x-mint-workspace-path') ||
1824
+ event.dataTransfer.types.includes('text/plain')
1825
+ ) {
1826
+ event.preventDefault()
1827
+ event.dataTransfer.dropEffect = 'copy'
1828
+ }
1829
+ }}
1667
1830
  onDrop={handleWorkspaceDrop}
1668
1831
  placeholder="Ask anything, @ to mention, / for actions"
1669
1832
  rows={1}
@@ -1752,22 +1915,31 @@ export default function ChatPanel({
1752
1915
  <path d="M8 22h8M12 18v4M7 10h10M12 5v10" />
1753
1916
  </svg>
1754
1917
  </button>
1755
- <ModelSelectorPopover
1756
- activeProvider={status?.activeProvider ?? ''}
1757
- activeModel={activeModel}
1758
- availableProviders={status?.availableProviders ?? []}
1759
- settingsConfig={settingsConfig}
1760
- dynamicOllamaModels={dynamicOllamaModels}
1761
- onSelect={(provider, model) => {
1762
- if (onSelectModelAndProvider) {
1763
- onSelectModelAndProvider(provider, model)
1764
- } else {
1765
- onSetProvider(provider)
1766
- onSetModel(model)
1767
- }
1768
- }}
1769
- onUpdateSettings={onUpdateSettings}
1770
- />
1918
+ <div className="chat-composer-pills">
1919
+ <ModelSelectorPopover
1920
+ activeProvider={status?.activeProvider ?? ''}
1921
+ activeModel={activeModel}
1922
+ availableProviders={status?.availableProviders ?? []}
1923
+ settingsConfig={settingsConfig}
1924
+ dynamicOllamaModels={dynamicOllamaModels}
1925
+ onSelect={(provider, model) => {
1926
+ if (onSelectModelAndProvider) {
1927
+ onSelectModelAndProvider(provider, model)
1928
+ } else {
1929
+ onSetProvider(provider)
1930
+ onSetModel(model)
1931
+ }
1932
+ }}
1933
+ onUpdateSettings={onUpdateSettings}
1934
+ />
1935
+ <ChatModeSelector
1936
+ agentMode={agentMode}
1937
+ onSetAgentMode={onSetAgentMode}
1938
+ planMode={planMode}
1939
+ onSetPlanMode={onSetPlanMode}
1940
+ disabled={sending}
1941
+ />
1942
+ </div>
1771
1943
  <VoicePill
1772
1944
  id="voice-input-btn"
1773
1945
  className="composer-voice-pill"
@@ -1887,6 +2059,8 @@ export default function ChatPanel({
1887
2059
  onEndCall={() => geminiLive.setVoiceMode(false)}
1888
2060
  voice={settingsConfig?.geminiLiveVoice || 'Puck'}
1889
2061
  voices={GEMINI_LIVE_VOICES}
2062
+ pendingApproval={pendingApproval}
2063
+ onApproval={onApproval}
1890
2064
  onChangeVoice={async (voiceName) => {
1891
2065
  await onSetGeminiLiveVoice(voiceName)
1892
2066
  geminiLive.restart()
@@ -206,7 +206,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
206
206
  <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0, flex: 1 }}>
207
207
  <div
208
208
  className="management-card-icon"
209
- style={{ width: 34, height: 34, fontSize: '0.72rem', fontWeight: 700, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'color-mix(in srgb, var(--accent) 15%, transparent)', color: 'var(--interactive-fg)', borderRadius: '6px' }}
209
+ style={{ width: 34, height: 34, fontSize: '0.72rem', fontWeight: 700, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'color-mix(in srgb, var(--accent) 15%, transparent)', color: 'var(--interactive-fg)', borderRadius: 'var(--radius-sm, 8px)' }}
210
210
  >
211
211
  DIFF
212
212
  </div>
@@ -237,7 +237,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
237
237
  color: 'var(--interactive-fg)',
238
238
  background: 'color-mix(in srgb, var(--accent) 15%, transparent)',
239
239
  padding: '1px 6px',
240
- borderRadius: '4px',
240
+ borderRadius: 'var(--radius-xs, 6px)',
241
241
  }}
242
242
  >
243
243
  +{additions}
@@ -249,7 +249,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
249
249
  color: 'var(--status-error-fg)',
250
250
  background: 'var(--status-error-bg)',
251
251
  padding: '1px 6px',
252
- borderRadius: '4px',
252
+ borderRadius: 'var(--radius-xs, 6px)',
253
253
  }}
254
254
  >
255
255
  -{deletions}
@@ -262,7 +262,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
262
262
  color: 'var(--status-warning-fg)',
263
263
  background: 'var(--status-warning-bg)',
264
264
  padding: '1px 6px',
265
- borderRadius: '4px',
265
+ borderRadius: 'var(--radius-xs, 6px)',
266
266
  textTransform: 'none',
267
267
  }}
268
268
  >
@@ -533,9 +533,9 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
533
533
  >
534
534
  <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
535
535
  <span>Shortcut: </span>
536
- <kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius: 4, fontSize: '0.72rem' }}>Esc</kbd> Cancel
536
+ <kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius: 'var(--radius-xs, 6px)', fontSize: '0.72rem' }}>Esc</kbd> Cancel
537
537
  <span style={{ margin: '0 8px' }}>•</span>
538
- <kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius: 4, fontSize: '0.72rem' }}>Cmd/Ctrl+Enter</kbd> Approve
538
+ <kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius: 'var(--radius-xs, 6px)', fontSize: '0.72rem' }}>Cmd/Ctrl+Enter</kbd> Approve
539
539
  </div>
540
540
 
541
541
  <div style={{ display: 'flex', gap: 10 }}>
@@ -1,7 +1,13 @@
1
1
  import React from 'react'
2
2
  import { renderToStaticMarkup } from 'react-dom/server'
3
3
  import { describe, expect, it } from 'vitest'
4
- import GeminiLiveOverlay from './GeminiLiveOverlay'
4
+ import GeminiLiveOverlay, { resolveLiveOverlayEscape } from './GeminiLiveOverlay'
5
+ import {
6
+ applyLiveTranscriptFragment,
7
+ downsampleToPcm16,
8
+ routeGeminiLiveSideEffects,
9
+ stopQueuedAudioPlayback,
10
+ } from '../../utils/useGeminiLiveVoice'
5
11
 
6
12
  const props = {
7
13
  userTranscript: '', assistantTranscript: '', isPaused: false,
@@ -21,4 +27,139 @@ describe('Live conversation', () => {
21
27
  expect(html).toContain('สวัสดี')
22
28
  expect(html).toContain('Hello')
23
29
  })
30
+ it('stops and disconnects all queued audio sources when interrupted', () => {
31
+ let stopped1 = false, disconnected1 = false
32
+ let stopped2 = false, disconnected2 = false
33
+ const sources = new Set<any>([
34
+ { stop() { stopped1 = true }, disconnect() { disconnected1 = true } },
35
+ { stop() { stopped2 = true }, disconnect() { disconnected2 = true } },
36
+ ])
37
+ const nextPlaybackTimeRef = { current: 12.5 }
38
+ const speakingTimeoutRef = { current: null as number | null }
39
+ let speaking = true
40
+
41
+ stopQueuedAudioPlayback(sources, nextPlaybackTimeRef, speakingTimeoutRef, (val) => { speaking = val })
42
+
43
+ expect(stopped1).toBe(true)
44
+ expect(disconnected1).toBe(true)
45
+ expect(stopped2).toBe(true)
46
+ expect(disconnected2).toBe(true)
47
+ expect(sources.size).toBe(0)
48
+ expect(nextPlaybackTimeRef.current).toBe(0)
49
+ expect(speaking).toBe(false)
50
+ })
51
+ it('renders an inline ApprovalCard inside the Live overlay when a voice tool requests approval', () => {
52
+ const html = renderToStaticMarkup(
53
+ <GeminiLiveOverlay
54
+ {...props}
55
+ status="thinking"
56
+ pendingApproval={{
57
+ token: 'tok-1',
58
+ approval: {
59
+ RunShell: {
60
+ command: 'cargo test --workspace',
61
+ mode: 'workspace-write',
62
+ background: false,
63
+ },
64
+ },
65
+ }}
66
+ onApproval={() => {}}
67
+ />
68
+ )
69
+ expect(html).toContain('cargo test --workspace')
70
+ expect(html).toContain('Allow once')
71
+ expect(html).toContain('Reject')
72
+ })
73
+ it('routes turn persistence, tool activity, and approval requests from Gemini Live events', () => {
74
+ let turnsSaved = 0
75
+ let toolActivities = 0
76
+ const approvals: Array<{ token: string; approval: Record<string, unknown> }> = []
77
+ const callbacks = {
78
+ onTurnSaved: () => { turnsSaved += 1 },
79
+ onToolActivity: () => { toolActivities += 1 },
80
+ onApprovalRequested: (payload: { token: string; approval: Record<string, unknown> }) => {
81
+ approvals.push(payload)
82
+ },
83
+ }
84
+
85
+ routeGeminiLiveSideEffects({ type: 'turnComplete' }, callbacks)
86
+ expect(turnsSaved).toBe(1)
87
+
88
+ routeGeminiLiveSideEffects({ type: 'interrupted' }, callbacks)
89
+ expect(turnsSaved).toBe(2)
90
+
91
+ routeGeminiLiveSideEffects({ type: 'closed' }, callbacks)
92
+ expect(turnsSaved).toBe(3)
93
+
94
+ routeGeminiLiveSideEffects(
95
+ { type: 'toolStatus', action: 'read_file', input: { path: 'README.md' }, result: 'ok' },
96
+ callbacks,
97
+ )
98
+ expect(toolActivities).toBe(1)
99
+
100
+ routeGeminiLiveSideEffects(
101
+ {
102
+ type: 'approvalRequested',
103
+ token: 'tok-live-99',
104
+ approval: { RunShell: { command: 'ls', mode: 'read-only', background: false } },
105
+ },
106
+ callbacks,
107
+ )
108
+ expect(approvals).toEqual([
109
+ {
110
+ token: 'tok-live-99',
111
+ approval: { RunShell: { command: 'ls', mode: 'read-only', background: false } },
112
+ },
113
+ ])
114
+ })
115
+ it('downsamples mic audio to 16kHz PCM16 with boxcar anti-aliasing instead of nearest-neighbor aliasing', () => {
116
+ // 1. Direct 16kHz -> 16kHz conversion with clamping
117
+ const direct = downsampleToPcm16(new Float32Array([0, 0.5, -0.5, 1.5, -1.5]), 16000, 16000)
118
+ expect(Array.from(direct)).toEqual([0, 16384, -16384, 32767, -32768])
119
+
120
+ // 2. 48kHz -> 16kHz (ratio = 3): high-frequency alternating [1.0, -1.0, 0.0] averages to 0
121
+ // (nearest-neighbor would alias to +32767!), while steady [0.5, 0.5, 0.5] averages to 16384.
122
+ const antiAliased = downsampleToPcm16(
123
+ new Float32Array([1.0, -1.0, 0.0, 0.5, 0.5, 0.5]),
124
+ 48000,
125
+ 16000,
126
+ )
127
+ expect(Array.from(antiAliased)).toEqual([0, 16384])
128
+
129
+ // 3. Fractional 44.1kHz -> 16kHz (ratio = 2.75625) preserves DC level accurately
130
+ const dc44k = new Float32Array(441).fill(0.25)
131
+ const resampled = downsampleToPcm16(dc44k, 44100, 16000)
132
+ expect(resampled.length).toBe(160)
133
+ expect(resampled.every((sample) => Math.abs(sample - 8192) <= 1)).toBe(true)
134
+ })
135
+ it('accumulates interleaved user and assistant transcript fragments within a turn without wiping either line', () => {
136
+ const state = {
137
+ userTranscript: '',
138
+ assistantTranscript: '',
139
+ userFresh: true,
140
+ assistantFresh: true,
141
+ }
142
+ // User starts speaking, assistant starts replying while final user fragment arrives
143
+ applyLiveTranscriptFragment(state, 'user', 'ช่วยดู ')
144
+ applyLiveTranscriptFragment(state, 'assistant', 'ได้ครับ ')
145
+ applyLiveTranscriptFragment(state, 'user', 'ไฟล์ README หน่อย')
146
+ applyLiveTranscriptFragment(state, 'assistant', 'กำลังอ่านไฟล์ให้ครับ')
147
+
148
+ expect(state.userTranscript).toBe('ช่วยดู ไฟล์ README หน่อย')
149
+ expect(state.assistantTranscript).toBe('ได้ครับ กำลังอ่านไฟล์ให้ครับ')
150
+
151
+ // After turnComplete (resetting fresh flags), the next turn starts clean lines
152
+ state.userFresh = true
153
+ state.assistantFresh = true
154
+ applyLiveTranscriptFragment(state, 'user', 'ขอบคุณครับ')
155
+ expect(state.userTranscript).toBe('ขอบคุณครับ')
156
+ expect(state.assistantTranscript).toBe('ได้ครับ กำลังอ่านไฟล์ให้ครับ')
157
+ })
158
+ it('guards Escape key in GeminiLiveOverlay when voice picker or pending approval is active', () => {
159
+ expect(resolveLiveOverlayEscape({ voicePickerOpen: true, hasPendingApproval: false })).toBe('close_voice_picker')
160
+ expect(resolveLiveOverlayEscape({ voicePickerOpen: false, hasPendingApproval: true })).toBe('ignore')
161
+ expect(resolveLiveOverlayEscape({ voicePickerOpen: false, hasPendingApproval: false })).toBe('end_call')
162
+ })
24
163
  })
164
+
165
+