@pheem49/mint 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/Release_Note.md +71 -72
- package/apps/mint-companion/src-tauri/src/compact.rs +69 -0
- package/apps/mint-companion/src-tauri/src/main.rs +218 -11
- package/apps/mint-companion/vite.config.ts +2 -1
- package/crates/mint-cli/src/agent/live_status.rs +43 -2
- package/crates/mint-cli/src/agent.rs +178 -38
- package/crates/mint-cli/src/commands/agent.rs +225 -31
- package/crates/mint-cli/src/commands/browser.rs +127 -0
- package/crates/mint-cli/src/commands/config.rs +14 -1
- package/crates/mint-cli/src/commands/knowledge.rs +16 -2
- package/crates/mint-cli/src/commands/mod.rs +15 -1
- package/crates/mint-cli/src/commands/tailnet_web.rs +2 -7
- package/crates/mint-cli/src/companion_navigation.rs +268 -0
- package/crates/mint-cli/src/interactive/chat_tui.rs +1065 -63
- package/crates/mint-cli/src/interactive/command_ui.rs +39 -0
- package/crates/mint-cli/src/interactive/slash_commands.rs +114 -6
- package/crates/mint-cli/src/interactive/tool_activity.rs +375 -36
- package/crates/mint-cli/src/interactive.rs +37 -5
- package/crates/mint-cli/src/main.rs +23 -2
- package/crates/mint-cli/src/onboard.rs +3494 -1336
- package/crates/mint-cli/tests/mods_testing.rs +5 -1
- package/crates/mint-companion-protocol/src/lib.rs +113 -0
- package/crates/mint-core/src/agent/chat.rs +240 -1
- package/crates/mint-core/src/agent/memory.rs +73 -6
- package/crates/mint-core/src/agent/safety.rs +56 -4
- package/crates/mint-core/src/agent/skill_inventory.rs +3 -1
- package/crates/mint-core/src/agent/skills.rs +1 -0
- package/crates/mint-core/src/api_server/routes/auth.rs +1 -4
- package/crates/mint-core/src/api_server/routes/chat.rs +29 -15
- package/crates/mint-core/src/api_server/routes/misc.rs +116 -0
- package/crates/mint-core/src/api_server/routes/profile_oauth.rs +43 -26
- package/crates/mint-core/src/api_server/routes/sessions.rs +93 -76
- package/crates/mint-core/src/api_server/routes/skills_subagents.rs +11 -7
- package/crates/mint-core/src/api_server/routes/status_health.rs +18 -8
- package/crates/mint-core/src/api_server.rs +211 -65
- package/crates/mint-core/src/avatar_bridge.rs +5 -2
- package/crates/mint-core/src/browser/cdp.rs +82 -26
- package/crates/mint-core/src/browser/lifecycle.rs +143 -43
- package/crates/mint-core/src/browser/mod.rs +5 -4
- package/crates/mint-core/src/browser/navigate.rs +82 -9
- package/crates/mint-core/src/browser/runtime.rs +284 -0
- package/crates/mint-core/src/browser/session.rs +263 -7
- package/crates/mint-core/src/browser/workflow.rs +386 -0
- package/crates/mint-core/src/companion/navigation.rs +115 -0
- package/crates/mint-core/src/companion.rs +109 -1
- package/crates/mint-core/src/integrations/mcp_result.rs +45 -0
- package/crates/mint-core/src/lib.rs +10 -6
- package/crates/mint-core/src/media/image_proxy.rs +563 -0
- package/crates/mint-core/src/media/tool_images.rs +196 -0
- package/crates/mint-core/src/media/tts/playback.rs +609 -0
- package/crates/mint-core/src/media/tts.rs +1 -0
- package/crates/mint-core/src/media.rs +2 -0
- package/crates/mint-core/src/orchestration/context_render.rs +450 -33
- package/crates/mint-core/src/orchestration/decision_parsing.rs +5 -23
- package/crates/mint-core/src/orchestration/diagnostics.rs +307 -0
- package/crates/mint-core/src/orchestration/image_regressions.rs +379 -0
- package/crates/mint-core/src/orchestration/loop_guard.rs +334 -0
- package/crates/mint-core/src/orchestration/mod.rs +1104 -560
- package/crates/mint-core/src/orchestration/reliability.rs +541 -0
- package/crates/mint-core/src/orchestration/repeated_failures.rs +8 -0
- package/crates/mint-core/src/orchestration/review_regressions.rs +174 -28
- package/crates/mint-core/src/orchestration/tools/files.rs +31 -3
- package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +19 -4
- package/crates/mint-core/src/orchestration/tools/project.rs +1 -1
- package/crates/mint-core/src/orchestration/tools/shell.rs +29 -5
- package/crates/mint-core/src/orchestration/tools/web.rs +5 -1
- package/crates/mint-core/src/orchestration/trajectory_schema.rs +353 -0
- package/crates/mint-core/src/orchestration/truncation.rs +211 -0
- package/crates/mint-core/src/orchestration/verification.rs +36 -6
- package/crates/mint-core/src/orchestration/workspace_helpers.rs +54 -23
- package/crates/mint-core/src/prompts/agent.rs +31 -40
- package/crates/mint-core/src/prompts/tool_catalog.rs +134 -40
- package/crates/mint-core/src/search/web_search.rs +44 -3
- package/crates/mint-core/src/slash/mod.rs +464 -1
- package/crates/mint-core/src/swarm/batch.rs +643 -0
- package/crates/mint-core/src/swarm/dossier.rs +152 -0
- package/crates/mint-core/src/swarm/labels.rs +100 -0
- package/crates/mint-core/src/swarm/mod.rs +158 -0
- package/crates/mint-core/src/swarm/permissions.rs +167 -0
- package/crates/mint-core/src/system/auth.rs +156 -7
- package/crates/mint-core/src/system/bg_shell.rs +77 -9
- package/crates/mint-core/src/system/config.rs +92 -3
- package/crates/mint-core/src/system/mint_web.rs +33 -12
- package/crates/mint-core/src/system/shell.rs +179 -10
- package/crates/mint-core/src/system/workspace.rs +204 -0
- package/crates/mint-core/tests/background_retention.rs +1 -1
- package/crates/mint-core/tests/memory_persistence.rs +27 -0
- package/out/web/assets/ArtifactPreviewPanel-DAT_Y7i0.css +1 -0
- package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +2 -0
- package/out/web/assets/CalculationCard-CRjP3E1j.js +1 -0
- package/out/web/assets/{CliSessionsView-CtzUJpK_.css → CliSessionsView-C3Br2v5Y.css} +1 -1
- package/out/web/assets/CliSessionsView-MI8kXMAb.js +1 -0
- package/out/web/assets/CodeReviewPage-CUJceQme.css +1 -0
- package/out/web/assets/CodeReviewPage-VCUgPla_.js +2 -0
- package/out/web/assets/CommandPalette-BD8SlTE5.js +1 -0
- package/out/web/assets/ImageGenCard-BCXxkVJX.js +1 -0
- package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +1 -0
- package/out/web/assets/ImageStudioPanel-BP7N4rLr.js +1 -0
- package/out/web/assets/LibraryPage-WlviRdR8.js +2 -0
- package/out/web/assets/LinkedFoldersView-CKRgroWK.js +1 -0
- package/out/web/assets/McpRegistryPicker-CoS83vtc.js +1 -0
- package/out/web/assets/McpServersView-D3wHnuHg.js +1 -0
- package/out/web/assets/MermaidCard-NqrHdyJW.js +2 -0
- package/out/web/assets/MintDashboard-B2DJsDDO.css +1 -0
- package/out/web/assets/MintDashboard-COw_xol7.js +63 -0
- package/out/web/assets/PluginsView-jBYmcKh4.js +1 -0
- package/out/web/assets/RewindModal-DempuDod.js +1 -0
- package/out/web/assets/ScheduledTasksView-5uw9-V9H.js +1 -0
- package/out/web/assets/SelectField-DXBQtrUK.js +1 -0
- package/out/web/assets/SelectField-lFEkkWCU.css +1 -0
- package/out/web/assets/SettingsWindow-2YP3yj7s.js +4 -0
- package/out/web/assets/SettingsWindow-Dz8LQVgD.css +1 -0
- package/out/web/assets/SkillsView-C-gp9J7m.js +2 -0
- package/out/web/assets/StockCard-BDMCe7mx.js +1 -0
- package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +38 -0
- package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +1 -0
- package/out/web/assets/UiFeatureCard-6BwxTOCe.js +1 -0
- package/out/web/assets/UiGridCard-DvZcLNxb.js +1 -0
- package/out/web/assets/UiMockupWidget-DD73QWg1.js +1 -0
- package/out/web/assets/VeoStudioPanel-DfsOJKv0.js +4 -0
- package/out/web/assets/WeatherCard-CPucIfq8.js +1 -0
- package/out/web/assets/WorkspacePanel-BfHKkulU.js +1 -0
- package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +2 -0
- package/out/web/assets/abnfDiagram-VCTEODGH-BOo3Ckng.js +1 -0
- package/out/web/assets/architecture-7GRP2DOG-ZOwheW3n.js +1 -0
- package/out/web/assets/architectureDiagram-5GKGNRK7-DC2FDzVd.js +36 -0
- package/out/web/assets/{blockDiagram-NRAW4CY4-BrrqzHW5.js → blockDiagram-I7D4REHJ-BrKFWdAH.js} +4 -4
- package/out/web/assets/c4Diagram-7LVT6UL2-BHTHK1Yd.js +38 -0
- package/out/web/assets/chunk-2Q5K7J3B-D2JLPQzF.js +1 -0
- package/out/web/assets/{chunk-TEH6E4GO-G4Cdlyrt.js → chunk-4HAMMTFA-BYFkZwNj.js} +3 -3
- package/out/web/assets/{chunk-5VM5RSS4-BiPuE0io.js → chunk-5VM5RSS4-BZ2oBPYQ.js} +1 -1
- package/out/web/assets/chunk-75Z2AOVW-BSKfSKSA.js +2 -0
- package/out/web/assets/chunk-DU6HZSFF-vIhsiW6n.js +128 -0
- package/out/web/assets/chunk-F27PBJKO-BlYpW9xq.js +1 -0
- package/out/web/assets/chunk-FOHPRMQF-JXEeeJ9r.js +161 -0
- package/out/web/assets/{chunk-GMAD6QVW-BuhEcgHq.js → chunk-GMAD6QVW-BIBz_Ug5.js} +12 -12
- package/out/web/assets/chunk-GVQU2GXP-NPSHxeqC.js +1 -0
- package/out/web/assets/chunk-IMKFNOWR-YlJK8_Fp.js +231 -0
- package/out/web/assets/chunk-JWPE2WC7-BeEMUFDw.js +1 -0
- package/out/web/assets/chunk-L3NEJ4N5-DMYi4-af.js +1 -0
- package/out/web/assets/chunk-OSK3NFVY-Begd3-jA.js +10 -0
- package/out/web/assets/chunk-P2QGCYS3-D1I4xFHN.js +1 -0
- package/out/web/assets/chunk-POPQ4Y6H-CafZZzNE.js +1 -0
- package/out/web/assets/chunk-PWAF6VOD-Dwh6_Lnt.js +1 -0
- package/out/web/assets/chunk-SHT3W25Y-B0XrCSEb.js +168 -0
- package/out/web/assets/{chunk-SVP7TREG-Bu_yYb-V.js → chunk-SVP7TREG-CxLsddxC.js} +1 -1
- package/out/web/assets/{chunk-LCL6LL3I-BhXV9zyH.js → chunk-TICWLB2K-y156QInb.js} +4 -4
- package/out/web/assets/chunk-XXDRQBXY-B9YlhOkE.js +1 -0
- package/out/web/assets/chunk-Y2CYZVJY-BxCWjNVG.js +1 -0
- package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +1 -0
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +1 -0
- package/out/web/assets/config-CvIO5hkn.js +2 -0
- package/out/web/assets/config-DqXJXeZM.css +1 -0
- package/out/web/assets/core-Mi3s5D4_.js +1 -0
- package/out/web/assets/cose-bilkent-JH36ORCC-qlna31ty.js +1 -0
- package/out/web/assets/cynefin-OW5HDTMX-SMVvrLf-.js +1 -0
- package/out/web/assets/cynefinDiagram-5FMLGOSQ-rZznN24T.js +62 -0
- package/out/web/assets/cytoscape.esm-DWtAUG0r.js +321 -0
- package/out/web/assets/dagre-CnWJJM5f.js +1 -0
- package/out/web/assets/dagre-GXQ25YYZ-BOToydFu.js +4 -0
- package/out/web/assets/diagram-S7CK7UJ4-TMVaGQEJ.js +30 -0
- package/out/web/assets/diagram-UQ7AKVKN-C2JK-GP2.js +41 -0
- package/out/web/assets/{diagram-VSXAHHWV-BJ86Ov2Y.js → diagram-VSXAHHWV-Cw8VSi2K.js} +1 -1
- package/out/web/assets/diagram-VX7I27RA-DXp4fKk9.js +24 -0
- package/out/web/assets/diagram-Z3DM3KII-CzSicYiW.js +24 -0
- package/out/web/assets/dist-9ISJ7r-g.js +1 -0
- package/out/web/assets/dist-js-BV3mI7uw.js +1 -0
- package/out/web/assets/ebnfDiagram-PWID7BFC-Dqj-zqPu.js +1 -0
- package/out/web/assets/{erDiagram-SSCWMZ5O-CzzmnZWT.js → erDiagram-RLTQ6QDP-DZwFZAuD.js} +3 -3
- package/out/web/assets/event-D7D-EBy9.js +1 -0
- package/out/web/assets/eventmodeling-NTZA5JFV-C0O-Lvgp.js +1 -0
- package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +1 -0
- package/out/web/assets/ganttDiagram-EL5Y4UJY-Da4ULXTl.js +292 -0
- package/out/web/assets/gitGraph-4MIJSDKK-B9wNFZIP.js +1 -0
- package/out/web/assets/gitGraphDiagram-WWUBYQGX-RjrhB0MP.js +106 -0
- package/out/web/assets/graphlib-Bh9gHply.js +1 -0
- package/out/web/assets/index-BmpulIkg.js +168 -0
- package/out/web/assets/index-CkvBwhX_.css +1 -0
- package/out/web/assets/info-A6RAGUB7-B3dfCpeY.js +1 -0
- package/out/web/assets/infoDiagram-27XIBGKW-QVWkLVx7.js +2 -0
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-s38yo28y.js → ishikawaDiagram-5VMMS53U-CLFNIRI9.js} +4 -4
- package/out/web/assets/journeyDiagram-3NMN7TZE-fgOQadYF.js +139 -0
- package/out/web/assets/jsx-runtime-DmUwWVTj.js +1 -0
- package/out/web/assets/{kanban-definition-3QL26DDD-DodjgySL.js → kanban-definition-UXKFOSKX-G3Q_9g9O.js} +6 -6
- package/out/web/assets/katex-MCdQaEAD.js +257 -0
- package/out/web/assets/mermaid-parser.core-v3Jm7nco.js +7 -0
- package/out/web/assets/mermaid.core-DTEjLUA1.js +44 -0
- package/out/web/assets/{mindmap-definition-FBJOCRG2-260WBDxZ.js → mindmap-definition-YA3MSWOX-C_D42_fF.js} +4 -4
- package/out/web/assets/modelManager-BPLycQrN.js +2 -0
- package/out/web/assets/oauthManager-BUK7ic7q.js +1 -0
- package/out/web/assets/packet-AYTQ26CC-B-R-SHlF.js +1 -0
- package/out/web/assets/pdf-Bcahc2yC.js +56 -0
- package/out/web/assets/pdf.worker.min-Dkey6ZUl.mjs +27 -0
- package/out/web/assets/pegDiagram-XKGWAZYB-Da2q_giz.js +1 -0
- package/out/web/assets/pie-WAS4IAKB-BC8F0N7-.js +1 -0
- package/out/web/assets/pieDiagram-E7YTZNPT-Iy9REJNC.js +39 -0
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-B8D3gU5d.js → quadrantDiagram-AXDQQJYC-ztsiYw7h.js} +4 -4
- package/out/web/assets/radar-RG4KPBEZ-B_1P8Z_z.js +1 -0
- package/out/web/assets/railroad-74A4TZTK-DuTqczSo.js +1 -0
- package/out/web/assets/railroad-abnf-HS5TGJTU-Df29WLmp.js +1 -0
- package/out/web/assets/railroad-ebnf-LZEXJU2U-ePFMo-qp.js +1 -0
- package/out/web/assets/railroad-peg-WCYAUIDC-DYXLW4_E.js +1 -0
- package/out/web/assets/railroadDiagram-O6MQD6OU-LStnZf2S.js +1 -0
- package/out/web/assets/{requirementDiagram-EFPCY7ZU-D2X-Ewik.js → requirementDiagram-BXWQKSXE-DmzonMH-.js} +3 -3
- package/out/web/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
- package/out/web/assets/rough.esm-CNnFptCR.js +1 -0
- package/out/web/assets/sankeyDiagram-P5KCCOFB-B8vjmUU1.js +40 -0
- package/out/web/assets/sequenceDiagram-WJ2MYXX4-CTnplOAX.js +162 -0
- package/out/web/assets/sizeCapture-INFHLROL-C8Q8DbV5.js +1 -0
- package/out/web/assets/src-CRDfRlHM.js +1 -0
- package/out/web/assets/stateDiagram-D77RDMKH-BFrIaLdI.js +1 -0
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +1 -0
- package/out/web/assets/studio-BBZQ_-QK.js +1 -0
- package/out/web/assets/swimlanes-42K2YHIH-BXGngUNU.js +1 -0
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +8 -0
- package/out/web/assets/timeline-definition-24CTP7MA-CegH7n43.js +120 -0
- package/out/web/assets/treeView-Q6P3EWNA-DTpJv2Tw.js +1 -0
- package/out/web/assets/treemap-WGGIJYW6-i8M9iJU6.js +1 -0
- package/out/web/assets/vennDiagram-4TSXK5OY-Dpzo1S8U.js +34 -0
- package/out/web/assets/wardley-WFR3VGLG-BrNsgtfw.js +1 -0
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-DAdXELbQ.js → wardleyDiagram-VM6X3IG4-lQ92LFE1.js} +35 -35
- package/out/web/assets/window-CkUOP7t0.js +1 -0
- package/out/web/assets/workspaces-SYPrmbgC.js +1 -0
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-IZIXf3Vo.js → xychartDiagram-S5SC5T6Z-BaOaEoBd.js} +4 -4
- package/out/web/index-web.html +4 -4
- package/out/web/index.html +4 -4
- package/out/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/out/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/out/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/out/web/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/out/web/pdfjs/cmaps/LICENSE +36 -0
- package/out/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Roman.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/out/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/out/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
- package/out/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
- package/out/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
- package/out/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
- package/out/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
- package/out/web/pdfjs/wasm/LICENSE_QCMS +21 -0
- package/out/web/pdfjs/wasm/jbig2.wasm +0 -0
- package/out/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
- package/out/web/pdfjs/wasm/openjpeg.wasm +0 -0
- package/out/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
- package/out/web/pdfjs/wasm/qcms_bg.wasm +0 -0
- package/out/web/pdfjs/wasm/quickjs-eval.js +16 -0
- package/out/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
- package/package.json +6 -4
- package/slash-commands.json +8 -2
- package/src/bin/index.js +4 -1
- package/src/bin/pdfjs-assets.mjs +31 -0
- package/src/renderer/node_modules/.vite/vitest/CACHEDIR.TAG +3 -0
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/renderer/shared/agentProgress.ts +11 -2
- package/src/renderer/shared/components/Settings/AgentsTab.tsx +70 -6
- package/src/renderer/shared/components/Settings/AudioTab.tsx +1 -1
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +57 -0
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +18 -12
- package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.test.tsx +65 -0
- package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.tsx +456 -0
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +40 -82
- package/src/renderer/shared/components/Settings/PluginsTab.tsx +101 -24
- package/src/renderer/shared/components/Settings/ProfileTab.tsx +1 -1
- package/src/renderer/shared/components/Settings/SettingsWindow.tsx +796 -0
- package/src/renderer/shared/components/Settings/ThemeTab.tsx +13 -1
- package/src/renderer/shared/components/{AgentActivityDrawer.tsx → activity/AgentActivityDrawer.tsx} +77 -61
- package/src/renderer/shared/components/{AgentActivityTable.tsx → activity/AgentActivityTable.tsx} +46 -7
- package/src/renderer/shared/components/{McpToolResults.tsx → activity/McpToolResults.tsx} +32 -13
- package/src/renderer/shared/components/{PlanChecklistWidget.tsx → activity/PlanChecklistWidget.tsx} +2 -2
- package/src/renderer/shared/components/activity/RunSummaryDashboard.test.tsx +127 -0
- package/src/renderer/shared/components/activity/RunSummaryDashboard.tsx +222 -0
- package/src/renderer/shared/components/activity/SubagentDagView.test.ts +123 -0
- package/src/renderer/shared/components/{SubagentDagView.tsx → activity/SubagentDagView.tsx} +97 -43
- package/src/renderer/shared/components/activity/SubagentStatusStrip.test.tsx +82 -0
- package/src/renderer/shared/components/activity/SubagentStatusStrip.tsx +107 -0
- package/src/renderer/shared/components/activity/ViewedImages.tsx +140 -0
- package/src/renderer/shared/components/{AuthGate.tsx → app/AuthGate.tsx} +3 -3
- package/src/renderer/shared/components/{ChunkErrorBoundary.tsx → app/ChunkErrorBoundary.tsx} +10 -10
- package/src/renderer/shared/components/{CommandPalette.tsx → app/CommandPalette.tsx} +25 -25
- package/src/renderer/shared/components/{DashboardSidebar.tsx → app/DashboardSidebar.tsx} +7 -5
- package/src/renderer/shared/components/{DesktopTitlebar.tsx → app/DesktopTitlebar.tsx} +7 -24
- package/src/renderer/shared/components/{ErrorNotice.tsx → app/ErrorNotice.tsx} +1 -1
- package/src/renderer/shared/components/{MintDashboard.tsx → app/MintDashboard.tsx} +191 -212
- package/src/renderer/shared/components/{ModalErrorBoundary.tsx → app/ModalErrorBoundary.tsx} +5 -5
- package/src/renderer/shared/components/{CalculationCard.tsx → cards/CalculationCard.tsx} +13 -27
- package/src/renderer/shared/components/{ImageGenCard.tsx → cards/ImageGenCard.tsx} +9 -22
- package/src/renderer/shared/components/{ImageSearchCard.tsx → cards/ImageSearchCard.tsx} +9 -22
- package/src/renderer/shared/components/{MermaidCard.tsx → cards/MermaidCard.tsx} +37 -21
- package/src/renderer/shared/components/{StockCard.tsx → cards/StockCard.tsx} +15 -31
- package/src/renderer/shared/components/cards/ToolResultCards.tsx +86 -0
- package/src/renderer/shared/components/{UiFeatureCard.tsx → cards/UiFeatureCard.tsx} +6 -6
- package/src/renderer/shared/components/{UiGridCard.tsx → cards/UiGridCard.tsx} +3 -3
- package/src/renderer/shared/components/{WeatherCard.tsx → cards/WeatherCard.tsx} +1 -1
- package/src/renderer/shared/components/{ApprovalCard.test.tsx → chat/ApprovalCard.test.tsx} +1 -1
- package/src/renderer/shared/components/{ApprovalCard.tsx → chat/ApprovalCard.tsx} +5 -5
- package/src/renderer/shared/components/{ChatCodeBlock.tsx → chat/ChatCodeBlock.tsx} +1 -1
- package/src/renderer/shared/components/{ChatMessageItem.test.tsx → chat/ChatMessageItem.test.tsx} +92 -15
- package/src/renderer/shared/components/{ChatMessageItem.tsx → chat/ChatMessageItem.tsx} +73 -157
- package/src/renderer/shared/components/chat/ChatPanel.test.tsx +31 -0
- package/src/renderer/shared/components/{ChatPanel.tsx → chat/ChatPanel.tsx} +244 -493
- package/src/renderer/shared/components/{CollapsiblePasteInput.tsx → chat/CollapsiblePasteInput.tsx} +2 -2
- package/src/renderer/shared/components/{DiffReviewModal.tsx → chat/DiffReviewModal.tsx} +40 -40
- package/src/renderer/shared/components/{GeminiLiveOverlay.tsx → chat/GeminiLiveOverlay.tsx} +2 -2
- package/src/renderer/shared/components/{ModelSelectorPopover.tsx → chat/ModelSelectorPopover.tsx} +3 -3
- package/src/renderer/shared/components/chat/QueuedMessagesBanner.test.tsx +89 -0
- package/src/renderer/shared/components/chat/QueuedMessagesBanner.tsx +201 -0
- package/src/renderer/shared/components/{RewindModal.tsx → chat/RewindModal.tsx} +4 -4
- package/src/renderer/shared/components/{SlashChoicePanel.tsx → chat/SlashChoicePanel.tsx} +2 -2
- package/src/renderer/shared/components/{SlashSuggestions.tsx → chat/SlashSuggestions.tsx} +4 -4
- package/src/renderer/shared/components/{SourcesBlock.tsx → chat/SourcesBlock.tsx} +30 -37
- package/src/renderer/shared/components/{ThinkingBlock.tsx → chat/ThinkingBlock.tsx} +1 -1
- package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +86 -0
- package/src/renderer/shared/components/chat/TurnFileChanges.tsx +430 -0
- package/src/renderer/shared/components/{VoicePill.css → chat/VoicePill.css} +1 -1
- package/src/renderer/shared/components/{VoicePill.jsx → chat/VoicePill.jsx} +3 -2
- package/src/renderer/shared/components/{CliSessionsView.test.tsx → extensions/CliSessionsView.test.tsx} +2 -2
- package/src/renderer/shared/components/{CliSessionsView.tsx → extensions/CliSessionsView.tsx} +14 -12
- package/src/renderer/shared/components/{LinkedFoldersView.tsx → extensions/LinkedFoldersView.tsx} +3 -3
- package/src/renderer/shared/components/{McpRegistryPicker.tsx → extensions/McpRegistryPicker.tsx} +3 -3
- package/src/renderer/shared/components/{McpServersView.tsx → extensions/McpServersView.tsx} +105 -36
- package/src/renderer/shared/components/{McpToolAllowlist.tsx → extensions/McpToolAllowlist.tsx} +2 -2
- package/src/renderer/shared/components/{ModUi.test.tsx → extensions/ModUi.test.tsx} +9 -4
- package/src/renderer/shared/components/{ModsPanel.tsx → extensions/ModsPanel.tsx} +59 -7
- package/src/renderer/shared/components/{PluginsView.tsx → extensions/PluginsView.tsx} +6 -6
- package/src/renderer/shared/components/{ScheduledTasksView.tsx → extensions/ScheduledTasksView.tsx} +5 -5
- package/src/renderer/shared/components/extensions/SkillsView.test.tsx +31 -0
- package/src/renderer/shared/components/{SkillsView.tsx → extensions/SkillsView.tsx} +20 -4
- package/src/renderer/shared/components/{ImageStudioPanel.tsx → studio/ImageStudioPanel.tsx} +6 -6
- package/src/renderer/shared/components/{LibraryItemCard.tsx → studio/LibraryItemCard.tsx} +2 -2
- package/src/renderer/shared/components/{LibraryPage.tsx → studio/LibraryPage.tsx} +5 -5
- package/src/renderer/shared/components/{LibraryPreview.tsx → studio/LibraryPreview.tsx} +2 -2
- package/src/renderer/shared/components/{VeoStudioPanel.tsx → studio/VeoStudioPanel.tsx} +7 -7
- package/src/renderer/shared/components/{GlideSelect.css → ui/GlideSelect.css} +7 -6
- package/src/renderer/shared/components/ui/ProviderBrandIcon.test.tsx +67 -0
- package/src/renderer/shared/components/ui/ProviderBrandIcon.tsx +167 -0
- package/src/renderer/shared/components/ui/SelectField.tsx +506 -0
- package/src/renderer/shared/components/ui/error-recovery.css +10 -10
- package/src/renderer/shared/components/ui/error-recovery.tsx +1 -1
- package/src/renderer/shared/components/{ArtifactPreviewPanel.tsx → workspace/ArtifactPreviewPanel.tsx} +256 -112
- package/src/renderer/shared/components/workspace/BackgroundTerminals.test.tsx +63 -0
- package/src/renderer/shared/components/{BackgroundTerminals.tsx → workspace/BackgroundTerminals.tsx} +40 -9
- package/src/renderer/shared/components/{CodeReviewPage.tsx → workspace/CodeReviewPage.tsx} +47 -8
- package/src/renderer/shared/components/{GitBranchSelector.tsx → workspace/GitBranchSelector.tsx} +2 -2
- package/src/renderer/shared/components/workspace/SessionSources.tsx +113 -0
- package/src/renderer/shared/components/{TerminalDock.tsx → workspace/TerminalDock.tsx} +5 -2
- package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +68 -0
- package/src/renderer/shared/components/{ToolSurfacePage.tsx → workspace/ToolSurfacePage.tsx} +322 -71
- package/src/renderer/shared/components/{ToolsPanelToggle.tsx → workspace/ToolsPanelToggle.tsx} +1 -1
- package/src/renderer/shared/components/workspace/WorkspaceFileInfo.tsx +28 -0
- package/src/renderer/shared/components/workspace/WorkspaceMediaPreview.tsx +17 -0
- package/src/renderer/shared/components/{WorkspacePaneResizer.tsx → workspace/WorkspacePaneResizer.tsx} +1 -1
- package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +89 -0
- package/src/renderer/shared/components/{WorkspacePreviewActions.test.tsx → workspace/WorkspacePreviewActions.test.tsx} +48 -3
- package/src/renderer/shared/components/{WorkspacePreviewActions.tsx → workspace/WorkspacePreviewActions.tsx} +3 -3
- package/src/renderer/shared/components/{WorkspaceSelector.tsx → workspace/WorkspaceSelector.tsx} +99 -7
- package/src/renderer/shared/components/{workspace-selector.css → workspace/workspace-selector.css} +84 -2
- package/src/renderer/shared/constants/config.ts +7 -0
- package/src/renderer/shared/conversation/coordinator.test.ts +46 -0
- package/src/renderer/shared/conversation/coordinator.ts +50 -2
- package/src/renderer/shared/conversation/lifecycle.test.ts +163 -1
- package/src/renderer/shared/conversation/lifecycle.ts +164 -7
- package/src/renderer/shared/conversation/projectNavigation.test.ts +18 -0
- package/src/renderer/shared/conversation/projectNavigation.ts +11 -0
- package/src/renderer/shared/conversation/transport.test.ts +20 -0
- package/src/renderer/shared/conversation/transport.ts +51 -9
- package/src/renderer/shared/conversation/useConversationCoordinator.ts +42 -12
- package/src/renderer/shared/css/agent-observability.css +72 -11
- package/src/renderer/shared/css/chat-ui-cards.css +35 -35
- package/src/renderer/shared/css/code-review.css +86 -86
- package/src/renderer/shared/css/code-sessions.css +10 -10
- package/src/renderer/shared/css/file-changes.css +167 -5
- package/src/renderer/shared/css/management-views.css +78 -37
- package/src/renderer/shared/css/mcp-results.css +46 -3
- package/src/renderer/shared/css/queued-messages.css +221 -0
- package/src/renderer/shared/css/session-sources.css +12 -7
- package/src/renderer/shared/css/settings/agents.css +2 -2
- package/src/renderer/{src-web → shared}/css/settings/base.css +101 -107
- package/src/renderer/shared/css/settings/general.css +384 -92
- package/src/renderer/shared/css/settings/plugins.css +8 -8
- package/src/renderer/shared/css/settings/theme.css +44 -32
- package/src/renderer/shared/css/ui-tailwind.css +2 -2
- package/src/renderer/shared/css/viewed-images.css +89 -0
- package/src/renderer/shared/css/workspace-file-info.css +12 -0
- package/src/renderer/shared/css/workspace-media-preview.css +13 -0
- package/src/renderer/shared/env.d.ts +9 -0
- package/src/renderer/shared/platform.test.ts +143 -0
- package/src/renderer/shared/platform.ts +10 -4
- package/src/renderer/shared/speech/player.ts +160 -0
- package/src/renderer/shared/speech/types.ts +11 -0
- package/src/renderer/shared/theme/themeManager.test.ts +20 -0
- package/src/renderer/shared/theme/themeManager.ts +3 -1
- package/src/renderer/shared/theme/themes.ts +17 -4
- package/src/renderer/shared/theme/tokens.css +448 -51
- package/src/renderer/shared/types.ts +56 -1
- package/src/renderer/shared/utils/agentActivity.test.ts +165 -6
- package/src/renderer/shared/utils/agentActivity.ts +181 -31
- package/src/renderer/shared/utils/artifactBrowser.ts +1 -1
- package/src/renderer/shared/utils/artifacts.ts +55 -0
- package/src/renderer/shared/utils/contextCompaction.test.tsx +1 -1
- package/src/renderer/shared/utils/externalLinks.ts +91 -0
- package/src/renderer/shared/utils/lazyWithRetry.ts +13 -0
- package/src/renderer/shared/utils/markdown.tsx +169 -35
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +124 -2
- package/src/renderer/shared/utils/mcpResults.test.tsx +24 -1
- package/src/renderer/shared/utils/pdfRange.ts +16 -0
- package/src/renderer/shared/utils/previewObjectUrls.ts +20 -0
- package/src/renderer/shared/utils/sessionSources.test.ts +41 -1
- package/src/renderer/shared/utils/sessionSources.ts +2 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
- package/src/renderer/shared/utils/speech.ts +18 -4
- package/src/renderer/shared/utils/useNativeVoiceInput.ts +19 -5
- package/src/renderer/shared/utils/useSessionSources.ts +4 -0
- package/src/renderer/shared/utils/viewedImages.test.ts +129 -0
- package/src/renderer/shared/utils/viewedImages.ts +81 -10
- package/src/renderer/shared/utils/workspaceDrag.ts +30 -0
- package/src/renderer/shared/utils/workspaceImage.ts +17 -0
- package/src/renderer/shared/utils/workspaceListPreferences.test.ts +64 -0
- package/src/renderer/shared/utils/workspaceListPreferences.ts +54 -0
- package/src/renderer/shared/utils/workspaceMedia.ts +41 -0
- package/src/renderer/shared/utils/workspacePreview.test.ts +147 -2
- package/src/renderer/shared/utils/workspacePreview.ts +34 -3
- package/src/renderer/shared/utils/workspaces.ts +3 -2
- package/src/renderer/src/App.tsx +6 -8
- package/src/renderer/src/components/ChatPanel.tsx +1 -1
- package/src/renderer/src/components/DashboardSidebar.tsx +2 -2
- package/src/renderer/src/components/ImageStudioPanel.tsx +1 -1
- package/src/renderer/src/components/LiveTranslateControls.tsx +1 -1
- package/src/renderer/src/components/MintDashboard.tsx +1 -1
- package/src/renderer/src/components/ProactiveGlow.tsx +1 -1
- package/src/renderer/src/components/ScreenPicker.tsx +1 -1
- package/src/renderer/src/components/SettingsWindow.tsx +6 -830
- package/src/renderer/src/components/SpotlightWindow.tsx +4 -32
- package/src/renderer/src/components/VeoStudioPanel.tsx +1 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +45 -12
- package/src/renderer/src/components/browser-shell.css +26 -26
- package/src/renderer/src/components/browser-suggestions-popup.css +8 -8
- package/src/renderer/src/css/app-shell.css +22 -102
- package/src/renderer/src/css/chat.css +221 -34
- package/src/renderer/src/css/desktop-tools.css +6 -337
- package/src/renderer/src/css/sidebar-workspace.css +20 -0
- package/src/renderer/src/css/spotlight.css +2 -2
- package/src/renderer/src/css/tool-surfaces.css +328 -58
- package/src/renderer/src/css/widget.css +6 -5
- package/src/renderer/src/env.d.ts +13 -0
- package/src/renderer/src/index.css +15 -63
- package/src/renderer/src/tauri.ts +77 -11
- package/src/renderer/src/voiceInput.ts +3 -1
- package/src/renderer/src-web/App.tsx +6 -8
- package/src/renderer/src-web/components/ChatPanel.tsx +1 -1
- package/src/renderer/src-web/components/DashboardSidebar.tsx +2 -2
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +1 -1
- package/src/renderer/src-web/components/MintDashboard.tsx +1 -1
- package/src/renderer/src-web/components/SettingsWindow.tsx +6 -851
- package/src/renderer/src-web/components/VeoStudioPanel.tsx +1 -1
- package/src/renderer/src-web/css/app-shell.css +16 -100
- package/src/renderer/src-web/css/chat.css +221 -34
- package/src/renderer/src-web/css/sidebar-workspace.css +20 -0
- package/src/renderer/src-web/env.d.ts +14 -0
- package/src/renderer/src-web/index.css +15 -63
- package/src/renderer/src-web/tauri.ts +71 -13
- package/src/renderer/tests/browserArtifacts.browser.html +1 -0
- package/src/renderer/tests/browserArtifacts.browser.tsx +68 -0
- package/src/renderer/tests/chatEscape.browser.html +1 -0
- package/src/renderer/tests/chatEscape.browser.tsx +25 -0
- package/src/renderer/tests/chatPerformance.browser.html +1 -1
- package/src/renderer/tests/collapsedPaste.browser.html +1 -1
- package/src/renderer/tests/composerMenus.browser.html +3 -3
- package/src/renderer/tests/contextCompaction.browser.html +1 -1
- package/src/renderer/tests/errorRecovery.browser.html +1 -1
- package/src/renderer/tests/fixtures/preview.webm +0 -0
- package/src/renderer/tests/glideSelect.browser.html +1 -2
- package/src/renderer/tests/glideSelect.preview.html +1 -1
- package/src/renderer/tests/library.browser.tsx +1 -1
- package/src/renderer/tests/liveOrb.browser.html +1 -1
- package/src/renderer/tests/mediaFixtures.ts +32 -0
- package/src/renderer/tests/modInput.browser.html +1 -1
- package/src/renderer/tests/sessionSources.browser.tsx +76 -15
- package/src/renderer/tests/toolsPanel.browser.tsx +5 -5
- package/src/renderer/tests/viewedImages.browser.tsx +3 -3
- package/src/renderer/tests/voiceDictation.browser.html +101 -0
- package/src/renderer/tests/voicePill.browser.html +3 -3
- package/src/renderer/tests/voicePill.preview.html +1 -1
- package/src/renderer/tests/webSources.browser.tsx +2 -98
- package/src/renderer/tests/workspaceDrop.browser.html +1 -0
- package/src/renderer/tests/workspaceDrop.browser.tsx +183 -0
- package/src/renderer/tests/workspaceExpansion.browser.html +1 -0
- package/src/renderer/tests/workspaceExpansion.browser.tsx +66 -0
- package/src/renderer/tests/workspaceRemoval.browser.html +66 -0
- package/src/renderer/tests/workspaceUndo.browser.html +10 -2
- package/src-tauri/src/app/lifecycle.rs +44 -1
- package/src-tauri/src/commands/conversation.rs +99 -73
- package/src-tauri/src/commands/integrations.rs +7 -1
- package/src-tauri/src/commands/media.rs +7 -0
- package/src-tauri/src/commands/workspace.rs +38 -0
- package/tsconfig.json +15 -1
- package/vite.config.ts +2 -1
- package/vite.config.web.ts +2 -0
- package/out/web/assets/CliSessionsView-D7hw0Mgj.js +0 -1
- package/out/web/assets/CodeReviewPage-C7bEOJfd.js +0 -4
- package/out/web/assets/CodeReviewPage-Dte9uLiL.css +0 -1
- package/out/web/assets/CommandPalette-0RrF-AOT.js +0 -1
- package/out/web/assets/ImageStudioPanel-CdxkAf_r.js +0 -1
- package/out/web/assets/LibraryPage--Qfisp6D.js +0 -2
- package/out/web/assets/LinkedFoldersView-2nfZbDgN.js +0 -1
- package/out/web/assets/McpRegistryPicker-h9uS8_wT.js +0 -1
- package/out/web/assets/McpServersView-slfQJzNB.js +0 -1
- package/out/web/assets/MintDashboard-BSR5XpXh.js +0 -25
- package/out/web/assets/MintDashboard-CWUcCGhd.css +0 -1
- package/out/web/assets/PluginsView-l7LSm503.js +0 -1
- package/out/web/assets/ScheduledTasksView-CttmLbq1.js +0 -1
- package/out/web/assets/SettingsWindow-BCMLYvEV.js +0 -4
- package/out/web/assets/SettingsWindow-DcMVmU9u.css +0 -1
- package/out/web/assets/SkillsView-rNs1qr_U.js +0 -2
- package/out/web/assets/ToolSurfacePage-B9DzEPWN.js +0 -37
- package/out/web/assets/ToolSurfacePage-CTfgggF2.css +0 -1
- package/out/web/assets/VeoStudioPanel-DiJs_Cg2.js +0 -4
- package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +0 -1
- package/out/web/assets/WorkspaceSelector-B2xIp04D.js +0 -1
- package/out/web/assets/WorkspaceSelector-Ca8uJOsS.css +0 -1
- package/out/web/assets/abnfDiagram-VCTEODGH-CD0CjoGl.js +0 -1
- package/out/web/assets/architecture-7GRP2DOG-RKV83OeZ.js +0 -1
- package/out/web/assets/architectureDiagram-5GKGNRK7-nN_HAFct.js +0 -36
- package/out/web/assets/c4Diagram-UCG6FXSJ-DTdV8_zE.js +0 -38
- package/out/web/assets/chunk-2Q5K7J3B-Dmqrq8Tp.js +0 -1
- package/out/web/assets/chunk-75Z2AOVW-_M0z5olV.js +0 -2
- package/out/web/assets/chunk-CKBBP62Z-BpQEgeHF.js +0 -2
- package/out/web/assets/chunk-CLGD4ZFX-D54yGvGS.js +0 -1
- package/out/web/assets/chunk-CacB07dV.js +0 -1
- package/out/web/assets/chunk-DSNCTWBM-C-gvT2ru.js +0 -2
- package/out/web/assets/chunk-DU6HZSFF-CnFngIhW.js +0 -128
- package/out/web/assets/chunk-F27PBJKO-BqtWrnEO.js +0 -1
- package/out/web/assets/chunk-FOHPRMQF-BaVC1uA7.js +0 -161
- package/out/web/assets/chunk-G27WJ6UU-Ckoq5mi2.js +0 -231
- package/out/web/assets/chunk-JQ64N6SF-CF8T7GiZ.js +0 -1
- package/out/web/assets/chunk-JWPE2WC7-BwtYOHqU.js +0 -1
- package/out/web/assets/chunk-LNGE3PJU-AdBoOWw2.js +0 -34
- package/out/web/assets/chunk-OBVCFTLP-CRIkunUB.js +0 -1
- package/out/web/assets/chunk-P2QGCYS3-UFEU6BxZ.js +0 -1
- package/out/web/assets/chunk-POPQ4Y6H-CQcrNbjr.js +0 -1
- package/out/web/assets/chunk-PWAF6VOD-Cc2uVDsG.js +0 -1
- package/out/web/assets/chunk-R7TYR2AO-DiCxzdAY.js +0 -10
- package/out/web/assets/chunk-RHFEMEQ7-b7hnPmrR.js +0 -168
- package/out/web/assets/chunk-XXDRQBXY-DnJkbUaw.js +0 -1
- package/out/web/assets/chunk-Y2CYZVJY-C9o-s6OY.js +0 -1
- package/out/web/assets/classDiagram-DTDB5LWJ-RnC2mYO4.js +0 -1
- package/out/web/assets/classDiagram-v2-JRS7N3AN-RnC2mYO4.js +0 -1
- package/out/web/assets/config-CnvCChUM.js +0 -2
- package/out/web/assets/config-DM6oIVO3.css +0 -1
- package/out/web/assets/core-DsKkQ7ig.js +0 -1
- package/out/web/assets/cose-bilkent-JH36ORCC--UN4aw5-.js +0 -1
- package/out/web/assets/cynefin-OW5HDTMX-CnzQfOGp.js +0 -1
- package/out/web/assets/cynefinDiagram-5FMLGOSQ-DVtQnfpm.js +0 -62
- package/out/web/assets/cytoscape.esm-Df1UN4Y1.js +0 -321
- package/out/web/assets/dagre-3AP2YEHR-DG76_k5_.js +0 -4
- package/out/web/assets/dagre-BWadQu2F.js +0 -1
- package/out/web/assets/diagram-S7CK7UJ4-CaouUoze.js +0 -30
- package/out/web/assets/diagram-UQ7AKVKN-B4bEc26B.js +0 -41
- package/out/web/assets/diagram-VX7I27RA-7hH5IPYf.js +0 -24
- package/out/web/assets/diagram-Z3DM3KII-CCM4clo8.js +0 -24
- package/out/web/assets/dist-BoTOASLF.js +0 -1
- package/out/web/assets/dist-js-BkYNznHX.js +0 -1
- package/out/web/assets/ebnfDiagram-PWID7BFC-Da7WAM3k.js +0 -1
- package/out/web/assets/event-Cdt7Byyj.js +0 -1
- package/out/web/assets/eventmodeling-NTZA5JFV-xcfsA0XD.js +0 -1
- package/out/web/assets/flowDiagram-A5DVABFB-JdtH1673.js +0 -1
- package/out/web/assets/ganttDiagram-EL5Y4UJY-B3ClFGmL.js +0 -292
- package/out/web/assets/gitGraph-4MIJSDKK-BZ_y_cDU.js +0 -1
- package/out/web/assets/gitGraphDiagram-WWUBYQGX-drhRSYm3.js +0 -106
- package/out/web/assets/graphlib-CTR5ZhLf.js +0 -1
- package/out/web/assets/index-BZ9xgmMP.css +0 -1
- package/out/web/assets/index-Ca8cCS4h.js +0 -168
- package/out/web/assets/info-A6RAGUB7-B7I3K0F1.js +0 -1
- package/out/web/assets/infoDiagram-RXCK75RN-deX_1MPh.js +0 -2
- package/out/web/assets/journeyDiagram-EYS64GPL-CZn2kB8b.js +0 -139
- package/out/web/assets/jsx-runtime-DN4NhQUK.js +0 -1
- package/out/web/assets/katex-O3jvqgnU.js +0 -257
- package/out/web/assets/markdown-CMhYIcWB.js +0 -39
- package/out/web/assets/mermaid-parser.core-Whu2rlJC.js +0 -7
- package/out/web/assets/mermaid.core-CKyPo8VH.js +0 -11
- package/out/web/assets/modelManager-CUaGWMWu.js +0 -2
- package/out/web/assets/oauthManager-DlNhXJr6.js +0 -1
- package/out/web/assets/packet-AYTQ26CC-CEeIRKRG.js +0 -1
- package/out/web/assets/pegDiagram-XKGWAZYB-BYadI-KO.js +0 -1
- package/out/web/assets/pie-WAS4IAKB-D8uf4dh5.js +0 -1
- package/out/web/assets/pieDiagram-E7YTZNPT-CjSDy4hO.js +0 -39
- package/out/web/assets/radar-RG4KPBEZ-DZJxnokY.js +0 -1
- package/out/web/assets/railroad-74A4TZTK-DBysUiUi.js +0 -1
- package/out/web/assets/railroad-abnf-HS5TGJTU-CHWxoJYg.js +0 -1
- package/out/web/assets/railroad-ebnf-LZEXJU2U-DDv7X6bY.js +0 -1
- package/out/web/assets/railroad-peg-WCYAUIDC-DpfcYYNJ.js +0 -1
- package/out/web/assets/railroadDiagram-O6MQD6OU-CYtyv0t3.js +0 -1
- package/out/web/assets/rough.esm-DFbTUy8a.js +0 -1
- package/out/web/assets/sankeyDiagram-P5KCCOFB-Bn2x6C4Y.js +0 -40
- package/out/web/assets/sequenceDiagram-WJ2MYXX4-BvFLKtPd.js +0 -162
- package/out/web/assets/sizeCapture-X5ZJPWSS-DKeOCaN2.js +0 -1
- package/out/web/assets/src-DnfeXQPr.js +0 -1
- package/out/web/assets/stateDiagram-HBIQ2CUA-Bx0GWeaY.js +0 -1
- package/out/web/assets/stateDiagram-v2-4QOOHH4V--hol1yg_.js +0 -1
- package/out/web/assets/studio-B1mk40wD.js +0 -1
- package/out/web/assets/swimlanes-XN3QIQJK-CCma0T7L.js +0 -1
- package/out/web/assets/swimlanesDiagram-VK2B7HYN-BkohoVsU.js +0 -8
- package/out/web/assets/timeline-definition-24CTP7MA-iCRhhJ7C.js +0 -120
- package/out/web/assets/treeView-Q6P3EWNA-DZ-y08qp.js +0 -1
- package/out/web/assets/treemap-WGGIJYW6-De0CxzMx.js +0 -1
- package/out/web/assets/vennDiagram-4TSXK5OY-w_7aZ6xe.js +0 -34
- package/out/web/assets/wardley-WFR3VGLG-DT0y0xJ4.js +0 -1
- package/out/web/assets/window-BJCVtyET.js +0 -1
- package/src/renderer/shared/components/ChatPanel.test.tsx +0 -21
- package/src/renderer/shared/components/FormattedMessage.tsx +0 -11
- package/src/renderer/shared/components/GlideSelect.d.ts +0 -31
- package/src/renderer/shared/components/GlideSelect.jsx +0 -266
- package/src/renderer/shared/components/InlineThoughtNote.tsx +0 -27
- package/src/renderer/shared/components/ModSessionControls.tsx +0 -8
- package/src/renderer/shared/components/ModUi.tsx +0 -26
- package/src/renderer/shared/components/ProgressActivity.tsx +0 -17
- package/src/renderer/shared/components/ReviewHistory.tsx +0 -42
- package/src/renderer/shared/components/RunSummaryDashboard.tsx +0 -143
- package/src/renderer/shared/components/SelectField.tsx +0 -38
- package/src/renderer/shared/components/SessionSources.tsx +0 -48
- package/src/renderer/shared/components/SessionSourcesPopover.tsx +0 -61
- package/src/renderer/shared/components/ToolSurfacePage.test.tsx +0 -32
- package/src/renderer/shared/components/ViewedImages.tsx +0 -36
- package/src/renderer/shared/components/WorkspaceContextPopover.tsx +0 -138
- package/src/renderer/shared/components/ui/demo.tsx +0 -26
- package/src/renderer/shared/css/session-sources-popover.css +0 -9
- package/src/renderer/src/components/CalculationCard.tsx +0 -3
- package/src/renderer/src/components/ImageGenCard.tsx +0 -2
- package/src/renderer/src/components/ImageSearchCard.tsx +0 -2
- package/src/renderer/src/components/MermaidCard.tsx +0 -1
- package/src/renderer/src/components/PicturesLibrary.tsx +0 -2
- package/src/renderer/src/components/Settings/AgentsTab.tsx +0 -4
- package/src/renderer/src/components/Settings/AudioTab.tsx +0 -3
- package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -3
- package/src/renderer/src/components/Settings/GeneralTab.tsx +0 -3
- package/src/renderer/src/components/Settings/MemoryTab.tsx +0 -3
- package/src/renderer/src/components/Settings/PluginsTab.tsx +0 -3
- package/src/renderer/src/components/Settings/ProfileTab.tsx +0 -3
- package/src/renderer/src/components/Settings/ShortcutsTab.tsx +0 -3
- package/src/renderer/src/components/Settings/ThemeTab.tsx +0 -3
- package/src/renderer/src/components/StockCard.tsx +0 -3
- package/src/renderer/src/components/WeatherCard.tsx +0 -3
- package/src/renderer/src/css/pictures.css +0 -2
- package/src/renderer/src/css/settings/base.css +0 -1266
- package/src/renderer/src-web/components/PicturesLibrary.tsx +0 -2
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +0 -4
- package/src/renderer/src-web/components/Settings/AudioTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/MemoryTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/ProfileTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +0 -3
- package/src/renderer/src-web/css/pictures.css +0 -2
- package/src/renderer/src-web/css/spotlight.css +0 -102
- package/src/renderer/src-web/css/widget.css +0 -213
- /package/src/renderer/shared/components/{UiMockupWidget.tsx → cards/UiMockupWidget.tsx} +0 -0
- /package/src/renderer/shared/components/{ContextCompactionStatus.tsx → chat/ContextCompactionStatus.tsx} +0 -0
- /package/src/renderer/shared/components/{GeminiLiveOverlay.css → chat/GeminiLiveOverlay.css} +0 -0
- /package/src/renderer/shared/components/{GeminiLiveOverlay.test.tsx → chat/GeminiLiveOverlay.test.tsx} +0 -0
- /package/src/renderer/shared/components/{SlashSuggestions.test.tsx → chat/SlashSuggestions.test.tsx} +0 -0
- /package/src/renderer/shared/components/{VirtualChatHistory.tsx → chat/VirtualChatHistory.tsx} +0 -0
- /package/src/renderer/shared/components/{VoicePill.d.ts → chat/VoicePill.d.ts} +0 -0
- /package/src/renderer/shared/components/{VoicePill.test.tsx → chat/VoicePill.test.tsx} +0 -0
- /package/src/renderer/shared/components/{ScreenCaptureDialog.css → studio/ScreenCaptureDialog.css} +0 -0
- /package/src/renderer/shared/components/{ScreenCaptureDialog.tsx → studio/ScreenCaptureDialog.tsx} +0 -0
- /package/src/renderer/shared/components/{SelectField.test.tsx → ui/SelectField.test.tsx} +0 -0
- /package/src/renderer/shared/components/{background-terminals.css → workspace/background-terminals.css} +0 -0
|
@@ -12,10 +12,19 @@ use std::time::Instant;
|
|
|
12
12
|
use thiserror::Error;
|
|
13
13
|
|
|
14
14
|
pub mod approvals;
|
|
15
|
+
#[cfg(test)]
|
|
16
|
+
mod image_regressions;
|
|
15
17
|
mod mcp_review;
|
|
16
18
|
#[cfg(test)]
|
|
17
19
|
mod mod_authoring_regressions;
|
|
18
20
|
mod progress_clock;
|
|
21
|
+
pub mod reliability;
|
|
22
|
+
pub use reliability::{ExecutionEvidence, ExecutionState};
|
|
23
|
+
pub mod diagnostics;
|
|
24
|
+
pub mod loop_guard;
|
|
25
|
+
pub mod trajectory_schema;
|
|
26
|
+
pub mod truncation;
|
|
27
|
+
pub use trajectory_schema::{StandardTrajectoryRun, StandardTrajectoryStep, StepTokens};
|
|
19
28
|
mod repeated_failures;
|
|
20
29
|
#[cfg(all(test, unix))]
|
|
21
30
|
mod review_regressions;
|
|
@@ -35,7 +44,6 @@ use crate::knowledge::KnowledgeStore;
|
|
|
35
44
|
use crate::plugins::execute_native_plugin;
|
|
36
45
|
use crate::prompts::tool_catalog::tool_catalog;
|
|
37
46
|
use crate::semantic::{index_semantic_code, search_semantic_code};
|
|
38
|
-
use crate::shell::run_shell_command;
|
|
39
47
|
use crate::symbols::build_symbol_index;
|
|
40
48
|
use crate::{
|
|
41
49
|
Capability, ChatError, ChatRequest, ChatResponse, DEFAULT_CONVERSATION_ID, MemoryError,
|
|
@@ -98,6 +106,7 @@ struct TurnLease {
|
|
|
98
106
|
impl TurnLease {
|
|
99
107
|
async fn start(memory: &MemoryStore, chat_id: &str, text: &str) -> Result<Self, MemoryError> {
|
|
100
108
|
let id = memory.start_turn(chat_id, text)?;
|
|
109
|
+
crate::tts::playback::cancel_chat(memory, chat_id);
|
|
101
110
|
crate::companion::queued(chat_id, id, text);
|
|
102
111
|
let _ = TURN_START_LISTENER.try_with(|listener| {
|
|
103
112
|
if listener.chat_id == chat_id {
|
|
@@ -479,12 +488,16 @@ fn model_tool_result(action: &str, result: &str) -> String {
|
|
|
479
488
|
{
|
|
480
489
|
result.to_owned()
|
|
481
490
|
} else {
|
|
482
|
-
|
|
491
|
+
truncation::head_tail_truncate(result, truncation::TruncationConfig::default())
|
|
483
492
|
}
|
|
484
493
|
}
|
|
485
494
|
|
|
486
495
|
fn tool_progress_input(root: &Path, decision: &AgentDecision) -> Value {
|
|
487
496
|
let mut input = serde_json::to_value(&decision.input).unwrap_or(Value::Null);
|
|
497
|
+
if matches!(decision.action.as_str(), "browser_type" | "browser_fill") {
|
|
498
|
+
input["text"] = "[redacted input]".into();
|
|
499
|
+
input["query"] = "[redacted input]".into();
|
|
500
|
+
}
|
|
488
501
|
if decision.action == "read_file"
|
|
489
502
|
&& let Some(skill) = crate::skills::skill_for_read_path(root, &decision.input.path)
|
|
490
503
|
&& let Some(object) = input.as_object_mut()
|
|
@@ -497,7 +510,7 @@ fn tool_progress_input(root: &Path, decision: &AgentDecision) -> Value {
|
|
|
497
510
|
/// Truncates `text` to at most `max_chars` characters (not bytes, so this
|
|
498
511
|
/// never splits a multi-byte UTF-8 character) for injection into a "recent
|
|
499
512
|
/// context" summary. Cheap no-op for the common case of a short message.
|
|
500
|
-
fn truncate_for_context(text: &str, max_chars: usize) -> String {
|
|
513
|
+
pub(super) fn truncate_for_context(text: &str, max_chars: usize) -> String {
|
|
501
514
|
if text.chars().count() <= max_chars {
|
|
502
515
|
return text.to_owned();
|
|
503
516
|
}
|
|
@@ -627,8 +640,7 @@ use workspace_helpers::*;
|
|
|
627
640
|
/// whole accumulated conversation, so total tokens for one task scale
|
|
628
641
|
/// roughly with step count. Was lowered from 32 to 24 to cap runaway/looping
|
|
629
642
|
/// tasks earlier, but 24 turned out too tight for legitimate multi-file
|
|
630
|
-
/// work, cutting it off mid-task —
|
|
631
|
-
const MAX_STEPS: usize = 40;
|
|
643
|
+
/// work, cutting it off mid-task — now configurable via `MintConfig::agent_max_steps` (default 40).
|
|
632
644
|
// A tool gets its own correction budget, renewed after successful execution.
|
|
633
645
|
const MAX_TOOL_ARGUMENT_RETRIES: usize = 5;
|
|
634
646
|
const MAX_OBSERVATION_BYTES: usize = 16_000;
|
|
@@ -643,6 +655,13 @@ const COMPACTION_TRIGGER_RATIO: f64 = 0.75;
|
|
|
643
655
|
/// Number of most-recent Assistant/Tool step-pairs kept verbatim (uncompacted)
|
|
644
656
|
/// in `native_messages`.
|
|
645
657
|
const COMPACTION_KEEP_RECENT_STEPS: usize = 3;
|
|
658
|
+
/// Number of most-recent Tool step-pairs that retain full `ContentBlock::Image` pixels.
|
|
659
|
+
/// Older tool turns have image blocks pruned to reduce prompt cache and token usage.
|
|
660
|
+
const MAX_RETAINED_IMAGE_STEPS: usize = 2;
|
|
661
|
+
/// Adaptive token threshold to compact context in long-running agent tasks (>= 20 steps),
|
|
662
|
+
/// preventing cumulative prompt cache and token bloat before hitting the full window ratio.
|
|
663
|
+
const ADAPTIVE_COMPACTION_TOKEN_CEILING: u32 = 40_000;
|
|
664
|
+
const ADAPTIVE_COMPACTION_MIN_STEPS: usize = 20;
|
|
646
665
|
/// How many times a step retries after every configured provider comes back
|
|
647
666
|
/// `ChatError::NetworkUnavailable` before finally giving up — swapping
|
|
648
667
|
/// providers is pointless when the network itself is down, so this waits and
|
|
@@ -761,6 +780,11 @@ pub enum AgentProgress {
|
|
|
761
780
|
artifacts: Vec<crate::integrations::mcp_result::McpArtifact>,
|
|
762
781
|
warnings: Vec<String>,
|
|
763
782
|
},
|
|
783
|
+
ExecutionUpdate {
|
|
784
|
+
#[serde(rename = "callId")]
|
|
785
|
+
call_id: String,
|
|
786
|
+
executions: Vec<reliability::ExecutionEvidence>,
|
|
787
|
+
},
|
|
764
788
|
Thinking {
|
|
765
789
|
elapsed_secs: u64,
|
|
766
790
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
@@ -873,6 +897,50 @@ pub enum AgentProgress {
|
|
|
873
897
|
RunCompleted {
|
|
874
898
|
summary: RunTelemetrySummary,
|
|
875
899
|
},
|
|
900
|
+
SubagentStarted {
|
|
901
|
+
subagent: String,
|
|
902
|
+
profile: String,
|
|
903
|
+
task: String,
|
|
904
|
+
},
|
|
905
|
+
SubagentFinished {
|
|
906
|
+
subagent: String,
|
|
907
|
+
status: String,
|
|
908
|
+
#[serde(default, alias = "elapsed_ms", rename = "elapsedMs")]
|
|
909
|
+
elapsed_ms: u64,
|
|
910
|
+
steps: u32,
|
|
911
|
+
#[serde(default, alias = "summary_preview", rename = "summaryPreview")]
|
|
912
|
+
summary_preview: String,
|
|
913
|
+
},
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
impl From<crate::swarm::SwarmEvent> for AgentProgress {
|
|
917
|
+
fn from(event: crate::swarm::SwarmEvent) -> Self {
|
|
918
|
+
match event {
|
|
919
|
+
crate::swarm::SwarmEvent::Started {
|
|
920
|
+
label,
|
|
921
|
+
profile,
|
|
922
|
+
task,
|
|
923
|
+
} => AgentProgress::SubagentStarted {
|
|
924
|
+
subagent: label,
|
|
925
|
+
profile,
|
|
926
|
+
task,
|
|
927
|
+
},
|
|
928
|
+
crate::swarm::SwarmEvent::Progress(p) => p,
|
|
929
|
+
crate::swarm::SwarmEvent::Finished {
|
|
930
|
+
label,
|
|
931
|
+
status,
|
|
932
|
+
elapsed_ms,
|
|
933
|
+
steps,
|
|
934
|
+
summary_preview,
|
|
935
|
+
} => AgentProgress::SubagentFinished {
|
|
936
|
+
subagent: label,
|
|
937
|
+
status: status.as_str().to_string(),
|
|
938
|
+
elapsed_ms,
|
|
939
|
+
steps,
|
|
940
|
+
summary_preview,
|
|
941
|
+
},
|
|
942
|
+
}
|
|
943
|
+
}
|
|
876
944
|
}
|
|
877
945
|
|
|
878
946
|
/// Determines whether a thought string represents long-form / internal chain-of-thought
|
|
@@ -1005,6 +1073,8 @@ pub struct RunTelemetrySummary {
|
|
|
1005
1073
|
pub duration_secs: f64,
|
|
1006
1074
|
pub tool_timeline: Vec<ToolExecutionRecord>,
|
|
1007
1075
|
pub retries_count: usize,
|
|
1076
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
1077
|
+
pub trajectory: Option<trajectory_schema::StandardTrajectoryRun>,
|
|
1008
1078
|
}
|
|
1009
1079
|
|
|
1010
1080
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
@@ -1029,6 +1099,8 @@ pub struct AgentResult {
|
|
|
1029
1099
|
pub model: String,
|
|
1030
1100
|
pub summary: String,
|
|
1031
1101
|
pub verification: String,
|
|
1102
|
+
#[serde(default)]
|
|
1103
|
+
pub completion_status: String,
|
|
1032
1104
|
pub fallback: Option<String>,
|
|
1033
1105
|
#[serde(default)]
|
|
1034
1106
|
pub fallback_reason: Option<String>,
|
|
@@ -1064,11 +1136,36 @@ struct AgentDecision {
|
|
|
1064
1136
|
input: AgentInput,
|
|
1065
1137
|
}
|
|
1066
1138
|
|
|
1139
|
+
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
|
1140
|
+
#[serde(rename_all = "camelCase")]
|
|
1141
|
+
pub struct SwarmTaskInput {
|
|
1142
|
+
#[serde(default)]
|
|
1143
|
+
pub role: Option<String>,
|
|
1144
|
+
#[serde(default)]
|
|
1145
|
+
pub base: Option<String>,
|
|
1146
|
+
#[serde(default)]
|
|
1147
|
+
pub instruction: String,
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
impl From<SwarmTaskInput> for crate::swarm::SwarmTask {
|
|
1151
|
+
fn from(input: SwarmTaskInput) -> Self {
|
|
1152
|
+
Self {
|
|
1153
|
+
role: input.role,
|
|
1154
|
+
base: input.base,
|
|
1155
|
+
instruction: input.instruction,
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1067
1160
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
|
1068
1161
|
#[serde(rename_all = "camelCase")]
|
|
1069
1162
|
struct AgentInput {
|
|
1163
|
+
#[serde(default)]
|
|
1164
|
+
tasks: Vec<SwarmTaskInput>,
|
|
1070
1165
|
#[serde(default)]
|
|
1071
1166
|
path: String,
|
|
1167
|
+
#[serde(default)]
|
|
1168
|
+
artifact_id: String,
|
|
1072
1169
|
#[serde(
|
|
1073
1170
|
default,
|
|
1074
1171
|
alias = "q",
|
|
@@ -1092,10 +1189,20 @@ struct AgentInput {
|
|
|
1092
1189
|
city: String,
|
|
1093
1190
|
#[serde(default, alias = "expr", alias = "math")]
|
|
1094
1191
|
expression: String,
|
|
1095
|
-
#[serde(
|
|
1192
|
+
#[serde(
|
|
1193
|
+
default,
|
|
1194
|
+
alias = "cmd",
|
|
1195
|
+
alias = "script",
|
|
1196
|
+
alias = "exec",
|
|
1197
|
+
alias = "code",
|
|
1198
|
+
alias = "bash",
|
|
1199
|
+
alias = "sh"
|
|
1200
|
+
)]
|
|
1096
1201
|
command: String,
|
|
1097
1202
|
#[serde(default)]
|
|
1098
1203
|
background: bool,
|
|
1204
|
+
#[serde(default, alias = "retryReason")]
|
|
1205
|
+
retry_reason: String,
|
|
1099
1206
|
#[serde(default, alias = "job_id")]
|
|
1100
1207
|
job_id: String,
|
|
1101
1208
|
#[serde(default)]
|
|
@@ -1109,6 +1216,8 @@ struct AgentInput {
|
|
|
1109
1216
|
#[serde(default)]
|
|
1110
1217
|
verification: String,
|
|
1111
1218
|
#[serde(default)]
|
|
1219
|
+
completion_status: String,
|
|
1220
|
+
#[serde(default)]
|
|
1112
1221
|
plan: String,
|
|
1113
1222
|
#[serde(default)]
|
|
1114
1223
|
reason: String,
|
|
@@ -1163,6 +1272,8 @@ struct AgentInput {
|
|
|
1163
1272
|
#[serde(default)]
|
|
1164
1273
|
operation: String,
|
|
1165
1274
|
#[serde(default)]
|
|
1275
|
+
mode: String,
|
|
1276
|
+
#[serde(default)]
|
|
1166
1277
|
condition: String,
|
|
1167
1278
|
#[serde(default)]
|
|
1168
1279
|
value: String,
|
|
@@ -1280,9 +1391,97 @@ where
|
|
|
1280
1391
|
.map(|input| input.unwrap_or_default())
|
|
1281
1392
|
}
|
|
1282
1393
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1394
|
+
/// Returns true if a string is an AI dummy placeholder such as `"null"`, `"undefined"`, `"none"`, etc.
|
|
1395
|
+
pub fn is_dummy_value(s: &str) -> bool {
|
|
1396
|
+
let lower = s.trim().to_ascii_lowercase();
|
|
1397
|
+
matches!(
|
|
1398
|
+
lower.as_str(),
|
|
1399
|
+
"null" | "undefined" | "none" | "nil" | "(null)" | "\"\"" | "''"
|
|
1400
|
+
)
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
fn prune_null_values(value: &mut Value) {
|
|
1404
|
+
if let Value::Object(map) = value {
|
|
1405
|
+
map.retain(|k, v| {
|
|
1406
|
+
if v.is_null() && k != "path" {
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
if let Value::String(s) = v {
|
|
1410
|
+
if is_dummy_value(s) {
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
true
|
|
1415
|
+
});
|
|
1416
|
+
for v in map.values_mut() {
|
|
1417
|
+
prune_null_values(v);
|
|
1418
|
+
}
|
|
1419
|
+
} else if let Value::Array(arr) = value {
|
|
1420
|
+
for v in arr.iter_mut() {
|
|
1421
|
+
prune_null_values(v);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Some providers encode the structured patch twice or double-wrap arguments.
|
|
1427
|
+
// Decode those fields, prune nulls for robust serde defaults, and retain strict
|
|
1428
|
+
// typing for all other arguments.
|
|
1285
1429
|
fn decode_agent_input(mut value: Value) -> Result<AgentInput, String> {
|
|
1430
|
+
// 1. If value is a string, check if it's an encoded JSON object
|
|
1431
|
+
if let Value::String(s) = &value {
|
|
1432
|
+
let trimmed = s.trim();
|
|
1433
|
+
if trimmed.starts_with('{') && trimmed.ends_with('}') {
|
|
1434
|
+
if let Ok(parsed) = serde_json::from_str::<Value>(trimmed) {
|
|
1435
|
+
if parsed.is_object() {
|
|
1436
|
+
value = parsed;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// 2. Prune null fields so serde default values apply for missing keys
|
|
1443
|
+
prune_null_values(&mut value);
|
|
1444
|
+
|
|
1445
|
+
// 3. Unpack nested "arguments" if top-level command is missing
|
|
1446
|
+
if let Value::Object(map) = &mut value {
|
|
1447
|
+
let has_command = map
|
|
1448
|
+
.get("command")
|
|
1449
|
+
.or_else(|| map.get("cmd"))
|
|
1450
|
+
.or_else(|| map.get("script"))
|
|
1451
|
+
.or_else(|| map.get("exec"))
|
|
1452
|
+
.or_else(|| map.get("code"))
|
|
1453
|
+
.or_else(|| map.get("bash"))
|
|
1454
|
+
.or_else(|| map.get("sh"))
|
|
1455
|
+
.and_then(Value::as_str)
|
|
1456
|
+
.is_some_and(|s| !s.trim().is_empty());
|
|
1457
|
+
|
|
1458
|
+
if !has_command {
|
|
1459
|
+
if let Some(args_val) = map.get("arguments").cloned() {
|
|
1460
|
+
let inner_obj = match args_val {
|
|
1461
|
+
Value::Object(inner_map) => Some(inner_map),
|
|
1462
|
+
Value::String(inner_str) => serde_json::from_str::<Value>(&inner_str)
|
|
1463
|
+
.ok()
|
|
1464
|
+
.and_then(|v| match v {
|
|
1465
|
+
Value::Object(m) => Some(m),
|
|
1466
|
+
_ => None,
|
|
1467
|
+
}),
|
|
1468
|
+
_ => None,
|
|
1469
|
+
};
|
|
1470
|
+
if let Some(inner) = inner_obj {
|
|
1471
|
+
let mut wrapped = Value::Object(inner);
|
|
1472
|
+
prune_null_values(&mut wrapped);
|
|
1473
|
+
if let Value::Object(cleaned) = wrapped {
|
|
1474
|
+
for (k, v) in cleaned {
|
|
1475
|
+
if !map.contains_key(&k) && !v.is_null() {
|
|
1476
|
+
map.insert(k, v);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1286
1485
|
let outer_path = value.get("path").and_then(Value::as_str).map(str::to_owned);
|
|
1287
1486
|
if let Some(patch) = value.get_mut("patch").filter(|patch| !patch.is_null()) {
|
|
1288
1487
|
if let Value::String(encoded) = patch {
|
|
@@ -1765,7 +1964,7 @@ where
|
|
|
1765
1964
|
);
|
|
1766
1965
|
let mut mod_usage = crate::mods::UsageTracker::new(&root, chat_id, companion_turn_id);
|
|
1767
1966
|
let resolved_task = resolve_github_links(task, config).await;
|
|
1768
|
-
let agent_result = crate::browser::run_session(async {
|
|
1967
|
+
let agent_result = reliability::scope(Box::pin(crate::browser::run_session(async {
|
|
1769
1968
|
// Subagent runs use a synthetic `{parent_chat_id}::subagent::{name}` chat id
|
|
1770
1969
|
// (see the `dispatch_subagent` arm in `execute_tool`) so their own memory
|
|
1771
1970
|
// interaction doesn't leak into the parent conversation's history. That
|
|
@@ -1837,17 +2036,16 @@ where
|
|
|
1837
2036
|
let mut repeated_failures = RepeatedFailures::default();
|
|
1838
2037
|
let mut streamed_finish_summary = String::new();
|
|
1839
2038
|
let mut streamed_direct_text = String::new();
|
|
1840
|
-
//
|
|
1841
|
-
// (`apply_patch`/`write_file`) and the most recent step that ran `verify`,
|
|
1842
|
-
// so `finish` can be rejected when code was changed but never checked —
|
|
1843
|
-
// see the gate right before the `finish` handling block below.
|
|
1844
|
-
let mut last_modify_step: Option<usize> = None;
|
|
1845
|
-
let mut last_verify_step: Option<usize> = None;
|
|
1846
|
-
// Whether the most recent `verify` call actually passed — separate from
|
|
1847
|
-
// `last_verify_step`, which only records that one *ran*. Lets `finish` be
|
|
1848
|
-
// rejected when the agent ignores a real failure and claims success anyway;
|
|
1849
|
-
// see `unacknowledged_verify_failure` right before the `finish` handling block.
|
|
2039
|
+
// Used only for retry telemetry; finish validation uses the typed ledger.
|
|
1850
2040
|
let mut last_verify_failed: Option<bool> = None;
|
|
2041
|
+
let run_id = format!("run-{}", chrono::Local::now().format("%Y%m%d%H%M%S"));
|
|
2042
|
+
let mut trajectory_run = trajectory_schema::StandardTrajectoryRun::new(
|
|
2043
|
+
&run_id,
|
|
2044
|
+
chat_id,
|
|
2045
|
+
&resolved_task,
|
|
2046
|
+
config.active_model(),
|
|
2047
|
+
&config.ai_provider,
|
|
2048
|
+
);
|
|
1851
2049
|
let mut trajectory: Vec<String> = Vec::new();
|
|
1852
2050
|
// Structured history for native tool-calling providers, maintained alongside
|
|
1853
2051
|
// `trajectory`/`observation` (which remain the source of truth for the
|
|
@@ -1890,8 +2088,11 @@ where
|
|
|
1890
2088
|
std::collections::BTreeSet::new();
|
|
1891
2089
|
let mut files_created: std::collections::BTreeSet<String> =
|
|
1892
2090
|
std::collections::BTreeSet::new();
|
|
2091
|
+
let mut repetition_guard = loop_guard::RepetitionGuard::default();
|
|
2092
|
+
let max_steps = config.resolved_agent_max_steps();
|
|
2093
|
+
let warning_threshold = max_steps.saturating_sub(5);
|
|
1893
2094
|
|
|
1894
|
-
'steps: for step in 1..=
|
|
2095
|
+
'steps: for step in 1..=max_steps {
|
|
1895
2096
|
crate::mods::set_turn_policy(&root, chat_id, plan_mode, pinned_mcp_server);
|
|
1896
2097
|
let _ = execute_mod_requests(&root, chat_id, config, plan_mode, &mut approve, &mut progress, None).await;
|
|
1897
2098
|
if run_control::cancelled() { turn.interrupt(); return Err(OrchestrationError::Agent("Agent turn canceled by user".into())); }
|
|
@@ -1941,13 +2142,32 @@ where
|
|
|
1941
2142
|
));
|
|
1942
2143
|
}
|
|
1943
2144
|
|
|
2145
|
+
if step >= warning_threshold {
|
|
2146
|
+
let remaining = max_steps.saturating_sub(step);
|
|
2147
|
+
if remaining == 0 {
|
|
2148
|
+
active_system_prompt.push_str(&format!(
|
|
2149
|
+
"\n\n[CRITICAL STEP LIMIT REACHED - FINAL STEP]\n\
|
|
2150
|
+
You are at step {step} of {max_steps} (the absolute final step). \
|
|
2151
|
+
Do NOT execute any new tool calls or start new exploration. \
|
|
2152
|
+
You MUST call 'finish' right now, synthesize all findings, results, and links discovered so far, \
|
|
2153
|
+
and provide a complete, clear, and comprehensive answer to the user."
|
|
2154
|
+
));
|
|
2155
|
+
} else {
|
|
2156
|
+
active_system_prompt.push_str(&format!(
|
|
2157
|
+
"\n\n[STEP BUDGET WARNING]\n\
|
|
2158
|
+
You are at step {step} of {max_steps} ({remaining} step{} remaining). \
|
|
2159
|
+
Do not initiate new broad exploratory tasks or repeat failed attempts. \
|
|
2160
|
+
Consolidate your findings and call 'finish' with your final report before the budget is exhausted.",
|
|
2161
|
+
if remaining == 1 { "" } else { "s" }
|
|
2162
|
+
));
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
1944
2166
|
let tool_mode = active_config.tool_calling_mode();
|
|
1945
2167
|
// A finish can still be rejected after the provider finishes
|
|
1946
2168
|
// streaming when edited files have not been verified. Don't show
|
|
1947
2169
|
// that provisional summary to the user as if the run were done.
|
|
1948
|
-
let allow_final_stream =
|
|
1949
|
-
!unverified_modification(last_modify_step, last_verify_step, "")
|
|
1950
|
-
&& last_verify_failed != Some(true);
|
|
2170
|
+
let allow_final_stream = reliability::completion("verified", "").is_ok();
|
|
1951
2171
|
|
|
1952
2172
|
if tool_mode == ToolCallingMode::JsonPrompt
|
|
1953
2173
|
&& active_config.ai_provider == "ollama"
|
|
@@ -2164,6 +2384,20 @@ where
|
|
|
2164
2384
|
progress(AgentProgress::ToolStart { call_id: None, action: action.clone(), input: input.clone(), subagent: None });
|
|
2165
2385
|
progress(AgentProgress::ToolEnd { elapsed_ms: None, call_id: None, status: None, action: action.clone(), input: input.clone(), result: error.clone(), subagent: None });
|
|
2166
2386
|
trajectory.push(format_trajectory_step(step, "", action, error));
|
|
2387
|
+
trajectory_run.record_step(StandardTrajectoryStep {
|
|
2388
|
+
step,
|
|
2389
|
+
timestamp: chrono::Utc::now().to_rfc3339(),
|
|
2390
|
+
thought: String::new(),
|
|
2391
|
+
action: action.clone(),
|
|
2392
|
+
input: input.clone(),
|
|
2393
|
+
observation: error.clone(),
|
|
2394
|
+
status: "failed".to_string(),
|
|
2395
|
+
duration_ms: thought_started.elapsed().as_millis() as u64,
|
|
2396
|
+
tokens: StepTokens {
|
|
2397
|
+
input_tokens: last_input_tokens,
|
|
2398
|
+
generated_tokens: response.output_tokens.unwrap_or(0) as u64,
|
|
2399
|
+
},
|
|
2400
|
+
});
|
|
2167
2401
|
}
|
|
2168
2402
|
step_tool_results.extend(errors);
|
|
2169
2403
|
decisions
|
|
@@ -2277,22 +2511,7 @@ where
|
|
|
2277
2511
|
let mut step_images: std::collections::HashMap<String, Vec<String>> =
|
|
2278
2512
|
std::collections::HashMap::new();
|
|
2279
2513
|
|
|
2280
|
-
if
|
|
2281
|
-
let completed = run_parallel_subagent_batch(
|
|
2282
|
-
&decisions,
|
|
2283
|
-
step,
|
|
2284
|
-
&root,
|
|
2285
|
-
config,
|
|
2286
|
-
chat_id,
|
|
2287
|
-
&mut approve,
|
|
2288
|
-
&mut progress,
|
|
2289
|
-
&mut action_counts,
|
|
2290
|
-
&mut trajectory,
|
|
2291
|
-
)
|
|
2292
|
-
.await;
|
|
2293
|
-
invalid_tool_calls.observe_completed(&completed);
|
|
2294
|
-
step_tool_results.extend(completed);
|
|
2295
|
-
} else if decisions_are_parallel_read_only_batch(&decisions) {
|
|
2514
|
+
if decisions_are_parallel_read_only_batch(&decisions) {
|
|
2296
2515
|
let completed = run_parallel_read_only_batch(
|
|
2297
2516
|
&decisions,
|
|
2298
2517
|
step,
|
|
@@ -2361,59 +2580,18 @@ where
|
|
|
2361
2580
|
reject_native_finish(tool_mode, &mut native_messages, &response.text, error);
|
|
2362
2581
|
continue 'steps;
|
|
2363
2582
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
step,
|
|
2377
|
-
&decision.thought,
|
|
2378
|
-
&decision.action,
|
|
2379
|
-
err_msg,
|
|
2380
|
-
));
|
|
2381
|
-
rebuild_observation(task, &root, &trajectory, &mut observation);
|
|
2382
|
-
reject_native_finish(
|
|
2383
|
-
tool_mode,
|
|
2384
|
-
&mut native_messages,
|
|
2385
|
-
&response.text,
|
|
2386
|
-
err_msg,
|
|
2387
|
-
);
|
|
2388
|
-
continue 'steps;
|
|
2389
|
-
}
|
|
2390
|
-
if unacknowledged_verify_failure(
|
|
2391
|
-
last_verify_failed,
|
|
2392
|
-
&decision.input.verification,
|
|
2393
|
-
) {
|
|
2394
|
-
let err_msg = "Error: Your last verify call reported a failure \
|
|
2395
|
-
(non-zero exit code), but you're finishing without \
|
|
2396
|
-
addressing it. Read the stdout/stderr from that verify \
|
|
2397
|
-
call, fix the actual problem, and run verify again until \
|
|
2398
|
-
it passes. Do not report success in the finish summary \
|
|
2399
|
-
while a real check is failing — if the failure is genuinely \
|
|
2400
|
-
unrelated to your change (e.g. pre-existing), say so \
|
|
2401
|
-
explicitly in the finish action's 'verification' field and \
|
|
2402
|
-
finish again.";
|
|
2403
|
-
trajectory.push(format_trajectory_step(
|
|
2404
|
-
step,
|
|
2405
|
-
&decision.thought,
|
|
2406
|
-
&decision.action,
|
|
2407
|
-
err_msg,
|
|
2408
|
-
));
|
|
2409
|
-
rebuild_observation(task, &root, &trajectory, &mut observation);
|
|
2410
|
-
reject_native_finish(
|
|
2411
|
-
tool_mode,
|
|
2412
|
-
&mut native_messages,
|
|
2413
|
-
&response.text,
|
|
2414
|
-
err_msg,
|
|
2415
|
-
);
|
|
2416
|
-
continue 'steps;
|
|
2583
|
+
let completion_status = match reliability::completion(&decision.input.completion_status, &decision.input.verification) {
|
|
2584
|
+
Ok(status) => status,
|
|
2585
|
+
Err(error) => {
|
|
2586
|
+
trajectory.push(format_trajectory_step(step, &decision.thought, &decision.action, &error));
|
|
2587
|
+
rebuild_observation(task, &root, &trajectory, &mut observation);
|
|
2588
|
+
reject_native_finish(tool_mode, &mut native_messages, &response.text, &error);
|
|
2589
|
+
continue 'steps;
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
if completion_status != "verified" {
|
|
2593
|
+
let notice = format!("\n\nCompletion: {}. {}", completion_status, decision.input.verification.trim());
|
|
2594
|
+
summary.push_str(¬ice);
|
|
2417
2595
|
}
|
|
2418
2596
|
// Auto-append generated media (image/video) and model feedback to summary if LLM omitted it
|
|
2419
2597
|
let mut media_blocks = Vec::new();
|
|
@@ -2478,10 +2656,17 @@ where
|
|
|
2478
2656
|
summary.clone(),
|
|
2479
2657
|
turn.id,
|
|
2480
2658
|
);
|
|
2659
|
+
trajectory_run.finish(
|
|
2660
|
+
completion_status.to_ascii_uppercase(),
|
|
2661
|
+
turn_total_tokens,
|
|
2662
|
+
started_at.elapsed().as_secs_f64(),
|
|
2663
|
+
files_created.iter().cloned().collect(),
|
|
2664
|
+
files_modified.iter().cloned().collect(),
|
|
2665
|
+
);
|
|
2481
2666
|
let run_summary = RunTelemetrySummary {
|
|
2482
|
-
run_id:
|
|
2667
|
+
run_id: run_id.clone(),
|
|
2483
2668
|
task: task.to_string(),
|
|
2484
|
-
outcome:
|
|
2669
|
+
outcome: completion_status.to_ascii_uppercase(),
|
|
2485
2670
|
total_tokens: turn_total_tokens as usize,
|
|
2486
2671
|
tool_calls_count: executed_tools.len(),
|
|
2487
2672
|
files_changed: files_created
|
|
@@ -2493,6 +2678,7 @@ where
|
|
|
2493
2678
|
duration_secs: started_at.elapsed().as_secs_f64(),
|
|
2494
2679
|
tool_timeline: executed_tools.clone(),
|
|
2495
2680
|
retries_count: executed_tools.iter().filter(|t| t.retried).count(),
|
|
2681
|
+
trajectory: Some(trajectory_run),
|
|
2496
2682
|
};
|
|
2497
2683
|
let _ = execute_mod_requests(&root, chat_id, config, plan_mode, &mut approve, &mut progress, None).await;
|
|
2498
2684
|
progress(AgentProgress::RunCompleted {
|
|
@@ -2511,6 +2697,7 @@ where
|
|
|
2511
2697
|
model: final_model,
|
|
2512
2698
|
summary,
|
|
2513
2699
|
verification,
|
|
2700
|
+
completion_status,
|
|
2514
2701
|
fallback: final_fallback,
|
|
2515
2702
|
fallback_reason: final_fallback_reason,
|
|
2516
2703
|
total_tokens: turn_total_tokens,
|
|
@@ -2522,6 +2709,20 @@ where
|
|
|
2522
2709
|
if decision.action == "mcp_tool" && mcp_review.blocks_repeat(&decision.input.server,&decision.input.tool,&decision.input.arguments) {
|
|
2523
2710
|
let message="Error: Automatic repeat blocked: this Device command's completion is unconfirmed. Read its status or wait for a new user instruction; do not send the command again this turn.".to_string();
|
|
2524
2711
|
trajectory.push(format_trajectory_step(step,&decision.thought,&decision.action,&message));
|
|
2712
|
+
trajectory_run.record_step(StandardTrajectoryStep {
|
|
2713
|
+
step,
|
|
2714
|
+
timestamp: chrono::Utc::now().to_rfc3339(),
|
|
2715
|
+
thought: decision.thought.clone(),
|
|
2716
|
+
action: decision.action.clone(),
|
|
2717
|
+
input: tool_progress_input(&root,&decision),
|
|
2718
|
+
observation: message.clone(),
|
|
2719
|
+
status: "intercepted".to_string(),
|
|
2720
|
+
duration_ms: thought_started.elapsed().as_millis() as u64,
|
|
2721
|
+
tokens: StepTokens {
|
|
2722
|
+
input_tokens: last_input_tokens,
|
|
2723
|
+
generated_tokens: response.output_tokens.unwrap_or(0) as u64,
|
|
2724
|
+
},
|
|
2725
|
+
});
|
|
2525
2726
|
step_tool_results.push((call_id.clone(),decision.action.clone(),tool_progress_input(&root,&decision),message).into());
|
|
2526
2727
|
continue;
|
|
2527
2728
|
}
|
|
@@ -2536,6 +2737,20 @@ where
|
|
|
2536
2737
|
turn.interrupt();return Err(OrchestrationError::Agent(preparation.text));
|
|
2537
2738
|
}
|
|
2538
2739
|
trajectory.push(format_trajectory_step(step,&decision.thought,&decision.action,&preparation.text));
|
|
2740
|
+
trajectory_run.record_step(StandardTrajectoryStep {
|
|
2741
|
+
step,
|
|
2742
|
+
timestamp: chrono::Utc::now().to_rfc3339(),
|
|
2743
|
+
thought: decision.thought.clone(),
|
|
2744
|
+
action: decision.action.clone(),
|
|
2745
|
+
input: tool_progress_input(&root,&decision),
|
|
2746
|
+
observation: preparation.text.clone(),
|
|
2747
|
+
status: "intercepted".to_string(),
|
|
2748
|
+
duration_ms: thought_started.elapsed().as_millis() as u64,
|
|
2749
|
+
tokens: StepTokens {
|
|
2750
|
+
input_tokens: last_input_tokens,
|
|
2751
|
+
generated_tokens: response.output_tokens.unwrap_or(0) as u64,
|
|
2752
|
+
},
|
|
2753
|
+
});
|
|
2539
2754
|
step_tool_results.push(ToolResultEntry{call_id:call_id.clone(),action:decision.action.clone(),input:tool_progress_input(&root,&decision),text:preparation.text,status:preparation.status});
|
|
2540
2755
|
continue;
|
|
2541
2756
|
}
|
|
@@ -2548,7 +2763,7 @@ where
|
|
|
2548
2763
|
};
|
|
2549
2764
|
|
|
2550
2765
|
// Set only on the real `execute_tool` path below (PreHookOutcome::Allowed);
|
|
2551
|
-
// stays false for plan-mode/hook blocks
|
|
2766
|
+
// stays false for plan-mode/hook blocks, since
|
|
2552
2767
|
// those don't actually run anything and shouldn't count toward verification.
|
|
2553
2768
|
let mut action_succeeded = false;
|
|
2554
2769
|
let mut outcome_status = None;
|
|
@@ -2638,11 +2853,6 @@ where
|
|
|
2638
2853
|
"Blocked: '{}' is not available in plan mode (read-only investigation only). Call exit_plan_mode with your proposed plan once you are ready to implement it; the user will approve or reject it.",
|
|
2639
2854
|
decision.action
|
|
2640
2855
|
)
|
|
2641
|
-
} else if decision.action == "run_shell" && action_count > 1 {
|
|
2642
|
-
format!(
|
|
2643
|
-
"Skipped duplicate shell command: {}\n\n[System Tip: This exact shell command already ran once in this task. Do not run it again. Use the finish action now and tell the user the action was completed.]",
|
|
2644
|
-
decision.input.command.trim()
|
|
2645
|
-
)
|
|
2646
2856
|
} else if pinned_mcp_server.is_some_and(|p| {
|
|
2647
2857
|
(decision.action == "mcp_tool" || decision.action == "mcp_list_tools")
|
|
2648
2858
|
&& decision.input.server != p
|
|
@@ -2654,89 +2864,116 @@ where
|
|
|
2654
2864
|
} else {
|
|
2655
2865
|
let input_val =
|
|
2656
2866
|
tool_progress_input(&root, &decision);
|
|
2867
|
+
let loop_decision = repetition_guard.check(&decision.action, &input_val);
|
|
2657
2868
|
progress(AgentProgress::ToolStart { call_id: Some(activity_call_id.clone()),
|
|
2658
2869
|
action: decision.action.clone(),
|
|
2659
2870
|
input: input_val.clone(),
|
|
2660
2871
|
subagent: None,
|
|
2661
2872
|
});
|
|
2662
2873
|
|
|
2663
|
-
match
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
"Blocked by hook: {}\n\n[System Tip: A configured PreToolUse hook rejected this action. Adjust your approach or ask the user for guidance.]",
|
|
2672
|
-
reason
|
|
2673
|
-
)
|
|
2874
|
+
match loop_decision {
|
|
2875
|
+
loop_guard::LoopGuardDecision::InterceptStuckLoop { ref action, ref reason, ref instruction } => {
|
|
2876
|
+
outcome_status = Some(crate::integrations::mcp_result::ToolStatus::Failed);
|
|
2877
|
+
action_succeeded = false;
|
|
2878
|
+
progress(AgentProgress::Thought {
|
|
2879
|
+
thought: format!("[Stuck-Loop Guard] Intercepted repeated call to '{action}': {reason}"),
|
|
2880
|
+
});
|
|
2881
|
+
instruction.clone()
|
|
2674
2882
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
decision.action.as_str(),
|
|
2678
|
-
"write_file" | "apply_patch" | "note_write"
|
|
2679
|
-
) {
|
|
2680
|
-
let target_path = if !decision.input.path.is_empty() {
|
|
2681
|
-
Some(decision.input.path.as_str())
|
|
2682
|
-
} else {
|
|
2683
|
-
None
|
|
2684
|
-
};
|
|
2685
|
-
let _ = crate::git::create_checkpoint(
|
|
2686
|
-
&root,
|
|
2687
|
-
chat_id,
|
|
2688
|
-
step,
|
|
2689
|
-
&decision.action,
|
|
2690
|
-
target_path,
|
|
2691
|
-
&format!("Before Step {step}: {}", decision.action),
|
|
2692
|
-
);
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
let (tool_result, success) = match execute_tool_outcome_reviewed(
|
|
2696
|
-
&root,
|
|
2697
|
-
config,
|
|
2698
|
-
&decision,
|
|
2699
|
-
chat_id,
|
|
2700
|
-
&mut approve,
|
|
2701
|
-
&mut progress,
|
|
2702
|
-
&activity_call_id,
|
|
2703
|
-
Some(&mcp_review),
|
|
2704
|
-
)
|
|
2705
|
-
.await
|
|
2706
|
-
{
|
|
2707
|
-
Ok(outcome) => {
|
|
2708
|
-
if decision.action=="mcp_tool" {mcp_review.record_result(&decision.input.server,&decision.input.tool,&decision.input.arguments,&outcome);}
|
|
2709
|
-
if matches!(decision.action.as_str(),"mcp_tool"|"mcp_list_tools") { outcome_status = Some(outcome.status); }
|
|
2710
|
-
if !outcome.images.is_empty() { step_images.insert(call_id.clone(), outcome.images); }
|
|
2711
|
-
let success = outcome.status == crate::integrations::mcp_result::ToolStatus::Success;
|
|
2712
|
-
(outcome.text, success)
|
|
2713
|
-
},
|
|
2714
|
-
Err(error) => (format!("Error: {}", error), false),
|
|
2715
|
-
};
|
|
2716
|
-
action_succeeded = success;
|
|
2717
|
-
if success {
|
|
2718
|
-
invalid_tool_calls.succeeded(&decision.action);
|
|
2719
|
-
}
|
|
2720
|
-
let hook_messages = crate::hooks::run_post_tool_hooks(
|
|
2883
|
+
_ => {
|
|
2884
|
+
match crate::hooks::run_pre_tool_hooks(
|
|
2721
2885
|
&hooks,
|
|
2722
2886
|
&decision.action,
|
|
2723
2887
|
&input_val,
|
|
2724
|
-
&tool_result,
|
|
2725
|
-
success,
|
|
2726
2888
|
&root,
|
|
2727
|
-
)
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
.
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2889
|
+
) {
|
|
2890
|
+
crate::hooks::PreHookOutcome::Blocked(reason) => {
|
|
2891
|
+
format!(
|
|
2892
|
+
"Blocked by hook: {}\n\n[System Tip: A configured PreToolUse hook rejected this action. Adjust your approach or ask the user for guidance.]",
|
|
2893
|
+
reason
|
|
2894
|
+
)
|
|
2895
|
+
}
|
|
2896
|
+
crate::hooks::PreHookOutcome::Allowed => {
|
|
2897
|
+
if matches!(
|
|
2898
|
+
decision.action.as_str(),
|
|
2899
|
+
"write_file" | "apply_patch" | "note_write"
|
|
2900
|
+
) {
|
|
2901
|
+
let target_path = if !decision.input.path.is_empty() {
|
|
2902
|
+
Some(decision.input.path.as_str())
|
|
2903
|
+
} else {
|
|
2904
|
+
None
|
|
2905
|
+
};
|
|
2906
|
+
let _ = crate::git::create_checkpoint(
|
|
2907
|
+
&root,
|
|
2908
|
+
chat_id,
|
|
2909
|
+
step,
|
|
2910
|
+
&decision.action,
|
|
2911
|
+
target_path,
|
|
2912
|
+
&format!("Before Step {step}: {}", decision.action),
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
let (mut tool_result, success) = match execute_tool_outcome_reviewed(
|
|
2917
|
+
&root,
|
|
2918
|
+
config,
|
|
2919
|
+
&decision,
|
|
2920
|
+
chat_id,
|
|
2921
|
+
&mut approve,
|
|
2922
|
+
&mut progress,
|
|
2923
|
+
&activity_call_id,
|
|
2924
|
+
Some(&mcp_review),
|
|
2925
|
+
)
|
|
2926
|
+
.await
|
|
2927
|
+
{
|
|
2928
|
+
Ok(outcome) => {
|
|
2929
|
+
if decision.action=="mcp_tool" {mcp_review.record_result(&decision.input.server,&decision.input.tool,&decision.input.arguments,&outcome);}
|
|
2930
|
+
outcome_status = Some(outcome.status);
|
|
2931
|
+
if !outcome.images.is_empty() { step_images.insert(call_id.clone(), outcome.images); }
|
|
2932
|
+
let success = outcome.status == crate::integrations::mcp_result::ToolStatus::Success;
|
|
2933
|
+
(outcome.text, success)
|
|
2934
|
+
},
|
|
2935
|
+
Err(error) => (format!("Error: {}", error), false),
|
|
2936
|
+
};
|
|
2937
|
+
action_succeeded = success;
|
|
2938
|
+
if success {
|
|
2939
|
+
invalid_tool_calls.succeeded(&decision.action);
|
|
2940
|
+
}
|
|
2941
|
+
if let loop_guard::LoopGuardDecision::WarnConsecutiveRepeat { ref notice, .. } = loop_decision {
|
|
2942
|
+
tool_result.push_str(&format!("\n\n{notice}"));
|
|
2943
|
+
}
|
|
2944
|
+
if step >= warning_threshold {
|
|
2945
|
+
let remaining = max_steps.saturating_sub(step);
|
|
2946
|
+
if remaining == 0 {
|
|
2947
|
+
tool_result.push_str("\n\n[CRITICAL STEP LIMIT REACHED - FINAL STEP]: Step budget exhausted. Do not call more tools. Call 'finish' now with your summary.");
|
|
2948
|
+
} else {
|
|
2949
|
+
tool_result.push_str(&format!(
|
|
2950
|
+
"\n\n[Step Budget Warning]: Step {}/{} ({} step{} remaining). Please begin consolidating your findings and call 'finish'.",
|
|
2951
|
+
step, max_steps, remaining, if remaining == 1 { "" } else { "s" }
|
|
2952
|
+
));
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
let hook_messages = crate::hooks::run_post_tool_hooks(
|
|
2956
|
+
&hooks,
|
|
2957
|
+
&decision.action,
|
|
2958
|
+
&input_val,
|
|
2959
|
+
&tool_result,
|
|
2960
|
+
success,
|
|
2961
|
+
&root,
|
|
2962
|
+
);
|
|
2963
|
+
if hook_messages.is_empty() {
|
|
2964
|
+
tool_result
|
|
2965
|
+
} else {
|
|
2966
|
+
format!(
|
|
2967
|
+
"{}\n\n{}",
|
|
2968
|
+
tool_result,
|
|
2969
|
+
hook_messages
|
|
2970
|
+
.iter()
|
|
2971
|
+
.map(|message| format!("[Hook] {}", message))
|
|
2972
|
+
.collect::<Vec<_>>()
|
|
2973
|
+
.join("\n")
|
|
2974
|
+
)
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2740
2977
|
}
|
|
2741
2978
|
}
|
|
2742
2979
|
}
|
|
@@ -2757,7 +2994,7 @@ where
|
|
|
2757
2994
|
subagent: None,
|
|
2758
2995
|
});
|
|
2759
2996
|
|
|
2760
|
-
let success =
|
|
2997
|
+
let success = outcome_status.map(|s| s == crate::mcp_result::ToolStatus::Success).unwrap_or(action_succeeded);
|
|
2761
2998
|
let target = if !decision.input.path.is_empty() {
|
|
2762
2999
|
decision.input.path.clone()
|
|
2763
3000
|
} else if !decision.input.command.is_empty() {
|
|
@@ -2786,7 +3023,7 @@ where
|
|
|
2786
3023
|
if action_succeeded {
|
|
2787
3024
|
match decision.action.as_str() {
|
|
2788
3025
|
"apply_patch" | "write_file" | "note_write" => {
|
|
2789
|
-
|
|
3026
|
+
repetition_guard.notify_state_change();
|
|
2790
3027
|
let path = if let Some(patch) = &decision.input.patch {
|
|
2791
3028
|
patch.path.to_string_lossy().into_owned()
|
|
2792
3029
|
} else if !decision.input.path.is_empty() {
|
|
@@ -2811,8 +3048,7 @@ where
|
|
|
2811
3048
|
// actually passed is tracked separately in `last_verify_failed`
|
|
2812
3049
|
// and enforced by `unacknowledged_verify_failure` at finish time.
|
|
2813
3050
|
"verify" | "run_tests" | "run_typecheck" | "run_linter" => {
|
|
2814
|
-
|
|
2815
|
-
last_verify_failed = Some(shell_result_failed(&result));
|
|
3051
|
+
last_verify_failed = Some(!success);
|
|
2816
3052
|
}
|
|
2817
3053
|
_ => {}
|
|
2818
3054
|
}
|
|
@@ -2845,21 +3081,24 @@ where
|
|
|
2845
3081
|
"run_shell" | "verify" | "run_tests" | "run_typecheck" | "run_linter"
|
|
2846
3082
|
) {
|
|
2847
3083
|
if shell_result_failed(&result) {
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3084
|
+
if let Some(diag) = diagnostics::diagnose_command_failure(&decision.input.command, &result, None) {
|
|
3085
|
+
final_result.push_str(&format!("\n\n{diag}"));
|
|
3086
|
+
} else {
|
|
3087
|
+
final_result.push_str(
|
|
3088
|
+
"\n\n[System Tip: The command failed with a non-zero exit code. \
|
|
3089
|
+
Analyze the stdout/stderr above to locate the error, read the offending files, \
|
|
3090
|
+
apply corrected edits (using apply_patch), and run the verification command again. \
|
|
3091
|
+
If checks cannot pass, finish with completionStatus=blocked/failed/unverified and explain the actual result; never claim they passed.]"
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
2854
3094
|
}
|
|
2855
3095
|
}
|
|
2856
3096
|
if decision.action == "apply_patch" || decision.action == "write_file" {
|
|
2857
3097
|
final_result.push_str(
|
|
2858
3098
|
"\n\n[System Tip: The file edit was approved and applied successfully. \
|
|
2859
3099
|
Before finishing, verify this change with the verify tool (build/test/lint, \
|
|
2860
|
-
whatever fits this project)
|
|
2861
|
-
|
|
2862
|
-
test suite, documentation-only change). Do not broaden the scope, do not make \
|
|
3100
|
+
whatever fits this project). If no check applies, finish with completionStatus=unverified \
|
|
3101
|
+
and explain why in verification. A written explanation is not a passed check. Do not broaden the scope, do not make \
|
|
2863
3102
|
additional unrelated edits, and do not reread the same file unless you need one \
|
|
2864
3103
|
concise verification read.]",
|
|
2865
3104
|
);
|
|
@@ -2879,8 +3118,32 @@ where
|
|
|
2879
3118
|
&final_result,
|
|
2880
3119
|
));
|
|
2881
3120
|
|
|
3121
|
+
let step_input_val = serde_json::to_value(&decision.input).unwrap_or(Value::Null);
|
|
3122
|
+
let step_status_str = match outcome_status {
|
|
3123
|
+
Some(crate::integrations::mcp_result::ToolStatus::Success) => "success",
|
|
3124
|
+
Some(crate::integrations::mcp_result::ToolStatus::Failed) => "failed",
|
|
3125
|
+
Some(crate::integrations::mcp_result::ToolStatus::TimedOut) => "timed_out",
|
|
3126
|
+
Some(crate::integrations::mcp_result::ToolStatus::Cancelled) => "cancelled",
|
|
3127
|
+
Some(crate::integrations::mcp_result::ToolStatus::Skipped) => "skipped",
|
|
3128
|
+
_ => if success { "success" } else { "failed" },
|
|
3129
|
+
};
|
|
3130
|
+
trajectory_run.record_step(StandardTrajectoryStep {
|
|
3131
|
+
step,
|
|
3132
|
+
timestamp: chrono::Utc::now().to_rfc3339(),
|
|
3133
|
+
thought: decision.thought.clone(),
|
|
3134
|
+
action: decision.action.clone(),
|
|
3135
|
+
input: step_input_val,
|
|
3136
|
+
observation: final_result.clone(),
|
|
3137
|
+
status: step_status_str.to_string(),
|
|
3138
|
+
duration_ms: thought_started.elapsed().as_millis() as u64,
|
|
3139
|
+
tokens: StepTokens {
|
|
3140
|
+
input_tokens: last_input_tokens,
|
|
3141
|
+
generated_tokens: response.output_tokens.unwrap_or(0) as u64,
|
|
3142
|
+
},
|
|
3143
|
+
});
|
|
3144
|
+
|
|
2882
3145
|
step_tool_results.push(ToolResultEntry {
|
|
2883
|
-
call_id, action:decision.action.clone(), input:
|
|
3146
|
+
call_id, action:decision.action.clone(), input:serde_json::to_value(&decision.input).unwrap_or(Value::Null),
|
|
2884
3147
|
text:final_result, status:outcome_status.unwrap_or(if success {crate::mcp_result::ToolStatus::Success}else{crate::mcp_result::ToolStatus::Failed}),
|
|
2885
3148
|
});
|
|
2886
3149
|
} // end `for (call_id, decision) in decisions`
|
|
@@ -2915,13 +3178,19 @@ where
|
|
|
2915
3178
|
&step_thought_signatures,
|
|
2916
3179
|
&step_images,
|
|
2917
3180
|
);
|
|
3181
|
+
prune_historic_images_from_native_messages(
|
|
3182
|
+
&mut native_messages,
|
|
3183
|
+
MAX_RETAINED_IMAGE_STEPS,
|
|
3184
|
+
);
|
|
2918
3185
|
|
|
2919
3186
|
if let Some(total_tokens) = response.total_tokens {
|
|
2920
3187
|
let window = active_config.context_window_tokens();
|
|
2921
3188
|
last_context_pct = Some(
|
|
2922
3189
|
((total_tokens as f64 / window as f64) * 100.0).clamp(0.0, 255.0) as u8,
|
|
2923
3190
|
);
|
|
2924
|
-
|
|
3191
|
+
let compaction_needed = (total_tokens as f64) >= (window as f64) * COMPACTION_TRIGGER_RATIO
|
|
3192
|
+
|| (step >= ADAPTIVE_COMPACTION_MIN_STEPS && total_tokens >= ADAPTIVE_COMPACTION_TOKEN_CEILING);
|
|
3193
|
+
if compaction_needed {
|
|
2925
3194
|
let eligible = native_messages.len().saturating_sub(1) / 2 > COMPACTION_KEEP_RECENT_STEPS;
|
|
2926
3195
|
if let Ok(Some((compacted, _))) = run_control::run(report_context_compaction(
|
|
2927
3196
|
eligible,
|
|
@@ -2944,10 +3213,123 @@ where
|
|
|
2944
3213
|
rebuild_observation(task, &root, &trajectory, &mut observation);
|
|
2945
3214
|
}
|
|
2946
3215
|
|
|
3216
|
+
progress(AgentProgress::Thought {
|
|
3217
|
+
thought: format!(
|
|
3218
|
+
"[Step Limit Reached] Reached maximum allowed steps ({max_steps}). Synthesizing final findings for user..."
|
|
3219
|
+
),
|
|
3220
|
+
});
|
|
3221
|
+
|
|
3222
|
+
let (wrapup_config, _, _, _) = resolve_agent_config(config, agent_id, &trajectory);
|
|
3223
|
+
let mut final_streamed_summary = String::new();
|
|
3224
|
+
let mut emit_wrapup_chunk = |chunk| match chunk {
|
|
3225
|
+
AgentStreamChunk::DirectText(delta) | AgentStreamChunk::FinalSummary(delta) => {
|
|
3226
|
+
final_streamed_summary.push_str(&delta);
|
|
3227
|
+
on_chunk(delta);
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
|
|
3231
|
+
let synthesis_instruction = format!(
|
|
3232
|
+
"You are an AI assistant completing a multi-step task. The step limit of {max_steps} steps has been reached. \
|
|
3233
|
+
Synthesize all findings, observations, errors encountered, and links discovered so far. \
|
|
3234
|
+
Provide a comprehensive, direct, and well-structured final answer to the user in their requested language."
|
|
3235
|
+
);
|
|
3236
|
+
|
|
3237
|
+
let synthesis_succeeded = if wrapup_config.tool_calling_mode() == ToolCallingMode::Native {
|
|
3238
|
+
let mut final_native = native_messages.clone();
|
|
3239
|
+
final_native.push(ChatMessage {
|
|
3240
|
+
role: ChatRole::User,
|
|
3241
|
+
content: vec![ContentBlock::Text {
|
|
3242
|
+
text: format!(
|
|
3243
|
+
"Maximum step limit of {max_steps} reached. Do not call any tools. Please output your final answer and summary of findings now."
|
|
3244
|
+
),
|
|
3245
|
+
}],
|
|
3246
|
+
});
|
|
3247
|
+
let wrapup_req = ChatRequest {
|
|
3248
|
+
message: String::new(),
|
|
3249
|
+
system_instruction: synthesis_instruction,
|
|
3250
|
+
chat_id: Some(chat_id.to_owned()),
|
|
3251
|
+
image_data_uri: None,
|
|
3252
|
+
audio_data_uri: None,
|
|
3253
|
+
video_data_uri: None,
|
|
3254
|
+
document_attachment: None,
|
|
3255
|
+
workspace_path: None,
|
|
3256
|
+
agent_id: None,
|
|
3257
|
+
plan_mode: false,
|
|
3258
|
+
pinned_mcp_server: None,
|
|
3259
|
+
messages: Some(final_native),
|
|
3260
|
+
tools: None,
|
|
3261
|
+
temperature: wrapup_config.temperature,
|
|
3262
|
+
..Default::default()
|
|
3263
|
+
};
|
|
3264
|
+
run_control::run(stream_chat_with_network_retry(
|
|
3265
|
+
&wrapup_config,
|
|
3266
|
+
&wrapup_req,
|
|
3267
|
+
&format!("step-limit-wrapup-{chat_id}"),
|
|
3268
|
+
Instant::now(),
|
|
3269
|
+
fast_mode,
|
|
3270
|
+
&mut progress,
|
|
3271
|
+
false,
|
|
3272
|
+
true,
|
|
3273
|
+
&mut emit_wrapup_chunk,
|
|
3274
|
+
))
|
|
3275
|
+
.await
|
|
3276
|
+
.is_ok()
|
|
3277
|
+
} else {
|
|
3278
|
+
let wrapup_req = ChatRequest {
|
|
3279
|
+
message: format!(
|
|
3280
|
+
"Observation: Maximum step limit of {max_steps} reached. Please synthesize your findings and output your final answer now.\nPrevious observation:\n{observation}"
|
|
3281
|
+
),
|
|
3282
|
+
system_instruction: synthesis_instruction,
|
|
3283
|
+
chat_id: Some(chat_id.to_owned()),
|
|
3284
|
+
image_data_uri: None,
|
|
3285
|
+
audio_data_uri: None,
|
|
3286
|
+
video_data_uri: None,
|
|
3287
|
+
document_attachment: None,
|
|
3288
|
+
workspace_path: None,
|
|
3289
|
+
agent_id: None,
|
|
3290
|
+
plan_mode: false,
|
|
3291
|
+
pinned_mcp_server: None,
|
|
3292
|
+
messages: None,
|
|
3293
|
+
tools: None,
|
|
3294
|
+
temperature: wrapup_config.temperature,
|
|
3295
|
+
..Default::default()
|
|
3296
|
+
};
|
|
3297
|
+
run_control::run(stream_chat_with_network_retry(
|
|
3298
|
+
&wrapup_config,
|
|
3299
|
+
&wrapup_req,
|
|
3300
|
+
&format!("step-limit-wrapup-{chat_id}"),
|
|
3301
|
+
Instant::now(),
|
|
3302
|
+
fast_mode,
|
|
3303
|
+
&mut progress,
|
|
3304
|
+
false,
|
|
3305
|
+
true,
|
|
3306
|
+
&mut emit_wrapup_chunk,
|
|
3307
|
+
))
|
|
3308
|
+
.await
|
|
3309
|
+
.is_ok()
|
|
3310
|
+
};
|
|
3311
|
+
|
|
3312
|
+
let summary = if synthesis_succeeded && !final_streamed_summary.trim().is_empty() {
|
|
3313
|
+
final_streamed_summary
|
|
3314
|
+
} else {
|
|
3315
|
+
format!(
|
|
3316
|
+
"Task concluded after reaching the step limit ({} steps). Recent activity:\n{}",
|
|
3317
|
+
max_steps,
|
|
3318
|
+
trajectory.iter().rev().take(5).cloned().collect::<Vec<_>>().join("\n")
|
|
3319
|
+
)
|
|
3320
|
+
};
|
|
3321
|
+
|
|
3322
|
+
trajectory_run.finish(
|
|
3323
|
+
"COMPLETED",
|
|
3324
|
+
turn_total_tokens,
|
|
3325
|
+
started_at.elapsed().as_secs_f64(),
|
|
3326
|
+
files_created.iter().cloned().collect(),
|
|
3327
|
+
files_modified.iter().cloned().collect(),
|
|
3328
|
+
);
|
|
2947
3329
|
let run_summary = RunTelemetrySummary {
|
|
2948
|
-
run_id:
|
|
3330
|
+
run_id: run_id.clone(),
|
|
2949
3331
|
task: task.to_string(),
|
|
2950
|
-
outcome: "
|
|
3332
|
+
outcome: "COMPLETED".to_string(),
|
|
2951
3333
|
total_tokens: turn_total_tokens as usize,
|
|
2952
3334
|
tool_calls_count: executed_tools.len(),
|
|
2953
3335
|
files_changed: files_created
|
|
@@ -2959,17 +3341,32 @@ where
|
|
|
2959
3341
|
duration_secs: started_at.elapsed().as_secs_f64(),
|
|
2960
3342
|
tool_timeline: executed_tools.clone(),
|
|
2961
3343
|
retries_count: executed_tools.iter().filter(|t| t.retried).count(),
|
|
3344
|
+
trajectory: Some(trajectory_run),
|
|
2962
3345
|
};
|
|
2963
3346
|
progress(AgentProgress::RunCompleted {
|
|
2964
3347
|
summary: run_summary,
|
|
2965
3348
|
});
|
|
2966
3349
|
|
|
2967
|
-
turn.
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
3350
|
+
turn.complete_with_summary(
|
|
3351
|
+
&summary,
|
|
3352
|
+
&final_provider,
|
|
3353
|
+
&final_model,
|
|
3354
|
+
final_fallback.as_deref(),
|
|
3355
|
+
)?;
|
|
3356
|
+
|
|
3357
|
+
return Ok(AgentResult {
|
|
3358
|
+
provider: final_provider,
|
|
3359
|
+
model: final_model,
|
|
3360
|
+
summary,
|
|
3361
|
+
verification: String::new(),
|
|
3362
|
+
completion_status: "step_limit_reached".to_string(),
|
|
3363
|
+
fallback: final_fallback,
|
|
3364
|
+
fallback_reason: final_fallback_reason,
|
|
3365
|
+
total_tokens: turn_total_tokens,
|
|
3366
|
+
input_tokens: last_input_tokens,
|
|
3367
|
+
generated_tokens: turn_generated_tokens,
|
|
3368
|
+
});
|
|
3369
|
+
}))).await;
|
|
2973
3370
|
if agent_result.is_err() && !turn.finished {
|
|
2974
3371
|
if run_control::cancelled() {
|
|
2975
3372
|
turn.interrupt();
|
|
@@ -3016,315 +3413,166 @@ fn plan_mode_allows(action: &str, input: &AgentInput) -> bool {
|
|
|
3016
3413
|
action == "finish" || crate::prompts::agent::PLAN_MODE_ALLOWED_ACTIONS.contains(&action)
|
|
3017
3414
|
}
|
|
3018
3415
|
|
|
3019
|
-
/// Runs
|
|
3020
|
-
|
|
3021
|
-
/// sequential path (a single subagent call, or one mixed in with other actions)
|
|
3022
|
-
/// and the parallel path (`decisions_are_parallel_subagent_batch`, driven by
|
|
3023
|
-
/// `buffer_unordered` in the main step loop) share one implementation. Takes
|
|
3024
|
-
/// the same `&mut dyn FnMut(...)` trait object as `execute_tool` — see the
|
|
3025
|
-
/// comment on that function for why a trait object rather than a generic —
|
|
3026
|
-
/// which lets the parallel path pass a small Mutex-backed adapter closure so
|
|
3027
|
-
/// concurrently-running subagents still share one real approval gate.
|
|
3028
|
-
async fn dispatch_one_subagent(
|
|
3416
|
+
/// Runs a swarm batch of sub-agents to completion and returns the aggregated dossier.
|
|
3417
|
+
pub(super) async fn dispatch_swarm(
|
|
3029
3418
|
root: &Path,
|
|
3030
3419
|
config: &MintConfig,
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
task: &str,
|
|
3420
|
+
parent_chat_id: &str,
|
|
3421
|
+
tasks: Vec<crate::swarm::SwarmTask>,
|
|
3034
3422
|
approve_cb: &mut (dyn FnMut(&AgentApproval) -> Result<ApprovalOutcome, String> + Send),
|
|
3035
3423
|
progress: &mut (dyn FnMut(AgentProgress) + Send),
|
|
3036
3424
|
) -> Result<String, OrchestrationError> {
|
|
3037
|
-
let
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3425
|
+
let (is_plan_mode, _) = crate::mods::turn_policy(root, parent_chat_id);
|
|
3426
|
+
let effective_cap = crate::swarm::effective_max_parallel(Some(config.swarm_max_parallel));
|
|
3427
|
+
let profiles = |name: &str| crate::subagents::find_subagent(name, Some(root));
|
|
3428
|
+
let ctx = crate::swarm::BatchCtx {
|
|
3429
|
+
parent_chat_id,
|
|
3430
|
+
max_parallel: effective_cap,
|
|
3431
|
+
profiles: &profiles,
|
|
3432
|
+
plan_mode: is_plan_mode,
|
|
3042
3433
|
};
|
|
3043
3434
|
|
|
3044
|
-
let
|
|
3045
|
-
|
|
3046
|
-
sub_config.ai_provider = provider.clone();
|
|
3047
|
-
}
|
|
3048
|
-
if let Some(model) = &definition.model {
|
|
3049
|
-
match sub_config.ai_provider.as_str() {
|
|
3050
|
-
"anthropic" => sub_config.anthropic_model = model.clone(),
|
|
3051
|
-
"openai" => sub_config.openai_model = model.clone(),
|
|
3052
|
-
"openrouter" => sub_config.openrouter_model = model.clone(),
|
|
3053
|
-
"deepseek" => sub_config.deepseek_model = model.clone(),
|
|
3054
|
-
"huggingface" => sub_config.hf_model = model.clone(),
|
|
3055
|
-
"local_openai" => sub_config.local_model_name = model.clone(),
|
|
3056
|
-
"ollama" => sub_config.ollama_model = model.clone(),
|
|
3057
|
-
"gemini" => sub_config.gemini_model = model.clone(),
|
|
3058
|
-
_ => {}
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3435
|
+
let approve_mutex = std::sync::Mutex::new(approve_cb);
|
|
3436
|
+
let progress_mutex = std::sync::Mutex::new(progress);
|
|
3061
3437
|
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
.tools
|
|
3082
|
-
.as_ref()
|
|
3083
|
-
.is_none_or(|tools| tools.iter().any(|t| t == "run_shell"));
|
|
3084
|
-
let backend = definition
|
|
3085
|
-
.sandbox
|
|
3086
|
-
.as_deref()
|
|
3087
|
-
.unwrap_or(&config.sandbox_backend);
|
|
3088
|
-
let docker_started = if backend == "docker" && allows_run_shell {
|
|
3089
|
-
match crate::docker_sandbox::start_session(&sub_chat_id, root, &sub_config) {
|
|
3090
|
-
Ok(()) => true,
|
|
3091
|
-
Err(error) if config.sandbox_mode.trim().eq_ignore_ascii_case("enforce") => {
|
|
3092
|
-
return Err(OrchestrationError::Agent(format!(
|
|
3093
|
-
"subagent '{}' requires the docker sandbox (sandboxMode=enforce) but it \
|
|
3094
|
-
failed to start: {error}",
|
|
3095
|
-
definition.name
|
|
3096
|
-
)));
|
|
3438
|
+
let runner = |run: crate::swarm::AgentRun, emitter: crate::swarm::Emitter| {
|
|
3439
|
+
let approve_mutex = &approve_mutex;
|
|
3440
|
+
Box::pin(async move {
|
|
3441
|
+
let mut sub_config = config.clone();
|
|
3442
|
+
if let Some(provider) = &run.provider {
|
|
3443
|
+
sub_config.ai_provider = provider.clone();
|
|
3444
|
+
}
|
|
3445
|
+
if let Some(model) = &run.model {
|
|
3446
|
+
match sub_config.ai_provider.as_str() {
|
|
3447
|
+
"anthropic" => sub_config.anthropic_model = model.clone(),
|
|
3448
|
+
"openai" => sub_config.openai_model = model.clone(),
|
|
3449
|
+
"openrouter" => sub_config.openrouter_model = model.clone(),
|
|
3450
|
+
"deepseek" => sub_config.deepseek_model = model.clone(),
|
|
3451
|
+
"huggingface" => sub_config.hf_model = model.clone(),
|
|
3452
|
+
"local_openai" => sub_config.local_model_name = model.clone(),
|
|
3453
|
+
"ollama" => sub_config.ollama_model = model.clone(),
|
|
3454
|
+
"gemini" => sub_config.gemini_model = model.clone(),
|
|
3455
|
+
_ => {}
|
|
3456
|
+
}
|
|
3097
3457
|
}
|
|
3098
|
-
// "prefer" (or any other non-"enforce" value): fall back to the
|
|
3099
|
-
// subagent's shell commands running unconfined/OS-sandboxed,
|
|
3100
|
-
// matching `run_shell_command`'s own fallback semantics for a
|
|
3101
|
-
// missing OS sandbox binary.
|
|
3102
|
-
Err(_) => false,
|
|
3103
|
-
}
|
|
3104
|
-
} else {
|
|
3105
|
-
false
|
|
3106
|
-
};
|
|
3107
3458
|
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
// (see its doc comment) specifically so this recursive call can
|
|
3125
|
-
// just be awaited directly, with no manual boxing needed here.
|
|
3126
|
-
let subagent_name = definition.name.clone();
|
|
3127
|
-
let mut nested_progress = |event: AgentProgress| {
|
|
3128
|
-
let tagged = match event {
|
|
3129
|
-
AgentProgress::ToolStart {
|
|
3130
|
-
action,
|
|
3131
|
-
input,
|
|
3132
|
-
call_id,
|
|
3133
|
-
..
|
|
3134
|
-
} => AgentProgress::ToolStart {
|
|
3135
|
-
call_id,
|
|
3136
|
-
action,
|
|
3137
|
-
input,
|
|
3138
|
-
subagent: Some(subagent_name.clone()),
|
|
3139
|
-
},
|
|
3140
|
-
AgentProgress::ToolEnd {
|
|
3141
|
-
elapsed_ms,
|
|
3142
|
-
action,
|
|
3143
|
-
input,
|
|
3144
|
-
result,
|
|
3145
|
-
call_id,
|
|
3146
|
-
status,
|
|
3147
|
-
..
|
|
3148
|
-
} => AgentProgress::ToolEnd {
|
|
3149
|
-
elapsed_ms,
|
|
3150
|
-
call_id,
|
|
3151
|
-
status,
|
|
3152
|
-
action,
|
|
3153
|
-
input,
|
|
3154
|
-
result,
|
|
3155
|
-
subagent: Some(subagent_name.clone()),
|
|
3156
|
-
},
|
|
3157
|
-
AgentProgress::ContextCompaction {
|
|
3158
|
-
status, message, ..
|
|
3159
|
-
} => AgentProgress::ContextCompaction {
|
|
3160
|
-
status,
|
|
3161
|
-
message,
|
|
3162
|
-
subagent: Some(subagent_name.clone()),
|
|
3163
|
-
},
|
|
3164
|
-
other => other,
|
|
3165
|
-
};
|
|
3166
|
-
progress(tagged);
|
|
3167
|
-
};
|
|
3168
|
-
let result = orchestrate_agent_loop(
|
|
3169
|
-
&sub_config,
|
|
3170
|
-
&sub_task,
|
|
3171
|
-
root,
|
|
3172
|
-
None,
|
|
3173
|
-
None,
|
|
3174
|
-
None,
|
|
3175
|
-
Some(&sub_chat_id),
|
|
3176
|
-
None,
|
|
3177
|
-
None,
|
|
3178
|
-
None,
|
|
3179
|
-
true,
|
|
3180
|
-
false,
|
|
3181
|
-
&mut *approve_cb,
|
|
3182
|
-
&mut nested_progress,
|
|
3183
|
-
|_| {},
|
|
3184
|
-
)
|
|
3185
|
-
.await;
|
|
3186
|
-
|
|
3187
|
-
let outcome = match result {
|
|
3188
|
-
Ok(agent_result) => Ok(format!(
|
|
3189
|
-
"[Subagent '{}' result]\n{}",
|
|
3190
|
-
definition.name, agent_result.summary
|
|
3191
|
-
)),
|
|
3192
|
-
Err(error) => Err(OrchestrationError::Agent(format!(
|
|
3193
|
-
"subagent '{}' failed: {error}",
|
|
3194
|
-
definition.name
|
|
3195
|
-
))),
|
|
3196
|
-
};
|
|
3197
|
-
// Unconditional teardown — runs on both the Ok and Err arms above, since
|
|
3198
|
-
// an orphaned container left running after a failed subagent is exactly
|
|
3199
|
-
// the failure mode a session-scoped sandbox needs to avoid.
|
|
3200
|
-
if docker_started {
|
|
3201
|
-
crate::docker_sandbox::stop_session(&sub_chat_id);
|
|
3202
|
-
}
|
|
3203
|
-
outcome
|
|
3204
|
-
}
|
|
3459
|
+
let allows_run_shell = run.permissions.allows("run_shell");
|
|
3460
|
+
let backend = run.sandbox.as_deref().unwrap_or(&config.sandbox_backend);
|
|
3461
|
+
let docker_started = if backend == "docker" && allows_run_shell {
|
|
3462
|
+
match crate::docker_sandbox::start_session(&run.chat_id, root, &sub_config) {
|
|
3463
|
+
Ok(()) => true,
|
|
3464
|
+
Err(error) if config.sandbox_mode.trim().eq_ignore_ascii_case("enforce") => {
|
|
3465
|
+
return Err(format!(
|
|
3466
|
+
"agent '{}' requires docker sandbox (sandboxMode=enforce) but failed: {error}",
|
|
3467
|
+
run.label
|
|
3468
|
+
));
|
|
3469
|
+
}
|
|
3470
|
+
Err(_) => false,
|
|
3471
|
+
}
|
|
3472
|
+
} else {
|
|
3473
|
+
false
|
|
3474
|
+
};
|
|
3205
3475
|
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
root: &Path,
|
|
3214
|
-
config: &MintConfig,
|
|
3215
|
-
chat_id: &str,
|
|
3216
|
-
approve: &mut (dyn FnMut(&AgentApproval) -> Result<ApprovalOutcome, String> + Send),
|
|
3217
|
-
progress: &mut (dyn FnMut(AgentProgress) + Send),
|
|
3218
|
-
action_counts: &mut BTreeMap<String, usize>,
|
|
3219
|
-
trajectory: &mut Vec<String>,
|
|
3220
|
-
) -> Vec<ToolResultEntry> {
|
|
3221
|
-
let approve_mutex = std::sync::Mutex::new(approve);
|
|
3222
|
-
// Concurrently-running subagents share one real `progress` sink the same
|
|
3223
|
-
// way they share one real `approve` gate above — each gets a small
|
|
3224
|
-
// Mutex-backed adapter closure so their nested `ToolStart`/`ToolEnd`
|
|
3225
|
-
// events (tagged with their own subagent name in `dispatch_one_subagent`)
|
|
3226
|
-
// still reach the CLI/GUI, interleaved but not corrupted, instead of the
|
|
3227
|
-
// batch staying silent until every item in it finishes.
|
|
3228
|
-
let progress_mutex = std::sync::Mutex::new(progress);
|
|
3476
|
+
let subagent_label = run.label.clone();
|
|
3477
|
+
let sub_task = format!(
|
|
3478
|
+
"{}\n\nTask from parent agent: {}",
|
|
3479
|
+
run.system_prompt, run.instruction
|
|
3480
|
+
);
|
|
3481
|
+
let run_chat_id = run.chat_id.clone();
|
|
3482
|
+
let permissions = run.permissions.clone();
|
|
3229
3483
|
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3484
|
+
let steps_counter = std::sync::atomic::AtomicU32::new(1);
|
|
3485
|
+
let mut nested_progress = |event: AgentProgress| {
|
|
3486
|
+
if matches!(event, AgentProgress::ToolStart { .. }) {
|
|
3487
|
+
steps_counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
3488
|
+
}
|
|
3489
|
+
let tagged = match event {
|
|
3490
|
+
AgentProgress::ToolStart {
|
|
3491
|
+
action,
|
|
3492
|
+
input,
|
|
3493
|
+
call_id,
|
|
3494
|
+
..
|
|
3495
|
+
} => AgentProgress::ToolStart {
|
|
3496
|
+
call_id,
|
|
3497
|
+
action,
|
|
3498
|
+
input,
|
|
3499
|
+
subagent: Some(subagent_label.clone()),
|
|
3500
|
+
},
|
|
3501
|
+
AgentProgress::ToolEnd {
|
|
3502
|
+
elapsed_ms,
|
|
3503
|
+
action,
|
|
3504
|
+
input,
|
|
3505
|
+
result,
|
|
3506
|
+
call_id,
|
|
3507
|
+
status,
|
|
3508
|
+
..
|
|
3509
|
+
} => AgentProgress::ToolEnd {
|
|
3510
|
+
elapsed_ms,
|
|
3511
|
+
call_id,
|
|
3512
|
+
status,
|
|
3513
|
+
action,
|
|
3514
|
+
input,
|
|
3515
|
+
result,
|
|
3516
|
+
subagent: Some(subagent_label.clone()),
|
|
3517
|
+
},
|
|
3518
|
+
AgentProgress::ContextCompaction {
|
|
3519
|
+
status, message, ..
|
|
3520
|
+
} => AgentProgress::ContextCompaction {
|
|
3521
|
+
status,
|
|
3522
|
+
message,
|
|
3523
|
+
subagent: Some(subagent_label.clone()),
|
|
3524
|
+
},
|
|
3525
|
+
other => other,
|
|
3526
|
+
};
|
|
3527
|
+
emitter.progress(tagged);
|
|
3272
3528
|
};
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3529
|
+
|
|
3530
|
+
let mut nested_approve = |approval: &AgentApproval| -> Result<ApprovalOutcome, String> {
|
|
3531
|
+
let mut guard = approve_mutex.lock().unwrap();
|
|
3532
|
+
(*guard)(approval)
|
|
3276
3533
|
};
|
|
3277
|
-
progress_adapter(AgentProgress::ToolEnd {
|
|
3278
|
-
elapsed_ms: None,
|
|
3279
|
-
call_id: Some(call_id.clone()),
|
|
3280
|
-
status: None,
|
|
3281
|
-
action: action.clone(),
|
|
3282
|
-
input: input_val.clone(),
|
|
3283
|
-
result: tool_result.clone(),
|
|
3284
|
-
subagent: None,
|
|
3285
|
-
});
|
|
3286
|
-
(
|
|
3287
|
-
index,
|
|
3288
|
-
call_id,
|
|
3289
|
-
thought,
|
|
3290
|
-
action,
|
|
3291
|
-
input_val,
|
|
3292
|
-
action_key,
|
|
3293
|
-
tool_result,
|
|
3294
|
-
)
|
|
3295
|
-
});
|
|
3296
|
-
}
|
|
3297
3534
|
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
use the finish action now. Otherwise choose a different necessary action.]",
|
|
3316
|
-
);
|
|
3317
|
-
}
|
|
3535
|
+
let loop_fut = Box::pin(orchestrate_agent_loop(
|
|
3536
|
+
&sub_config,
|
|
3537
|
+
&sub_task,
|
|
3538
|
+
root,
|
|
3539
|
+
None,
|
|
3540
|
+
None,
|
|
3541
|
+
None,
|
|
3542
|
+
Some(&run_chat_id),
|
|
3543
|
+
None,
|
|
3544
|
+
None,
|
|
3545
|
+
None,
|
|
3546
|
+
true,
|
|
3547
|
+
false,
|
|
3548
|
+
&mut nested_approve,
|
|
3549
|
+
&mut nested_progress,
|
|
3550
|
+
|_| {},
|
|
3551
|
+
));
|
|
3318
3552
|
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3553
|
+
let res = crate::swarm::scope_permissions(Some(permissions), loop_fut).await;
|
|
3554
|
+
|
|
3555
|
+
if docker_started {
|
|
3556
|
+
crate::docker_sandbox::stop_session(&run_chat_id);
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
match res {
|
|
3560
|
+
Ok(agent_result) => Ok(crate::swarm::RunnerOutput {
|
|
3561
|
+
text: agent_result.summary,
|
|
3562
|
+
steps: steps_counter.load(std::sync::atomic::Ordering::Relaxed),
|
|
3563
|
+
}),
|
|
3564
|
+
Err(err) => Err(err.to_string()),
|
|
3565
|
+
}
|
|
3566
|
+
})
|
|
3567
|
+
};
|
|
3568
|
+
|
|
3569
|
+
let mut on_event = |event: crate::swarm::SwarmEvent| {
|
|
3570
|
+
let mut guard = progress_mutex.lock().unwrap();
|
|
3571
|
+
(*guard)(event.into());
|
|
3572
|
+
};
|
|
3573
|
+
|
|
3574
|
+
let outcomes = crate::swarm::run_batch(&ctx, tasks, runner, &mut on_event).await;
|
|
3575
|
+
Ok(crate::swarm::format_batch_result(&outcomes))
|
|
3328
3576
|
}
|
|
3329
3577
|
|
|
3330
3578
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -3358,6 +3606,7 @@ async fn run_parallel_read_only_batch(
|
|
|
3358
3606
|
let decision = decision;
|
|
3359
3607
|
|
|
3360
3608
|
tasks.push(async move {
|
|
3609
|
+
let activity_call_id=call_id.clone();
|
|
3361
3610
|
if !fast_mode && !thought.trim().is_empty() {
|
|
3362
3611
|
let t = thought.trim();
|
|
3363
3612
|
let mut guard = progress_mutex.lock().unwrap();
|
|
@@ -3376,13 +3625,15 @@ async fn run_parallel_read_only_batch(
|
|
|
3376
3625
|
|
|
3377
3626
|
{
|
|
3378
3627
|
let mut guard = progress_mutex.lock().unwrap();
|
|
3379
|
-
(*guard)(AgentProgress::ToolStart { call_id: Some(
|
|
3628
|
+
(*guard)(AgentProgress::ToolStart { call_id: Some(activity_call_id.clone()),
|
|
3380
3629
|
action: action.clone(),
|
|
3381
3630
|
input: input_val.clone(),
|
|
3382
3631
|
subagent: None,
|
|
3383
3632
|
});
|
|
3384
3633
|
}
|
|
3385
3634
|
|
|
3635
|
+
let mut images=Vec::new();
|
|
3636
|
+
let mut typed_status=None;
|
|
3386
3637
|
let result: String = if pinned_mcp_server.is_some_and(|p| {
|
|
3387
3638
|
(action == "mcp_tool" || action == "mcp_list_tools") && decision.input.server != p
|
|
3388
3639
|
}) {
|
|
@@ -3409,17 +3660,18 @@ async fn run_parallel_read_only_batch(
|
|
|
3409
3660
|
(*guard)(event);
|
|
3410
3661
|
};
|
|
3411
3662
|
|
|
3412
|
-
let (tool_result, success) = match
|
|
3663
|
+
let (tool_result, success) = match execute_tool_outcome(
|
|
3413
3664
|
root,
|
|
3414
3665
|
config,
|
|
3415
3666
|
&decision,
|
|
3416
3667
|
chat_id,
|
|
3417
3668
|
&mut approve_adapter,
|
|
3418
3669
|
&mut progress_adapter,
|
|
3670
|
+
&activity_call_id,
|
|
3419
3671
|
)
|
|
3420
3672
|
.await
|
|
3421
3673
|
{
|
|
3422
|
-
Ok(
|
|
3674
|
+
Ok(outcome) => {images=outcome.images;typed_status=Some(outcome.status);(outcome.text,outcome.status==crate::mcp_result::ToolStatus::Success)},
|
|
3423
3675
|
Err(err) => (format!("Error: {}", err), false),
|
|
3424
3676
|
};
|
|
3425
3677
|
|
|
@@ -3451,7 +3703,7 @@ async fn run_parallel_read_only_batch(
|
|
|
3451
3703
|
|
|
3452
3704
|
{
|
|
3453
3705
|
let mut guard = progress_mutex.lock().unwrap();
|
|
3454
|
-
(*guard)(AgentProgress::ToolEnd { elapsed_ms: None, call_id: Some(
|
|
3706
|
+
(*guard)(AgentProgress::ToolEnd { elapsed_ms: None, call_id: Some(activity_call_id.clone()), status: typed_status,
|
|
3455
3707
|
action: action.clone(),
|
|
3456
3708
|
input: input_val.clone(),
|
|
3457
3709
|
result: result.clone(),
|
|
@@ -3460,7 +3712,7 @@ async fn run_parallel_read_only_batch(
|
|
|
3460
3712
|
}
|
|
3461
3713
|
|
|
3462
3714
|
(
|
|
3463
|
-
index, call_id, thought, action, input_val, action_key, result,
|
|
3715
|
+
index, call_id, thought, action, input_val, action_key, result, images, typed_status,
|
|
3464
3716
|
)
|
|
3465
3717
|
});
|
|
3466
3718
|
}
|
|
@@ -3473,7 +3725,12 @@ async fn run_parallel_read_only_batch(
|
|
|
3473
3725
|
results.sort_by_key(|(index, ..)| *index);
|
|
3474
3726
|
|
|
3475
3727
|
let mut step_tool_results = Vec::with_capacity(results.len());
|
|
3476
|
-
for (_, call_id, thought, action, input_val, action_key, result) in
|
|
3728
|
+
for (_, call_id, thought, action, input_val, action_key, result, images, typed_status) in
|
|
3729
|
+
results
|
|
3730
|
+
{
|
|
3731
|
+
if !images.is_empty() {
|
|
3732
|
+
step_images.insert(call_id.clone(), images);
|
|
3733
|
+
}
|
|
3477
3734
|
let action_count = {
|
|
3478
3735
|
let count = action_counts.entry(action_key).or_insert(0);
|
|
3479
3736
|
*count += 1;
|
|
@@ -3515,7 +3772,11 @@ async fn run_parallel_read_only_batch(
|
|
|
3515
3772
|
&final_result,
|
|
3516
3773
|
));
|
|
3517
3774
|
|
|
3518
|
-
|
|
3775
|
+
let mut entry = ToolResultEntry::from((call_id, action, input_val, final_result));
|
|
3776
|
+
if let Some(status) = typed_status {
|
|
3777
|
+
entry.status = status;
|
|
3778
|
+
}
|
|
3779
|
+
step_tool_results.push(entry);
|
|
3519
3780
|
}
|
|
3520
3781
|
|
|
3521
3782
|
step_tool_results
|
|
@@ -3590,10 +3851,19 @@ async fn execute_tool_outcome_reviewed(
|
|
|
3590
3851
|
origin.as_deref(),
|
|
3591
3852
|
)
|
|
3592
3853
|
.map_err(OrchestrationError::Agent)?;
|
|
3593
|
-
let
|
|
3854
|
+
let execution = execute_tool_outcome_reviewed_inner(
|
|
3594
3855
|
root, config, decision, chat_id, approve, progress, call_id, expected,
|
|
3595
|
-
)
|
|
3596
|
-
|
|
3856
|
+
);
|
|
3857
|
+
let result = if matches!(
|
|
3858
|
+
decision.action.as_str(),
|
|
3859
|
+
"run_shell" | "verify" | "run_tests" | "run_typecheck" | "run_linter"
|
|
3860
|
+
) {
|
|
3861
|
+
// Foreground shell cancellation joins process cleanup and imports any
|
|
3862
|
+
// checkpointed screenshots before the turn is marked interrupted.
|
|
3863
|
+
execution.await
|
|
3864
|
+
} else {
|
|
3865
|
+
run_control::run(Box::pin(execution)).await
|
|
3866
|
+
};
|
|
3597
3867
|
let _ = crate::mods::event(
|
|
3598
3868
|
root,
|
|
3599
3869
|
chat_id,
|
|
@@ -3694,6 +3964,67 @@ async fn execute_tool_outcome_reviewed_inner(
|
|
|
3694
3964
|
expected: Option<&mcp_review::McpReview>,
|
|
3695
3965
|
) -> Result<crate::integrations::mcp_result::ToolOutcome, OrchestrationError> {
|
|
3696
3966
|
run_control::check()?;
|
|
3967
|
+
if decision.action == "view_image" {
|
|
3968
|
+
if decision.input.path.is_empty() == decision.input.artifact_id.is_empty() {
|
|
3969
|
+
return Err(OrchestrationError::Agent(
|
|
3970
|
+
"Provide exactly one of path or artifactId".into(),
|
|
3971
|
+
));
|
|
3972
|
+
}
|
|
3973
|
+
let root = root.to_path_buf();
|
|
3974
|
+
let config = config.clone();
|
|
3975
|
+
let chat = chat_id.to_owned();
|
|
3976
|
+
let call = call_id.to_owned();
|
|
3977
|
+
let path = decision.input.path.clone();
|
|
3978
|
+
let artifact_id = decision.input.artifact_id.clone();
|
|
3979
|
+
let out = tokio::task::spawn_blocking(
|
|
3980
|
+
move || -> Result<crate::mcp_result::ToolOutcome, String> {
|
|
3981
|
+
if !artifact_id.is_empty() {
|
|
3982
|
+
return crate::media::tool_images::view_artifact(&artifact_id, &chat);
|
|
3983
|
+
}
|
|
3984
|
+
let path = agent_read_path(&root, &path, &config).map_err(|e| e.to_string())?;
|
|
3985
|
+
let path = assert_path_capability(&path, Capability::Read, &config)
|
|
3986
|
+
.map_err(|e| e.to_string())?;
|
|
3987
|
+
if std::fs::metadata(&path).map_err(|e| e.to_string())?.len()
|
|
3988
|
+
> crate::media::tool_images::ORIGINAL_LIMIT as u64
|
|
3989
|
+
{
|
|
3990
|
+
return Err("Image exceeds 20 MiB".into());
|
|
3991
|
+
}
|
|
3992
|
+
let mime = match path
|
|
3993
|
+
.extension()
|
|
3994
|
+
.and_then(|e| e.to_str())
|
|
3995
|
+
.unwrap_or("")
|
|
3996
|
+
.to_ascii_lowercase()
|
|
3997
|
+
.as_str()
|
|
3998
|
+
{
|
|
3999
|
+
"png" => "image/png",
|
|
4000
|
+
"jpg" | "jpeg" => "image/jpeg",
|
|
4001
|
+
"gif" => "image/gif",
|
|
4002
|
+
"webp" => "image/webp",
|
|
4003
|
+
"svg" => "image/svg+xml",
|
|
4004
|
+
_ => return Err("Unsupported image type".into()),
|
|
4005
|
+
};
|
|
4006
|
+
let bytes = std::fs::read(&path).map_err(|e| e.to_string())?;
|
|
4007
|
+
crate::media::tool_images::save(
|
|
4008
|
+
&bytes,
|
|
4009
|
+
mime,
|
|
4010
|
+
path.file_name().and_then(|n| n.to_str()).unwrap_or("Image"),
|
|
4011
|
+
&chat,
|
|
4012
|
+
&call,
|
|
4013
|
+
"view_image",
|
|
4014
|
+
None,
|
|
4015
|
+
)
|
|
4016
|
+
},
|
|
4017
|
+
)
|
|
4018
|
+
.await
|
|
4019
|
+
.map_err(|e| OrchestrationError::Agent(e.to_string()))?
|
|
4020
|
+
.map_err(OrchestrationError::Agent)?;
|
|
4021
|
+
progress(AgentProgress::ToolArtifacts {
|
|
4022
|
+
call_id: call_id.into(),
|
|
4023
|
+
artifacts: out.artifacts.clone(),
|
|
4024
|
+
warnings: out.warnings.clone(),
|
|
4025
|
+
});
|
|
4026
|
+
return Ok(out);
|
|
4027
|
+
}
|
|
3697
4028
|
if matches!(decision.action.as_str(), "mcp_tool" | "mcp_list_tools") {
|
|
3698
4029
|
let mut out = tools::plugins_mcp::execute_reviewed(
|
|
3699
4030
|
&decision.action,
|
|
@@ -3723,9 +4054,134 @@ async fn execute_tool_outcome_reviewed_inner(
|
|
|
3723
4054
|
}
|
|
3724
4055
|
Ok(out)
|
|
3725
4056
|
} else {
|
|
3726
|
-
|
|
4057
|
+
let mut tool_config = config.clone();
|
|
4058
|
+
let bridge = if decision.action == "run_shell" && !decision.input.background {
|
|
4059
|
+
crate::browser::workflow::ShellBridge::prepare(
|
|
4060
|
+
&decision.input.command,
|
|
4061
|
+
root,
|
|
4062
|
+
&mut tool_config,
|
|
4063
|
+
)
|
|
4064
|
+
.map_err(OrchestrationError::Agent)?
|
|
4065
|
+
} else {
|
|
4066
|
+
None
|
|
4067
|
+
};
|
|
4068
|
+
let checkpoint = reliability::checkpoint();
|
|
4069
|
+
let text = match execute_tool_text(root, &tool_config, decision, chat_id, approve, progress)
|
|
3727
4070
|
.await
|
|
3728
|
-
|
|
4071
|
+
{
|
|
4072
|
+
Ok(text) => text,
|
|
4073
|
+
Err(error) => {
|
|
4074
|
+
if decision.action.starts_with("browser_") {
|
|
4075
|
+
reliability::record_browser(&decision.action, None, false);
|
|
4076
|
+
}
|
|
4077
|
+
reliability::complete_tool(&decision.action, call_id, checkpoint, false);
|
|
4078
|
+
return Err(error);
|
|
4079
|
+
}
|
|
4080
|
+
};
|
|
4081
|
+
let mut out = if decision.action == "browser_screenshot"
|
|
4082
|
+
&& text.starts_with("data:image/png;base64,")
|
|
4083
|
+
{
|
|
4084
|
+
let bytes = BASE64_STANDARD
|
|
4085
|
+
.decode(text.trim_start_matches("data:image/png;base64,"))
|
|
4086
|
+
.map_err(|e| OrchestrationError::Agent(e.to_string()))?;
|
|
4087
|
+
let metadata = crate::browser::capture_metadata().await;
|
|
4088
|
+
let out = crate::media::tool_images::save(
|
|
4089
|
+
&bytes,
|
|
4090
|
+
"image/png",
|
|
4091
|
+
&format!("screenshot-{call_id}.png"),
|
|
4092
|
+
chat_id,
|
|
4093
|
+
call_id,
|
|
4094
|
+
"browser",
|
|
4095
|
+
Some(metadata),
|
|
4096
|
+
)
|
|
4097
|
+
.map_err(OrchestrationError::Agent)?;
|
|
4098
|
+
progress(AgentProgress::ToolArtifacts {
|
|
4099
|
+
call_id: call_id.into(),
|
|
4100
|
+
artifacts: out.artifacts.clone(),
|
|
4101
|
+
warnings: out.warnings.clone(),
|
|
4102
|
+
});
|
|
4103
|
+
out
|
|
4104
|
+
} else {
|
|
4105
|
+
crate::mcp_result::ToolOutcome::from(text)
|
|
4106
|
+
};
|
|
4107
|
+
let mut workflow_verified = None;
|
|
4108
|
+
if let Some(bridge) = bridge {
|
|
4109
|
+
match bridge.images(chat_id, call_id) {
|
|
4110
|
+
Ok((images, verified)) => {
|
|
4111
|
+
workflow_verified = Some(verified);
|
|
4112
|
+
out.text.push_str(&images.text);
|
|
4113
|
+
out.images.extend(images.images);
|
|
4114
|
+
out.artifacts.extend(images.artifacts);
|
|
4115
|
+
out.warnings.extend(images.warnings);
|
|
4116
|
+
progress(AgentProgress::ToolArtifacts {
|
|
4117
|
+
call_id: call_id.into(),
|
|
4118
|
+
artifacts: out.artifacts.clone(),
|
|
4119
|
+
warnings: out.warnings.clone(),
|
|
4120
|
+
});
|
|
4121
|
+
}
|
|
4122
|
+
Err(error) => {
|
|
4123
|
+
out.text.push_str(&format!("\nWarning: {error}"));
|
|
4124
|
+
out.warnings.push(error);
|
|
4125
|
+
workflow_verified = Some(false);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
if decision.action.starts_with("browser_") {
|
|
4130
|
+
let report = serde_json::from_str::<Value>(&out.text).ok();
|
|
4131
|
+
if report
|
|
4132
|
+
.as_ref()
|
|
4133
|
+
.is_some_and(|r| r.get("error").is_some_and(|e| !e.is_null()))
|
|
4134
|
+
{
|
|
4135
|
+
out.status = crate::mcp_result::ToolStatus::Failed;
|
|
4136
|
+
}
|
|
4137
|
+
reliability::record_browser(
|
|
4138
|
+
&decision.action,
|
|
4139
|
+
report.as_ref(),
|
|
4140
|
+
out.status == crate::mcp_result::ToolStatus::Success,
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
let mut entries = reliability::complete_tool(
|
|
4144
|
+
&decision.action,
|
|
4145
|
+
call_id,
|
|
4146
|
+
checkpoint,
|
|
4147
|
+
out.status == crate::mcp_result::ToolStatus::Success,
|
|
4148
|
+
);
|
|
4149
|
+
if let Some(verified) = workflow_verified {
|
|
4150
|
+
entries = reliability::mark_workflow(call_id, verified);
|
|
4151
|
+
}
|
|
4152
|
+
if decision.action == "shell_output" {
|
|
4153
|
+
entries = reliability::for_job(&decision.input.job_id);
|
|
4154
|
+
}
|
|
4155
|
+
if !entries.is_empty() {
|
|
4156
|
+
out.status = if entries
|
|
4157
|
+
.iter()
|
|
4158
|
+
.any(|e| e.state == reliability::ExecutionState::Cancelled)
|
|
4159
|
+
{
|
|
4160
|
+
crate::mcp_result::ToolStatus::Cancelled
|
|
4161
|
+
} else if entries
|
|
4162
|
+
.iter()
|
|
4163
|
+
.any(|e| e.state == reliability::ExecutionState::Failed)
|
|
4164
|
+
{
|
|
4165
|
+
crate::mcp_result::ToolStatus::Failed
|
|
4166
|
+
} else if entries
|
|
4167
|
+
.iter()
|
|
4168
|
+
.any(|e| e.state == reliability::ExecutionState::Running)
|
|
4169
|
+
{
|
|
4170
|
+
crate::mcp_result::ToolStatus::Running
|
|
4171
|
+
} else if entries
|
|
4172
|
+
.iter()
|
|
4173
|
+
.any(|e| e.state == reliability::ExecutionState::Unknown)
|
|
4174
|
+
{
|
|
4175
|
+
crate::mcp_result::ToolStatus::Unknown
|
|
4176
|
+
} else {
|
|
4177
|
+
crate::mcp_result::ToolStatus::Success
|
|
4178
|
+
};
|
|
4179
|
+
progress(AgentProgress::ExecutionUpdate {
|
|
4180
|
+
call_id: call_id.into(),
|
|
4181
|
+
executions: entries,
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
Ok(out)
|
|
3729
4185
|
}
|
|
3730
4186
|
}
|
|
3731
4187
|
|
|
@@ -3737,6 +4193,14 @@ async fn execute_tool_text(
|
|
|
3737
4193
|
approve_cb: &mut (dyn FnMut(&AgentApproval) -> Result<ApprovalOutcome, String> + Send),
|
|
3738
4194
|
progress: &mut (dyn FnMut(AgentProgress) + Send),
|
|
3739
4195
|
) -> Result<String, OrchestrationError> {
|
|
4196
|
+
if let Some(permissions) = crate::swarm::current_permissions() {
|
|
4197
|
+
if !permissions.allows(&decision.action) {
|
|
4198
|
+
return Err(OrchestrationError::Agent(format!(
|
|
4199
|
+
"action '{}' is not permitted by this subagent's permissions",
|
|
4200
|
+
decision.action
|
|
4201
|
+
)));
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
3740
4204
|
let input = &decision.input;
|
|
3741
4205
|
match decision.action.as_str() {
|
|
3742
4206
|
"mod_help" | "mod_list" | "mod_validate" | "mod_publish" => {
|
|
@@ -3787,11 +4251,6 @@ async fn execute_tool_text(
|
|
|
3787
4251
|
)
|
|
3788
4252
|
.await
|
|
3789
4253
|
}
|
|
3790
|
-
"conversation_summary" => Ok(serde_json::json!({
|
|
3791
|
-
"status": "acknowledged",
|
|
3792
|
-
"message": "Previous steps summary is already recorded in context."
|
|
3793
|
-
})
|
|
3794
|
-
.to_string()),
|
|
3795
4254
|
"browser_open"
|
|
3796
4255
|
| "browser_click"
|
|
3797
4256
|
| "browser_type"
|
|
@@ -3853,7 +4312,7 @@ async fn execute_tool_text(
|
|
|
3853
4312
|
.await
|
|
3854
4313
|
}
|
|
3855
4314
|
"avatar_signal" => tools::avatar::execute(input).await,
|
|
3856
|
-
"run_plugin" | "dispatch_subagent" | "mcp_tool" | "mcp_list_tools" => {
|
|
4315
|
+
"run_plugin" | "dispatch_agents" | "dispatch_subagent" | "mcp_tool" | "mcp_list_tools" => {
|
|
3857
4316
|
tools::plugins_mcp::execute(
|
|
3858
4317
|
decision.action.as_str(),
|
|
3859
4318
|
input,
|
|
@@ -3953,7 +4412,23 @@ where
|
|
|
3953
4412
|
}
|
|
3954
4413
|
|
|
3955
4414
|
fn parse_tool_input(action: &str, input: Value) -> Result<AgentInput, OrchestrationError> {
|
|
3956
|
-
|
|
4415
|
+
let normalized = if action == "run_shell" {
|
|
4416
|
+
match &input {
|
|
4417
|
+
Value::String(cmd) if !cmd.trim().is_empty() => {
|
|
4418
|
+
if let Ok(val) = serde_json::from_str::<Value>(cmd)
|
|
4419
|
+
&& val.is_object()
|
|
4420
|
+
{
|
|
4421
|
+
val
|
|
4422
|
+
} else {
|
|
4423
|
+
serde_json::json!({ "command": cmd.trim() })
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
_ => input,
|
|
4427
|
+
}
|
|
4428
|
+
} else {
|
|
4429
|
+
input
|
|
4430
|
+
};
|
|
4431
|
+
decode_agent_input(normalized).map_err(|error| {
|
|
3957
4432
|
OrchestrationError::Agent(format!("invalid arguments for tool '{action}': {error}"))
|
|
3958
4433
|
})
|
|
3959
4434
|
}
|
|
@@ -3994,7 +4469,12 @@ fn append_native_tool_results(
|
|
|
3994
4469
|
tool_result_content.push(ContentBlock::ToolResult {
|
|
3995
4470
|
tool_use_id: call_id.clone(),
|
|
3996
4471
|
content: final_result.clone(),
|
|
3997
|
-
is_error:
|
|
4472
|
+
is_error: matches!(
|
|
4473
|
+
*status,
|
|
4474
|
+
crate::mcp_result::ToolStatus::Failed
|
|
4475
|
+
| crate::mcp_result::ToolStatus::TimedOut
|
|
4476
|
+
| crate::mcp_result::ToolStatus::Cancelled
|
|
4477
|
+
),
|
|
3998
4478
|
});
|
|
3999
4479
|
if let Some(data_uris) = images.get(call_id) {
|
|
4000
4480
|
for data_uri in data_uris {
|
|
@@ -4019,12 +4499,12 @@ fn append_native_tool_results(
|
|
|
4019
4499
|
}
|
|
4020
4500
|
|
|
4021
4501
|
#[derive(Debug, Clone)]
|
|
4022
|
-
struct ToolResultEntry {
|
|
4023
|
-
call_id: String,
|
|
4024
|
-
action: String,
|
|
4025
|
-
input: Value,
|
|
4026
|
-
text: String,
|
|
4027
|
-
status: crate::mcp_result::ToolStatus,
|
|
4502
|
+
pub(super) struct ToolResultEntry {
|
|
4503
|
+
pub(super) call_id: String,
|
|
4504
|
+
pub(super) action: String,
|
|
4505
|
+
pub(super) input: Value,
|
|
4506
|
+
pub(super) text: String,
|
|
4507
|
+
pub(super) status: crate::mcp_result::ToolStatus,
|
|
4028
4508
|
}
|
|
4029
4509
|
|
|
4030
4510
|
// Legacy tools determine their outcome once at this adapter; MCP supplies its
|
|
@@ -4032,11 +4512,12 @@ struct ToolResultEntry {
|
|
|
4032
4512
|
impl From<(String, String, Value, String)> for ToolResultEntry {
|
|
4033
4513
|
fn from((call_id, action, input, text): (String, String, Value, String)) -> Self {
|
|
4034
4514
|
let mut status = crate::mcp_result::ToolOutcome::from(text.clone()).status;
|
|
4035
|
-
if text.starts_with("Skipped")
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4515
|
+
if text.starts_with("Skipped") {
|
|
4516
|
+
status = crate::mcp_result::ToolStatus::Skipped;
|
|
4517
|
+
} else if matches!(
|
|
4518
|
+
action.as_str(),
|
|
4519
|
+
"run_shell" | "verify" | "run_tests" | "run_typecheck" | "run_linter"
|
|
4520
|
+
) && shell_result_failed(&text)
|
|
4040
4521
|
{
|
|
4041
4522
|
status = crate::mcp_result::ToolStatus::Failed;
|
|
4042
4523
|
}
|
|
@@ -4857,7 +5338,7 @@ mod tests {
|
|
|
4857
5338
|
"exit: 1\nmode: normal\nsandboxed: true\nstdout:\nfail\nstderr:\n"
|
|
4858
5339
|
));
|
|
4859
5340
|
// A killed/unknown-status process isn't treated as a confirmed failure.
|
|
4860
|
-
assert!(
|
|
5341
|
+
assert!(shell_result_failed(
|
|
4861
5342
|
"exit: unknown\nmode: normal\nsandboxed: true\nstdout:\nstderr:\n"
|
|
4862
5343
|
));
|
|
4863
5344
|
}
|
|
@@ -4882,8 +5363,8 @@ mod tests {
|
|
|
4882
5363
|
assert!(unverified_modification(Some(2), Some(1), ""));
|
|
4883
5364
|
// Edited, verified after — satisfied regardless of what verify found.
|
|
4884
5365
|
assert!(!unverified_modification(Some(2), Some(3), ""));
|
|
4885
|
-
//
|
|
4886
|
-
assert!(
|
|
5366
|
+
// An explanation cannot promote an unchecked edit to verified.
|
|
5367
|
+
assert!(unverified_modification(
|
|
4887
5368
|
Some(2),
|
|
4888
5369
|
None,
|
|
4889
5370
|
"Docs-only change, no test suite."
|
|
@@ -4899,9 +5380,8 @@ mod tests {
|
|
|
4899
5380
|
// "claims success while a check is actually failing" case.
|
|
4900
5381
|
assert!(unacknowledged_verify_failure(Some(true), ""));
|
|
4901
5382
|
assert!(unacknowledged_verify_failure(Some(true), "n/a"));
|
|
4902
|
-
//
|
|
4903
|
-
|
|
4904
|
-
assert!(!unacknowledged_verify_failure(
|
|
5383
|
+
// Even an explanation of pre-existing failures does not make them pass.
|
|
5384
|
+
assert!(unacknowledged_verify_failure(
|
|
4905
5385
|
Some(true),
|
|
4906
5386
|
"2 pre-existing test failures unrelated to this change; see notes."
|
|
4907
5387
|
));
|
|
@@ -5171,8 +5651,8 @@ mod tests {
|
|
|
5171
5651
|
}
|
|
5172
5652
|
|
|
5173
5653
|
#[test]
|
|
5174
|
-
fn
|
|
5175
|
-
assert!(
|
|
5654
|
+
fn explicit_written_justification_does_not_verify_an_unchecked_edit() {
|
|
5655
|
+
assert!(unverified_modification(
|
|
5176
5656
|
Some(2),
|
|
5177
5657
|
None,
|
|
5178
5658
|
"Documentation-only change; no test suite in this repo."
|
|
@@ -5198,41 +5678,8 @@ mod tests {
|
|
|
5198
5678
|
)
|
|
5199
5679
|
}
|
|
5200
5680
|
|
|
5201
|
-
#[test]
|
|
5202
|
-
fn two_or_more_subagent_only_decisions_qualify_for_the_parallel_batch() {
|
|
5203
|
-
let decisions = vec![
|
|
5204
|
-
decision_with_action("dispatch_subagent"),
|
|
5205
|
-
decision_with_action("dispatch_subagent"),
|
|
5206
|
-
];
|
|
5207
|
-
assert!(decisions_are_parallel_subagent_batch(&decisions));
|
|
5208
|
-
|
|
5209
|
-
let decisions = vec![
|
|
5210
|
-
decision_with_action("dispatch_subagent"),
|
|
5211
|
-
decision_with_action("dispatch_subagent"),
|
|
5212
|
-
decision_with_action("dispatch_subagent"),
|
|
5213
|
-
];
|
|
5214
|
-
assert!(decisions_are_parallel_subagent_batch(&decisions));
|
|
5215
|
-
}
|
|
5216
|
-
|
|
5217
|
-
#[test]
|
|
5218
|
-
fn a_lone_subagent_dispatch_stays_sequential() {
|
|
5219
|
-
let decisions = vec![decision_with_action("dispatch_subagent")];
|
|
5220
|
-
assert!(!decisions_are_parallel_subagent_batch(&decisions));
|
|
5221
|
-
}
|
|
5222
|
-
|
|
5223
|
-
#[test]
|
|
5224
|
-
fn subagent_dispatch_mixed_with_another_action_stays_sequential() {
|
|
5225
|
-
let decisions = vec![
|
|
5226
|
-
decision_with_action("dispatch_subagent"),
|
|
5227
|
-
decision_with_action("dispatch_subagent"),
|
|
5228
|
-
decision_with_action("read_file"),
|
|
5229
|
-
];
|
|
5230
|
-
assert!(!decisions_are_parallel_subagent_batch(&decisions));
|
|
5231
|
-
}
|
|
5232
|
-
|
|
5233
5681
|
#[test]
|
|
5234
5682
|
fn empty_decisions_never_qualify_for_the_parallel_batch() {
|
|
5235
|
-
assert!(!decisions_are_parallel_subagent_batch(&[]));
|
|
5236
5683
|
assert!(!decisions_are_parallel_read_only_batch(&[]));
|
|
5237
5684
|
}
|
|
5238
5685
|
|
|
@@ -6088,4 +6535,101 @@ for line in sys.stdin:
|
|
|
6088
6535
|
assert!(result.contains("render failed"));
|
|
6089
6536
|
crate::mcp::close_mcp_session(&server);
|
|
6090
6537
|
}
|
|
6538
|
+
|
|
6539
|
+
#[test]
|
|
6540
|
+
fn test_fault_tolerant_null_handling() {
|
|
6541
|
+
let input = serde_json::json!({
|
|
6542
|
+
"command": "curl -sL https://example.com",
|
|
6543
|
+
"city": null,
|
|
6544
|
+
"columns": null,
|
|
6545
|
+
"verification": null,
|
|
6546
|
+
"filter": null
|
|
6547
|
+
});
|
|
6548
|
+
let parsed = decode_agent_input(input).unwrap();
|
|
6549
|
+
assert_eq!(parsed.command, "curl -sL https://example.com");
|
|
6550
|
+
assert_eq!(parsed.city, "");
|
|
6551
|
+
assert_eq!(parsed.verification, "");
|
|
6552
|
+
}
|
|
6553
|
+
|
|
6554
|
+
#[test]
|
|
6555
|
+
fn test_unpack_nested_arguments() {
|
|
6556
|
+
// Stringified JSON arguments
|
|
6557
|
+
let str_args = serde_json::json!({
|
|
6558
|
+
"action": "run_shell",
|
|
6559
|
+
"arguments": "{\"command\": \"ls -la\", \"city\": null}"
|
|
6560
|
+
});
|
|
6561
|
+
let parsed_str = decode_agent_input(str_args).unwrap();
|
|
6562
|
+
assert_eq!(parsed_str.command, "ls -la");
|
|
6563
|
+
|
|
6564
|
+
// Object arguments
|
|
6565
|
+
let obj_args = serde_json::json!({
|
|
6566
|
+
"action": "run_shell",
|
|
6567
|
+
"arguments": { "command": "pwd" }
|
|
6568
|
+
});
|
|
6569
|
+
let parsed_obj = decode_agent_input(obj_args).unwrap();
|
|
6570
|
+
assert_eq!(parsed_obj.command, "pwd");
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
#[test]
|
|
6574
|
+
fn test_shell_command_aliases() {
|
|
6575
|
+
for (alias, expected) in [
|
|
6576
|
+
("cmd", "echo 1"),
|
|
6577
|
+
("script", "echo 2"),
|
|
6578
|
+
("exec", "echo 3"),
|
|
6579
|
+
("code", "echo 4"),
|
|
6580
|
+
("bash", "echo 5"),
|
|
6581
|
+
("sh", "echo 6"),
|
|
6582
|
+
] {
|
|
6583
|
+
let input = serde_json::json!({ alias: expected });
|
|
6584
|
+
let parsed = decode_agent_input(input).unwrap();
|
|
6585
|
+
assert_eq!(parsed.command, expected, "Failed for alias {alias}");
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6589
|
+
#[test]
|
|
6590
|
+
fn test_parse_tool_input_raw_shell_string() {
|
|
6591
|
+
let raw = Value::String("curl -sL --max-time 25 https://sndiary02.com".into());
|
|
6592
|
+
let parsed = parse_tool_input("run_shell", raw).unwrap();
|
|
6593
|
+
assert_eq!(
|
|
6594
|
+
parsed.command,
|
|
6595
|
+
"curl -sL --max-time 25 https://sndiary02.com"
|
|
6596
|
+
);
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6599
|
+
#[test]
|
|
6600
|
+
fn test_parse_tool_input_prunes_dummy_null_strings() {
|
|
6601
|
+
let raw = serde_json::json!({
|
|
6602
|
+
"command": "curl -sL https://example.com",
|
|
6603
|
+
"path": "null",
|
|
6604
|
+
"query": "null",
|
|
6605
|
+
"name": "null",
|
|
6606
|
+
"title": "undefined",
|
|
6607
|
+
"url": "none"
|
|
6608
|
+
});
|
|
6609
|
+
let parsed = parse_tool_input("run_shell", raw).unwrap();
|
|
6610
|
+
assert_eq!(parsed.command, "curl -sL https://example.com");
|
|
6611
|
+
assert_eq!(parsed.path, "");
|
|
6612
|
+
assert_eq!(parsed.query, "");
|
|
6613
|
+
assert_eq!(parsed.name, "");
|
|
6614
|
+
assert_eq!(parsed.title, "");
|
|
6615
|
+
assert_eq!(parsed.url, "");
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6618
|
+
#[test]
|
|
6619
|
+
fn test_is_dummy_value() {
|
|
6620
|
+
assert!(is_dummy_value("null"));
|
|
6621
|
+
assert!(is_dummy_value("NULL"));
|
|
6622
|
+
assert!(is_dummy_value(" null "));
|
|
6623
|
+
assert!(is_dummy_value("undefined"));
|
|
6624
|
+
assert!(is_dummy_value("none"));
|
|
6625
|
+
assert!(is_dummy_value("nil"));
|
|
6626
|
+
assert!(is_dummy_value("(null)"));
|
|
6627
|
+
assert!(is_dummy_value("\"\""));
|
|
6628
|
+
assert!(is_dummy_value("''"));
|
|
6629
|
+
|
|
6630
|
+
assert!(!is_dummy_value("echo null"));
|
|
6631
|
+
assert!(!is_dummy_value("cat /dev/null"));
|
|
6632
|
+
assert!(!is_dummy_value("src/main.rs"));
|
|
6633
|
+
assert!(!is_dummy_value("pwd"));
|
|
6634
|
+
}
|
|
6091
6635
|
}
|