@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
@@ -3,19 +3,11 @@
3
3
  use super::{
4
4
  cdp::{cdp_call_raw, fetch_pages},
5
5
  interact::selector_to_js_find,
6
- logging::log_action,
7
6
  };
8
7
  use crate::MintConfig;
9
8
  use serde::{Deserialize, Serialize};
10
9
  use serde_json::{Value, json};
11
- use std::{
12
- collections::{BTreeMap, BTreeSet},
13
- fs::File,
14
- future::Future,
15
- hash::{Hash, Hasher},
16
- sync::Arc,
17
- time::{Duration, Instant},
18
- };
10
+ use std::{collections::BTreeSet, future::Future, sync::Arc, time::Duration};
19
11
  use tokio::sync::Mutex;
20
12
 
21
13
  tokio::task_local! {
@@ -55,14 +47,9 @@ pub struct BrowserActionResult {
55
47
  pub struct BrowserSession {
56
48
  id: String,
57
49
  tab: Option<String>,
58
- leases: BTreeMap<String, TabLease>,
59
50
  observation: Option<BrowserObservation>,
60
51
  last_known: Option<BrowserObservation>,
61
52
  screenshot: Option<super::evidence::ScreenshotEvidence>,
62
- attempts: BTreeMap<ActionAttemptKey, usize>,
63
- preceding_attempt: Option<ActionAttemptKey>,
64
- blocked: bool,
65
- used: bool,
66
53
  runtime: Option<super::runtime::ManagedBrowser>,
67
54
  runtime_config: Option<MintConfig>,
68
55
  owned_tabs: BTreeSet<String>,
@@ -74,14 +61,9 @@ impl Default for BrowserSession {
74
61
  Self {
75
62
  id: uuid::Uuid::new_v4().to_string(),
76
63
  tab: None,
77
- leases: BTreeMap::new(),
78
64
  observation: None,
79
65
  last_known: None,
80
66
  screenshot: None,
81
- attempts: BTreeMap::new(),
82
- preceding_attempt: None,
83
- blocked: false,
84
- used: false,
85
67
  runtime: None,
86
68
  runtime_config: None,
87
69
  owned_tabs: BTreeSet::new(),
@@ -138,60 +120,8 @@ pub async fn capture_metadata() -> Value {
138
120
  state.lock().await.capture_metadata.clone()
139
121
  }
140
122
 
141
- // Explicit unlock also releases a briefly inherited descriptor during a
142
- // concurrent fork/exec; relying on the last descriptor closing can leave a
143
- // finished tab looking busy until the child reaches exec.
144
- struct TabLease(File);
145
- impl Drop for TabLease {
146
- fn drop(&mut self) {
147
- let _ = self.0.unlock();
148
- }
149
- }
150
-
151
- fn lease(config: &MintConfig, tab: &str) -> Result<TabLease, String> {
152
- let mut hash = std::collections::hash_map::DefaultHasher::new();
153
- endpoint(config).hash(&mut hash);
154
- tab.hash(&mut hash);
155
- let dir = std::env::temp_dir().join("mint-browser-leases");
156
- std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
157
- let file = std::fs::OpenOptions::new()
158
- .create(true)
159
- .truncate(false)
160
- .read(true)
161
- .write(true)
162
- .open(dir.join(format!("{:x}.lock", hash.finish())))
163
- .map_err(|e| e.to_string())?;
164
- file.try_lock()
165
- .map_err(|_| "tab_busy: another Mint operation owns this tab".to_string())?;
166
- Ok(TabLease(file))
167
- }
168
-
169
- /// Desktop callers can opt into explicit targeting without breaking old calls.
170
- pub async fn with_tab<F: Future>(
171
- config: &MintConfig,
172
- tab: Option<&str>,
173
- future: F,
174
- ) -> Result<F::Output, String> {
175
- let id = match tab {
176
- Some(id) => id.to_owned(),
177
- None => fetch_pages(config)
178
- .await?
179
- .iter()
180
- .find(|p| p["type"] == "page")
181
- .and_then(|p| p["id"].as_str())
182
- .ok_or("tab_unavailable")?
183
- .to_owned(),
184
- };
185
- let _lease = lease(config, &id)?;
186
- Ok(TARGET.scope(id, future).await)
187
- }
188
-
189
123
  fn endpoint(config: &MintConfig) -> &str {
190
- config
191
- .extra
192
- .get("browserDebugUrl")
193
- .and_then(Value::as_str)
194
- .unwrap_or("http://127.0.0.1:9222/json/list")
124
+ super::cdp::debug_endpoint(config)
195
125
  }
196
126
 
197
127
  async fn tab_request(config: &MintConfig, operation: &str) -> Result<Value, String> {
@@ -298,29 +228,6 @@ fn target(args: &Value, session: &BrowserSession) -> Result<String, String> {
298
228
  }
299
229
  }
300
230
 
301
- fn modifying(action: &str) -> bool {
302
- matches!(
303
- action,
304
- "browser_open"
305
- | "browser_click"
306
- | "browser_type"
307
- | "browser_fill"
308
- | "browser_select"
309
- | "browser_scroll"
310
- | "browser_mouse_click"
311
- | "browser_key_press"
312
- | "browser_mouse_move"
313
- )
314
- }
315
-
316
- #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
317
- struct ActionAttemptKey {
318
- tab: String,
319
- action: String,
320
- target: String,
321
- arguments: String,
322
- }
323
-
324
231
  async fn target_state(config: &MintConfig, selector: Option<&str>) -> Result<Value, String> {
325
232
  let Some(selector) = selector else {
326
233
  return Ok(Value::Null);
@@ -328,51 +235,6 @@ async fn target_state(config: &MintConfig, selector: Option<&str>) -> Result<Val
328
235
  evaluate(config, format!("(() => {{ {} globalThis.__mintAttemptTarget=el; if (!el?.isConnected) return null; const nodes=globalThis.__mintNodes ||= {{ids:new WeakMap(),next:0,documentId:(crypto.randomUUID?.() || String(performance.timeOrigin)+Math.random())}}; if (!nodes.ids.has(el)) nodes.ids.set(el,++nodes.next); return {{id:nodes.documentId+':'+nodes.ids.get(el),value:el.value ?? null,checked:el.checked ?? null,expanded:el.getAttribute('aria-expanded')}}; }})()",selector_to_js_find(selector))).await
329
236
  }
330
237
 
331
- fn attempt_arguments(action: &str, args: &Value) -> String {
332
- match action {
333
- "browser_type" | "browser_fill" => json!([args["text"].as_str().unwrap_or("")]),
334
- "browser_select" => json!([args["value"].as_str().unwrap_or("")]),
335
- "browser_open" => json!([args["url"].as_str().unwrap_or("").trim()]),
336
- "browser_key_press" => json!([args["key"].as_str().unwrap_or("")]),
337
- "browser_scroll" => json!([
338
- args["x"].as_f64().unwrap_or(0.0),
339
- args["y"].as_f64().unwrap_or(600.0)
340
- ]),
341
- "browser_mouse_click" | "browser_mouse_move" => json!([
342
- args["x"],
343
- args["y"],
344
- args["button"]
345
- .as_str()
346
- .filter(|s| !s.is_empty())
347
- .unwrap_or("left")
348
- ]),
349
- _ => json!([]),
350
- }
351
- .to_string()
352
- }
353
-
354
- pub(crate) async fn finish_evidence() -> bool {
355
- match SESSION.try_with(|s| s.try_lock().map(|s| s.used).unwrap_or(true)) {
356
- Ok(used) => used,
357
- Err(_) => false,
358
- }
359
- }
360
-
361
- #[allow(dead_code)]
362
- pub(crate) async fn last_state() -> String {
363
- SESSION
364
- .try_with(|s| {
365
- s.try_lock().ok().and_then(|s| {
366
- s.last_known
367
- .as_ref()
368
- .map(|o| format!("tab={}, url={}, title={}", o.tab_id, o.url, o.title))
369
- })
370
- })
371
- .ok()
372
- .flatten()
373
- .unwrap_or_default()
374
- }
375
-
376
238
  /// Box the operation future so callers can compose many browser steps without
377
239
  /// multiplying the CDP transport's async state on their stack.
378
240
  pub fn execute_action<'a>(
@@ -384,44 +246,24 @@ pub fn execute_action<'a>(
384
246
  if SESSION.try_with(|_| ()).is_err() {
385
247
  return run_session(execute_action(config, action, args)).await;
386
248
  }
387
- let started = Instant::now();
249
+ let action = super::workflow::normalize_action(action);
388
250
  let state = SESSION.with(Arc::clone);
389
251
  let mut session = state.lock().await;
390
- if config.disabled_tools.iter().any(|tool| tool == action)
391
- || config.extra.get("browserAutomationEnabled") == Some(&json!(false))
392
- {
393
- return Err(format!("Browser tool '{action}' is disabled"));
394
- }
395
- if action == "browser_open" {
252
+ if action == "open" {
396
253
  super::navigate::validate_url(args["url"].as_str().unwrap_or(""))?;
397
254
  }
398
255
  let starting = session.runtime_config.is_none();
399
256
  if starting {
400
- let endpoint = endpoint(config);
401
- let parsed = reqwest::Url::parse(endpoint).map_err(|e| e.to_string())?;
402
- let external = config.extra.get("browserUseExisting") == Some(&json!(true))
403
- || !matches!(
404
- parsed.host_str(),
405
- Some("localhost" | "127.0.0.1" | "::1" | "[::1]")
406
- )
407
- || parsed.port_or_known_default() != Some(9222);
408
- if external {
409
- if !super::is_browser_running(config).await {
410
- return Err("Configured browser endpoint is unavailable; start the external browser or remove the endpoint override.".into());
411
- }
412
- session.runtime_config = Some(config.clone());
413
- } else {
414
- let runtime = super::runtime::ManagedBrowser::launch(config).await?;
415
- let mut run_config = config.clone();
416
- run_config
417
- .extra
418
- .insert("browserDebugUrl".into(), json!(runtime.endpoint));
419
- run_config
420
- .extra
421
- .insert("__managedBrowser".into(), json!(true));
422
- session.runtime_config = Some(run_config);
423
- session.runtime = Some(runtime);
424
- }
257
+ let runtime = super::runtime::ManagedBrowser::launch(config).await?;
258
+ let mut run_config = config.clone();
259
+ run_config
260
+ .extra
261
+ .insert("browserDebugUrl".into(), json!(runtime.endpoint));
262
+ run_config
263
+ .extra
264
+ .insert("__managedBrowser".into(), json!(true));
265
+ session.runtime_config = Some(run_config);
266
+ session.runtime = Some(runtime);
425
267
  }
426
268
  let run_config = session.runtime_config.as_ref().unwrap().clone();
427
269
  let mut result = execute_owned(&run_config, action, args, &mut session).await;
@@ -432,28 +274,10 @@ pub fn execute_action<'a>(
432
274
  report.insert("runtime".into(), runtime.metadata());
433
275
  *text = Value::Object(report).to_string();
434
276
  }
435
- let report = result
277
+ let _report = result
436
278
  .as_ref()
437
279
  .ok()
438
280
  .and_then(|r| serde_json::from_str::<Value>(r).ok());
439
- log_action(
440
- "SESSION_ACTION",
441
- &format!(
442
- "session={} tab={} action={} durationMs={} status={} verification={}",
443
- session.id,
444
- session.tab.as_deref().unwrap_or("none"),
445
- action,
446
- started.elapsed().as_millis(),
447
- report
448
- .as_ref()
449
- .and_then(|r| r["status"].as_str())
450
- .unwrap_or(if result.is_ok() { "returned" } else { "failed" }),
451
- report
452
- .as_ref()
453
- .and_then(|r| r["verification"].as_str())
454
- .unwrap_or("unverified")
455
- ),
456
- );
457
281
  result
458
282
  })
459
283
  }
@@ -464,29 +288,16 @@ async fn execute_owned(
464
288
  args: &Value,
465
289
  session: &mut BrowserSession,
466
290
  ) -> Result<String, String> {
467
- session.used = true;
468
- let tab_operation = args["operation"].as_str().unwrap_or("list");
469
- if session.blocked
470
- && (modifying(action)
471
- || (action == "browser_tabs" && matches!(tab_operation, "open" | "close")))
472
- {
473
- return Err(
474
- "browser_blocked: three attempts without progress; ask the user for takeover".into(),
475
- );
476
- }
477
- if action == "browser_open" {
291
+ let action = super::workflow::normalize_action(action);
292
+ if action == "open" {
478
293
  super::navigate::validate_url(args["url"].as_str().unwrap_or("").trim())?;
479
294
  }
480
- if !super::lifecycle::is_browser_running(config).await {
481
- super::lifecycle::spawn_automation_browser(config).await?;
482
- }
483
- if session.tab.is_none() && action != "browser_tabs" {
295
+ if session.tab.is_none() && action != "tabs" {
484
296
  let page = tab_request(config, "new?about:blank").await?;
485
297
  let tab = page["id"]
486
298
  .as_str()
487
299
  .ok_or("Could not create task tab")?
488
300
  .to_owned();
489
- session.leases.insert(tab.clone(), lease(config, &tab)?);
490
301
  session.owned_tabs.insert(tab.clone());
491
302
  session.tab = Some(tab);
492
303
  if config.extra.get("__managedBrowser") == Some(&json!(true)) {
@@ -495,7 +306,7 @@ async fn execute_owned(
495
306
  .await?;
496
307
  }
497
308
  }
498
- if action == "browser_tabs" {
309
+ if action == "tabs" {
499
310
  return tabs(config, args, session).await;
500
311
  }
501
312
  let tab = session.tab.clone().ok_or("tab_unavailable")?;
@@ -508,7 +319,7 @@ async fn execute_owned(
508
319
  }
509
320
  TARGET
510
321
  .scope(tab.clone(), async {
511
- if action == "browser_screenshot" {
322
+ if action == "screenshot" {
512
323
  let mode=args["mode"].as_str().filter(|s|!s.is_empty()).unwrap_or("viewport");
513
324
  let data=if mode=="viewport" {
514
325
  let (data,evidence)=super::evidence::capture(config,&tab).await?;
@@ -527,26 +338,17 @@ async fn execute_owned(
527
338
  session.capture_metadata=json!({"url":url,"capturedAt":chrono::Utc::now().to_rfc3339(),"sessionId":session.id,"tabId":tab,"mode":mode});
528
339
  return Ok(format!("data:image/png;base64,{data}"));
529
340
  }
530
- if action == "browser_read" {
341
+ if action == "read" {
531
342
  return super::read_page_text(config).await;
532
343
  }
533
- if action == "browser_observe" {
344
+ if action == "observe" {
534
345
  let observation = observe_retry(config, &tab, args).await?;
535
346
  session.observation = Some(observation.clone());
536
347
  session.last_known = Some(observation.clone());
537
- // Explicit takeover confirmation is the only way to clear a recovery stop.
538
- if args["takeoverComplete"] == true {
539
- session.blocked = false;
540
- session.attempts.clear();
541
- session.preceding_attempt = None;
542
- }
543
348
  return serde_json::to_string(&observation).map_err(|e| e.to_string());
544
349
  }
545
- if action == "browser_wait" {
350
+ if action == "wait" {
546
351
  wait(config, args, session).await?;
547
- if let Some(key) = session.preceding_attempt.take() {
548
- session.attempts.remove(&key);
549
- }
550
352
  let observation = observe_retry(config, &tab, &json!({})).await?;
551
353
  session.observation = Some(observation.clone());
552
354
  session.last_known = Some(observation.clone());
@@ -562,8 +364,8 @@ async fn execute_owned(
562
364
  }
563
365
  let mut selector = if matches!(
564
366
  action,
565
- "browser_click" | "browser_type" | "browser_fill" | "browser_select"
566
- ) || (action == "browser_scroll"
367
+ "click" | "type" | "fill" | "select"
368
+ ) || (action == "scroll"
567
369
  && (args["elementRef"].as_str().is_some_and(|s| !s.is_empty())
568
370
  || args["selector"].as_str().is_some_and(|s| !s.is_empty())))
569
371
  {
@@ -571,20 +373,20 @@ async fn execute_owned(
571
373
  } else {
572
374
  None
573
375
  };
574
- if matches!(action, "browser_mouse_click" | "browser_mouse_move") {
376
+ if matches!(action, "mouse_click" | "mouse_move") {
575
377
  let evidence = session
576
378
  .screenshot
577
379
  .as_ref()
578
380
  .ok_or("screenshot_required: capture this tab before coordinate input")?;
579
381
  super::evidence::validate(config, evidence, &tab, args).await?;
580
382
  }
581
- if matches!(action, "browser_mouse_click" | "browser_mouse_move") {
383
+ if matches!(action, "mouse_click" | "mouse_move") {
582
384
  selector = Some(format!(
583
385
  "mint-point={},{}",
584
386
  args["x"].as_f64().ok_or("x required")?,
585
387
  args["y"].as_f64().ok_or("y required")?
586
388
  ));
587
- } else if action == "browser_key_press" {
389
+ } else if action == "key_press" {
588
390
  selector = Some("mint-focus=".into());
589
391
  }
590
392
  let relevant_before = target_state(config, selector.as_deref()).await?;
@@ -593,16 +395,6 @@ async fn execute_owned(
593
395
  let before_document =
594
396
  evaluate(config, "globalThis.__mintNodes?.documentId".into()).await?;
595
397
  let before_tabs = super::list_tabs(config).await?;
596
- let key = ActionAttemptKey {
597
- tab: tab.clone(),
598
- action: action.into(),
599
- target: relevant_before["id"]
600
- .as_str()
601
- .unwrap_or(selector.as_deref().unwrap_or("viewport"))
602
- .into(),
603
- arguments: attempt_arguments(action, args),
604
- };
605
- session.preceding_attempt = Some(key.clone());
606
398
  let result = if matches!(action, "browser_mouse_click" | "browser_mouse_move") {
607
399
  super::evidence::input(
608
400
  config,
@@ -653,20 +445,13 @@ async fn execute_owned(
653
445
  || (!relevant_before.is_null()
654
446
  && relevant_before["id"] == relevant_after["id"]
655
447
  && relevant_before != relevant_after)
656
- || (action == "browser_scroll"
448
+ || (action == "scroll"
657
449
  && (viewport_changed
658
450
  || result.as_ref().is_ok_and(|v| v == "scroll_verified")));
659
451
  if progress {
660
452
  changes.push(
661
453
  "Action outcome changed; inspect observation for task completion".into(),
662
454
  );
663
- session.attempts.remove(&key);
664
- } else {
665
- let attempts = session.attempts.entry(key).or_default();
666
- *attempts += 1;
667
- if *attempts >= 3 {
668
- session.blocked = true;
669
- }
670
455
  }
671
456
  if result.as_ref().is_ok_and(|value| {
672
457
  matches!(value.as_str(), "value_verified" | "scroll_verified")
@@ -677,16 +462,9 @@ async fn execute_owned(
677
462
  session.last_known = Some(after.clone());
678
463
  } else {
679
464
  session.observation = None;
680
- let attempts = session.attempts.entry(key).or_default();
681
- *attempts += 1;
682
- if *attempts >= 3 {
683
- session.blocked = true;
684
- }
685
465
  }
686
466
  let error = result.err().or_else(|| {
687
- if session.blocked {
688
- Some("browser_blocked: no progress after three attempts".into())
689
- } else if observation.is_none() {
467
+ if observation.is_none() {
690
468
  Some("observation_unavailable: action outcome is ambiguous".into())
691
469
  } else {
692
470
  None
@@ -750,7 +528,6 @@ async fn tabs(
750
528
  }
751
529
  let tab = tab_request(config, "new?about:blank").await?;
752
530
  let id = tab["id"].as_str().ok_or("Missing tab ID")?.to_owned();
753
- session.leases.insert(id.clone(), lease(config, &id)?);
754
531
  session.owned_tabs.insert(id.clone());
755
532
  session.tab = Some(id.clone());
756
533
  if config.extra.get("__managedBrowser") == Some(&json!(true)) {
@@ -758,13 +535,11 @@ async fn tabs(
758
535
  }
759
536
  session.observation = None;
760
537
  session.screenshot = None;
761
- session.preceding_attempt = None;
762
538
  if !url.is_empty() {
763
539
  TARGET.scope(id, super::navigate(config, url)).await?;
764
540
  }
765
541
  session.observation = None;
766
542
  session.screenshot = None;
767
- session.preceding_attempt = None;
768
543
  }
769
544
  "select" | "close" => {
770
545
  let id = args["tabId"]
@@ -774,12 +549,8 @@ async fn tabs(
774
549
  if !super::list_tabs(config).await?.iter().any(|t| t.id == id) {
775
550
  return Err("tab_unavailable".into());
776
551
  }
777
- if !session.leases.contains_key(id) {
778
- session.leases.insert(id.to_owned(), lease(config, id)?);
779
- }
780
552
  if operation == "close" {
781
553
  tab_request(config, &format!("close/{id}")).await?;
782
- session.leases.remove(id);
783
554
  session.owned_tabs.remove(id);
784
555
  // Retain the closed ID: later calls must fail instead of choosing a new tab.
785
556
  } else {
@@ -787,7 +558,6 @@ async fn tabs(
787
558
  }
788
559
  session.observation = None;
789
560
  session.screenshot = None;
790
- session.preceding_attempt = None;
791
561
  }
792
562
  _ => return Err("Unknown tab operation".into()),
793
563
  }
@@ -800,17 +570,16 @@ async fn perform(
800
570
  args: &Value,
801
571
  selector: Option<&str>,
802
572
  ) -> Result<String, String> {
573
+ let action = super::workflow::normalize_action(action);
803
574
  let text = args["text"].as_str().unwrap_or("");
804
575
  match action {
805
- "browser_open" => {
806
- super::navigate(config, args["url"].as_str().ok_or("url required")?.trim()).await
807
- }
808
- "browser_click" => super::click(config, selector.ok_or("target required")?).await,
809
- "browser_type" => super::type_text(config, selector.ok_or("target required")?, text).await,
810
- "browser_fill" => {
576
+ "open" => super::navigate(config, args["url"].as_str().ok_or("url required")?.trim()).await,
577
+ "click" => super::click(config, selector.ok_or("target required")?).await,
578
+ "type" => super::type_text(config, selector.ok_or("target required")?, text).await,
579
+ "fill" => {
811
580
  super::interact::edit_text(config, selector.ok_or("target required")?, text, true).await
812
581
  }
813
- "browser_select" => {
582
+ "select" => {
814
583
  let selector = selector.ok_or("target required")?;
815
584
  super::get_element_coordinates(config, selector).await?;
816
585
  let value = args["value"].as_str().ok_or("value required")?;
@@ -821,7 +590,7 @@ async fn perform(
821
590
  }
822
591
  Ok("value_verified".into())
823
592
  }
824
- "browser_scroll" => {
593
+ "scroll" => {
825
594
  let x = args["x"].as_f64().unwrap_or(0.0);
826
595
  let y = args["y"].as_f64().unwrap_or(600.0);
827
596
  let expression = if let Some(selector) = selector {
@@ -842,18 +611,16 @@ async fn perform(
842
611
  }
843
612
  .into())
844
613
  }
845
- "browser_mouse_move" | "browser_mouse_click" => {
614
+ "mouse_move" | "mouse_click" => {
846
615
  let x = args["x"].as_f64().ok_or("x required")?;
847
616
  let y = args["y"].as_f64().ok_or("y required")?;
848
- if action == "browser_mouse_move" {
617
+ if action == "mouse_move" {
849
618
  super::mouse_move(config, x, y).await
850
619
  } else {
851
620
  super::mouse_click(config, x, y, args["button"].as_str().unwrap_or("left")).await
852
621
  }
853
622
  }
854
- "browser_key_press" => {
855
- super::key_press(config, args["key"].as_str().ok_or("key required")?).await
856
- }
623
+ "key_press" => super::key_press(config, args["key"].as_str().ok_or("key required")?).await,
857
624
  _ => Err(format!("Unsupported browser action: {action}")),
858
625
  }
859
626
  }
@@ -902,58 +669,11 @@ async fn wait(config: &MintConfig, args: &Value, session: &BrowserSession) -> Re
902
669
  mod tests {
903
670
  use super::*;
904
671
  use std::process::{Child, Command, Stdio};
672
+ use std::time::Instant;
905
673
  use tokio::io::{AsyncReadExt, AsyncWriteExt};
906
674
 
907
- #[tokio::test]
908
- async fn browser_session_prevents_duplicate_external_url_launches() {
909
- run_session(async {
910
- SESSION.with(|s| s.try_lock().unwrap().used = true);
911
- let config = MintConfig::default();
912
- for background in [false, true] {
913
- let mut requested_approval = false;
914
- let result = crate::orchestration::execute_tool_from_json(
915
- std::path::Path::new("/tmp"),
916
- &config,
917
- "run_shell",
918
- json!({"command":"xdg-open 'https://example.com'", "background":background}),
919
- "browser-test",
920
- &mut |_| {
921
- requested_approval = true;
922
- Ok(crate::orchestration::ApprovalOutcome::Denied)
923
- },
924
- )
925
- .await;
926
- assert!(result.unwrap_err().to_string().contains("browser_open"));
927
- assert!(
928
- !requested_approval,
929
- "duplicate launch must stop before approval or spawn"
930
- );
931
- }
932
- let result = crate::orchestration::execute_tool_from_json(
933
- std::path::Path::new("/tmp"),
934
- &config,
935
- "verify",
936
- json!({"commands":["xdg-open https://example.com"]}),
937
- "browser-test",
938
- &mut |_| panic!("verification must not launch another browser"),
939
- )
940
- .await;
941
- assert!(result.unwrap_err().to_string().contains("browser_open"));
942
-
943
- // Opening local documents still follows the normal approval path.
944
- let result = crate::orchestration::execute_tool_from_json(
945
- std::path::Path::new("/tmp"),
946
- &config,
947
- "run_shell",
948
- json!({"command":"xdg-open /tmp/report.pdf"}),
949
- "browser-test",
950
- &mut |_| Ok(crate::orchestration::ApprovalOutcome::Denied),
951
- )
952
- .await
953
- .unwrap();
954
- assert!(result.contains("User denied shell command"));
955
- })
956
- .await;
675
+ async fn is_endpoint_running(config: &MintConfig) -> bool {
676
+ super::super::cdp::fetch_pages(config).await.is_ok()
957
677
  }
958
678
 
959
679
  #[test]
@@ -987,16 +707,6 @@ mod tests {
987
707
  assert!(target(&json!({"selector":"mint-ref=a:1"}), &session).is_err());
988
708
  }
989
709
 
990
- #[test]
991
- fn tab_lease_is_exclusive_and_released_on_drop() {
992
- let config = MintConfig::default();
993
- let tab = uuid::Uuid::new_v4().to_string();
994
- let lock = lease(&config, &tab).unwrap();
995
- assert!(lease(&config, &tab).is_err());
996
- drop(lock);
997
- assert!(lease(&config, &tab).is_ok());
998
- }
999
-
1000
710
  #[test]
1001
711
  fn cdp_errors_are_not_successful_actions() {
1002
712
  assert!(
@@ -1014,38 +724,11 @@ mod tests {
1014
724
  assert!(!expression.contains("=== `"));
1015
725
  }
1016
726
 
1017
- #[tokio::test]
1018
- async fn headed_mode_does_not_hijack_running_port_9222_browser_without_explicit_flag() {
1019
- let mut config = MintConfig::default();
1020
- config
1021
- .extra
1022
- .insert("browserAgentMode".into(), json!("headed"));
727
+ #[test]
728
+ fn session_uses_managed_dynamic_endpoint() {
729
+ let config = MintConfig::default();
1023
730
  let endpoint = endpoint(&config);
1024
- let parsed = reqwest::Url::parse(endpoint).unwrap();
1025
- let external = config.extra.get("browserUseExisting") == Some(&json!(true))
1026
- || !matches!(
1027
- parsed.host_str(),
1028
- Some("localhost" | "127.0.0.1" | "::1" | "[::1]")
1029
- )
1030
- || parsed.port_or_known_default() != Some(9222);
1031
- assert!(
1032
- !external,
1033
- "Headed mode without browserUseExisting must not attach to port 9222"
1034
- );
1035
-
1036
- config
1037
- .extra
1038
- .insert("browserUseExisting".into(), json!(true));
1039
- let external_with_flag = config.extra.get("browserUseExisting") == Some(&json!(true))
1040
- || !matches!(
1041
- parsed.host_str(),
1042
- Some("localhost" | "127.0.0.1" | "::1" | "[::1]")
1043
- )
1044
- || parsed.port_or_known_default() != Some(9222);
1045
- assert!(
1046
- external_with_flag,
1047
- "Explicit browserUseExisting should allow external attachment"
1048
- );
731
+ assert!(!endpoint.is_empty());
1049
732
  }
1050
733
 
1051
734
  struct BrowserFixture {
@@ -1127,7 +810,7 @@ mod tests {
1127
810
  profile,
1128
811
  };
1129
812
  for _ in 0..100 {
1130
- if super::super::is_browser_running(&fixture.config).await {
813
+ if is_endpoint_running(&fixture.config).await {
1131
814
  return fixture;
1132
815
  }
1133
816
  tokio::time::sleep(Duration::from_millis(100)).await;
@@ -1178,7 +861,7 @@ mod tests {
1178
861
  assert_eq!(viewport, (1440, 900));
1179
862
  assert!(full.1 > viewport.1);
1180
863
  assert!(element.1 < viewport.1);
1181
- assert!(super::super::is_browser_running(&fixture.config).await);
864
+ assert!(is_endpoint_running(&fixture.config).await);
1182
865
  assert!(!serde_json::to_string(&report).unwrap().contains("base64"));
1183
866
  let _ = std::fs::remove_dir_all(output);
1184
867
  }
@@ -1415,32 +1098,6 @@ mod tests {
1415
1098
  json!({"operation":"select","tabId":tab}),
1416
1099
  )
1417
1100
  .await;
1418
- // Another session cannot steal a tab, even though its own tab is independent.
1419
- run_session(async {
1420
- assert!(
1421
- execute_action(
1422
- config,
1423
- "browser_tabs",
1424
- &json!({"operation":"select","tabId":tab})
1425
- )
1426
- .await
1427
- .unwrap_err()
1428
- .contains("tab_busy")
1429
- );
1430
- let independent = call(config, "browser_observe", json!({})).await;
1431
- assert_ne!(independent["tabId"], tab);
1432
- })
1433
- .await;
1434
- for _ in 0..3 {
1435
- call(config, "browser_click", json!({"selector":"#noop"})).await;
1436
- }
1437
- assert!(
1438
- execute_action(config, "browser_click", &json!({"selector":"#save"}))
1439
- .await
1440
- .unwrap_err()
1441
- .contains("browser_blocked")
1442
- );
1443
- call(config, "browser_observe", json!({"takeoverComplete":true})).await;
1444
1101
  call(
1445
1102
  config,
1446
1103
  "browser_fill",
@@ -1522,16 +1179,6 @@ mod tests {
1522
1179
  call(config,"browser_click",json!({"selector":"#save"})).await;
1523
1180
  assert!(execute_action(config,"browser_wait",&json!({"condition":"text","value":"Never confirmed","timeoutMs":20})).await.is_err());
1524
1181
  TARGET.scope(tab.clone(),async {assert_eq!(evaluate(config,"window.submissions".into()).await.unwrap(),1);}).await;
1525
- // Re-observing and changing reference IDs must not evade the recovery limit.
1526
- for _ in 0..3 {
1527
- let observed = call(config,"browser_observe",json!({})).await;
1528
- let reference = element(&observed,"No operation");
1529
- call(config,"browser_click",json!({"elementRef":reference})).await;
1530
- }
1531
- assert!(execute_action(config,"browser_fill",&json!({"selector":"#name","text":"blocked"})).await.err().is_some_and(|e|e.contains("browser_blocked")));
1532
- TARGET.scope(tab.clone(),async {evaluate(config,"document.querySelector('#name').value='User takeover';true".into()).await.unwrap();}).await;
1533
- let takeover = call(config,"browser_observe",json!({"takeoverComplete":true})).await;
1534
- assert!(takeover["elements"].as_array().unwrap().iter().any(|e|e["value"] == "User takeover"));
1535
1182
  let failed_navigation = call(config,"browser_open",json!({"url":"http://127.0.0.1:1/"})).await;
1536
1183
  assert_eq!(failed_navigation["status"],"failed_or_ambiguous");
1537
1184
  assert!(failed_navigation["error"].as_str().unwrap().contains("navigation_failed"));
@@ -1568,7 +1215,7 @@ mod tests {
1568
1215
  assert_eq!(before,call(config,"browser_tabs",json!({"operation":"list"})).await);
1569
1216
  }
1570
1217
  call(config,"browser_tabs",json!({"operation":"open","url":fixture.url})).await;
1571
- let current=call(config,"browser_observe",json!({})).await["tabId"].as_str().unwrap().to_owned();
1218
+ let _current = call(config,"browser_observe",json!({})).await["tabId"].as_str().unwrap().to_owned();
1572
1219
  for _ in 0..4 {
1573
1220
  let result=call(config,"browser_open",json!({"url":fixture.url})).await;
1574
1221
  assert!(result["error"].is_null(),"{result}");
@@ -1593,16 +1240,6 @@ mod tests {
1593
1240
  assert_eq!(result["error"].is_null(),i<2,"{result}");
1594
1241
  agent_execute(config,"browser_read",json!({})).await.unwrap();
1595
1242
  }
1596
- let tabs_before=call(config,"browser_tabs",json!({"operation":"list"})).await;
1597
- for (action,args) in [("browser_tabs",json!({"operation":"open","url":fixture.url})),("browser_tabs",json!({"operation":"close","tabId":current})),("browser_open",json!({"url":fixture.url}))] {
1598
- assert!(agent_execute(config,action,args).await.unwrap_err().contains("browser_blocked"));
1599
- assert_eq!(tabs_before,call(config,"browser_tabs",json!({"operation":"list"})).await);
1600
- }
1601
- let other=before["selectedTabId"].as_str().unwrap();
1602
- call(config,"browser_tabs",json!({"operation":"select","tabId":other})).await;
1603
- assert!(agent_execute(config,"browser_open",json!({"url":fixture.url})).await.unwrap_err().contains("browser_blocked"));
1604
- call(config,"browser_tabs",json!({"operation":"select","tabId":current})).await;
1605
- call(config,"browser_observe",json!({"takeoverComplete":true})).await;
1606
1243
  // Click and key handlers can steal focus or replace the original node.
1607
1244
  for (action,handler,event,replace,text) in [
1608
1245
  ("browser_type","document.querySelector('#query').focus()","onclick",false,"unwanted"),
@@ -1624,7 +1261,6 @@ mod tests {
1624
1261
  let error=result["error"].as_str().unwrap_or_else(||panic!("Expected {event} focus rejection: {result}"));
1625
1262
  assert!(error.contains("focus_changed") || (replace && error.contains("stale_reference")),"{result}");
1626
1263
  assert_eq!(script(config,"[document.querySelector('#name').value,document.querySelector('#query').value]").await,json!(["old","other"]));
1627
- call(config,"browser_observe",json!({"takeoverComplete":true})).await;
1628
1264
  }
1629
1265
  script(config,"document.querySelector('#shadow').shadowRoot.innerHTML='<input aria-label=Shadow value=shadow>';true").await;
1630
1266
  let observed=call(config,"browser_observe",json!({})).await;
@@ -1678,58 +1314,6 @@ mod tests {
1678
1314
  }).await;
1679
1315
  }
1680
1316
 
1681
- #[tokio::test]
1682
- #[ignore = "isolated Chrome; actual Gemini Live task runner and agent executor"]
1683
- async fn browser_reliability_live_session_keeps_state() {
1684
- let fixture = BrowserFixture::start().await;
1685
- let config = fixture.config.clone();
1686
- let url = fixture.url.clone();
1687
- // Use the production background runner, not a test-owned run_session.
1688
- let tab = crate::gemini_live::spawn_live_session(async move {
1689
- let opened = call(&config,"browser_open",json!({"url":url})).await;
1690
- let tab = opened["observation"]["tabId"].as_str().unwrap().to_owned();
1691
- let observed = call(&config,"browser_observe",json!({})).await;
1692
- assert_eq!(observed["tabId"],tab);
1693
- assert_eq!(observed["url"],format!("{url}/"));
1694
- let filled = call(&config,"browser_fill",json!({"elementRef":element(&observed,"Name"),"text":"Live session"})).await;
1695
- assert_eq!(filled["verification"],"verified");
1696
- assert_eq!(filled["observation"]["tabId"],tab);
1697
- let point = script(&config,"(() => {const el=document.querySelector('#noop');el.scrollIntoView({behavior:'instant'});const r=el.getBoundingClientRect();return [r.left+r.width/2,r.top+r.height/2]})()").await;
1698
- agent_execute(&config,"browser_screenshot",json!({})).await.unwrap();
1699
- // A separate observation/tool batch must retain screenshot evidence.
1700
- call(&config,"browser_observe",json!({})).await;
1701
- assert!(script(&config,"scrollY").await.as_f64().unwrap()>0.0);
1702
- let clicked = call(&config,"browser_mouse_click",json!({"x":point[0],"y":point[1]})).await;
1703
- assert!(clicked["error"].is_null(),"{clicked}");
1704
- call(&config,"browser_wait",json!({"condition":"visible","selector":"#noop"})).await;
1705
- for _ in 0..3 {
1706
- call(&config,"browser_click",json!({"selector":"#noop"})).await;
1707
- }
1708
- assert!(agent_execute(&config,"browser_tabs",json!({"operation":"open"})).await.unwrap_err().contains("browser_blocked"));
1709
- assert!(agent_execute(&config,"browser_fill",json!({"selector":"#name","text":"blocked"})).await.unwrap_err().contains("browser_blocked"));
1710
- assert_eq!(call(&config,"browser_observe",json!({})).await["tabId"],tab);
1711
- tab
1712
- }).await.unwrap();
1713
- // Session shutdown releases its leases even when it ends blocked.
1714
- drop(lease(&fixture.config, &tab).unwrap());
1715
- let config = fixture.config.clone();
1716
- let url = fixture.url.clone();
1717
- let next_tab = crate::gemini_live::spawn_live_session(async move {
1718
- let opened = call(&config, "browser_open", json!({"url":url})).await;
1719
- assert!(opened["error"].is_null(), "{opened}");
1720
- let next_tab = opened["observation"]["tabId"].as_str().unwrap().to_owned();
1721
- assert_eq!(
1722
- call(&config, "browser_observe", json!({})).await["tabId"],
1723
- next_tab
1724
- );
1725
- next_tab
1726
- })
1727
- .await
1728
- .unwrap();
1729
- assert_ne!(tab, next_tab);
1730
- drop(lease(&fixture.config, &next_tab).unwrap());
1731
- }
1732
-
1733
1317
  #[tokio::test]
1734
1318
  #[ignore = "isolated Chrome; continuously changing unrelated DOM and layout"]
1735
1319
  async fn browser_reliability_continuous_ticker_allows_click() {