@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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +2 -2
- package/README.md +8 -2
- package/Release_Note.md +222 -71
- package/crates/mint-cli/src/agent/diff_render.rs +66 -44
- package/crates/mint-cli/src/agent/live_status.rs +21 -4
- package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
- package/crates/mint-cli/src/agent.rs +25 -10
- package/crates/mint-cli/src/commands/agent.rs +4 -92
- package/crates/mint-cli/src/commands/browser.rs +2 -15
- package/crates/mint-cli/src/commands/integrations.rs +3 -3
- package/crates/mint-cli/src/commands/mod.rs +0 -3
- package/crates/mint-cli/src/commands/tailnet_web.rs +1 -0
- package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
- package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
- package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
- package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
- package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
- package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
- package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
- package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
- package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
- package/crates/mint-cli/src/interactive/command_ui.rs +53 -5
- package/crates/mint-cli/src/interactive/composer.rs +186 -0
- package/crates/mint-cli/src/interactive/format.rs +78 -3
- package/crates/mint-cli/src/interactive/input_box.rs +61 -1
- package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
- package/crates/mint-cli/src/interactive/slash_commands.rs +648 -371
- package/crates/mint-cli/src/interactive/tool_activity.rs +93 -20
- package/crates/mint-cli/src/interactive.rs +37 -0
- package/crates/mint-cli/src/main.rs +6 -3
- package/crates/mint-cli/src/markdown.rs +2 -37
- package/crates/mint-cli/src/onboard.rs +700 -282
- package/crates/mint-cli/src/setup.rs +1342 -624
- package/crates/mint-cli/src/terminal_theme.rs +159 -35
- package/crates/mint-cli/tests/library_intake.rs +1 -0
- package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
- package/crates/mint-core/src/agent/chat/custom.rs +149 -0
- package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
- package/crates/mint-core/src/agent/chat/mod.rs +857 -0
- package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
- package/crates/mint-core/src/agent/chat/openai.rs +582 -0
- package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
- package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
- package/crates/mint-core/src/agent/memory.rs +56 -2
- package/crates/mint-core/src/agent/tasks.rs +2 -2
- package/crates/mint-core/src/api_server/routes/chat.rs +24 -2
- package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
- package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
- package/crates/mint-core/src/api_server/routes/sessions.rs +21 -0
- package/crates/mint-core/src/avatar_bridge.rs +0 -22
- package/crates/mint-core/src/browser/cdp.rs +10 -4
- package/crates/mint-core/src/browser/headless.rs +582 -0
- package/crates/mint-core/src/browser/input.rs +7 -62
- package/crates/mint-core/src/browser/interact.rs +0 -14
- package/crates/mint-core/src/browser/mod.rs +8 -29
- package/crates/mint-core/src/browser/navigate.rs +2 -36
- package/crates/mint-core/src/browser/runtime.rs +2 -4
- package/crates/mint-core/src/browser/session.rs +50 -466
- package/crates/mint-core/src/browser/workflow.rs +142 -32
- package/crates/mint-core/src/cron/store.rs +2 -2
- package/crates/mint-core/src/integrations/mcp.rs +6 -8
- package/crates/mint-core/src/lib.rs +0 -5
- package/crates/mint-core/src/media/gemini_live.rs +636 -86
- package/crates/mint-core/src/media/library/migration.rs +26 -0
- package/crates/mint-core/src/media/library/tests.rs +1 -1
- package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
- package/crates/mint-core/src/media/pictures.rs +1 -1
- package/crates/mint-core/src/media/tool_images.rs +92 -1
- package/crates/mint-core/src/mods/manager.rs +101 -35
- package/crates/mint-core/src/orchestration/context_render.rs +441 -24
- package/crates/mint-core/src/orchestration/decision_parsing.rs +531 -16
- package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
- package/crates/mint-core/src/orchestration/loop_guard.rs +145 -0
- package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
- package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
- package/crates/mint-core/src/orchestration/mod.rs +320 -4249
- package/crates/mint-core/src/orchestration/reliability.rs +67 -27
- package/crates/mint-core/src/orchestration/review_regressions.rs +5 -0
- package/crates/mint-core/src/orchestration/tests.rs +1665 -0
- package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
- package/crates/mint-core/src/orchestration/tools/files.rs +1 -0
- package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
- package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
- package/crates/mint-core/src/orchestration/tools/shell.rs +129 -58
- package/crates/mint-core/src/orchestration/truncation.rs +170 -13
- package/crates/mint-core/src/orchestration/types.rs +648 -0
- package/crates/mint-core/src/orchestration/verification.rs +0 -3
- package/crates/mint-core/src/orchestration/workspace_helpers.rs +24 -6
- package/crates/mint-core/src/prompts/agent.rs +52 -76
- package/crates/mint-core/src/prompts/tool_catalog.rs +8 -151
- package/crates/mint-core/src/search/files.rs +20 -0
- package/crates/mint-core/src/slash/mod.rs +228 -265
- package/crates/mint-core/src/slash/models.rs +6 -1
- package/crates/mint-core/src/system/bg_shell.rs +1 -0
- package/crates/mint-core/src/system/config.rs +176 -34
- package/crates/mint-mod-runtime/src/lib.rs +37 -23
- package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
- package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
- package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
- package/out/web/assets/{CliSessionsView-MI8kXMAb.js → CliSessionsView-z5i6qGyb.js} +1 -1
- package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
- package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
- package/out/web/assets/{CommandPalette-BD8SlTE5.js → CommandPalette-DaKlzbKs.js} +1 -1
- package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
- package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
- package/out/web/assets/{ImageStudioPanel-BP7N4rLr.js → ImageStudioPanel-B5N7TjeB.js} +1 -1
- package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
- package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
- package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
- package/out/web/assets/{McpRegistryPicker-CoS83vtc.js → McpRegistryPicker-DnXPlzI7.js} +1 -1
- package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
- package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
- package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
- package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
- package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
- package/out/web/assets/{PluginsView-jBYmcKh4.js → PluginsView-DoTg9MVa.js} +1 -1
- package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
- package/out/web/assets/{ScheduledTasksView-5uw9-V9H.js → ScheduledTasksView-BD5DVPkZ.js} +1 -1
- package/out/web/assets/{SelectField-DXBQtrUK.js → SelectField-Cy6CghrU.js} +1 -1
- package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
- package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
- package/out/web/assets/{SkillsView-C-gp9J7m.js → SkillsView-CIeWwq5z.js} +1 -1
- package/out/web/assets/StockCard-DxUciKyp.js +1 -0
- package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
- package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
- package/out/web/assets/{UiFeatureCard-6BwxTOCe.js → UiFeatureCard-CdY0hGWy.js} +1 -1
- package/out/web/assets/{UiGridCard-DvZcLNxb.js → UiGridCard-BBK3I5hC.js} +1 -1
- package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
- package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css → VeoStudioPanel-9kU9YLEN.css} +1 -1
- package/out/web/assets/{VeoStudioPanel-DfsOJKv0.js → VeoStudioPanel-Co6-wEWM.js} +2 -2
- package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
- package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
- package/out/web/assets/{abnfDiagram-VCTEODGH-BOo3Ckng.js → abnfDiagram-VCTEODGH-CMfR95N2.js} +1 -1
- package/out/web/assets/{architecture-7GRP2DOG-ZOwheW3n.js → architecture-7GRP2DOG-DhAM4HyO.js} +1 -1
- package/out/web/assets/{architectureDiagram-5GKGNRK7-DC2FDzVd.js → architectureDiagram-5GKGNRK7-Yk3TUJ_F.js} +1 -1
- package/out/web/assets/{blockDiagram-I7D4REHJ-BrKFWdAH.js → blockDiagram-I7D4REHJ-X9fBgJik.js} +1 -1
- package/out/web/assets/{c4Diagram-7LVT6UL2-BHTHK1Yd.js → c4Diagram-7LVT6UL2-Bk9x7N-8.js} +1 -1
- package/out/web/assets/{chunk-4HAMMTFA-BYFkZwNj.js → chunk-4HAMMTFA-Dx5fk1_0.js} +1 -1
- package/out/web/assets/{chunk-75Z2AOVW-BSKfSKSA.js → chunk-75Z2AOVW-BszTQoM4.js} +1 -1
- package/out/web/assets/{chunk-DU6HZSFF-vIhsiW6n.js → chunk-DU6HZSFF-DK-As3rk.js} +1 -1
- package/out/web/assets/{chunk-F27PBJKO-BlYpW9xq.js → chunk-F27PBJKO-LmncZZIS.js} +1 -1
- package/out/web/assets/{chunk-GMAD6QVW-BIBz_Ug5.js → chunk-GMAD6QVW-DSQ_sDRJ.js} +1 -1
- package/out/web/assets/{chunk-GVQU2GXP-NPSHxeqC.js → chunk-GVQU2GXP-Cno3ulje.js} +1 -1
- package/out/web/assets/{chunk-IMKFNOWR-YlJK8_Fp.js → chunk-IMKFNOWR-bIzfY9Jc.js} +1 -1
- package/out/web/assets/{chunk-L3NEJ4N5-DMYi4-af.js → chunk-L3NEJ4N5-Dl6eOa0D.js} +1 -1
- package/out/web/assets/{chunk-OSK3NFVY-Begd3-jA.js → chunk-OSK3NFVY-BaUGJ-cM.js} +1 -1
- package/out/web/assets/{chunk-P2QGCYS3-D1I4xFHN.js → chunk-P2QGCYS3-_066ndhH.js} +1 -1
- package/out/web/assets/{chunk-POPQ4Y6H-CafZZzNE.js → chunk-POPQ4Y6H-YT1AKhTv.js} +1 -1
- package/out/web/assets/{chunk-PWAF6VOD-Dwh6_Lnt.js → chunk-PWAF6VOD-CrEgDsxh.js} +1 -1
- package/out/web/assets/{chunk-SHT3W25Y-B0XrCSEb.js → chunk-SHT3W25Y-_KwaRLOl.js} +1 -1
- package/out/web/assets/{chunk-SVP7TREG-CxLsddxC.js → chunk-SVP7TREG-BQ1qb32L.js} +1 -1
- package/out/web/assets/{chunk-TICWLB2K-y156QInb.js → chunk-TICWLB2K-BDyehCtg.js} +1 -1
- package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
- package/out/web/assets/{config-DqXJXeZM.css → config-2suVFPZg.css} +1 -1
- package/out/web/assets/config-CkRCdkxn.js +2 -0
- package/out/web/assets/{cynefin-OW5HDTMX-SMVvrLf-.js → cynefin-OW5HDTMX-Dl_31aaN.js} +1 -1
- package/out/web/assets/{cynefinDiagram-5FMLGOSQ-rZznN24T.js → cynefinDiagram-5FMLGOSQ-Bpir8jha.js} +1 -1
- package/out/web/assets/{dagre-GXQ25YYZ-BOToydFu.js → dagre-GXQ25YYZ-D9xu3nsq.js} +1 -1
- package/out/web/assets/{diagram-S7CK7UJ4-TMVaGQEJ.js → diagram-S7CK7UJ4-DeveoEeB.js} +1 -1
- package/out/web/assets/{diagram-UQ7AKVKN-C2JK-GP2.js → diagram-UQ7AKVKN-p4xWRESV.js} +1 -1
- package/out/web/assets/{diagram-VSXAHHWV-Cw8VSi2K.js → diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
- package/out/web/assets/{diagram-VX7I27RA-DXp4fKk9.js → diagram-VX7I27RA-DRiCa3Vd.js} +1 -1
- package/out/web/assets/{diagram-Z3DM3KII-CzSicYiW.js → diagram-Z3DM3KII-Bt7zduQX.js} +1 -1
- package/out/web/assets/{ebnfDiagram-PWID7BFC-Dqj-zqPu.js → ebnfDiagram-PWID7BFC-DV9WpwdR.js} +1 -1
- package/out/web/assets/{erDiagram-RLTQ6QDP-DZwFZAuD.js → erDiagram-RLTQ6QDP-BJ_nwG2m.js} +1 -1
- package/out/web/assets/{eventmodeling-NTZA5JFV-C0O-Lvgp.js → eventmodeling-NTZA5JFV-BTBMrfVa.js} +1 -1
- package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
- package/out/web/assets/{ganttDiagram-EL5Y4UJY-Da4ULXTl.js → ganttDiagram-EL5Y4UJY-DQLdsXrC.js} +1 -1
- package/out/web/assets/{gitGraph-4MIJSDKK-B9wNFZIP.js → gitGraph-4MIJSDKK-CL1OEOQ_.js} +1 -1
- package/out/web/assets/{gitGraphDiagram-WWUBYQGX-RjrhB0MP.js → gitGraphDiagram-WWUBYQGX-8cSi42H7.js} +1 -1
- package/out/web/assets/index-BocxnrDG.js +168 -0
- package/out/web/assets/index-yeh_r55a.css +1 -0
- package/out/web/assets/{info-A6RAGUB7-B3dfCpeY.js → info-A6RAGUB7-C1zsX5sc.js} +1 -1
- package/out/web/assets/{infoDiagram-27XIBGKW-QVWkLVx7.js → infoDiagram-27XIBGKW-DsRYtIyI.js} +1 -1
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-CLFNIRI9.js → ishikawaDiagram-5VMMS53U-B2crwIXj.js} +1 -1
- package/out/web/assets/{journeyDiagram-3NMN7TZE-fgOQadYF.js → journeyDiagram-3NMN7TZE-BTH-GyVR.js} +1 -1
- package/out/web/assets/{kanban-definition-UXKFOSKX-G3Q_9g9O.js → kanban-definition-UXKFOSKX-BeRfCnuA.js} +1 -1
- package/out/web/assets/{mermaid-parser.core-v3Jm7nco.js → mermaid-parser.core-BJwgCBBc.js} +3 -3
- package/out/web/assets/{mermaid.core-DTEjLUA1.js → mermaid.core-CsDDodN8.js} +4 -4
- package/out/web/assets/{mindmap-definition-YA3MSWOX-C_D42_fF.js → mindmap-definition-YA3MSWOX-B42efOQ1.js} +1 -1
- package/out/web/assets/{modelManager-BPLycQrN.js → modelManager-B3ZFMh8v.js} +2 -2
- package/out/web/assets/{oauthManager-BUK7ic7q.js → oauthManager-Ch5Du2fF.js} +1 -1
- package/out/web/assets/{packet-AYTQ26CC-B-R-SHlF.js → packet-AYTQ26CC-B6xUyE6r.js} +1 -1
- package/out/web/assets/{pegDiagram-XKGWAZYB-Da2q_giz.js → pegDiagram-XKGWAZYB-1LabVycL.js} +1 -1
- package/out/web/assets/{pie-WAS4IAKB-BC8F0N7-.js → pie-WAS4IAKB-DJwo-nf_.js} +1 -1
- package/out/web/assets/{pieDiagram-E7YTZNPT-Iy9REJNC.js → pieDiagram-E7YTZNPT-icmAqzy4.js} +1 -1
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-ztsiYw7h.js → quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +1 -1
- package/out/web/assets/{radar-RG4KPBEZ-B_1P8Z_z.js → radar-RG4KPBEZ-D7d-Xriw.js} +1 -1
- package/out/web/assets/{railroad-74A4TZTK-DuTqczSo.js → railroad-74A4TZTK-viuhdYbs.js} +1 -1
- package/out/web/assets/{railroad-abnf-HS5TGJTU-Df29WLmp.js → railroad-abnf-HS5TGJTU-DNXiCNea.js} +1 -1
- package/out/web/assets/{railroad-ebnf-LZEXJU2U-ePFMo-qp.js → railroad-ebnf-LZEXJU2U-nkSu1-0C.js} +1 -1
- package/out/web/assets/{railroad-peg-WCYAUIDC-DYXLW4_E.js → railroad-peg-WCYAUIDC-BhbMeQzz.js} +1 -1
- package/out/web/assets/{railroadDiagram-O6MQD6OU-LStnZf2S.js → railroadDiagram-O6MQD6OU-CLpG1jUX.js} +1 -1
- package/out/web/assets/{requirementDiagram-BXWQKSXE-DmzonMH-.js → requirementDiagram-BXWQKSXE-kRDrvWrc.js} +1 -1
- package/out/web/assets/{sankeyDiagram-P5KCCOFB-B8vjmUU1.js → sankeyDiagram-P5KCCOFB-DxAwmq4x.js} +1 -1
- package/out/web/assets/{sequenceDiagram-WJ2MYXX4-CTnplOAX.js → sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js} +1 -1
- package/out/web/assets/{stateDiagram-D77RDMKH-BFrIaLdI.js → stateDiagram-D77RDMKH-D5QWfOhp.js} +1 -1
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
- package/out/web/assets/{swimlanes-42K2YHIH-BXGngUNU.js → swimlanes-42K2YHIH-CRs5UyA-.js} +1 -1
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
- package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
- package/out/web/assets/{timeline-definition-24CTP7MA-CegH7n43.js → timeline-definition-24CTP7MA-Denqd9qb.js} +1 -1
- package/out/web/assets/{treeView-Q6P3EWNA-DTpJv2Tw.js → treeView-Q6P3EWNA-0my3Z3s4.js} +1 -1
- package/out/web/assets/{treemap-WGGIJYW6-i8M9iJU6.js → treemap-WGGIJYW6-B_EjkLYq.js} +1 -1
- package/out/web/assets/{vennDiagram-4TSXK5OY-Dpzo1S8U.js → vennDiagram-4TSXK5OY-DxVg3Ewr.js} +1 -1
- package/out/web/assets/{wardley-WFR3VGLG-BrNsgtfw.js → wardley-WFR3VGLG-CGD-Tj4p.js} +1 -1
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-lQ92LFE1.js → wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +1 -1
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-BaOaEoBd.js → xychartDiagram-S5SC5T6Z-BVM9bKis.js} +1 -1
- package/out/web/index-web.html +2 -2
- package/out/web/index.html +2 -2
- package/package.json +2 -2
- package/shared-modules/CODEOWNERS +3 -3
- package/shared-modules/SDL2/SDL2_mixer.json +27 -0
- package/shared-modules/appstream/README.md +13 -0
- package/shared-modules/appstream/appstream-compose.json +111 -0
- package/shared-modules/dbus-glib/dbus-glib.json +2 -2
- package/shared-modules/libportal/libportal.json +23 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
- package/shared-modules/linux-audio/fluidsynth2.json +2 -2
- package/shared-modules/linux-audio/libinstpatch.json +2 -2
- package/shared-modules/linux-audio/liblo-static.json +3 -0
- package/shared-modules/luajit/luajit.json +2 -2
- package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
- package/slash-commands.json +7 -2
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/renderer/shared/agentProgress.ts +51 -3
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -56
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +15 -1
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +25 -12
- package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
- package/src/renderer/shared/components/Settings/ThemeTab.tsx +106 -3
- package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +67 -17
- package/src/renderer/shared/components/activity/AgentActivityTable.tsx +376 -134
- package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
- package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
- package/src/renderer/shared/components/activity/McpToolResults.tsx +39 -2
- package/src/renderer/shared/components/activity/SubagentDagView.tsx +7 -7
- package/src/renderer/shared/components/activity/ViewedImages.tsx +41 -2
- package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +3 -3
- package/src/renderer/shared/components/app/CommandPalette.tsx +7 -7
- package/src/renderer/shared/components/app/DesktopTitlebar.tsx +2 -2
- package/src/renderer/shared/components/app/MintDashboard.tsx +27 -17
- package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +3 -3
- package/src/renderer/shared/components/cards/CalculationCard.tsx +3 -3
- package/src/renderer/shared/components/cards/ImageGenCard.tsx +2 -2
- package/src/renderer/shared/components/cards/ImageSearchCard.tsx +2 -2
- package/src/renderer/shared/components/cards/MermaidCard.tsx +1 -1
- package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
- package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
- package/src/renderer/shared/components/cards/StockCard.tsx +7 -7
- package/src/renderer/shared/components/cards/ToolResultCards.tsx +11 -3
- package/src/renderer/shared/components/cards/UiMockupWidget.tsx +4 -4
- package/src/renderer/shared/components/cards/WeatherCard.tsx +5 -5
- package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +11 -0
- package/src/renderer/shared/components/chat/ApprovalCard.tsx +21 -2
- package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
- package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +15 -1
- package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +40 -2
- package/src/renderer/shared/components/chat/ChatMessageItem.tsx +180 -34
- package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
- package/src/renderer/shared/components/chat/ChatPanel.test.tsx +22 -0
- package/src/renderer/shared/components/chat/ChatPanel.tsx +227 -53
- package/src/renderer/shared/components/chat/DiffReviewModal.tsx +6 -6
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +142 -1
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +45 -3
- package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +42 -14
- package/src/renderer/shared/components/chat/RewindModal.tsx +10 -10
- package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +14 -0
- package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
- package/src/renderer/shared/components/chat/SourcesBlock.tsx +236 -90
- package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +45 -1
- package/src/renderer/shared/components/chat/TurnFileChanges.tsx +202 -169
- package/src/renderer/shared/components/extensions/CliSessionsView.tsx +5 -4
- package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +3 -3
- package/src/renderer/shared/components/studio/LibraryPage.tsx +48 -5
- package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +125 -13
- package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
- package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +77 -16
- package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +78 -22
- package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +13 -0
- package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +48 -28
- package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +266 -47
- package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +18 -6
- package/src/renderer/shared/components/workspace/background-terminals.css +11 -11
- package/src/renderer/shared/constants/config.ts +1 -5
- package/src/renderer/shared/constants/models.test.ts +51 -0
- package/src/renderer/shared/constants/models.ts +139 -32
- package/src/renderer/shared/conversation/lifecycle.test.ts +91 -3
- package/src/renderer/shared/conversation/lifecycle.ts +104 -5
- package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
- package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
- package/src/renderer/shared/css/agent-observability.css +133 -0
- package/src/renderer/shared/css/chat-ui-cards.css +13 -13
- package/src/renderer/shared/css/code-review.css +63 -34
- package/src/renderer/shared/css/code-sessions.css +47 -18
- package/src/renderer/shared/css/collapsed-paste.css +1 -1
- package/src/renderer/shared/css/error-notice.css +1 -1
- package/src/renderer/shared/css/file-changes.css +165 -80
- package/src/renderer/shared/css/library.css +105 -10
- package/src/renderer/shared/css/management-views.css +1 -1
- package/src/renderer/shared/css/mcp-results.css +5 -5
- package/src/renderer/shared/css/model-selector.css +18 -18
- package/src/renderer/shared/css/mods.css +7 -7
- package/src/renderer/shared/css/notification-notice.css +2 -2
- package/src/renderer/shared/css/queued-messages.css +5 -5
- package/src/renderer/shared/css/settings/general.css +18 -13
- package/src/renderer/shared/css/settings/profile.css +5 -5
- package/src/renderer/shared/css/settings/theme.css +5 -5
- package/src/renderer/shared/css/slash-choice.css +2 -2
- package/src/renderer/shared/css/slash-suggestions.css +3 -3
- package/src/renderer/shared/css/sources-block.css +41 -2
- package/src/renderer/shared/css/tools-panel.css +2 -2
- package/src/renderer/shared/css/ui-tailwind.css +8 -0
- package/src/renderer/shared/css/veo-studio.css +2 -2
- package/src/renderer/shared/css/viewed-images.css +7 -7
- package/src/renderer/shared/platform.ts +2 -1
- package/src/renderer/shared/theme/themeManager.test.ts +17 -0
- package/src/renderer/shared/theme/themeManager.ts +21 -4
- package/src/renderer/shared/theme/themes.ts +54 -0
- package/src/renderer/shared/theme/tokens.css +6 -4
- package/src/renderer/shared/types.ts +1 -1
- package/src/renderer/shared/utils/agentActivity.test.ts +468 -2
- package/src/renderer/shared/utils/agentActivity.ts +602 -35
- package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
- package/src/renderer/shared/utils/artifactBrowser.ts +17 -19
- package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
- package/src/renderer/shared/utils/attachedDocument.ts +49 -0
- package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
- package/src/renderer/shared/utils/fileIcons.ts +149 -16
- package/src/renderer/shared/utils/markdown.tsx +72 -19
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +16 -0
- package/src/renderer/shared/utils/mcpResults.test.tsx +29 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
- package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
- package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
- package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
- package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
- package/src/renderer/shared/utils/workspaceDrag.ts +142 -0
- package/src/renderer/shared/utils/workspaceMedia.ts +10 -2
- package/src/renderer/src/App.tsx +14 -8
- package/src/renderer/src/agentProgress.ts +1 -0
- package/src/renderer/src/components/ScreenPicker.css +1 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +6 -5
- package/src/renderer/src/css/app-shell.css +4 -4
- package/src/renderer/src/css/chat.css +1261 -241
- package/src/renderer/src/css/desktop-tools.css +11 -11
- package/src/renderer/src/css/sidebar-workspace.css +2 -2
- package/src/renderer/src/css/tool-surfaces.css +12 -12
- package/src/renderer/src/index.css +38 -20
- package/src/renderer/src/tauri.ts +22 -2
- package/src/renderer/src-web/App.tsx +14 -0
- package/src/renderer/src-web/agentProgress.ts +1 -0
- package/src/renderer/src-web/css/app-shell.css +4 -4
- package/src/renderer/src-web/css/chat.css +1278 -190
- package/src/renderer/src-web/css/mobile.css +29 -13
- package/src/renderer/src-web/css/settings/mobile.css +2 -2
- package/src/renderer/src-web/css/sidebar-workspace.css +1 -1
- package/src/renderer/src-web/index.css +38 -20
- package/src/renderer/src-web/tauri.ts +22 -2
- package/src/renderer/tests/browserArtifacts.browser.tsx +1 -1
- package/src/renderer/tests/library.browser.tsx +3 -0
- package/src/renderer/tests/liveVoice.browser.html +1 -0
- package/src/renderer/tests/workspaceDrop.browser.tsx +1 -1
- package/src-tauri/src/app/lifecycle.rs +1 -14
- package/src-tauri/src/background/headless.rs +14 -8
- package/src-tauri/src/commands/conversation.rs +38 -0
- package/src-tauri/src/native/mod.rs +0 -1
- package/src-tauri/src/native/system.rs +4 -47
- package/crates/mint-cli/src/interactive/chat_tui.rs +0 -8855
- package/crates/mint-core/src/agent/chat.rs +0 -4394
- package/crates/mint-core/src/browser/lifecycle.rs +0 -291
- package/crates/mint-core/src/browser/logging.rs +0 -20
- package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
- package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +0 -2
- package/out/web/assets/CalculationCard-CRjP3E1j.js +0 -1
- package/out/web/assets/CliSessionsView-C3Br2v5Y.css +0 -1
- package/out/web/assets/CodeReviewPage-CUJceQme.css +0 -1
- package/out/web/assets/CodeReviewPage-VCUgPla_.js +0 -2
- package/out/web/assets/ImageGenCard-BCXxkVJX.js +0 -1
- package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +0 -1
- package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
- package/out/web/assets/LibraryPage-WlviRdR8.js +0 -2
- package/out/web/assets/LinkedFoldersView-CKRgroWK.js +0 -1
- package/out/web/assets/McpServersView-D3wHnuHg.js +0 -1
- package/out/web/assets/MermaidCard-NqrHdyJW.js +0 -2
- package/out/web/assets/MintDashboard-B2DJsDDO.css +0 -1
- package/out/web/assets/MintDashboard-COw_xol7.js +0 -63
- package/out/web/assets/RewindModal-DempuDod.js +0 -1
- package/out/web/assets/SettingsWindow-2YP3yj7s.js +0 -4
- package/out/web/assets/SettingsWindow-Dz8LQVgD.css +0 -1
- package/out/web/assets/StockCard-BDMCe7mx.js +0 -1
- package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +0 -38
- package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +0 -1
- package/out/web/assets/UiMockupWidget-DD73QWg1.js +0 -1
- package/out/web/assets/WeatherCard-CPucIfq8.js +0 -1
- package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +0 -2
- package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +0 -1
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +0 -1
- package/out/web/assets/config-CvIO5hkn.js +0 -2
- package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +0 -1
- package/out/web/assets/index-BmpulIkg.js +0 -168
- package/out/web/assets/index-CkvBwhX_.css +0 -1
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +0 -1
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +0 -8
- package/shared-modules/libsecret/libsecret.json +0 -23
- package/shared-modules/libusb/libusb.json +0 -27
- package/shared-modules/lzo/lzo.json +0 -27
- package/src/renderer/src/components/BrowserShell.tsx +0 -108
- package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
- package/src/renderer/src/components/browser-shell.css +0 -185
- 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
|
|
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
|
|
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
|
|
401
|
-
let
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
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
|
-
|
|
468
|
-
|
|
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
|
|
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 == "
|
|
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 == "
|
|
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 == "
|
|
341
|
+
if action == "read" {
|
|
531
342
|
return super::read_page_text(config).await;
|
|
532
343
|
}
|
|
533
|
-
if action == "
|
|
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 == "
|
|
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
|
-
"
|
|
566
|
-
) || (action == "
|
|
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, "
|
|
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, "
|
|
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 == "
|
|
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 == "
|
|
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
|
|
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
|
-
"
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 == "
|
|
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
|
-
"
|
|
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
|
-
|
|
908
|
-
|
|
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
|
-
#[
|
|
1018
|
-
|
|
1019
|
-
let
|
|
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
|
-
|
|
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
|
|
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!(
|
|
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
|
|
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() {
|