@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.
Files changed (848) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +1 -1
  4. package/Release_Note.md +71 -72
  5. package/apps/mint-companion/src-tauri/src/compact.rs +69 -0
  6. package/apps/mint-companion/src-tauri/src/main.rs +218 -11
  7. package/apps/mint-companion/vite.config.ts +2 -1
  8. package/crates/mint-cli/src/agent/live_status.rs +43 -2
  9. package/crates/mint-cli/src/agent.rs +178 -38
  10. package/crates/mint-cli/src/commands/agent.rs +225 -31
  11. package/crates/mint-cli/src/commands/browser.rs +127 -0
  12. package/crates/mint-cli/src/commands/config.rs +14 -1
  13. package/crates/mint-cli/src/commands/knowledge.rs +16 -2
  14. package/crates/mint-cli/src/commands/mod.rs +15 -1
  15. package/crates/mint-cli/src/commands/tailnet_web.rs +2 -7
  16. package/crates/mint-cli/src/companion_navigation.rs +268 -0
  17. package/crates/mint-cli/src/interactive/chat_tui.rs +1065 -63
  18. package/crates/mint-cli/src/interactive/command_ui.rs +39 -0
  19. package/crates/mint-cli/src/interactive/slash_commands.rs +114 -6
  20. package/crates/mint-cli/src/interactive/tool_activity.rs +375 -36
  21. package/crates/mint-cli/src/interactive.rs +37 -5
  22. package/crates/mint-cli/src/main.rs +23 -2
  23. package/crates/mint-cli/src/onboard.rs +3494 -1336
  24. package/crates/mint-cli/tests/mods_testing.rs +5 -1
  25. package/crates/mint-companion-protocol/src/lib.rs +113 -0
  26. package/crates/mint-core/src/agent/chat.rs +240 -1
  27. package/crates/mint-core/src/agent/memory.rs +73 -6
  28. package/crates/mint-core/src/agent/safety.rs +56 -4
  29. package/crates/mint-core/src/agent/skill_inventory.rs +3 -1
  30. package/crates/mint-core/src/agent/skills.rs +1 -0
  31. package/crates/mint-core/src/api_server/routes/auth.rs +1 -4
  32. package/crates/mint-core/src/api_server/routes/chat.rs +29 -15
  33. package/crates/mint-core/src/api_server/routes/misc.rs +116 -0
  34. package/crates/mint-core/src/api_server/routes/profile_oauth.rs +43 -26
  35. package/crates/mint-core/src/api_server/routes/sessions.rs +93 -76
  36. package/crates/mint-core/src/api_server/routes/skills_subagents.rs +11 -7
  37. package/crates/mint-core/src/api_server/routes/status_health.rs +18 -8
  38. package/crates/mint-core/src/api_server.rs +211 -65
  39. package/crates/mint-core/src/avatar_bridge.rs +5 -2
  40. package/crates/mint-core/src/browser/cdp.rs +82 -26
  41. package/crates/mint-core/src/browser/lifecycle.rs +143 -43
  42. package/crates/mint-core/src/browser/mod.rs +5 -4
  43. package/crates/mint-core/src/browser/navigate.rs +82 -9
  44. package/crates/mint-core/src/browser/runtime.rs +284 -0
  45. package/crates/mint-core/src/browser/session.rs +263 -7
  46. package/crates/mint-core/src/browser/workflow.rs +386 -0
  47. package/crates/mint-core/src/companion/navigation.rs +115 -0
  48. package/crates/mint-core/src/companion.rs +109 -1
  49. package/crates/mint-core/src/integrations/mcp_result.rs +45 -0
  50. package/crates/mint-core/src/lib.rs +10 -6
  51. package/crates/mint-core/src/media/image_proxy.rs +563 -0
  52. package/crates/mint-core/src/media/tool_images.rs +196 -0
  53. package/crates/mint-core/src/media/tts/playback.rs +609 -0
  54. package/crates/mint-core/src/media/tts.rs +1 -0
  55. package/crates/mint-core/src/media.rs +2 -0
  56. package/crates/mint-core/src/orchestration/context_render.rs +450 -33
  57. package/crates/mint-core/src/orchestration/decision_parsing.rs +5 -23
  58. package/crates/mint-core/src/orchestration/diagnostics.rs +307 -0
  59. package/crates/mint-core/src/orchestration/image_regressions.rs +379 -0
  60. package/crates/mint-core/src/orchestration/loop_guard.rs +334 -0
  61. package/crates/mint-core/src/orchestration/mod.rs +1104 -560
  62. package/crates/mint-core/src/orchestration/reliability.rs +541 -0
  63. package/crates/mint-core/src/orchestration/repeated_failures.rs +8 -0
  64. package/crates/mint-core/src/orchestration/review_regressions.rs +174 -28
  65. package/crates/mint-core/src/orchestration/tools/files.rs +31 -3
  66. package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +19 -4
  67. package/crates/mint-core/src/orchestration/tools/project.rs +1 -1
  68. package/crates/mint-core/src/orchestration/tools/shell.rs +29 -5
  69. package/crates/mint-core/src/orchestration/tools/web.rs +5 -1
  70. package/crates/mint-core/src/orchestration/trajectory_schema.rs +353 -0
  71. package/crates/mint-core/src/orchestration/truncation.rs +211 -0
  72. package/crates/mint-core/src/orchestration/verification.rs +36 -6
  73. package/crates/mint-core/src/orchestration/workspace_helpers.rs +54 -23
  74. package/crates/mint-core/src/prompts/agent.rs +31 -40
  75. package/crates/mint-core/src/prompts/tool_catalog.rs +134 -40
  76. package/crates/mint-core/src/search/web_search.rs +44 -3
  77. package/crates/mint-core/src/slash/mod.rs +464 -1
  78. package/crates/mint-core/src/swarm/batch.rs +643 -0
  79. package/crates/mint-core/src/swarm/dossier.rs +152 -0
  80. package/crates/mint-core/src/swarm/labels.rs +100 -0
  81. package/crates/mint-core/src/swarm/mod.rs +158 -0
  82. package/crates/mint-core/src/swarm/permissions.rs +167 -0
  83. package/crates/mint-core/src/system/auth.rs +156 -7
  84. package/crates/mint-core/src/system/bg_shell.rs +77 -9
  85. package/crates/mint-core/src/system/config.rs +92 -3
  86. package/crates/mint-core/src/system/mint_web.rs +33 -12
  87. package/crates/mint-core/src/system/shell.rs +179 -10
  88. package/crates/mint-core/src/system/workspace.rs +204 -0
  89. package/crates/mint-core/tests/background_retention.rs +1 -1
  90. package/crates/mint-core/tests/memory_persistence.rs +27 -0
  91. package/out/web/assets/ArtifactPreviewPanel-DAT_Y7i0.css +1 -0
  92. package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +2 -0
  93. package/out/web/assets/CalculationCard-CRjP3E1j.js +1 -0
  94. package/out/web/assets/{CliSessionsView-CtzUJpK_.css → CliSessionsView-C3Br2v5Y.css} +1 -1
  95. package/out/web/assets/CliSessionsView-MI8kXMAb.js +1 -0
  96. package/out/web/assets/CodeReviewPage-CUJceQme.css +1 -0
  97. package/out/web/assets/CodeReviewPage-VCUgPla_.js +2 -0
  98. package/out/web/assets/CommandPalette-BD8SlTE5.js +1 -0
  99. package/out/web/assets/ImageGenCard-BCXxkVJX.js +1 -0
  100. package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +1 -0
  101. package/out/web/assets/ImageStudioPanel-BP7N4rLr.js +1 -0
  102. package/out/web/assets/LibraryPage-WlviRdR8.js +2 -0
  103. package/out/web/assets/LinkedFoldersView-CKRgroWK.js +1 -0
  104. package/out/web/assets/McpRegistryPicker-CoS83vtc.js +1 -0
  105. package/out/web/assets/McpServersView-D3wHnuHg.js +1 -0
  106. package/out/web/assets/MermaidCard-NqrHdyJW.js +2 -0
  107. package/out/web/assets/MintDashboard-B2DJsDDO.css +1 -0
  108. package/out/web/assets/MintDashboard-COw_xol7.js +63 -0
  109. package/out/web/assets/PluginsView-jBYmcKh4.js +1 -0
  110. package/out/web/assets/RewindModal-DempuDod.js +1 -0
  111. package/out/web/assets/ScheduledTasksView-5uw9-V9H.js +1 -0
  112. package/out/web/assets/SelectField-DXBQtrUK.js +1 -0
  113. package/out/web/assets/SelectField-lFEkkWCU.css +1 -0
  114. package/out/web/assets/SettingsWindow-2YP3yj7s.js +4 -0
  115. package/out/web/assets/SettingsWindow-Dz8LQVgD.css +1 -0
  116. package/out/web/assets/SkillsView-C-gp9J7m.js +2 -0
  117. package/out/web/assets/StockCard-BDMCe7mx.js +1 -0
  118. package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +38 -0
  119. package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +1 -0
  120. package/out/web/assets/UiFeatureCard-6BwxTOCe.js +1 -0
  121. package/out/web/assets/UiGridCard-DvZcLNxb.js +1 -0
  122. package/out/web/assets/UiMockupWidget-DD73QWg1.js +1 -0
  123. package/out/web/assets/VeoStudioPanel-DfsOJKv0.js +4 -0
  124. package/out/web/assets/WeatherCard-CPucIfq8.js +1 -0
  125. package/out/web/assets/WorkspacePanel-BfHKkulU.js +1 -0
  126. package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +2 -0
  127. package/out/web/assets/abnfDiagram-VCTEODGH-BOo3Ckng.js +1 -0
  128. package/out/web/assets/architecture-7GRP2DOG-ZOwheW3n.js +1 -0
  129. package/out/web/assets/architectureDiagram-5GKGNRK7-DC2FDzVd.js +36 -0
  130. package/out/web/assets/{blockDiagram-NRAW4CY4-BrrqzHW5.js → blockDiagram-I7D4REHJ-BrKFWdAH.js} +4 -4
  131. package/out/web/assets/c4Diagram-7LVT6UL2-BHTHK1Yd.js +38 -0
  132. package/out/web/assets/chunk-2Q5K7J3B-D2JLPQzF.js +1 -0
  133. package/out/web/assets/{chunk-TEH6E4GO-G4Cdlyrt.js → chunk-4HAMMTFA-BYFkZwNj.js} +3 -3
  134. package/out/web/assets/{chunk-5VM5RSS4-BiPuE0io.js → chunk-5VM5RSS4-BZ2oBPYQ.js} +1 -1
  135. package/out/web/assets/chunk-75Z2AOVW-BSKfSKSA.js +2 -0
  136. package/out/web/assets/chunk-DU6HZSFF-vIhsiW6n.js +128 -0
  137. package/out/web/assets/chunk-F27PBJKO-BlYpW9xq.js +1 -0
  138. package/out/web/assets/chunk-FOHPRMQF-JXEeeJ9r.js +161 -0
  139. package/out/web/assets/{chunk-GMAD6QVW-BuhEcgHq.js → chunk-GMAD6QVW-BIBz_Ug5.js} +12 -12
  140. package/out/web/assets/chunk-GVQU2GXP-NPSHxeqC.js +1 -0
  141. package/out/web/assets/chunk-IMKFNOWR-YlJK8_Fp.js +231 -0
  142. package/out/web/assets/chunk-JWPE2WC7-BeEMUFDw.js +1 -0
  143. package/out/web/assets/chunk-L3NEJ4N5-DMYi4-af.js +1 -0
  144. package/out/web/assets/chunk-OSK3NFVY-Begd3-jA.js +10 -0
  145. package/out/web/assets/chunk-P2QGCYS3-D1I4xFHN.js +1 -0
  146. package/out/web/assets/chunk-POPQ4Y6H-CafZZzNE.js +1 -0
  147. package/out/web/assets/chunk-PWAF6VOD-Dwh6_Lnt.js +1 -0
  148. package/out/web/assets/chunk-SHT3W25Y-B0XrCSEb.js +168 -0
  149. package/out/web/assets/{chunk-SVP7TREG-Bu_yYb-V.js → chunk-SVP7TREG-CxLsddxC.js} +1 -1
  150. package/out/web/assets/{chunk-LCL6LL3I-BhXV9zyH.js → chunk-TICWLB2K-y156QInb.js} +4 -4
  151. package/out/web/assets/chunk-XXDRQBXY-B9YlhOkE.js +1 -0
  152. package/out/web/assets/chunk-Y2CYZVJY-BxCWjNVG.js +1 -0
  153. package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +1 -0
  154. package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +1 -0
  155. package/out/web/assets/config-CvIO5hkn.js +2 -0
  156. package/out/web/assets/config-DqXJXeZM.css +1 -0
  157. package/out/web/assets/core-Mi3s5D4_.js +1 -0
  158. package/out/web/assets/cose-bilkent-JH36ORCC-qlna31ty.js +1 -0
  159. package/out/web/assets/cynefin-OW5HDTMX-SMVvrLf-.js +1 -0
  160. package/out/web/assets/cynefinDiagram-5FMLGOSQ-rZznN24T.js +62 -0
  161. package/out/web/assets/cytoscape.esm-DWtAUG0r.js +321 -0
  162. package/out/web/assets/dagre-CnWJJM5f.js +1 -0
  163. package/out/web/assets/dagre-GXQ25YYZ-BOToydFu.js +4 -0
  164. package/out/web/assets/diagram-S7CK7UJ4-TMVaGQEJ.js +30 -0
  165. package/out/web/assets/diagram-UQ7AKVKN-C2JK-GP2.js +41 -0
  166. package/out/web/assets/{diagram-VSXAHHWV-BJ86Ov2Y.js → diagram-VSXAHHWV-Cw8VSi2K.js} +1 -1
  167. package/out/web/assets/diagram-VX7I27RA-DXp4fKk9.js +24 -0
  168. package/out/web/assets/diagram-Z3DM3KII-CzSicYiW.js +24 -0
  169. package/out/web/assets/dist-9ISJ7r-g.js +1 -0
  170. package/out/web/assets/dist-js-BV3mI7uw.js +1 -0
  171. package/out/web/assets/ebnfDiagram-PWID7BFC-Dqj-zqPu.js +1 -0
  172. package/out/web/assets/{erDiagram-SSCWMZ5O-CzzmnZWT.js → erDiagram-RLTQ6QDP-DZwFZAuD.js} +3 -3
  173. package/out/web/assets/event-D7D-EBy9.js +1 -0
  174. package/out/web/assets/eventmodeling-NTZA5JFV-C0O-Lvgp.js +1 -0
  175. package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +1 -0
  176. package/out/web/assets/ganttDiagram-EL5Y4UJY-Da4ULXTl.js +292 -0
  177. package/out/web/assets/gitGraph-4MIJSDKK-B9wNFZIP.js +1 -0
  178. package/out/web/assets/gitGraphDiagram-WWUBYQGX-RjrhB0MP.js +106 -0
  179. package/out/web/assets/graphlib-Bh9gHply.js +1 -0
  180. package/out/web/assets/index-BmpulIkg.js +168 -0
  181. package/out/web/assets/index-CkvBwhX_.css +1 -0
  182. package/out/web/assets/info-A6RAGUB7-B3dfCpeY.js +1 -0
  183. package/out/web/assets/infoDiagram-27XIBGKW-QVWkLVx7.js +2 -0
  184. package/out/web/assets/{ishikawaDiagram-5VMMS53U-s38yo28y.js → ishikawaDiagram-5VMMS53U-CLFNIRI9.js} +4 -4
  185. package/out/web/assets/journeyDiagram-3NMN7TZE-fgOQadYF.js +139 -0
  186. package/out/web/assets/jsx-runtime-DmUwWVTj.js +1 -0
  187. package/out/web/assets/{kanban-definition-3QL26DDD-DodjgySL.js → kanban-definition-UXKFOSKX-G3Q_9g9O.js} +6 -6
  188. package/out/web/assets/katex-MCdQaEAD.js +257 -0
  189. package/out/web/assets/mermaid-parser.core-v3Jm7nco.js +7 -0
  190. package/out/web/assets/mermaid.core-DTEjLUA1.js +44 -0
  191. package/out/web/assets/{mindmap-definition-FBJOCRG2-260WBDxZ.js → mindmap-definition-YA3MSWOX-C_D42_fF.js} +4 -4
  192. package/out/web/assets/modelManager-BPLycQrN.js +2 -0
  193. package/out/web/assets/oauthManager-BUK7ic7q.js +1 -0
  194. package/out/web/assets/packet-AYTQ26CC-B-R-SHlF.js +1 -0
  195. package/out/web/assets/pdf-Bcahc2yC.js +56 -0
  196. package/out/web/assets/pdf.worker.min-Dkey6ZUl.mjs +27 -0
  197. package/out/web/assets/pegDiagram-XKGWAZYB-Da2q_giz.js +1 -0
  198. package/out/web/assets/pie-WAS4IAKB-BC8F0N7-.js +1 -0
  199. package/out/web/assets/pieDiagram-E7YTZNPT-Iy9REJNC.js +39 -0
  200. package/out/web/assets/{quadrantDiagram-AXDQQJYC-B8D3gU5d.js → quadrantDiagram-AXDQQJYC-ztsiYw7h.js} +4 -4
  201. package/out/web/assets/radar-RG4KPBEZ-B_1P8Z_z.js +1 -0
  202. package/out/web/assets/railroad-74A4TZTK-DuTqczSo.js +1 -0
  203. package/out/web/assets/railroad-abnf-HS5TGJTU-Df29WLmp.js +1 -0
  204. package/out/web/assets/railroad-ebnf-LZEXJU2U-ePFMo-qp.js +1 -0
  205. package/out/web/assets/railroad-peg-WCYAUIDC-DYXLW4_E.js +1 -0
  206. package/out/web/assets/railroadDiagram-O6MQD6OU-LStnZf2S.js +1 -0
  207. package/out/web/assets/{requirementDiagram-EFPCY7ZU-D2X-Ewik.js → requirementDiagram-BXWQKSXE-DmzonMH-.js} +3 -3
  208. package/out/web/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
  209. package/out/web/assets/rough.esm-CNnFptCR.js +1 -0
  210. package/out/web/assets/sankeyDiagram-P5KCCOFB-B8vjmUU1.js +40 -0
  211. package/out/web/assets/sequenceDiagram-WJ2MYXX4-CTnplOAX.js +162 -0
  212. package/out/web/assets/sizeCapture-INFHLROL-C8Q8DbV5.js +1 -0
  213. package/out/web/assets/src-CRDfRlHM.js +1 -0
  214. package/out/web/assets/stateDiagram-D77RDMKH-BFrIaLdI.js +1 -0
  215. package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +1 -0
  216. package/out/web/assets/studio-BBZQ_-QK.js +1 -0
  217. package/out/web/assets/swimlanes-42K2YHIH-BXGngUNU.js +1 -0
  218. package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +8 -0
  219. package/out/web/assets/timeline-definition-24CTP7MA-CegH7n43.js +120 -0
  220. package/out/web/assets/treeView-Q6P3EWNA-DTpJv2Tw.js +1 -0
  221. package/out/web/assets/treemap-WGGIJYW6-i8M9iJU6.js +1 -0
  222. package/out/web/assets/vennDiagram-4TSXK5OY-Dpzo1S8U.js +34 -0
  223. package/out/web/assets/wardley-WFR3VGLG-BrNsgtfw.js +1 -0
  224. package/out/web/assets/{wardleyDiagram-VM6X3IG4-DAdXELbQ.js → wardleyDiagram-VM6X3IG4-lQ92LFE1.js} +35 -35
  225. package/out/web/assets/window-CkUOP7t0.js +1 -0
  226. package/out/web/assets/workspaces-SYPrmbgC.js +1 -0
  227. package/out/web/assets/{xychartDiagram-S5SC5T6Z-IZIXf3Vo.js → xychartDiagram-S5SC5T6Z-BaOaEoBd.js} +4 -4
  228. package/out/web/index-web.html +4 -4
  229. package/out/web/index.html +4 -4
  230. package/out/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  231. package/out/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  232. package/out/web/pdfjs/cmaps/78-H.bcmap +0 -0
  233. package/out/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  234. package/out/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  235. package/out/web/pdfjs/cmaps/78-V.bcmap +0 -0
  236. package/out/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  237. package/out/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  238. package/out/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  239. package/out/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  240. package/out/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  241. package/out/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  242. package/out/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  243. package/out/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  244. package/out/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  245. package/out/web/pdfjs/cmaps/Add-H.bcmap +0 -0
  246. package/out/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  247. package/out/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  248. package/out/web/pdfjs/cmaps/Add-V.bcmap +0 -0
  249. package/out/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  250. package/out/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  251. package/out/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  252. package/out/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  253. package/out/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  254. package/out/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  255. package/out/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  256. package/out/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  257. package/out/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  258. package/out/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  259. package/out/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  260. package/out/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  261. package/out/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  262. package/out/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  263. package/out/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  264. package/out/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  265. package/out/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  266. package/out/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  267. package/out/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  268. package/out/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  269. package/out/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  270. package/out/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  271. package/out/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  272. package/out/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  273. package/out/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  274. package/out/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  275. package/out/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  276. package/out/web/pdfjs/cmaps/B5-H.bcmap +0 -0
  277. package/out/web/pdfjs/cmaps/B5-V.bcmap +0 -0
  278. package/out/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  279. package/out/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  280. package/out/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  281. package/out/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  282. package/out/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  283. package/out/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  284. package/out/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  285. package/out/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  286. package/out/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  287. package/out/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  288. package/out/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  289. package/out/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  290. package/out/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  291. package/out/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  292. package/out/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
  293. package/out/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
  294. package/out/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
  295. package/out/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  296. package/out/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  297. package/out/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
  298. package/out/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  299. package/out/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  300. package/out/web/pdfjs/cmaps/GB-H.bcmap +4 -0
  301. package/out/web/pdfjs/cmaps/GB-V.bcmap +0 -0
  302. package/out/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  303. package/out/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  304. package/out/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  305. package/out/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  306. package/out/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  307. package/out/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  308. package/out/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  309. package/out/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  310. package/out/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
  311. package/out/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
  312. package/out/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  313. package/out/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  314. package/out/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  315. package/out/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  316. package/out/web/pdfjs/cmaps/H.bcmap +0 -0
  317. package/out/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  318. package/out/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  319. package/out/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  320. package/out/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  321. package/out/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  322. package/out/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  323. package/out/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  324. package/out/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  325. package/out/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  326. package/out/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  327. package/out/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  328. package/out/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  329. package/out/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
  330. package/out/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
  331. package/out/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  332. package/out/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  333. package/out/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
  334. package/out/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  335. package/out/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  336. package/out/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
  337. package/out/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  338. package/out/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  339. package/out/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  340. package/out/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  341. package/out/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  342. package/out/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  343. package/out/web/pdfjs/cmaps/Katakana.bcmap +0 -0
  344. package/out/web/pdfjs/cmaps/LICENSE +36 -0
  345. package/out/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
  346. package/out/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
  347. package/out/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  348. package/out/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  349. package/out/web/pdfjs/cmaps/Roman.bcmap +0 -0
  350. package/out/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  351. package/out/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  352. package/out/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  353. package/out/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  354. package/out/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  355. package/out/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  356. package/out/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  357. package/out/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  358. package/out/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  359. package/out/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  360. package/out/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  361. package/out/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  362. package/out/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  363. package/out/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  364. package/out/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  365. package/out/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  366. package/out/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  367. package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  368. package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  369. package/out/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  370. package/out/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  371. package/out/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  372. package/out/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  373. package/out/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  374. package/out/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  375. package/out/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  376. package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  377. package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  378. package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  379. package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  380. package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  381. package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  382. package/out/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  383. package/out/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  384. package/out/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  385. package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  386. package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  387. package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  388. package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  389. package/out/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  390. package/out/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  391. package/out/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  392. package/out/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  393. package/out/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  394. package/out/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  395. package/out/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  396. package/out/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  397. package/out/web/pdfjs/cmaps/V.bcmap +0 -0
  398. package/out/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  399. package/out/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  400. package/out/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  401. package/out/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  402. package/out/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  403. package/out/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  404. package/out/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  405. package/out/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  406. package/out/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  407. package/out/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  408. package/out/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  409. package/out/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  410. package/out/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
  411. package/out/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  412. package/out/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  413. package/out/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  414. package/out/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  415. package/out/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
  416. package/out/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
  417. package/out/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  418. package/out/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  419. package/out/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
  420. package/out/web/pdfjs/wasm/LICENSE_QCMS +21 -0
  421. package/out/web/pdfjs/wasm/jbig2.wasm +0 -0
  422. package/out/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
  423. package/out/web/pdfjs/wasm/openjpeg.wasm +0 -0
  424. package/out/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
  425. package/out/web/pdfjs/wasm/qcms_bg.wasm +0 -0
  426. package/out/web/pdfjs/wasm/quickjs-eval.js +16 -0
  427. package/out/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
  428. package/package.json +6 -4
  429. package/slash-commands.json +8 -2
  430. package/src/bin/index.js +4 -1
  431. package/src/bin/pdfjs-assets.mjs +31 -0
  432. package/src/renderer/node_modules/.vite/vitest/CACHEDIR.TAG +3 -0
  433. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  434. package/src/renderer/shared/agentProgress.ts +11 -2
  435. package/src/renderer/shared/components/Settings/AgentsTab.tsx +70 -6
  436. package/src/renderer/shared/components/Settings/AudioTab.tsx +1 -1
  437. package/src/renderer/shared/components/Settings/AutomationTab.tsx +57 -0
  438. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +18 -12
  439. package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.test.tsx +65 -0
  440. package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.tsx +456 -0
  441. package/src/renderer/shared/components/Settings/GeneralTab.tsx +40 -82
  442. package/src/renderer/shared/components/Settings/PluginsTab.tsx +101 -24
  443. package/src/renderer/shared/components/Settings/ProfileTab.tsx +1 -1
  444. package/src/renderer/shared/components/Settings/SettingsWindow.tsx +796 -0
  445. package/src/renderer/shared/components/Settings/ThemeTab.tsx +13 -1
  446. package/src/renderer/shared/components/{AgentActivityDrawer.tsx → activity/AgentActivityDrawer.tsx} +77 -61
  447. package/src/renderer/shared/components/{AgentActivityTable.tsx → activity/AgentActivityTable.tsx} +46 -7
  448. package/src/renderer/shared/components/{McpToolResults.tsx → activity/McpToolResults.tsx} +32 -13
  449. package/src/renderer/shared/components/{PlanChecklistWidget.tsx → activity/PlanChecklistWidget.tsx} +2 -2
  450. package/src/renderer/shared/components/activity/RunSummaryDashboard.test.tsx +127 -0
  451. package/src/renderer/shared/components/activity/RunSummaryDashboard.tsx +222 -0
  452. package/src/renderer/shared/components/activity/SubagentDagView.test.ts +123 -0
  453. package/src/renderer/shared/components/{SubagentDagView.tsx → activity/SubagentDagView.tsx} +97 -43
  454. package/src/renderer/shared/components/activity/SubagentStatusStrip.test.tsx +82 -0
  455. package/src/renderer/shared/components/activity/SubagentStatusStrip.tsx +107 -0
  456. package/src/renderer/shared/components/activity/ViewedImages.tsx +140 -0
  457. package/src/renderer/shared/components/{AuthGate.tsx → app/AuthGate.tsx} +3 -3
  458. package/src/renderer/shared/components/{ChunkErrorBoundary.tsx → app/ChunkErrorBoundary.tsx} +10 -10
  459. package/src/renderer/shared/components/{CommandPalette.tsx → app/CommandPalette.tsx} +25 -25
  460. package/src/renderer/shared/components/{DashboardSidebar.tsx → app/DashboardSidebar.tsx} +7 -5
  461. package/src/renderer/shared/components/{DesktopTitlebar.tsx → app/DesktopTitlebar.tsx} +7 -24
  462. package/src/renderer/shared/components/{ErrorNotice.tsx → app/ErrorNotice.tsx} +1 -1
  463. package/src/renderer/shared/components/{MintDashboard.tsx → app/MintDashboard.tsx} +191 -212
  464. package/src/renderer/shared/components/{ModalErrorBoundary.tsx → app/ModalErrorBoundary.tsx} +5 -5
  465. package/src/renderer/shared/components/{CalculationCard.tsx → cards/CalculationCard.tsx} +13 -27
  466. package/src/renderer/shared/components/{ImageGenCard.tsx → cards/ImageGenCard.tsx} +9 -22
  467. package/src/renderer/shared/components/{ImageSearchCard.tsx → cards/ImageSearchCard.tsx} +9 -22
  468. package/src/renderer/shared/components/{MermaidCard.tsx → cards/MermaidCard.tsx} +37 -21
  469. package/src/renderer/shared/components/{StockCard.tsx → cards/StockCard.tsx} +15 -31
  470. package/src/renderer/shared/components/cards/ToolResultCards.tsx +86 -0
  471. package/src/renderer/shared/components/{UiFeatureCard.tsx → cards/UiFeatureCard.tsx} +6 -6
  472. package/src/renderer/shared/components/{UiGridCard.tsx → cards/UiGridCard.tsx} +3 -3
  473. package/src/renderer/shared/components/{WeatherCard.tsx → cards/WeatherCard.tsx} +1 -1
  474. package/src/renderer/shared/components/{ApprovalCard.test.tsx → chat/ApprovalCard.test.tsx} +1 -1
  475. package/src/renderer/shared/components/{ApprovalCard.tsx → chat/ApprovalCard.tsx} +5 -5
  476. package/src/renderer/shared/components/{ChatCodeBlock.tsx → chat/ChatCodeBlock.tsx} +1 -1
  477. package/src/renderer/shared/components/{ChatMessageItem.test.tsx → chat/ChatMessageItem.test.tsx} +92 -15
  478. package/src/renderer/shared/components/{ChatMessageItem.tsx → chat/ChatMessageItem.tsx} +73 -157
  479. package/src/renderer/shared/components/chat/ChatPanel.test.tsx +31 -0
  480. package/src/renderer/shared/components/{ChatPanel.tsx → chat/ChatPanel.tsx} +244 -493
  481. package/src/renderer/shared/components/{CollapsiblePasteInput.tsx → chat/CollapsiblePasteInput.tsx} +2 -2
  482. package/src/renderer/shared/components/{DiffReviewModal.tsx → chat/DiffReviewModal.tsx} +40 -40
  483. package/src/renderer/shared/components/{GeminiLiveOverlay.tsx → chat/GeminiLiveOverlay.tsx} +2 -2
  484. package/src/renderer/shared/components/{ModelSelectorPopover.tsx → chat/ModelSelectorPopover.tsx} +3 -3
  485. package/src/renderer/shared/components/chat/QueuedMessagesBanner.test.tsx +89 -0
  486. package/src/renderer/shared/components/chat/QueuedMessagesBanner.tsx +201 -0
  487. package/src/renderer/shared/components/{RewindModal.tsx → chat/RewindModal.tsx} +4 -4
  488. package/src/renderer/shared/components/{SlashChoicePanel.tsx → chat/SlashChoicePanel.tsx} +2 -2
  489. package/src/renderer/shared/components/{SlashSuggestions.tsx → chat/SlashSuggestions.tsx} +4 -4
  490. package/src/renderer/shared/components/{SourcesBlock.tsx → chat/SourcesBlock.tsx} +30 -37
  491. package/src/renderer/shared/components/{ThinkingBlock.tsx → chat/ThinkingBlock.tsx} +1 -1
  492. package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +86 -0
  493. package/src/renderer/shared/components/chat/TurnFileChanges.tsx +430 -0
  494. package/src/renderer/shared/components/{VoicePill.css → chat/VoicePill.css} +1 -1
  495. package/src/renderer/shared/components/{VoicePill.jsx → chat/VoicePill.jsx} +3 -2
  496. package/src/renderer/shared/components/{CliSessionsView.test.tsx → extensions/CliSessionsView.test.tsx} +2 -2
  497. package/src/renderer/shared/components/{CliSessionsView.tsx → extensions/CliSessionsView.tsx} +14 -12
  498. package/src/renderer/shared/components/{LinkedFoldersView.tsx → extensions/LinkedFoldersView.tsx} +3 -3
  499. package/src/renderer/shared/components/{McpRegistryPicker.tsx → extensions/McpRegistryPicker.tsx} +3 -3
  500. package/src/renderer/shared/components/{McpServersView.tsx → extensions/McpServersView.tsx} +105 -36
  501. package/src/renderer/shared/components/{McpToolAllowlist.tsx → extensions/McpToolAllowlist.tsx} +2 -2
  502. package/src/renderer/shared/components/{ModUi.test.tsx → extensions/ModUi.test.tsx} +9 -4
  503. package/src/renderer/shared/components/{ModsPanel.tsx → extensions/ModsPanel.tsx} +59 -7
  504. package/src/renderer/shared/components/{PluginsView.tsx → extensions/PluginsView.tsx} +6 -6
  505. package/src/renderer/shared/components/{ScheduledTasksView.tsx → extensions/ScheduledTasksView.tsx} +5 -5
  506. package/src/renderer/shared/components/extensions/SkillsView.test.tsx +31 -0
  507. package/src/renderer/shared/components/{SkillsView.tsx → extensions/SkillsView.tsx} +20 -4
  508. package/src/renderer/shared/components/{ImageStudioPanel.tsx → studio/ImageStudioPanel.tsx} +6 -6
  509. package/src/renderer/shared/components/{LibraryItemCard.tsx → studio/LibraryItemCard.tsx} +2 -2
  510. package/src/renderer/shared/components/{LibraryPage.tsx → studio/LibraryPage.tsx} +5 -5
  511. package/src/renderer/shared/components/{LibraryPreview.tsx → studio/LibraryPreview.tsx} +2 -2
  512. package/src/renderer/shared/components/{VeoStudioPanel.tsx → studio/VeoStudioPanel.tsx} +7 -7
  513. package/src/renderer/shared/components/{GlideSelect.css → ui/GlideSelect.css} +7 -6
  514. package/src/renderer/shared/components/ui/ProviderBrandIcon.test.tsx +67 -0
  515. package/src/renderer/shared/components/ui/ProviderBrandIcon.tsx +167 -0
  516. package/src/renderer/shared/components/ui/SelectField.tsx +506 -0
  517. package/src/renderer/shared/components/ui/error-recovery.css +10 -10
  518. package/src/renderer/shared/components/ui/error-recovery.tsx +1 -1
  519. package/src/renderer/shared/components/{ArtifactPreviewPanel.tsx → workspace/ArtifactPreviewPanel.tsx} +256 -112
  520. package/src/renderer/shared/components/workspace/BackgroundTerminals.test.tsx +63 -0
  521. package/src/renderer/shared/components/{BackgroundTerminals.tsx → workspace/BackgroundTerminals.tsx} +40 -9
  522. package/src/renderer/shared/components/{CodeReviewPage.tsx → workspace/CodeReviewPage.tsx} +47 -8
  523. package/src/renderer/shared/components/{GitBranchSelector.tsx → workspace/GitBranchSelector.tsx} +2 -2
  524. package/src/renderer/shared/components/workspace/SessionSources.tsx +113 -0
  525. package/src/renderer/shared/components/{TerminalDock.tsx → workspace/TerminalDock.tsx} +5 -2
  526. package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +68 -0
  527. package/src/renderer/shared/components/{ToolSurfacePage.tsx → workspace/ToolSurfacePage.tsx} +322 -71
  528. package/src/renderer/shared/components/{ToolsPanelToggle.tsx → workspace/ToolsPanelToggle.tsx} +1 -1
  529. package/src/renderer/shared/components/workspace/WorkspaceFileInfo.tsx +28 -0
  530. package/src/renderer/shared/components/workspace/WorkspaceMediaPreview.tsx +17 -0
  531. package/src/renderer/shared/components/{WorkspacePaneResizer.tsx → workspace/WorkspacePaneResizer.tsx} +1 -1
  532. package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +89 -0
  533. package/src/renderer/shared/components/{WorkspacePreviewActions.test.tsx → workspace/WorkspacePreviewActions.test.tsx} +48 -3
  534. package/src/renderer/shared/components/{WorkspacePreviewActions.tsx → workspace/WorkspacePreviewActions.tsx} +3 -3
  535. package/src/renderer/shared/components/{WorkspaceSelector.tsx → workspace/WorkspaceSelector.tsx} +99 -7
  536. package/src/renderer/shared/components/{workspace-selector.css → workspace/workspace-selector.css} +84 -2
  537. package/src/renderer/shared/constants/config.ts +7 -0
  538. package/src/renderer/shared/conversation/coordinator.test.ts +46 -0
  539. package/src/renderer/shared/conversation/coordinator.ts +50 -2
  540. package/src/renderer/shared/conversation/lifecycle.test.ts +163 -1
  541. package/src/renderer/shared/conversation/lifecycle.ts +164 -7
  542. package/src/renderer/shared/conversation/projectNavigation.test.ts +18 -0
  543. package/src/renderer/shared/conversation/projectNavigation.ts +11 -0
  544. package/src/renderer/shared/conversation/transport.test.ts +20 -0
  545. package/src/renderer/shared/conversation/transport.ts +51 -9
  546. package/src/renderer/shared/conversation/useConversationCoordinator.ts +42 -12
  547. package/src/renderer/shared/css/agent-observability.css +72 -11
  548. package/src/renderer/shared/css/chat-ui-cards.css +35 -35
  549. package/src/renderer/shared/css/code-review.css +86 -86
  550. package/src/renderer/shared/css/code-sessions.css +10 -10
  551. package/src/renderer/shared/css/file-changes.css +167 -5
  552. package/src/renderer/shared/css/management-views.css +78 -37
  553. package/src/renderer/shared/css/mcp-results.css +46 -3
  554. package/src/renderer/shared/css/queued-messages.css +221 -0
  555. package/src/renderer/shared/css/session-sources.css +12 -7
  556. package/src/renderer/shared/css/settings/agents.css +2 -2
  557. package/src/renderer/{src-web → shared}/css/settings/base.css +101 -107
  558. package/src/renderer/shared/css/settings/general.css +384 -92
  559. package/src/renderer/shared/css/settings/plugins.css +8 -8
  560. package/src/renderer/shared/css/settings/theme.css +44 -32
  561. package/src/renderer/shared/css/ui-tailwind.css +2 -2
  562. package/src/renderer/shared/css/viewed-images.css +89 -0
  563. package/src/renderer/shared/css/workspace-file-info.css +12 -0
  564. package/src/renderer/shared/css/workspace-media-preview.css +13 -0
  565. package/src/renderer/shared/env.d.ts +9 -0
  566. package/src/renderer/shared/platform.test.ts +143 -0
  567. package/src/renderer/shared/platform.ts +10 -4
  568. package/src/renderer/shared/speech/player.ts +160 -0
  569. package/src/renderer/shared/speech/types.ts +11 -0
  570. package/src/renderer/shared/theme/themeManager.test.ts +20 -0
  571. package/src/renderer/shared/theme/themeManager.ts +3 -1
  572. package/src/renderer/shared/theme/themes.ts +17 -4
  573. package/src/renderer/shared/theme/tokens.css +448 -51
  574. package/src/renderer/shared/types.ts +56 -1
  575. package/src/renderer/shared/utils/agentActivity.test.ts +165 -6
  576. package/src/renderer/shared/utils/agentActivity.ts +181 -31
  577. package/src/renderer/shared/utils/artifactBrowser.ts +1 -1
  578. package/src/renderer/shared/utils/artifacts.ts +55 -0
  579. package/src/renderer/shared/utils/contextCompaction.test.tsx +1 -1
  580. package/src/renderer/shared/utils/externalLinks.ts +91 -0
  581. package/src/renderer/shared/utils/lazyWithRetry.ts +13 -0
  582. package/src/renderer/shared/utils/markdown.tsx +169 -35
  583. package/src/renderer/shared/utils/markdown.webImages.test.tsx +124 -2
  584. package/src/renderer/shared/utils/mcpResults.test.tsx +24 -1
  585. package/src/renderer/shared/utils/pdfRange.ts +16 -0
  586. package/src/renderer/shared/utils/previewObjectUrls.ts +20 -0
  587. package/src/renderer/shared/utils/sessionSources.test.ts +41 -1
  588. package/src/renderer/shared/utils/sessionSources.ts +2 -0
  589. package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
  590. package/src/renderer/shared/utils/speech.ts +18 -4
  591. package/src/renderer/shared/utils/useNativeVoiceInput.ts +19 -5
  592. package/src/renderer/shared/utils/useSessionSources.ts +4 -0
  593. package/src/renderer/shared/utils/viewedImages.test.ts +129 -0
  594. package/src/renderer/shared/utils/viewedImages.ts +81 -10
  595. package/src/renderer/shared/utils/workspaceDrag.ts +30 -0
  596. package/src/renderer/shared/utils/workspaceImage.ts +17 -0
  597. package/src/renderer/shared/utils/workspaceListPreferences.test.ts +64 -0
  598. package/src/renderer/shared/utils/workspaceListPreferences.ts +54 -0
  599. package/src/renderer/shared/utils/workspaceMedia.ts +41 -0
  600. package/src/renderer/shared/utils/workspacePreview.test.ts +147 -2
  601. package/src/renderer/shared/utils/workspacePreview.ts +34 -3
  602. package/src/renderer/shared/utils/workspaces.ts +3 -2
  603. package/src/renderer/src/App.tsx +6 -8
  604. package/src/renderer/src/components/ChatPanel.tsx +1 -1
  605. package/src/renderer/src/components/DashboardSidebar.tsx +2 -2
  606. package/src/renderer/src/components/ImageStudioPanel.tsx +1 -1
  607. package/src/renderer/src/components/LiveTranslateControls.tsx +1 -1
  608. package/src/renderer/src/components/MintDashboard.tsx +1 -1
  609. package/src/renderer/src/components/ProactiveGlow.tsx +1 -1
  610. package/src/renderer/src/components/ScreenPicker.tsx +1 -1
  611. package/src/renderer/src/components/SettingsWindow.tsx +6 -830
  612. package/src/renderer/src/components/SpotlightWindow.tsx +4 -32
  613. package/src/renderer/src/components/VeoStudioPanel.tsx +1 -1
  614. package/src/renderer/src/components/WorkspacePanel.tsx +45 -12
  615. package/src/renderer/src/components/browser-shell.css +26 -26
  616. package/src/renderer/src/components/browser-suggestions-popup.css +8 -8
  617. package/src/renderer/src/css/app-shell.css +22 -102
  618. package/src/renderer/src/css/chat.css +221 -34
  619. package/src/renderer/src/css/desktop-tools.css +6 -337
  620. package/src/renderer/src/css/sidebar-workspace.css +20 -0
  621. package/src/renderer/src/css/spotlight.css +2 -2
  622. package/src/renderer/src/css/tool-surfaces.css +328 -58
  623. package/src/renderer/src/css/widget.css +6 -5
  624. package/src/renderer/src/env.d.ts +13 -0
  625. package/src/renderer/src/index.css +15 -63
  626. package/src/renderer/src/tauri.ts +77 -11
  627. package/src/renderer/src/voiceInput.ts +3 -1
  628. package/src/renderer/src-web/App.tsx +6 -8
  629. package/src/renderer/src-web/components/ChatPanel.tsx +1 -1
  630. package/src/renderer/src-web/components/DashboardSidebar.tsx +2 -2
  631. package/src/renderer/src-web/components/ImageStudioPanel.tsx +1 -1
  632. package/src/renderer/src-web/components/MintDashboard.tsx +1 -1
  633. package/src/renderer/src-web/components/SettingsWindow.tsx +6 -851
  634. package/src/renderer/src-web/components/VeoStudioPanel.tsx +1 -1
  635. package/src/renderer/src-web/css/app-shell.css +16 -100
  636. package/src/renderer/src-web/css/chat.css +221 -34
  637. package/src/renderer/src-web/css/sidebar-workspace.css +20 -0
  638. package/src/renderer/src-web/env.d.ts +14 -0
  639. package/src/renderer/src-web/index.css +15 -63
  640. package/src/renderer/src-web/tauri.ts +71 -13
  641. package/src/renderer/tests/browserArtifacts.browser.html +1 -0
  642. package/src/renderer/tests/browserArtifacts.browser.tsx +68 -0
  643. package/src/renderer/tests/chatEscape.browser.html +1 -0
  644. package/src/renderer/tests/chatEscape.browser.tsx +25 -0
  645. package/src/renderer/tests/chatPerformance.browser.html +1 -1
  646. package/src/renderer/tests/collapsedPaste.browser.html +1 -1
  647. package/src/renderer/tests/composerMenus.browser.html +3 -3
  648. package/src/renderer/tests/contextCompaction.browser.html +1 -1
  649. package/src/renderer/tests/errorRecovery.browser.html +1 -1
  650. package/src/renderer/tests/fixtures/preview.webm +0 -0
  651. package/src/renderer/tests/glideSelect.browser.html +1 -2
  652. package/src/renderer/tests/glideSelect.preview.html +1 -1
  653. package/src/renderer/tests/library.browser.tsx +1 -1
  654. package/src/renderer/tests/liveOrb.browser.html +1 -1
  655. package/src/renderer/tests/mediaFixtures.ts +32 -0
  656. package/src/renderer/tests/modInput.browser.html +1 -1
  657. package/src/renderer/tests/sessionSources.browser.tsx +76 -15
  658. package/src/renderer/tests/toolsPanel.browser.tsx +5 -5
  659. package/src/renderer/tests/viewedImages.browser.tsx +3 -3
  660. package/src/renderer/tests/voiceDictation.browser.html +101 -0
  661. package/src/renderer/tests/voicePill.browser.html +3 -3
  662. package/src/renderer/tests/voicePill.preview.html +1 -1
  663. package/src/renderer/tests/webSources.browser.tsx +2 -98
  664. package/src/renderer/tests/workspaceDrop.browser.html +1 -0
  665. package/src/renderer/tests/workspaceDrop.browser.tsx +183 -0
  666. package/src/renderer/tests/workspaceExpansion.browser.html +1 -0
  667. package/src/renderer/tests/workspaceExpansion.browser.tsx +66 -0
  668. package/src/renderer/tests/workspaceRemoval.browser.html +66 -0
  669. package/src/renderer/tests/workspaceUndo.browser.html +10 -2
  670. package/src-tauri/src/app/lifecycle.rs +44 -1
  671. package/src-tauri/src/commands/conversation.rs +99 -73
  672. package/src-tauri/src/commands/integrations.rs +7 -1
  673. package/src-tauri/src/commands/media.rs +7 -0
  674. package/src-tauri/src/commands/workspace.rs +38 -0
  675. package/tsconfig.json +15 -1
  676. package/vite.config.ts +2 -1
  677. package/vite.config.web.ts +2 -0
  678. package/out/web/assets/CliSessionsView-D7hw0Mgj.js +0 -1
  679. package/out/web/assets/CodeReviewPage-C7bEOJfd.js +0 -4
  680. package/out/web/assets/CodeReviewPage-Dte9uLiL.css +0 -1
  681. package/out/web/assets/CommandPalette-0RrF-AOT.js +0 -1
  682. package/out/web/assets/ImageStudioPanel-CdxkAf_r.js +0 -1
  683. package/out/web/assets/LibraryPage--Qfisp6D.js +0 -2
  684. package/out/web/assets/LinkedFoldersView-2nfZbDgN.js +0 -1
  685. package/out/web/assets/McpRegistryPicker-h9uS8_wT.js +0 -1
  686. package/out/web/assets/McpServersView-slfQJzNB.js +0 -1
  687. package/out/web/assets/MintDashboard-BSR5XpXh.js +0 -25
  688. package/out/web/assets/MintDashboard-CWUcCGhd.css +0 -1
  689. package/out/web/assets/PluginsView-l7LSm503.js +0 -1
  690. package/out/web/assets/ScheduledTasksView-CttmLbq1.js +0 -1
  691. package/out/web/assets/SettingsWindow-BCMLYvEV.js +0 -4
  692. package/out/web/assets/SettingsWindow-DcMVmU9u.css +0 -1
  693. package/out/web/assets/SkillsView-rNs1qr_U.js +0 -2
  694. package/out/web/assets/ToolSurfacePage-B9DzEPWN.js +0 -37
  695. package/out/web/assets/ToolSurfacePage-CTfgggF2.css +0 -1
  696. package/out/web/assets/VeoStudioPanel-DiJs_Cg2.js +0 -4
  697. package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +0 -1
  698. package/out/web/assets/WorkspaceSelector-B2xIp04D.js +0 -1
  699. package/out/web/assets/WorkspaceSelector-Ca8uJOsS.css +0 -1
  700. package/out/web/assets/abnfDiagram-VCTEODGH-CD0CjoGl.js +0 -1
  701. package/out/web/assets/architecture-7GRP2DOG-RKV83OeZ.js +0 -1
  702. package/out/web/assets/architectureDiagram-5GKGNRK7-nN_HAFct.js +0 -36
  703. package/out/web/assets/c4Diagram-UCG6FXSJ-DTdV8_zE.js +0 -38
  704. package/out/web/assets/chunk-2Q5K7J3B-Dmqrq8Tp.js +0 -1
  705. package/out/web/assets/chunk-75Z2AOVW-_M0z5olV.js +0 -2
  706. package/out/web/assets/chunk-CKBBP62Z-BpQEgeHF.js +0 -2
  707. package/out/web/assets/chunk-CLGD4ZFX-D54yGvGS.js +0 -1
  708. package/out/web/assets/chunk-CacB07dV.js +0 -1
  709. package/out/web/assets/chunk-DSNCTWBM-C-gvT2ru.js +0 -2
  710. package/out/web/assets/chunk-DU6HZSFF-CnFngIhW.js +0 -128
  711. package/out/web/assets/chunk-F27PBJKO-BqtWrnEO.js +0 -1
  712. package/out/web/assets/chunk-FOHPRMQF-BaVC1uA7.js +0 -161
  713. package/out/web/assets/chunk-G27WJ6UU-Ckoq5mi2.js +0 -231
  714. package/out/web/assets/chunk-JQ64N6SF-CF8T7GiZ.js +0 -1
  715. package/out/web/assets/chunk-JWPE2WC7-BwtYOHqU.js +0 -1
  716. package/out/web/assets/chunk-LNGE3PJU-AdBoOWw2.js +0 -34
  717. package/out/web/assets/chunk-OBVCFTLP-CRIkunUB.js +0 -1
  718. package/out/web/assets/chunk-P2QGCYS3-UFEU6BxZ.js +0 -1
  719. package/out/web/assets/chunk-POPQ4Y6H-CQcrNbjr.js +0 -1
  720. package/out/web/assets/chunk-PWAF6VOD-Cc2uVDsG.js +0 -1
  721. package/out/web/assets/chunk-R7TYR2AO-DiCxzdAY.js +0 -10
  722. package/out/web/assets/chunk-RHFEMEQ7-b7hnPmrR.js +0 -168
  723. package/out/web/assets/chunk-XXDRQBXY-DnJkbUaw.js +0 -1
  724. package/out/web/assets/chunk-Y2CYZVJY-C9o-s6OY.js +0 -1
  725. package/out/web/assets/classDiagram-DTDB5LWJ-RnC2mYO4.js +0 -1
  726. package/out/web/assets/classDiagram-v2-JRS7N3AN-RnC2mYO4.js +0 -1
  727. package/out/web/assets/config-CnvCChUM.js +0 -2
  728. package/out/web/assets/config-DM6oIVO3.css +0 -1
  729. package/out/web/assets/core-DsKkQ7ig.js +0 -1
  730. package/out/web/assets/cose-bilkent-JH36ORCC--UN4aw5-.js +0 -1
  731. package/out/web/assets/cynefin-OW5HDTMX-CnzQfOGp.js +0 -1
  732. package/out/web/assets/cynefinDiagram-5FMLGOSQ-DVtQnfpm.js +0 -62
  733. package/out/web/assets/cytoscape.esm-Df1UN4Y1.js +0 -321
  734. package/out/web/assets/dagre-3AP2YEHR-DG76_k5_.js +0 -4
  735. package/out/web/assets/dagre-BWadQu2F.js +0 -1
  736. package/out/web/assets/diagram-S7CK7UJ4-CaouUoze.js +0 -30
  737. package/out/web/assets/diagram-UQ7AKVKN-B4bEc26B.js +0 -41
  738. package/out/web/assets/diagram-VX7I27RA-7hH5IPYf.js +0 -24
  739. package/out/web/assets/diagram-Z3DM3KII-CCM4clo8.js +0 -24
  740. package/out/web/assets/dist-BoTOASLF.js +0 -1
  741. package/out/web/assets/dist-js-BkYNznHX.js +0 -1
  742. package/out/web/assets/ebnfDiagram-PWID7BFC-Da7WAM3k.js +0 -1
  743. package/out/web/assets/event-Cdt7Byyj.js +0 -1
  744. package/out/web/assets/eventmodeling-NTZA5JFV-xcfsA0XD.js +0 -1
  745. package/out/web/assets/flowDiagram-A5DVABFB-JdtH1673.js +0 -1
  746. package/out/web/assets/ganttDiagram-EL5Y4UJY-B3ClFGmL.js +0 -292
  747. package/out/web/assets/gitGraph-4MIJSDKK-BZ_y_cDU.js +0 -1
  748. package/out/web/assets/gitGraphDiagram-WWUBYQGX-drhRSYm3.js +0 -106
  749. package/out/web/assets/graphlib-CTR5ZhLf.js +0 -1
  750. package/out/web/assets/index-BZ9xgmMP.css +0 -1
  751. package/out/web/assets/index-Ca8cCS4h.js +0 -168
  752. package/out/web/assets/info-A6RAGUB7-B7I3K0F1.js +0 -1
  753. package/out/web/assets/infoDiagram-RXCK75RN-deX_1MPh.js +0 -2
  754. package/out/web/assets/journeyDiagram-EYS64GPL-CZn2kB8b.js +0 -139
  755. package/out/web/assets/jsx-runtime-DN4NhQUK.js +0 -1
  756. package/out/web/assets/katex-O3jvqgnU.js +0 -257
  757. package/out/web/assets/markdown-CMhYIcWB.js +0 -39
  758. package/out/web/assets/mermaid-parser.core-Whu2rlJC.js +0 -7
  759. package/out/web/assets/mermaid.core-CKyPo8VH.js +0 -11
  760. package/out/web/assets/modelManager-CUaGWMWu.js +0 -2
  761. package/out/web/assets/oauthManager-DlNhXJr6.js +0 -1
  762. package/out/web/assets/packet-AYTQ26CC-CEeIRKRG.js +0 -1
  763. package/out/web/assets/pegDiagram-XKGWAZYB-BYadI-KO.js +0 -1
  764. package/out/web/assets/pie-WAS4IAKB-D8uf4dh5.js +0 -1
  765. package/out/web/assets/pieDiagram-E7YTZNPT-CjSDy4hO.js +0 -39
  766. package/out/web/assets/radar-RG4KPBEZ-DZJxnokY.js +0 -1
  767. package/out/web/assets/railroad-74A4TZTK-DBysUiUi.js +0 -1
  768. package/out/web/assets/railroad-abnf-HS5TGJTU-CHWxoJYg.js +0 -1
  769. package/out/web/assets/railroad-ebnf-LZEXJU2U-DDv7X6bY.js +0 -1
  770. package/out/web/assets/railroad-peg-WCYAUIDC-DpfcYYNJ.js +0 -1
  771. package/out/web/assets/railroadDiagram-O6MQD6OU-CYtyv0t3.js +0 -1
  772. package/out/web/assets/rough.esm-DFbTUy8a.js +0 -1
  773. package/out/web/assets/sankeyDiagram-P5KCCOFB-Bn2x6C4Y.js +0 -40
  774. package/out/web/assets/sequenceDiagram-WJ2MYXX4-BvFLKtPd.js +0 -162
  775. package/out/web/assets/sizeCapture-X5ZJPWSS-DKeOCaN2.js +0 -1
  776. package/out/web/assets/src-DnfeXQPr.js +0 -1
  777. package/out/web/assets/stateDiagram-HBIQ2CUA-Bx0GWeaY.js +0 -1
  778. package/out/web/assets/stateDiagram-v2-4QOOHH4V--hol1yg_.js +0 -1
  779. package/out/web/assets/studio-B1mk40wD.js +0 -1
  780. package/out/web/assets/swimlanes-XN3QIQJK-CCma0T7L.js +0 -1
  781. package/out/web/assets/swimlanesDiagram-VK2B7HYN-BkohoVsU.js +0 -8
  782. package/out/web/assets/timeline-definition-24CTP7MA-iCRhhJ7C.js +0 -120
  783. package/out/web/assets/treeView-Q6P3EWNA-DZ-y08qp.js +0 -1
  784. package/out/web/assets/treemap-WGGIJYW6-De0CxzMx.js +0 -1
  785. package/out/web/assets/vennDiagram-4TSXK5OY-w_7aZ6xe.js +0 -34
  786. package/out/web/assets/wardley-WFR3VGLG-DT0y0xJ4.js +0 -1
  787. package/out/web/assets/window-BJCVtyET.js +0 -1
  788. package/src/renderer/shared/components/ChatPanel.test.tsx +0 -21
  789. package/src/renderer/shared/components/FormattedMessage.tsx +0 -11
  790. package/src/renderer/shared/components/GlideSelect.d.ts +0 -31
  791. package/src/renderer/shared/components/GlideSelect.jsx +0 -266
  792. package/src/renderer/shared/components/InlineThoughtNote.tsx +0 -27
  793. package/src/renderer/shared/components/ModSessionControls.tsx +0 -8
  794. package/src/renderer/shared/components/ModUi.tsx +0 -26
  795. package/src/renderer/shared/components/ProgressActivity.tsx +0 -17
  796. package/src/renderer/shared/components/ReviewHistory.tsx +0 -42
  797. package/src/renderer/shared/components/RunSummaryDashboard.tsx +0 -143
  798. package/src/renderer/shared/components/SelectField.tsx +0 -38
  799. package/src/renderer/shared/components/SessionSources.tsx +0 -48
  800. package/src/renderer/shared/components/SessionSourcesPopover.tsx +0 -61
  801. package/src/renderer/shared/components/ToolSurfacePage.test.tsx +0 -32
  802. package/src/renderer/shared/components/ViewedImages.tsx +0 -36
  803. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +0 -138
  804. package/src/renderer/shared/components/ui/demo.tsx +0 -26
  805. package/src/renderer/shared/css/session-sources-popover.css +0 -9
  806. package/src/renderer/src/components/CalculationCard.tsx +0 -3
  807. package/src/renderer/src/components/ImageGenCard.tsx +0 -2
  808. package/src/renderer/src/components/ImageSearchCard.tsx +0 -2
  809. package/src/renderer/src/components/MermaidCard.tsx +0 -1
  810. package/src/renderer/src/components/PicturesLibrary.tsx +0 -2
  811. package/src/renderer/src/components/Settings/AgentsTab.tsx +0 -4
  812. package/src/renderer/src/components/Settings/AudioTab.tsx +0 -3
  813. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -3
  814. package/src/renderer/src/components/Settings/GeneralTab.tsx +0 -3
  815. package/src/renderer/src/components/Settings/MemoryTab.tsx +0 -3
  816. package/src/renderer/src/components/Settings/PluginsTab.tsx +0 -3
  817. package/src/renderer/src/components/Settings/ProfileTab.tsx +0 -3
  818. package/src/renderer/src/components/Settings/ShortcutsTab.tsx +0 -3
  819. package/src/renderer/src/components/Settings/ThemeTab.tsx +0 -3
  820. package/src/renderer/src/components/StockCard.tsx +0 -3
  821. package/src/renderer/src/components/WeatherCard.tsx +0 -3
  822. package/src/renderer/src/css/pictures.css +0 -2
  823. package/src/renderer/src/css/settings/base.css +0 -1266
  824. package/src/renderer/src-web/components/PicturesLibrary.tsx +0 -2
  825. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +0 -4
  826. package/src/renderer/src-web/components/Settings/AudioTab.tsx +0 -3
  827. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -3
  828. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +0 -3
  829. package/src/renderer/src-web/components/Settings/MemoryTab.tsx +0 -3
  830. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +0 -3
  831. package/src/renderer/src-web/components/Settings/ProfileTab.tsx +0 -3
  832. package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +0 -3
  833. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +0 -3
  834. package/src/renderer/src-web/css/pictures.css +0 -2
  835. package/src/renderer/src-web/css/spotlight.css +0 -102
  836. package/src/renderer/src-web/css/widget.css +0 -213
  837. /package/src/renderer/shared/components/{UiMockupWidget.tsx → cards/UiMockupWidget.tsx} +0 -0
  838. /package/src/renderer/shared/components/{ContextCompactionStatus.tsx → chat/ContextCompactionStatus.tsx} +0 -0
  839. /package/src/renderer/shared/components/{GeminiLiveOverlay.css → chat/GeminiLiveOverlay.css} +0 -0
  840. /package/src/renderer/shared/components/{GeminiLiveOverlay.test.tsx → chat/GeminiLiveOverlay.test.tsx} +0 -0
  841. /package/src/renderer/shared/components/{SlashSuggestions.test.tsx → chat/SlashSuggestions.test.tsx} +0 -0
  842. /package/src/renderer/shared/components/{VirtualChatHistory.tsx → chat/VirtualChatHistory.tsx} +0 -0
  843. /package/src/renderer/shared/components/{VoicePill.d.ts → chat/VoicePill.d.ts} +0 -0
  844. /package/src/renderer/shared/components/{VoicePill.test.tsx → chat/VoicePill.test.tsx} +0 -0
  845. /package/src/renderer/shared/components/{ScreenCaptureDialog.css → studio/ScreenCaptureDialog.css} +0 -0
  846. /package/src/renderer/shared/components/{ScreenCaptureDialog.tsx → studio/ScreenCaptureDialog.tsx} +0 -0
  847. /package/src/renderer/shared/components/{SelectField.test.tsx → ui/SelectField.test.tsx} +0 -0
  848. /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
- truncate(result)
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 — raised to 40 as a middle ground.
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(default, alias = "cmd")]
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
- // Some providers encode the structured patch twice. Decode only that field;
1284
- // retain strict typing for all other arguments and never infer a target file.
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
- // Track the most recent step (if any) that successfully modified a file
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..=MAX_STEPS {
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 decisions_are_parallel_subagent_batch(&decisions) {
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
- if unverified_modification(
2365
- last_modify_step,
2366
- last_verify_step,
2367
- &decision.input.verification,
2368
- ) {
2369
- let err_msg = "Error: You modified a file (apply_patch/write_file) in this \
2370
- run but finished without verifying it. Call the verify tool \
2371
- with build/test/lint commands appropriate for this project \
2372
- before finishing. If no check genuinely applies (e.g. no test \
2373
- suite, documentation-only change), say so explicitly in the \
2374
- finish action's 'verification' field and finish again.";
2375
- trajectory.push(format_trajectory_step(
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(&notice);
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: format!("run-{}", chrono::Local::now().format("%Y%m%d%H%M%S")),
2667
+ run_id: run_id.clone(),
2483
2668
  task: task.to_string(),
2484
- outcome: "SUCCESS".to_string(),
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 and the duplicate-shell skip, since
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 crate::hooks::run_pre_tool_hooks(
2664
- &hooks,
2665
- &decision.action,
2666
- &input_val,
2667
- &root,
2668
- ) {
2669
- crate::hooks::PreHookOutcome::Blocked(reason) => {
2670
- format!(
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
- crate::hooks::PreHookOutcome::Allowed => {
2676
- if matches!(
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
- if hook_messages.is_empty() {
2729
- tool_result
2730
- } else {
2731
- format!(
2732
- "{}\n\n{}",
2733
- tool_result,
2734
- hook_messages
2735
- .iter()
2736
- .map(|message| format!("[Hook] {}", message))
2737
- .collect::<Vec<_>>()
2738
- .join("\n")
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 = action_succeeded && !shell_result_failed(&result);
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
- last_modify_step = Some(step);
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
- last_verify_step = Some(step);
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
- final_result.push_str(
2849
- "\n\n[System Tip: The command failed with a non-zero exit code. \
2850
- Analyze the stdout/stderr above to locate the error, read the offending files, \
2851
- apply corrected edits (using apply_patch), and run the verification command again. \
2852
- Do not finish or stop until the compilation or test errors are resolved!]"
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) — finish will be rejected until you do, unless you \
2861
- state in the finish action's verification field why no check applies (e.g. no \
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:tool_progress_input(&root,&decision),
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
- if (total_tokens as f64) >= (window as f64) * COMPACTION_TRIGGER_RATIO {
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: format!("run-{}", chrono::Local::now().format("%Y%m%d%H%M%S")),
3330
+ run_id: run_id.clone(),
2949
3331
  task: task.to_string(),
2950
- outcome: "FAILED".to_string(),
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.fail();
2968
- Err(OrchestrationError::Agent(format!(
2969
- "code agent reached the limit of {} steps; task incomplete. Last browser state: {}",
2970
- MAX_STEPS, crate::browser::session_last_state().await
2971
- )))
2972
- }).await;
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 one subagent to completion and returns its formatted result text.
3020
- /// Extracted from `execute_tool`'s `"dispatch_subagent"` arm so both the plain
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
- chat_id: &str,
3032
- name: &str,
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 Some(definition) = crate::subagents::find_subagent(name, Some(root)) else {
3038
- return Err(OrchestrationError::Agent(format!(
3039
- "no subagent named '{name}' found (check .agents/subagents/ or \
3040
- ~/.config/mint/mint-agents/)"
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 mut sub_config = config.clone();
3045
- if let Some(provider) = &definition.provider {
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
- // The subagent's own persona/instructions are folded into the task
3063
- // framing rather than replacing Mint's system prompt (which
3064
- // `orchestrate_agent_loop` builds internally and isn't a parameter),
3065
- // so the subagent still follows the same tool-use protocol and
3066
- // safety rules as any other agent run.
3067
- let sub_task = format!(
3068
- "{}\n\nTask from parent agent: {task}",
3069
- definition.system_prompt
3070
- );
3071
- let sub_chat_id = format!("{chat_id}::subagent::{}", definition.name);
3072
-
3073
- // Docker sandbox: a subagent whose resolved backend is "docker" gets one
3074
- // container for its whole run, started here and torn down unconditionally
3075
- // below (success or failure) — never per individual `run_shell` call. See
3076
- // `docker_sandbox`'s module doc for why this is a session-scoped resource
3077
- // rather than something `run_shell_command` starts itself. Subagents with
3078
- // no `run_shell` in their tool list (e.g. `explorer`/`plan`) never need a
3079
- // container, so skip starting one for them.
3080
- let allows_run_shell = definition
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
- // Recursing into `orchestrate_agent_loop` from inside `execute_tool`
3109
- // (which it itself calls) requires boxing this one call — Rust
3110
- // can't compute a finite size for a directly self-referential
3111
- // async fn cycle otherwise. `approve_cb` is reborrowed rather than
3112
- // moved so the subagent's mutating actions still go through the
3113
- // same approval gate as the caller's; `chunk` stays a no-op so the
3114
- // subagent's own streamed answer text never lands in the parent's chat
3115
- // — only its final summary (returned below) does. `progress` is *not*
3116
- // a no-op: `ToolStart`/`ToolEnd` are re-tagged with this subagent's name
3117
- // and forwarded to the caller's real `progress`, so the CLI/GUI can
3118
- // render the subagent's own tool calls nested under its
3119
- // `dispatch_subagent` call — this only affects what's shown in the UI,
3120
- // never what reaches the parent model's context (that isolation comes
3121
- // from `sub_chat_id`/`native_messages` staying local to this call, not
3122
- // from suppressing progress).
3123
- // `orchestrate_agent_loop` itself returns a boxed `dyn Future`
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
- /// Runs subagent decisions concurrently, capped at `PARALLEL_SUBAGENT_LIMIT`.
3207
- /// Progress and approvals use short Mutex-backed adapters during execution;
3208
- /// action counts and trajectory are updated afterward in decision order.
3209
- #[allow(clippy::too_many_arguments)]
3210
- async fn run_parallel_subagent_batch(
3211
- decisions: &[(String, AgentDecision)],
3212
- step: usize,
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
- let mut dispatches = Vec::with_capacity(decisions.len());
3231
- for (index, (call_id, decision)) in decisions.iter().enumerate() {
3232
- let call_id = call_id.clone();
3233
- let thought = decision.thought.clone();
3234
- let action = decision.action.clone();
3235
- let input_val = tool_progress_input(&root, &decision);
3236
- let action_key = action_fingerprint(decision);
3237
- let name = decision.input.name.clone();
3238
- let task_text = decision.input.instruction.clone();
3239
- let approve_mutex = &approve_mutex;
3240
- let progress_mutex = &progress_mutex;
3241
- dispatches.push(async move {
3242
- let mut progress_adapter = |event: AgentProgress| {
3243
- let mut guard = progress_mutex.lock().unwrap();
3244
- (*guard)(event);
3245
- };
3246
- progress_adapter(AgentProgress::ToolStart {
3247
- call_id: Some(call_id.clone()),
3248
- action: action.clone(),
3249
- input: input_val.clone(),
3250
- subagent: None,
3251
- });
3252
- let result: Result<String, OrchestrationError> = if name.trim().is_empty() {
3253
- Err(OrchestrationError::Agent("name is required".into()))
3254
- } else if task_text.trim().is_empty() {
3255
- Err(OrchestrationError::Agent("instruction is required".into()))
3256
- } else {
3257
- let mut approve_adapter =
3258
- |approval: &AgentApproval| -> Result<ApprovalOutcome, String> {
3259
- let mut guard = approve_mutex.lock().unwrap();
3260
- (*guard)(approval)
3261
- };
3262
- dispatch_one_subagent(
3263
- root,
3264
- config,
3265
- chat_id,
3266
- &name,
3267
- &task_text,
3268
- &mut approve_adapter,
3269
- &mut progress_adapter,
3270
- )
3271
- .await
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
- let tool_result = match result {
3274
- Ok(text) => text,
3275
- Err(error) => format!("Error: {}", error),
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
- let mut results = futures_util::stream::iter(dispatches)
3299
- .buffer_unordered(PARALLEL_SUBAGENT_LIMIT)
3300
- .collect::<Vec<_>>()
3301
- .await;
3302
- results.sort_by_key(|(index, ..)| *index);
3303
- let mut step_tool_results = Vec::with_capacity(results.len());
3304
- for (_, call_id, thought, action, input_val, action_key, tool_result) in results {
3305
- let action_count = {
3306
- let count = action_counts.entry(action_key).or_insert(0);
3307
- *count += 1;
3308
- *count
3309
- };
3310
- let mut final_result = model_tool_result(&action, &tool_result);
3311
- if action_count >= 3 && !action.starts_with("browser_") {
3312
- final_result.push_str(
3313
- "\n\n[System Tip: You repeated the same tool action three or more times. \
3314
- Stop repeating it. If you already have enough information or the requested edit is done, \
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
- trajectory.push(format_trajectory_step(
3320
- step,
3321
- &thought,
3322
- &action,
3323
- &final_result,
3324
- ));
3325
- step_tool_results.push((call_id, action, input_val, final_result).into());
3326
- }
3327
- step_tool_results
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(call_id.clone()),
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 execute_tool(
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(res) => (res, true),
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(call_id.clone()), status: None,
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 results {
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
- step_tool_results.push((call_id, action, input_val, final_result).into());
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 result = execute_tool_outcome_reviewed_inner(
3854
+ let execution = execute_tool_outcome_reviewed_inner(
3594
3855
  root, config, decision, chat_id, approve, progress, call_id, expected,
3595
- )
3596
- .await;
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
- execute_tool_text(root, config, decision, chat_id, approve, progress)
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
- .map(Into::into)
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
- decode_agent_input(input).map_err(|error| {
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: *status != crate::mcp_result::ToolStatus::Success,
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
- || (matches!(
4037
- action.as_str(),
4038
- "run_shell" | "verify" | "run_tests" | "run_typecheck" | "run_linter"
4039
- ) && shell_result_failed(&text))
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!(!shell_result_failed(
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
- // Edited, never verified, but explicitly explained why no check applies.
4886
- assert!(!unverified_modification(
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
- // Verify failed, but the agent explicitly addressed it in the finish
4903
- // action's verification field (e.g. explaining it's pre-existing).
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 explicit_written_justification_satisfies_the_gate_without_a_verify_call() {
5175
- assert!(!unverified_modification(
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
  }