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