@pheem49/mint 1.17.0 โ 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +2 -2
- package/README.md +8 -2
- package/Release_Note.md +222 -71
- package/crates/mint-cli/src/agent/diff_render.rs +66 -44
- package/crates/mint-cli/src/agent/live_status.rs +21 -4
- package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
- package/crates/mint-cli/src/agent.rs +25 -10
- package/crates/mint-cli/src/commands/agent.rs +4 -92
- package/crates/mint-cli/src/commands/browser.rs +2 -15
- package/crates/mint-cli/src/commands/integrations.rs +3 -3
- package/crates/mint-cli/src/commands/mod.rs +0 -3
- package/crates/mint-cli/src/commands/tailnet_web.rs +1 -0
- package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
- package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
- package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
- package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
- package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
- package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
- package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
- package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
- package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
- package/crates/mint-cli/src/interactive/command_ui.rs +53 -5
- package/crates/mint-cli/src/interactive/composer.rs +186 -0
- package/crates/mint-cli/src/interactive/format.rs +78 -3
- package/crates/mint-cli/src/interactive/input_box.rs +61 -1
- package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
- package/crates/mint-cli/src/interactive/slash_commands.rs +648 -371
- package/crates/mint-cli/src/interactive/tool_activity.rs +93 -20
- package/crates/mint-cli/src/interactive.rs +37 -0
- package/crates/mint-cli/src/main.rs +6 -3
- package/crates/mint-cli/src/markdown.rs +2 -37
- package/crates/mint-cli/src/onboard.rs +700 -282
- package/crates/mint-cli/src/setup.rs +1342 -624
- package/crates/mint-cli/src/terminal_theme.rs +159 -35
- package/crates/mint-cli/tests/library_intake.rs +1 -0
- package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
- package/crates/mint-core/src/agent/chat/custom.rs +149 -0
- package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
- package/crates/mint-core/src/agent/chat/mod.rs +857 -0
- package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
- package/crates/mint-core/src/agent/chat/openai.rs +582 -0
- package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
- package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
- package/crates/mint-core/src/agent/memory.rs +56 -2
- package/crates/mint-core/src/agent/tasks.rs +2 -2
- package/crates/mint-core/src/api_server/routes/chat.rs +24 -2
- package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
- package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
- package/crates/mint-core/src/api_server/routes/sessions.rs +21 -0
- package/crates/mint-core/src/avatar_bridge.rs +0 -22
- package/crates/mint-core/src/browser/cdp.rs +10 -4
- package/crates/mint-core/src/browser/headless.rs +582 -0
- package/crates/mint-core/src/browser/input.rs +7 -62
- package/crates/mint-core/src/browser/interact.rs +0 -14
- package/crates/mint-core/src/browser/mod.rs +8 -29
- package/crates/mint-core/src/browser/navigate.rs +2 -36
- package/crates/mint-core/src/browser/runtime.rs +2 -4
- package/crates/mint-core/src/browser/session.rs +50 -466
- package/crates/mint-core/src/browser/workflow.rs +142 -32
- package/crates/mint-core/src/cron/store.rs +2 -2
- package/crates/mint-core/src/integrations/mcp.rs +6 -8
- package/crates/mint-core/src/lib.rs +0 -5
- package/crates/mint-core/src/media/gemini_live.rs +636 -86
- package/crates/mint-core/src/media/library/migration.rs +26 -0
- package/crates/mint-core/src/media/library/tests.rs +1 -1
- package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
- package/crates/mint-core/src/media/pictures.rs +1 -1
- package/crates/mint-core/src/media/tool_images.rs +92 -1
- package/crates/mint-core/src/mods/manager.rs +101 -35
- package/crates/mint-core/src/orchestration/context_render.rs +441 -24
- package/crates/mint-core/src/orchestration/decision_parsing.rs +531 -16
- package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
- package/crates/mint-core/src/orchestration/loop_guard.rs +145 -0
- package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
- package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
- package/crates/mint-core/src/orchestration/mod.rs +320 -4249
- package/crates/mint-core/src/orchestration/reliability.rs +67 -27
- package/crates/mint-core/src/orchestration/review_regressions.rs +5 -0
- package/crates/mint-core/src/orchestration/tests.rs +1665 -0
- package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
- package/crates/mint-core/src/orchestration/tools/files.rs +1 -0
- package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
- package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
- package/crates/mint-core/src/orchestration/tools/shell.rs +129 -58
- package/crates/mint-core/src/orchestration/truncation.rs +170 -13
- package/crates/mint-core/src/orchestration/types.rs +648 -0
- package/crates/mint-core/src/orchestration/verification.rs +0 -3
- package/crates/mint-core/src/orchestration/workspace_helpers.rs +24 -6
- package/crates/mint-core/src/prompts/agent.rs +52 -76
- package/crates/mint-core/src/prompts/tool_catalog.rs +8 -151
- package/crates/mint-core/src/search/files.rs +20 -0
- package/crates/mint-core/src/slash/mod.rs +228 -265
- package/crates/mint-core/src/slash/models.rs +6 -1
- package/crates/mint-core/src/system/bg_shell.rs +1 -0
- package/crates/mint-core/src/system/config.rs +176 -34
- package/crates/mint-mod-runtime/src/lib.rs +37 -23
- package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
- package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
- package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
- package/out/web/assets/{CliSessionsView-MI8kXMAb.js โ CliSessionsView-z5i6qGyb.js} +1 -1
- package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
- package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
- package/out/web/assets/{CommandPalette-BD8SlTE5.js โ CommandPalette-DaKlzbKs.js} +1 -1
- package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
- package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
- package/out/web/assets/{ImageStudioPanel-BP7N4rLr.js โ ImageStudioPanel-B5N7TjeB.js} +1 -1
- package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
- package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
- package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
- package/out/web/assets/{McpRegistryPicker-CoS83vtc.js โ McpRegistryPicker-DnXPlzI7.js} +1 -1
- package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
- package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
- package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
- package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
- package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
- package/out/web/assets/{PluginsView-jBYmcKh4.js โ PluginsView-DoTg9MVa.js} +1 -1
- package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
- package/out/web/assets/{ScheduledTasksView-5uw9-V9H.js โ ScheduledTasksView-BD5DVPkZ.js} +1 -1
- package/out/web/assets/{SelectField-DXBQtrUK.js โ SelectField-Cy6CghrU.js} +1 -1
- package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
- package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
- package/out/web/assets/{SkillsView-C-gp9J7m.js โ SkillsView-CIeWwq5z.js} +1 -1
- package/out/web/assets/StockCard-DxUciKyp.js +1 -0
- package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
- package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
- package/out/web/assets/{UiFeatureCard-6BwxTOCe.js โ UiFeatureCard-CdY0hGWy.js} +1 -1
- package/out/web/assets/{UiGridCard-DvZcLNxb.js โ UiGridCard-BBK3I5hC.js} +1 -1
- package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
- package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css โ VeoStudioPanel-9kU9YLEN.css} +1 -1
- package/out/web/assets/{VeoStudioPanel-DfsOJKv0.js โ VeoStudioPanel-Co6-wEWM.js} +2 -2
- package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
- package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
- package/out/web/assets/{abnfDiagram-VCTEODGH-BOo3Ckng.js โ abnfDiagram-VCTEODGH-CMfR95N2.js} +1 -1
- package/out/web/assets/{architecture-7GRP2DOG-ZOwheW3n.js โ architecture-7GRP2DOG-DhAM4HyO.js} +1 -1
- package/out/web/assets/{architectureDiagram-5GKGNRK7-DC2FDzVd.js โ architectureDiagram-5GKGNRK7-Yk3TUJ_F.js} +1 -1
- package/out/web/assets/{blockDiagram-I7D4REHJ-BrKFWdAH.js โ blockDiagram-I7D4REHJ-X9fBgJik.js} +1 -1
- package/out/web/assets/{c4Diagram-7LVT6UL2-BHTHK1Yd.js โ c4Diagram-7LVT6UL2-Bk9x7N-8.js} +1 -1
- package/out/web/assets/{chunk-4HAMMTFA-BYFkZwNj.js โ chunk-4HAMMTFA-Dx5fk1_0.js} +1 -1
- package/out/web/assets/{chunk-75Z2AOVW-BSKfSKSA.js โ chunk-75Z2AOVW-BszTQoM4.js} +1 -1
- package/out/web/assets/{chunk-DU6HZSFF-vIhsiW6n.js โ chunk-DU6HZSFF-DK-As3rk.js} +1 -1
- package/out/web/assets/{chunk-F27PBJKO-BlYpW9xq.js โ chunk-F27PBJKO-LmncZZIS.js} +1 -1
- package/out/web/assets/{chunk-GMAD6QVW-BIBz_Ug5.js โ chunk-GMAD6QVW-DSQ_sDRJ.js} +1 -1
- package/out/web/assets/{chunk-GVQU2GXP-NPSHxeqC.js โ chunk-GVQU2GXP-Cno3ulje.js} +1 -1
- package/out/web/assets/{chunk-IMKFNOWR-YlJK8_Fp.js โ chunk-IMKFNOWR-bIzfY9Jc.js} +1 -1
- package/out/web/assets/{chunk-L3NEJ4N5-DMYi4-af.js โ chunk-L3NEJ4N5-Dl6eOa0D.js} +1 -1
- package/out/web/assets/{chunk-OSK3NFVY-Begd3-jA.js โ chunk-OSK3NFVY-BaUGJ-cM.js} +1 -1
- package/out/web/assets/{chunk-P2QGCYS3-D1I4xFHN.js โ chunk-P2QGCYS3-_066ndhH.js} +1 -1
- package/out/web/assets/{chunk-POPQ4Y6H-CafZZzNE.js โ chunk-POPQ4Y6H-YT1AKhTv.js} +1 -1
- package/out/web/assets/{chunk-PWAF6VOD-Dwh6_Lnt.js โ chunk-PWAF6VOD-CrEgDsxh.js} +1 -1
- package/out/web/assets/{chunk-SHT3W25Y-B0XrCSEb.js โ chunk-SHT3W25Y-_KwaRLOl.js} +1 -1
- package/out/web/assets/{chunk-SVP7TREG-CxLsddxC.js โ chunk-SVP7TREG-BQ1qb32L.js} +1 -1
- package/out/web/assets/{chunk-TICWLB2K-y156QInb.js โ chunk-TICWLB2K-BDyehCtg.js} +1 -1
- package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
- package/out/web/assets/{config-DqXJXeZM.css โ config-2suVFPZg.css} +1 -1
- package/out/web/assets/config-CkRCdkxn.js +2 -0
- package/out/web/assets/{cynefin-OW5HDTMX-SMVvrLf-.js โ cynefin-OW5HDTMX-Dl_31aaN.js} +1 -1
- package/out/web/assets/{cynefinDiagram-5FMLGOSQ-rZznN24T.js โ cynefinDiagram-5FMLGOSQ-Bpir8jha.js} +1 -1
- package/out/web/assets/{dagre-GXQ25YYZ-BOToydFu.js โ dagre-GXQ25YYZ-D9xu3nsq.js} +1 -1
- package/out/web/assets/{diagram-S7CK7UJ4-TMVaGQEJ.js โ diagram-S7CK7UJ4-DeveoEeB.js} +1 -1
- package/out/web/assets/{diagram-UQ7AKVKN-C2JK-GP2.js โ diagram-UQ7AKVKN-p4xWRESV.js} +1 -1
- package/out/web/assets/{diagram-VSXAHHWV-Cw8VSi2K.js โ diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
- package/out/web/assets/{diagram-VX7I27RA-DXp4fKk9.js โ diagram-VX7I27RA-DRiCa3Vd.js} +1 -1
- package/out/web/assets/{diagram-Z3DM3KII-CzSicYiW.js โ diagram-Z3DM3KII-Bt7zduQX.js} +1 -1
- package/out/web/assets/{ebnfDiagram-PWID7BFC-Dqj-zqPu.js โ ebnfDiagram-PWID7BFC-DV9WpwdR.js} +1 -1
- package/out/web/assets/{erDiagram-RLTQ6QDP-DZwFZAuD.js โ erDiagram-RLTQ6QDP-BJ_nwG2m.js} +1 -1
- package/out/web/assets/{eventmodeling-NTZA5JFV-C0O-Lvgp.js โ eventmodeling-NTZA5JFV-BTBMrfVa.js} +1 -1
- package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
- package/out/web/assets/{ganttDiagram-EL5Y4UJY-Da4ULXTl.js โ ganttDiagram-EL5Y4UJY-DQLdsXrC.js} +1 -1
- package/out/web/assets/{gitGraph-4MIJSDKK-B9wNFZIP.js โ gitGraph-4MIJSDKK-CL1OEOQ_.js} +1 -1
- package/out/web/assets/{gitGraphDiagram-WWUBYQGX-RjrhB0MP.js โ gitGraphDiagram-WWUBYQGX-8cSi42H7.js} +1 -1
- package/out/web/assets/index-BocxnrDG.js +168 -0
- package/out/web/assets/index-yeh_r55a.css +1 -0
- package/out/web/assets/{info-A6RAGUB7-B3dfCpeY.js โ info-A6RAGUB7-C1zsX5sc.js} +1 -1
- package/out/web/assets/{infoDiagram-27XIBGKW-QVWkLVx7.js โ infoDiagram-27XIBGKW-DsRYtIyI.js} +1 -1
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-CLFNIRI9.js โ ishikawaDiagram-5VMMS53U-B2crwIXj.js} +1 -1
- package/out/web/assets/{journeyDiagram-3NMN7TZE-fgOQadYF.js โ journeyDiagram-3NMN7TZE-BTH-GyVR.js} +1 -1
- package/out/web/assets/{kanban-definition-UXKFOSKX-G3Q_9g9O.js โ kanban-definition-UXKFOSKX-BeRfCnuA.js} +1 -1
- package/out/web/assets/{mermaid-parser.core-v3Jm7nco.js โ mermaid-parser.core-BJwgCBBc.js} +3 -3
- package/out/web/assets/{mermaid.core-DTEjLUA1.js โ mermaid.core-CsDDodN8.js} +4 -4
- package/out/web/assets/{mindmap-definition-YA3MSWOX-C_D42_fF.js โ mindmap-definition-YA3MSWOX-B42efOQ1.js} +1 -1
- package/out/web/assets/{modelManager-BPLycQrN.js โ modelManager-B3ZFMh8v.js} +2 -2
- package/out/web/assets/{oauthManager-BUK7ic7q.js โ oauthManager-Ch5Du2fF.js} +1 -1
- package/out/web/assets/{packet-AYTQ26CC-B-R-SHlF.js โ packet-AYTQ26CC-B6xUyE6r.js} +1 -1
- package/out/web/assets/{pegDiagram-XKGWAZYB-Da2q_giz.js โ pegDiagram-XKGWAZYB-1LabVycL.js} +1 -1
- package/out/web/assets/{pie-WAS4IAKB-BC8F0N7-.js โ pie-WAS4IAKB-DJwo-nf_.js} +1 -1
- package/out/web/assets/{pieDiagram-E7YTZNPT-Iy9REJNC.js โ pieDiagram-E7YTZNPT-icmAqzy4.js} +1 -1
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-ztsiYw7h.js โ quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +1 -1
- package/out/web/assets/{radar-RG4KPBEZ-B_1P8Z_z.js โ radar-RG4KPBEZ-D7d-Xriw.js} +1 -1
- package/out/web/assets/{railroad-74A4TZTK-DuTqczSo.js โ railroad-74A4TZTK-viuhdYbs.js} +1 -1
- package/out/web/assets/{railroad-abnf-HS5TGJTU-Df29WLmp.js โ railroad-abnf-HS5TGJTU-DNXiCNea.js} +1 -1
- package/out/web/assets/{railroad-ebnf-LZEXJU2U-ePFMo-qp.js โ railroad-ebnf-LZEXJU2U-nkSu1-0C.js} +1 -1
- package/out/web/assets/{railroad-peg-WCYAUIDC-DYXLW4_E.js โ railroad-peg-WCYAUIDC-BhbMeQzz.js} +1 -1
- package/out/web/assets/{railroadDiagram-O6MQD6OU-LStnZf2S.js โ railroadDiagram-O6MQD6OU-CLpG1jUX.js} +1 -1
- package/out/web/assets/{requirementDiagram-BXWQKSXE-DmzonMH-.js โ requirementDiagram-BXWQKSXE-kRDrvWrc.js} +1 -1
- package/out/web/assets/{sankeyDiagram-P5KCCOFB-B8vjmUU1.js โ sankeyDiagram-P5KCCOFB-DxAwmq4x.js} +1 -1
- package/out/web/assets/{sequenceDiagram-WJ2MYXX4-CTnplOAX.js โ sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js} +1 -1
- package/out/web/assets/{stateDiagram-D77RDMKH-BFrIaLdI.js โ stateDiagram-D77RDMKH-D5QWfOhp.js} +1 -1
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
- package/out/web/assets/{swimlanes-42K2YHIH-BXGngUNU.js โ swimlanes-42K2YHIH-CRs5UyA-.js} +1 -1
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
- package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
- package/out/web/assets/{timeline-definition-24CTP7MA-CegH7n43.js โ timeline-definition-24CTP7MA-Denqd9qb.js} +1 -1
- package/out/web/assets/{treeView-Q6P3EWNA-DTpJv2Tw.js โ treeView-Q6P3EWNA-0my3Z3s4.js} +1 -1
- package/out/web/assets/{treemap-WGGIJYW6-i8M9iJU6.js โ treemap-WGGIJYW6-B_EjkLYq.js} +1 -1
- package/out/web/assets/{vennDiagram-4TSXK5OY-Dpzo1S8U.js โ vennDiagram-4TSXK5OY-DxVg3Ewr.js} +1 -1
- package/out/web/assets/{wardley-WFR3VGLG-BrNsgtfw.js โ wardley-WFR3VGLG-CGD-Tj4p.js} +1 -1
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-lQ92LFE1.js โ wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +1 -1
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-BaOaEoBd.js โ xychartDiagram-S5SC5T6Z-BVM9bKis.js} +1 -1
- package/out/web/index-web.html +2 -2
- package/out/web/index.html +2 -2
- package/package.json +2 -2
- package/shared-modules/CODEOWNERS +3 -3
- package/shared-modules/SDL2/SDL2_mixer.json +27 -0
- package/shared-modules/appstream/README.md +13 -0
- package/shared-modules/appstream/appstream-compose.json +111 -0
- package/shared-modules/dbus-glib/dbus-glib.json +2 -2
- package/shared-modules/libportal/libportal.json +23 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
- package/shared-modules/linux-audio/fluidsynth2.json +2 -2
- package/shared-modules/linux-audio/libinstpatch.json +2 -2
- package/shared-modules/linux-audio/liblo-static.json +3 -0
- package/shared-modules/luajit/luajit.json +2 -2
- package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
- package/slash-commands.json +7 -2
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/renderer/shared/agentProgress.ts +51 -3
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -56
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +15 -1
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +25 -12
- package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
- package/src/renderer/shared/components/Settings/ThemeTab.tsx +106 -3
- package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +67 -17
- package/src/renderer/shared/components/activity/AgentActivityTable.tsx +376 -134
- package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
- package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
- package/src/renderer/shared/components/activity/McpToolResults.tsx +39 -2
- package/src/renderer/shared/components/activity/SubagentDagView.tsx +7 -7
- package/src/renderer/shared/components/activity/ViewedImages.tsx +41 -2
- package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +3 -3
- package/src/renderer/shared/components/app/CommandPalette.tsx +7 -7
- package/src/renderer/shared/components/app/DesktopTitlebar.tsx +2 -2
- package/src/renderer/shared/components/app/MintDashboard.tsx +27 -17
- package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +3 -3
- package/src/renderer/shared/components/cards/CalculationCard.tsx +3 -3
- package/src/renderer/shared/components/cards/ImageGenCard.tsx +2 -2
- package/src/renderer/shared/components/cards/ImageSearchCard.tsx +2 -2
- package/src/renderer/shared/components/cards/MermaidCard.tsx +1 -1
- package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
- package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
- package/src/renderer/shared/components/cards/StockCard.tsx +7 -7
- package/src/renderer/shared/components/cards/ToolResultCards.tsx +11 -3
- package/src/renderer/shared/components/cards/UiMockupWidget.tsx +4 -4
- package/src/renderer/shared/components/cards/WeatherCard.tsx +5 -5
- package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +11 -0
- package/src/renderer/shared/components/chat/ApprovalCard.tsx +21 -2
- package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
- package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +15 -1
- package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +40 -2
- package/src/renderer/shared/components/chat/ChatMessageItem.tsx +180 -34
- package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
- package/src/renderer/shared/components/chat/ChatPanel.test.tsx +22 -0
- package/src/renderer/shared/components/chat/ChatPanel.tsx +227 -53
- package/src/renderer/shared/components/chat/DiffReviewModal.tsx +6 -6
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +142 -1
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +45 -3
- package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +42 -14
- package/src/renderer/shared/components/chat/RewindModal.tsx +10 -10
- package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +14 -0
- package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
- package/src/renderer/shared/components/chat/SourcesBlock.tsx +236 -90
- package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +45 -1
- package/src/renderer/shared/components/chat/TurnFileChanges.tsx +202 -169
- package/src/renderer/shared/components/extensions/CliSessionsView.tsx +5 -4
- package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +3 -3
- package/src/renderer/shared/components/studio/LibraryPage.tsx +48 -5
- package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +125 -13
- package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
- package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +77 -16
- package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +78 -22
- package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +13 -0
- package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +48 -28
- package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +266 -47
- package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +18 -6
- package/src/renderer/shared/components/workspace/background-terminals.css +11 -11
- package/src/renderer/shared/constants/config.ts +1 -5
- package/src/renderer/shared/constants/models.test.ts +51 -0
- package/src/renderer/shared/constants/models.ts +139 -32
- package/src/renderer/shared/conversation/lifecycle.test.ts +91 -3
- package/src/renderer/shared/conversation/lifecycle.ts +104 -5
- package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
- package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
- package/src/renderer/shared/css/agent-observability.css +133 -0
- package/src/renderer/shared/css/chat-ui-cards.css +13 -13
- package/src/renderer/shared/css/code-review.css +63 -34
- package/src/renderer/shared/css/code-sessions.css +47 -18
- package/src/renderer/shared/css/collapsed-paste.css +1 -1
- package/src/renderer/shared/css/error-notice.css +1 -1
- package/src/renderer/shared/css/file-changes.css +165 -80
- package/src/renderer/shared/css/library.css +105 -10
- package/src/renderer/shared/css/management-views.css +1 -1
- package/src/renderer/shared/css/mcp-results.css +5 -5
- package/src/renderer/shared/css/model-selector.css +18 -18
- package/src/renderer/shared/css/mods.css +7 -7
- package/src/renderer/shared/css/notification-notice.css +2 -2
- package/src/renderer/shared/css/queued-messages.css +5 -5
- package/src/renderer/shared/css/settings/general.css +18 -13
- package/src/renderer/shared/css/settings/profile.css +5 -5
- package/src/renderer/shared/css/settings/theme.css +5 -5
- package/src/renderer/shared/css/slash-choice.css +2 -2
- package/src/renderer/shared/css/slash-suggestions.css +3 -3
- package/src/renderer/shared/css/sources-block.css +41 -2
- package/src/renderer/shared/css/tools-panel.css +2 -2
- package/src/renderer/shared/css/ui-tailwind.css +8 -0
- package/src/renderer/shared/css/veo-studio.css +2 -2
- package/src/renderer/shared/css/viewed-images.css +7 -7
- package/src/renderer/shared/platform.ts +2 -1
- package/src/renderer/shared/theme/themeManager.test.ts +17 -0
- package/src/renderer/shared/theme/themeManager.ts +21 -4
- package/src/renderer/shared/theme/themes.ts +54 -0
- package/src/renderer/shared/theme/tokens.css +6 -4
- package/src/renderer/shared/types.ts +1 -1
- package/src/renderer/shared/utils/agentActivity.test.ts +468 -2
- package/src/renderer/shared/utils/agentActivity.ts +602 -35
- package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
- package/src/renderer/shared/utils/artifactBrowser.ts +17 -19
- package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
- package/src/renderer/shared/utils/attachedDocument.ts +49 -0
- package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
- package/src/renderer/shared/utils/fileIcons.ts +149 -16
- package/src/renderer/shared/utils/markdown.tsx +72 -19
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +16 -0
- package/src/renderer/shared/utils/mcpResults.test.tsx +29 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
- package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
- package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
- package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
- package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
- package/src/renderer/shared/utils/workspaceDrag.ts +142 -0
- package/src/renderer/shared/utils/workspaceMedia.ts +10 -2
- package/src/renderer/src/App.tsx +14 -8
- package/src/renderer/src/agentProgress.ts +1 -0
- package/src/renderer/src/components/ScreenPicker.css +1 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +6 -5
- package/src/renderer/src/css/app-shell.css +4 -4
- package/src/renderer/src/css/chat.css +1261 -241
- package/src/renderer/src/css/desktop-tools.css +11 -11
- package/src/renderer/src/css/sidebar-workspace.css +2 -2
- package/src/renderer/src/css/tool-surfaces.css +12 -12
- package/src/renderer/src/index.css +38 -20
- package/src/renderer/src/tauri.ts +22 -2
- package/src/renderer/src-web/App.tsx +14 -0
- package/src/renderer/src-web/agentProgress.ts +1 -0
- package/src/renderer/src-web/css/app-shell.css +4 -4
- package/src/renderer/src-web/css/chat.css +1278 -190
- package/src/renderer/src-web/css/mobile.css +29 -13
- package/src/renderer/src-web/css/settings/mobile.css +2 -2
- package/src/renderer/src-web/css/sidebar-workspace.css +1 -1
- package/src/renderer/src-web/index.css +38 -20
- package/src/renderer/src-web/tauri.ts +22 -2
- package/src/renderer/tests/browserArtifacts.browser.tsx +1 -1
- package/src/renderer/tests/library.browser.tsx +3 -0
- package/src/renderer/tests/liveVoice.browser.html +1 -0
- package/src/renderer/tests/workspaceDrop.browser.tsx +1 -1
- package/src-tauri/src/app/lifecycle.rs +1 -14
- package/src-tauri/src/background/headless.rs +14 -8
- package/src-tauri/src/commands/conversation.rs +38 -0
- package/src-tauri/src/native/mod.rs +0 -1
- package/src-tauri/src/native/system.rs +4 -47
- package/crates/mint-cli/src/interactive/chat_tui.rs +0 -8855
- package/crates/mint-core/src/agent/chat.rs +0 -4394
- package/crates/mint-core/src/browser/lifecycle.rs +0 -291
- package/crates/mint-core/src/browser/logging.rs +0 -20
- package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
- package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +0 -2
- package/out/web/assets/CalculationCard-CRjP3E1j.js +0 -1
- package/out/web/assets/CliSessionsView-C3Br2v5Y.css +0 -1
- package/out/web/assets/CodeReviewPage-CUJceQme.css +0 -1
- package/out/web/assets/CodeReviewPage-VCUgPla_.js +0 -2
- package/out/web/assets/ImageGenCard-BCXxkVJX.js +0 -1
- package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +0 -1
- package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
- package/out/web/assets/LibraryPage-WlviRdR8.js +0 -2
- package/out/web/assets/LinkedFoldersView-CKRgroWK.js +0 -1
- package/out/web/assets/McpServersView-D3wHnuHg.js +0 -1
- package/out/web/assets/MermaidCard-NqrHdyJW.js +0 -2
- package/out/web/assets/MintDashboard-B2DJsDDO.css +0 -1
- package/out/web/assets/MintDashboard-COw_xol7.js +0 -63
- package/out/web/assets/RewindModal-DempuDod.js +0 -1
- package/out/web/assets/SettingsWindow-2YP3yj7s.js +0 -4
- package/out/web/assets/SettingsWindow-Dz8LQVgD.css +0 -1
- package/out/web/assets/StockCard-BDMCe7mx.js +0 -1
- package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +0 -38
- package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +0 -1
- package/out/web/assets/UiMockupWidget-DD73QWg1.js +0 -1
- package/out/web/assets/WeatherCard-CPucIfq8.js +0 -1
- package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +0 -2
- package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +0 -1
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +0 -1
- package/out/web/assets/config-CvIO5hkn.js +0 -2
- package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +0 -1
- package/out/web/assets/index-BmpulIkg.js +0 -168
- package/out/web/assets/index-CkvBwhX_.css +0 -1
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +0 -1
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +0 -8
- package/shared-modules/libsecret/libsecret.json +0 -23
- package/shared-modules/libusb/libusb.json +0 -27
- package/shared-modules/lzo/lzo.json +0 -27
- package/src/renderer/src/components/BrowserShell.tsx +0 -108
- package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
- package/src/renderer/src/components/browser-shell.css +0 -185
- package/src-tauri/src/native/browser.rs +0 -252
|
@@ -159,14 +159,46 @@ fn first_line_preview(path: &Path) -> String {
|
|
|
159
159
|
.map(str::trim)
|
|
160
160
|
.find(|l| !l.is_empty())
|
|
161
161
|
.unwrap_or("");
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
let clean = first.trim_start_matches('#').trim();
|
|
163
|
+
if clean.chars().count() > 80 {
|
|
164
|
+
let short: String = clean.chars().take(80).collect();
|
|
164
165
|
format!("{short}โฆ")
|
|
165
166
|
} else {
|
|
166
|
-
|
|
167
|
+
clean.to_string()
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
fn format_plan_meta(path: &Path) -> String {
|
|
172
|
+
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
|
|
173
|
+
if stem.len() >= 15 && stem.as_bytes().get(8) == Some(&b'-') {
|
|
174
|
+
let y = &stem[0..4];
|
|
175
|
+
let m = &stem[4..6];
|
|
176
|
+
let d = &stem[6..8];
|
|
177
|
+
let hh = &stem[9..11];
|
|
178
|
+
let mm = &stem[11..13];
|
|
179
|
+
let ss = &stem[13..15];
|
|
180
|
+
if y.chars().all(|c| c.is_ascii_digit())
|
|
181
|
+
&& m.chars().all(|c| c.is_ascii_digit())
|
|
182
|
+
&& d.chars().all(|c| c.is_ascii_digit())
|
|
183
|
+
&& hh.chars().all(|c| c.is_ascii_digit())
|
|
184
|
+
&& mm.chars().all(|c| c.is_ascii_digit())
|
|
185
|
+
&& ss.chars().all(|c| c.is_ascii_digit())
|
|
186
|
+
{
|
|
187
|
+
let date_str = format!("{y}-{m}-{d} {hh}:{mm}:{ss}");
|
|
188
|
+
if stem.len() > 16 {
|
|
189
|
+
let slug = &stem[16..];
|
|
190
|
+
return format!("{date_str} ยท {slug}");
|
|
191
|
+
} else {
|
|
192
|
+
return date_str;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
path.file_name()
|
|
197
|
+
.and_then(|n| n.to_str())
|
|
198
|
+
.unwrap_or("")
|
|
199
|
+
.to_string()
|
|
200
|
+
}
|
|
201
|
+
|
|
170
202
|
async fn execute_core_slash(
|
|
171
203
|
session: &mut InteractiveSession,
|
|
172
204
|
ui: &mut dyn CommandUi,
|
|
@@ -205,7 +237,19 @@ async fn execute_core_slash(
|
|
|
205
237
|
match response {
|
|
206
238
|
SlashResponse::Message { markdown } => {
|
|
207
239
|
if !markdown.is_empty() {
|
|
208
|
-
|
|
240
|
+
if matches!(
|
|
241
|
+
cmd,
|
|
242
|
+
"/release-notes"
|
|
243
|
+
| "/release_notes"
|
|
244
|
+
| "/release-note"
|
|
245
|
+
| "/release_note"
|
|
246
|
+
| "/releasenotes"
|
|
247
|
+
| "/releasenote"
|
|
248
|
+
) {
|
|
249
|
+
ui.push_release_notes(&markdown);
|
|
250
|
+
} else {
|
|
251
|
+
ui.push_command_output(markdown);
|
|
252
|
+
}
|
|
209
253
|
}
|
|
210
254
|
Some(SlashResult::Handled)
|
|
211
255
|
}
|
|
@@ -427,6 +471,34 @@ pub async fn handle_slash_command(
|
|
|
427
471
|
mint_core::slash::INIT_AGENTS_MD_PROMPT.to_string(),
|
|
428
472
|
)),
|
|
429
473
|
|
|
474
|
+
"/edit" | "/retry" => {
|
|
475
|
+
let scoped = mint_core::scoped_chat_id(
|
|
476
|
+
&session.chat_id,
|
|
477
|
+
Some(&session.current_dir.to_string_lossy()),
|
|
478
|
+
);
|
|
479
|
+
if let Ok(memory) = MemoryStore::open_default() {
|
|
480
|
+
if let Ok(rows) = memory.interactions_for_chat(&scoped) {
|
|
481
|
+
if let Some(last) = rows.last() {
|
|
482
|
+
let turn_id = last.id;
|
|
483
|
+
let prompt_to_run = if rest.is_empty() {
|
|
484
|
+
last.user_text.clone()
|
|
485
|
+
} else {
|
|
486
|
+
rest.to_string()
|
|
487
|
+
};
|
|
488
|
+
let _ = memory.truncate_interactions_from(&scoped, turn_id);
|
|
489
|
+
ui.push_notice(format!(
|
|
490
|
+
"Truncated turn #{} from history. Resending with prompt:\n{}",
|
|
491
|
+
rows.len(),
|
|
492
|
+
prompt_to_run
|
|
493
|
+
));
|
|
494
|
+
return Some(SlashResult::ForwardToAgent(prompt_to_run));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
ui.push_notice("No previous turn found to edit or retry.");
|
|
499
|
+
Some(SlashResult::Handled)
|
|
500
|
+
}
|
|
501
|
+
|
|
430
502
|
"/fast" => {
|
|
431
503
|
let choice = if rest.is_empty() {
|
|
432
504
|
let options = vec![
|
|
@@ -531,14 +603,15 @@ pub async fn handle_slash_command(
|
|
|
531
603
|
};
|
|
532
604
|
if rest.is_empty() {
|
|
533
605
|
if checkpoints.is_empty() {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
"{DIM}Checkpoints are created automatically before mutating file actions (write_file, apply_patch).{RESET}\n"
|
|
606
|
+
ui.push_notice(
|
|
607
|
+
"No git checkpoints recorded for this CLI session yet.\nCheckpoints are created automatically before mutating file actions (write_file, apply_patch).",
|
|
537
608
|
);
|
|
538
609
|
} else {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
610
|
+
let mut lines = vec![
|
|
611
|
+
format!("{BLUE}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}"),
|
|
612
|
+
format!("{MINT} โบ Available Git Checkpoints (Time Machine){RESET}"),
|
|
613
|
+
format!("{BLUE}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}"),
|
|
614
|
+
];
|
|
542
615
|
for cp in checkpoints.iter().rev() {
|
|
543
616
|
let hash_short = if cp.commit_hash.len() >= 7 {
|
|
544
617
|
&cp.commit_hash[..7]
|
|
@@ -546,24 +619,21 @@ pub async fn handle_slash_command(
|
|
|
546
619
|
&cp.commit_hash
|
|
547
620
|
};
|
|
548
621
|
let target = cp.target_path.as_deref().unwrap_or("-");
|
|
549
|
-
|
|
622
|
+
lines.push(format!(
|
|
550
623
|
" {MINT}Step {:<3}{RESET} {DIM}[{}]{RESET} {:<20} {DIM}({}){RESET}",
|
|
551
624
|
cp.step, hash_short, cp.action, target
|
|
552
|
-
);
|
|
625
|
+
));
|
|
553
626
|
}
|
|
554
|
-
|
|
555
|
-
"\n{DIM}Use {RESET}{MINT}/rewind <step>{RESET}{DIM} to restore workspace to before that step, or {RESET}{MINT}/rewind undo{RESET}{DIM} to recover from rescue snapshot.{RESET}
|
|
556
|
-
);
|
|
627
|
+
lines.push(format!(
|
|
628
|
+
"\n{DIM}Use {RESET}{MINT}/rewind <step>{RESET}{DIM} to restore workspace to before that step, or {RESET}{MINT}/rewind undo{RESET}{DIM} to recover from rescue snapshot.{RESET}"
|
|
629
|
+
));
|
|
630
|
+
ui.push_command_output(lines.join("\n"));
|
|
557
631
|
}
|
|
558
632
|
Some(SlashResult::Handled)
|
|
559
633
|
} else if rest.trim().eq_ignore_ascii_case("undo") {
|
|
560
634
|
match mint_core::git::undo_rollback(&session.current_dir) {
|
|
561
|
-
Ok(msg) =>
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
Err(err) => {
|
|
565
|
-
println!("\n{ERROR}Failed to undo rewind:{RESET} {err}\n");
|
|
566
|
-
}
|
|
635
|
+
Ok(msg) => ui.push_notice(msg),
|
|
636
|
+
Err(err) => ui.push_notice(format!("Failed to undo rewind: {err}")),
|
|
567
637
|
}
|
|
568
638
|
Some(SlashResult::Handled)
|
|
569
639
|
} else {
|
|
@@ -574,18 +644,14 @@ pub async fn handle_slash_command(
|
|
|
574
644
|
&target_rewind_chat_id,
|
|
575
645
|
step,
|
|
576
646
|
) {
|
|
577
|
-
Ok(msg) =>
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
Err(err) => {
|
|
581
|
-
println!("\n{ERROR}Failed to rollback:{RESET} {err}\n");
|
|
582
|
-
}
|
|
647
|
+
Ok(msg) => ui.push_notice(msg),
|
|
648
|
+
Err(err) => ui.push_notice(format!("Failed to rollback: {err}")),
|
|
583
649
|
}
|
|
584
650
|
}
|
|
585
651
|
Err(_) => {
|
|
586
|
-
|
|
587
|
-
"
|
|
588
|
-
);
|
|
652
|
+
ui.push_notice(format!(
|
|
653
|
+
"Invalid step number: \"{rest}\". Usage: /rewind <step> or /rewind undo"
|
|
654
|
+
));
|
|
589
655
|
}
|
|
590
656
|
}
|
|
591
657
|
Some(SlashResult::Handled)
|
|
@@ -702,28 +768,75 @@ pub async fn handle_slash_command(
|
|
|
702
768
|
}
|
|
703
769
|
|
|
704
770
|
"/thought" | "/think" => {
|
|
705
|
-
if let
|
|
706
|
-
|
|
707
|
-
} else {
|
|
708
|
-
ui.push_notice("No recent thought process recorded for this turn.");
|
|
771
|
+
if let Err(err) = ui.open_thought_viewer() {
|
|
772
|
+
ui.push_notice(format!("Could not open thought viewer: {err}"));
|
|
709
773
|
}
|
|
710
774
|
Some(SlashResult::Handled)
|
|
711
775
|
}
|
|
712
776
|
|
|
713
|
-
"/plan" if rest == "list" => {
|
|
777
|
+
"/plan" if rest == "list" || rest.starts_with("list ") => {
|
|
778
|
+
let arg = rest.trim_start_matches("list").trim();
|
|
714
779
|
let files = list_plan_files(&session.current_dir);
|
|
715
780
|
if files.is_empty() {
|
|
716
|
-
ui.push_notice("No saved plans yet.");
|
|
781
|
+
ui.push_notice("No saved plans yet under .agents/plans/");
|
|
782
|
+
} else if !arg.is_empty() {
|
|
783
|
+
let found = if let Ok(idx) = arg.parse::<usize>() {
|
|
784
|
+
idx.checked_sub(1).and_then(|i| files.get(i))
|
|
785
|
+
} else {
|
|
786
|
+
files.iter().find(|p| {
|
|
787
|
+
p.file_name()
|
|
788
|
+
.and_then(|n| n.to_str())
|
|
789
|
+
.is_some_and(|n| n.contains(arg))
|
|
790
|
+
})
|
|
791
|
+
};
|
|
792
|
+
match found {
|
|
793
|
+
Some(path) => match std::fs::read_to_string(path) {
|
|
794
|
+
Ok(content) => ui.push_plan(&content),
|
|
795
|
+
Err(e) => ui.push_notice(format!("Could not read plan file: {e}")),
|
|
796
|
+
},
|
|
797
|
+
None => ui.push_notice(format!(
|
|
798
|
+
"No saved plan matching \"{arg}\" โ run /plan list to see saved plans."
|
|
799
|
+
)),
|
|
800
|
+
}
|
|
717
801
|
} else {
|
|
718
|
-
let
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
802
|
+
let options = files
|
|
803
|
+
.iter()
|
|
804
|
+
.enumerate()
|
|
805
|
+
.map(|(idx, path)| {
|
|
806
|
+
let preview = first_line_preview(path);
|
|
807
|
+
let display_title = if preview.is_empty() {
|
|
808
|
+
path.file_name()
|
|
809
|
+
.and_then(|n| n.to_str())
|
|
810
|
+
.unwrap_or("Untitled Plan")
|
|
811
|
+
} else {
|
|
812
|
+
&preview
|
|
813
|
+
};
|
|
814
|
+
let meta = format_plan_meta(path);
|
|
815
|
+
ChoiceItem::with_description(
|
|
816
|
+
format!("[{}] {}", idx + 1, display_title),
|
|
817
|
+
meta,
|
|
818
|
+
path.to_string_lossy().to_string(),
|
|
819
|
+
)
|
|
820
|
+
})
|
|
821
|
+
.collect::<Vec<_>>();
|
|
822
|
+
|
|
823
|
+
match ui.prompt_choice(
|
|
824
|
+
"Select Saved Plan",
|
|
825
|
+
"Choose a plan to view its content (Press Enter to view, Esc to cancel)",
|
|
826
|
+
&options,
|
|
827
|
+
) {
|
|
828
|
+
Ok(Some(idx)) => {
|
|
829
|
+
if let Some(path) = files.get(idx) {
|
|
830
|
+
match std::fs::read_to_string(path) {
|
|
831
|
+
Ok(content) => {
|
|
832
|
+
ui.push_plan(&content);
|
|
833
|
+
}
|
|
834
|
+
Err(e) => ui.push_notice(format!("Could not read plan file: {e}")),
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
_ => {}
|
|
725
839
|
}
|
|
726
|
-
ui.push_command_output(lines.join("\n"));
|
|
727
840
|
}
|
|
728
841
|
Some(SlashResult::Handled)
|
|
729
842
|
}
|
|
@@ -742,7 +855,7 @@ pub async fn handle_slash_command(
|
|
|
742
855
|
};
|
|
743
856
|
match found {
|
|
744
857
|
Some(path) => match std::fs::read_to_string(path) {
|
|
745
|
-
Ok(content) => ui.
|
|
858
|
+
Ok(content) => ui.push_plan(&content),
|
|
746
859
|
Err(e) => ui.push_notice(format!("Could not read plan file: {e}")),
|
|
747
860
|
},
|
|
748
861
|
None => ui.push_notice(format!(
|
|
@@ -789,18 +902,18 @@ pub async fn handle_slash_command(
|
|
|
789
902
|
"/models" => {
|
|
790
903
|
let selected_provider = if rest.is_empty() {
|
|
791
904
|
let providers = session.config.available_providers();
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
) {
|
|
797
|
-
Ok(Some(
|
|
905
|
+
let choices: Vec<ChoiceItem> = providers
|
|
906
|
+
.iter()
|
|
907
|
+
.map(|p| ChoiceItem::new(p.clone(), p.clone()))
|
|
908
|
+
.collect();
|
|
909
|
+
match ui.prompt_choice("Select AI provider", "", &choices) {
|
|
910
|
+
Ok(Some(idx)) => providers.get(idx).cloned(),
|
|
798
911
|
Ok(None) => {
|
|
799
|
-
|
|
912
|
+
ui.push_notice("Cancelled provider selection.");
|
|
800
913
|
None
|
|
801
914
|
}
|
|
802
915
|
Err(e) => {
|
|
803
|
-
|
|
916
|
+
ui.push_notice(format!("Error selecting provider: {e}"));
|
|
804
917
|
None
|
|
805
918
|
}
|
|
806
919
|
}
|
|
@@ -809,25 +922,16 @@ pub async fn handle_slash_command(
|
|
|
809
922
|
};
|
|
810
923
|
|
|
811
924
|
if let Some(provider) = selected_provider {
|
|
812
|
-
// Switch provider first so `active_model()` reflects that
|
|
813
|
-
// provider's own default/last-used model, then offer a
|
|
814
|
-
// second picker (when a model list is known for it) so the
|
|
815
|
-
// user isn't stuck with the default after choosing a provider.
|
|
816
925
|
match session.config.set_active_model(&provider, None) {
|
|
817
926
|
Ok(mut display_name) => {
|
|
818
|
-
|
|
819
|
-
// static presets if the network is unavailable or the
|
|
820
|
-
// user hasn't entered an API key yet.
|
|
821
|
-
print!("{DIM}Fetching available models for {provider}...{RESET}\r");
|
|
822
|
-
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
927
|
+
ui.set_status(vec![format!("Fetching available models for {provider}...")]);
|
|
823
928
|
let mut model_options =
|
|
824
929
|
mint_core::slash::models::model_options_for_provider_async(
|
|
825
930
|
&session.config,
|
|
826
931
|
&provider,
|
|
827
932
|
)
|
|
828
933
|
.await;
|
|
829
|
-
|
|
830
|
-
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
934
|
+
ui.clear_status();
|
|
831
935
|
let current_model = session.config.active_model().to_string();
|
|
832
936
|
if !current_model.is_empty()
|
|
833
937
|
&& !model_options.iter().any(|m| m == ¤t_model)
|
|
@@ -836,32 +940,40 @@ pub async fn handle_slash_command(
|
|
|
836
940
|
}
|
|
837
941
|
|
|
838
942
|
if !model_options.is_empty() {
|
|
839
|
-
|
|
943
|
+
let choices: Vec<ChoiceItem> = model_options
|
|
944
|
+
.iter()
|
|
945
|
+
.map(|m| ChoiceItem::new(m.clone(), m.clone()))
|
|
946
|
+
.collect();
|
|
947
|
+
match ui.prompt_choice(
|
|
840
948
|
&format!("Select {provider} model"),
|
|
841
|
-
|
|
842
|
-
&
|
|
949
|
+
"",
|
|
950
|
+
&choices,
|
|
843
951
|
) {
|
|
844
|
-
Ok(Some(
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
952
|
+
Ok(Some(idx)) => {
|
|
953
|
+
if let Some(model) = model_options.get(idx)
|
|
954
|
+
&& model != ¤t_model
|
|
955
|
+
{
|
|
956
|
+
match session
|
|
957
|
+
.config
|
|
958
|
+
.set_active_model(&provider, Some(model))
|
|
959
|
+
{
|
|
960
|
+
Ok(name) => display_name = name,
|
|
961
|
+
Err(error) => {
|
|
962
|
+
ui.push_notice(format!("Config error: {error}"));
|
|
963
|
+
}
|
|
849
964
|
}
|
|
850
965
|
}
|
|
851
966
|
}
|
|
852
|
-
Ok(
|
|
967
|
+
Ok(None) => {}
|
|
853
968
|
Err(e) => {
|
|
854
|
-
|
|
969
|
+
ui.push_notice(format!("Error selecting model: {e}"));
|
|
855
970
|
}
|
|
856
971
|
}
|
|
857
972
|
}
|
|
858
973
|
|
|
859
|
-
|
|
860
|
-
"\n{DIM}โโโ{RESET} {MINT}{}{RESET} {DIM}โโโ{RESET}\n",
|
|
861
|
-
display_name
|
|
862
|
-
);
|
|
974
|
+
ui.push_notice(format!("Active model: {display_name}"));
|
|
863
975
|
}
|
|
864
|
-
Err(error) =>
|
|
976
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
865
977
|
}
|
|
866
978
|
}
|
|
867
979
|
Some(SlashResult::Handled)
|
|
@@ -871,29 +983,31 @@ pub async fn handle_slash_command(
|
|
|
871
983
|
if rest == "on" || rest == "off" {
|
|
872
984
|
session.config.enable_agent_collaboration = rest == "on";
|
|
873
985
|
match mint_core::save_config(&session.config) {
|
|
874
|
-
Ok(()) =>
|
|
875
|
-
"
|
|
986
|
+
Ok(()) => ui.push_notice(format!(
|
|
987
|
+
"Multi-Agent collaboration set to: {}",
|
|
876
988
|
if session.config.enable_agent_collaboration {
|
|
877
989
|
"Enabled"
|
|
878
990
|
} else {
|
|
879
991
|
"Disabled"
|
|
880
992
|
}
|
|
881
|
-
),
|
|
882
|
-
Err(error) =>
|
|
993
|
+
)),
|
|
994
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
883
995
|
}
|
|
884
996
|
} else if !rest.is_empty() {
|
|
885
|
-
|
|
997
|
+
ui.push_notice("Usage: /multi-agent [on|off]");
|
|
886
998
|
} else {
|
|
887
|
-
println!("\n{BLUE}Multi-Agent System Settings:{RESET}");
|
|
888
999
|
let collab_status = if session.config.enable_agent_collaboration {
|
|
889
1000
|
format!("{MINT}Enabled (on){RESET}")
|
|
890
1001
|
} else {
|
|
891
1002
|
format!("{DIM}Disabled (off){RESET}")
|
|
892
1003
|
};
|
|
893
|
-
|
|
894
|
-
|
|
1004
|
+
let mut lines = vec![
|
|
1005
|
+
format!("{BLUE}Multi-Agent System Settings:{RESET}"),
|
|
1006
|
+
format!(" Global Collaboration: {collab_status}"),
|
|
1007
|
+
format!("\n{BLUE}Configured Agents:{RESET}"),
|
|
1008
|
+
];
|
|
895
1009
|
if session.config.agents.is_empty() {
|
|
896
|
-
|
|
1010
|
+
lines.push(" No agents configured.".to_string());
|
|
897
1011
|
} else {
|
|
898
1012
|
for agent in &session.config.agents {
|
|
899
1013
|
let status_label = if agent.enabled {
|
|
@@ -901,10 +1015,10 @@ pub async fn handle_slash_command(
|
|
|
901
1015
|
} else {
|
|
902
1016
|
format!("{DIM}[Disabled]{RESET}")
|
|
903
1017
|
};
|
|
904
|
-
|
|
1018
|
+
lines.push(format!(
|
|
905
1019
|
" {:<15} {} Provider: {:<12} Model: {}",
|
|
906
1020
|
agent.name, status_label, agent.provider, agent.model
|
|
907
|
-
);
|
|
1021
|
+
));
|
|
908
1022
|
let truncated_instruction = if agent.system_instruction.chars().count() > 60
|
|
909
1023
|
{
|
|
910
1024
|
let head: String = agent.system_instruction.chars().take(60).collect();
|
|
@@ -912,38 +1026,34 @@ pub async fn handle_slash_command(
|
|
|
912
1026
|
} else {
|
|
913
1027
|
agent.system_instruction.replace('\n', " ")
|
|
914
1028
|
};
|
|
915
|
-
|
|
1029
|
+
lines.push(format!(
|
|
1030
|
+
" {DIM}Instruction: {truncated_instruction}{RESET}"
|
|
1031
|
+
));
|
|
916
1032
|
}
|
|
917
1033
|
}
|
|
918
|
-
|
|
1034
|
+
ui.push_command_output(lines.join("\n"));
|
|
919
1035
|
|
|
920
|
-
let
|
|
921
|
-
"Keep current status"
|
|
922
|
-
"on (enable collaboration)"
|
|
923
|
-
"off (disable collaboration)"
|
|
1036
|
+
let choices = vec![
|
|
1037
|
+
ChoiceItem::new("Keep current status", "keep"),
|
|
1038
|
+
ChoiceItem::new("on (enable collaboration)", "on"),
|
|
1039
|
+
ChoiceItem::new("off (disable collaboration)", "off"),
|
|
924
1040
|
];
|
|
925
|
-
let
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
Ok(()) => println!(
|
|
940
|
-
"{DIM}Multi-Agent collaboration set to: Disabled{RESET}\n"
|
|
941
|
-
),
|
|
942
|
-
Err(error) => println!("{ERROR}Config error:{RESET} {error}\n"),
|
|
943
|
-
}
|
|
1041
|
+
if let Ok(Some(idx)) =
|
|
1042
|
+
ui.prompt_choice("Toggle Collaboration Status?", "", &choices)
|
|
1043
|
+
{
|
|
1044
|
+
if idx == 1 {
|
|
1045
|
+
session.config.enable_agent_collaboration = true;
|
|
1046
|
+
match mint_core::save_config(&session.config) {
|
|
1047
|
+
Ok(()) => ui.push_notice("Multi-Agent collaboration set to: Enabled"),
|
|
1048
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1049
|
+
}
|
|
1050
|
+
} else if idx == 2 {
|
|
1051
|
+
session.config.enable_agent_collaboration = false;
|
|
1052
|
+
match mint_core::save_config(&session.config) {
|
|
1053
|
+
Ok(()) => ui.push_notice("Multi-Agent collaboration set to: Disabled"),
|
|
1054
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
944
1055
|
}
|
|
945
1056
|
}
|
|
946
|
-
_ => {}
|
|
947
1057
|
}
|
|
948
1058
|
}
|
|
949
1059
|
Some(SlashResult::Handled)
|
|
@@ -954,19 +1064,21 @@ pub async fn handle_slash_command(
|
|
|
954
1064
|
|
|
955
1065
|
// Soft toggle: `avatar_signal_disabled` hides the tool while keeping
|
|
956
1066
|
// the token so the paired viewer never needs re-linking.
|
|
957
|
-
let set_signal_disabled = |session: &mut InteractiveSession,
|
|
1067
|
+
let set_signal_disabled = |session: &mut InteractiveSession,
|
|
1068
|
+
ui: &mut dyn CommandUi,
|
|
1069
|
+
disabled: bool| {
|
|
958
1070
|
session.config.avatar_signal_disabled = disabled;
|
|
959
1071
|
match mint_core::save_config(&session.config) {
|
|
960
1072
|
Ok(()) => {
|
|
961
1073
|
if disabled {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1074
|
+
ui.push_notice(
|
|
1075
|
+
"[Avatar] avatar_signal disabled (token kept โ run /avatar to re-enable).",
|
|
1076
|
+
);
|
|
965
1077
|
} else {
|
|
966
|
-
|
|
1078
|
+
ui.push_notice("[Avatar] avatar_signal enabled.");
|
|
967
1079
|
}
|
|
968
1080
|
}
|
|
969
|
-
Err(error) =>
|
|
1081
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
970
1082
|
}
|
|
971
1083
|
};
|
|
972
1084
|
|
|
@@ -976,33 +1088,33 @@ pub async fn handle_slash_command(
|
|
|
976
1088
|
"web" | "link web" => "web",
|
|
977
1089
|
"desktop" | "link desktop" => "desktop",
|
|
978
1090
|
"on" | "enable" => {
|
|
979
|
-
set_signal_disabled(session, false);
|
|
1091
|
+
set_signal_disabled(session, ui, false);
|
|
980
1092
|
return Some(SlashResult::Handled);
|
|
981
1093
|
}
|
|
982
1094
|
"off" | "disable" => {
|
|
983
|
-
set_signal_disabled(session, true);
|
|
1095
|
+
set_signal_disabled(session, ui, true);
|
|
984
1096
|
return Some(SlashResult::Handled);
|
|
985
1097
|
}
|
|
986
1098
|
"" | "link" => {
|
|
987
1099
|
let options = vec![
|
|
988
|
-
"
|
|
989
|
-
"
|
|
990
|
-
"
|
|
991
|
-
"
|
|
1100
|
+
ChoiceItem::new("Web browser", "web"),
|
|
1101
|
+
ChoiceItem::new("Desktop app", "desktop"),
|
|
1102
|
+
ChoiceItem::new("Off (disable avatar_signal)", "off"),
|
|
1103
|
+
ChoiceItem::new("No", "no"),
|
|
992
1104
|
];
|
|
993
|
-
match
|
|
994
|
-
Ok(Some(
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1105
|
+
match ui.prompt_choice("Avatar output", "", &options) {
|
|
1106
|
+
Ok(Some(idx)) => match options[idx].value.as_str() {
|
|
1107
|
+
"no" => return Some(SlashResult::Handled),
|
|
1108
|
+
"off" => {
|
|
1109
|
+
set_signal_disabled(session, ui, true);
|
|
1110
|
+
return Some(SlashResult::Handled);
|
|
1111
|
+
}
|
|
1112
|
+
"desktop" => "desktop",
|
|
1113
|
+
_ => "web",
|
|
1114
|
+
},
|
|
1003
1115
|
Ok(None) => return Some(SlashResult::Handled),
|
|
1004
1116
|
Err(e) => {
|
|
1005
|
-
|
|
1117
|
+
ui.push_notice(format!("Error selecting target: {e}"));
|
|
1006
1118
|
return Some(SlashResult::Handled);
|
|
1007
1119
|
}
|
|
1008
1120
|
}
|
|
@@ -1010,44 +1122,41 @@ pub async fn handle_slash_command(
|
|
|
1010
1122
|
"status" => {
|
|
1011
1123
|
let cfg = AvatarBridgeConfig::from_mint_config(&session.config);
|
|
1012
1124
|
if !cfg.enabled {
|
|
1013
|
-
|
|
1125
|
+
ui.push_notice("[Avatar] No token yet โ run /avatar link first.");
|
|
1014
1126
|
} else {
|
|
1015
|
-
|
|
1016
|
-
"
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
"enabled"
|
|
1021
|
-
}
|
|
1022
|
-
);
|
|
1127
|
+
let tool_state = if session.config.avatar_signal_disabled {
|
|
1128
|
+
"disabled"
|
|
1129
|
+
} else {
|
|
1130
|
+
"enabled"
|
|
1131
|
+
};
|
|
1023
1132
|
match fetch_channel_state(&cfg).await {
|
|
1024
1133
|
Ok(state) => {
|
|
1025
1134
|
let now_ms = std::time::SystemTime::now()
|
|
1026
1135
|
.duration_since(std::time::UNIX_EPOCH)
|
|
1027
1136
|
.map(|d| d.as_millis() as i64)
|
|
1028
1137
|
.unwrap_or(0);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
"
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
);
|
|
1138
|
+
let last_ev = state
|
|
1139
|
+
.last_agent_event_at
|
|
1140
|
+
.map(|t| format!("{}s ago", (now_ms - t).max(0) / 1000))
|
|
1141
|
+
.unwrap_or_else(|| "never".into());
|
|
1142
|
+
ui.push_command_output(format!(
|
|
1143
|
+
"{DIM}[Avatar] avatar_signal tool: {tool_state}{RESET}\n\
|
|
1144
|
+
{BLUE}[Avatar] Channel status:{RESET}\n \
|
|
1145
|
+
Model: {}\n \
|
|
1146
|
+
Viewers: {}\n \
|
|
1147
|
+
Last event: {last_ev}",
|
|
1148
|
+
state.model.unwrap_or_else(|| "not selected".into()),
|
|
1149
|
+
state.connected_clients
|
|
1150
|
+
));
|
|
1042
1151
|
}
|
|
1043
|
-
Err(error) =>
|
|
1152
|
+
Err(error) => ui.push_notice(format!("[Avatar] {error}")),
|
|
1044
1153
|
}
|
|
1045
1154
|
}
|
|
1046
1155
|
return Some(SlashResult::Handled);
|
|
1047
1156
|
}
|
|
1048
1157
|
_ => {
|
|
1049
|
-
|
|
1050
|
-
"
|
|
1158
|
+
ui.push_notice(
|
|
1159
|
+
"Usage: /avatar [web|desktop|on|off|status] โ connects agent activity to Project Avatar",
|
|
1051
1160
|
);
|
|
1052
1161
|
return Some(SlashResult::Handled);
|
|
1053
1162
|
}
|
|
@@ -1060,179 +1169,185 @@ pub async fn handle_slash_command(
|
|
|
1060
1169
|
session.config.avatar_token = AvatarBridgeConfig::generate_token();
|
|
1061
1170
|
}
|
|
1062
1171
|
if let Err(error) = mint_core::save_config(&session.config) {
|
|
1063
|
-
|
|
1172
|
+
ui.push_notice(format!("Config error: {error}"));
|
|
1064
1173
|
return Some(SlashResult::Handled);
|
|
1065
1174
|
}
|
|
1066
1175
|
let cfg = AvatarBridgeConfig::from_mint_config(&session.config);
|
|
1067
1176
|
let token = session.config.avatar_token.clone();
|
|
1068
1177
|
if target == "desktop" {
|
|
1069
|
-
|
|
1070
|
-
"
|
|
1178
|
+
ui.push_command_output(format!(
|
|
1179
|
+
"{MINT}[Avatar] Desktop app:{RESET}\n\
|
|
1071
1180
|
1. Open the Project Avatar desktop app.\n\
|
|
1072
1181
|
2. It has no address bar, so on first launch it generates its \
|
|
1073
1182
|
own token โ paste this one into its \"Paste existing token...\" \
|
|
1074
1183
|
field instead so it connects to the same channel Mint pushes to:\n\
|
|
1075
1184
|
{DIM}{token}{RESET}\n\
|
|
1076
|
-
It remembers the token after that; you won't need to paste it again
|
|
1077
|
-
);
|
|
1185
|
+
It remembers the token after that; you won't need to paste it again."
|
|
1186
|
+
));
|
|
1078
1187
|
} else {
|
|
1079
|
-
|
|
1080
|
-
"
|
|
1188
|
+
ui.push_command_output(format!(
|
|
1189
|
+
"{MINT}[Avatar] Share link:{RESET}\n{}",
|
|
1081
1190
|
cfg.share_link().expect("token was just ensured non-empty")
|
|
1082
|
-
);
|
|
1191
|
+
));
|
|
1083
1192
|
}
|
|
1084
1193
|
Some(SlashResult::Handled)
|
|
1085
1194
|
}
|
|
1086
1195
|
|
|
1087
1196
|
"/autoskill" => {
|
|
1088
|
-
|
|
1089
|
-
session.config.auto_skill_writing =
|
|
1197
|
+
let apply = |session: &mut InteractiveSession, ui: &mut dyn CommandUi, on: bool| {
|
|
1198
|
+
session.config.auto_skill_writing = on;
|
|
1090
1199
|
match mint_core::save_config(&session.config) {
|
|
1091
|
-
Ok(()) =>
|
|
1092
|
-
"
|
|
1093
|
-
if
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
"Disabled"
|
|
1097
|
-
}
|
|
1098
|
-
),
|
|
1099
|
-
Err(error) => println!("{ERROR}Config error:{RESET} {error}\n"),
|
|
1200
|
+
Ok(()) => ui.push_notice(format!(
|
|
1201
|
+
"Skill suggestions (approval required to save) set to: {}",
|
|
1202
|
+
if on { "Enabled" } else { "Disabled" }
|
|
1203
|
+
)),
|
|
1204
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1100
1205
|
}
|
|
1206
|
+
};
|
|
1207
|
+
if rest == "on" || rest == "off" {
|
|
1208
|
+
apply(session, ui, rest == "on");
|
|
1101
1209
|
} else if !rest.is_empty() {
|
|
1102
|
-
|
|
1210
|
+
ui.push_notice("Usage: /autoskill [on|off]");
|
|
1103
1211
|
} else {
|
|
1104
|
-
println!(
|
|
1105
|
-
"{DIM}When enabled, the agent proposes a skill after a reusable task. Each proposed diff needs your approval before saving.{RESET}"
|
|
1106
|
-
);
|
|
1107
1212
|
let options = if session.config.auto_skill_writing {
|
|
1108
|
-
vec![
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
let current = if session.config.auto_skill_writing {
|
|
1113
|
-
&options[0]
|
|
1213
|
+
vec![
|
|
1214
|
+
ChoiceItem::new("on (current)", "on"),
|
|
1215
|
+
ChoiceItem::new("off", "off"),
|
|
1216
|
+
]
|
|
1114
1217
|
} else {
|
|
1115
|
-
|
|
1218
|
+
vec![
|
|
1219
|
+
ChoiceItem::new("off (current)", "off"),
|
|
1220
|
+
ChoiceItem::new("on", "on"),
|
|
1221
|
+
]
|
|
1116
1222
|
};
|
|
1117
|
-
match
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
} else {
|
|
1126
|
-
"Disabled"
|
|
1127
|
-
}
|
|
1128
|
-
),
|
|
1129
|
-
Err(error) => println!("{ERROR}Config error:{RESET} {error}\n"),
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
Ok(None) => println!("{DIM}Cancelled.{RESET}\n"),
|
|
1133
|
-
Err(e) => println!("{ERROR}Error selecting option:{RESET} {e}\n"),
|
|
1223
|
+
match ui.prompt_choice(
|
|
1224
|
+
"Skill Suggestions",
|
|
1225
|
+
"When enabled, the agent proposes a skill after a reusable task. Each proposed diff needs your approval before saving.",
|
|
1226
|
+
&options,
|
|
1227
|
+
) {
|
|
1228
|
+
Ok(Some(idx)) => apply(session, ui, options[idx].value == "on"),
|
|
1229
|
+
Ok(None) => ui.push_notice("Cancelled."),
|
|
1230
|
+
Err(e) => ui.push_notice(format!("Error selecting option: {e}")),
|
|
1134
1231
|
}
|
|
1135
1232
|
}
|
|
1136
1233
|
Some(SlashResult::Handled)
|
|
1137
1234
|
}
|
|
1138
1235
|
|
|
1139
1236
|
"/autorecall" => {
|
|
1140
|
-
let apply = |session: &mut InteractiveSession, on: bool| {
|
|
1237
|
+
let apply = |session: &mut InteractiveSession, ui: &mut dyn CommandUi, on: bool| {
|
|
1141
1238
|
session.config.memory_recall = on;
|
|
1142
1239
|
match mint_core::save_config(&session.config) {
|
|
1143
|
-
Ok(()) =>
|
|
1144
|
-
"
|
|
1240
|
+
Ok(()) => ui.push_notice(format!(
|
|
1241
|
+
"Memory recall set to: {}",
|
|
1145
1242
|
if on { "Enabled" } else { "Disabled" }
|
|
1146
|
-
),
|
|
1147
|
-
Err(error) =>
|
|
1243
|
+
)),
|
|
1244
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1148
1245
|
}
|
|
1149
1246
|
};
|
|
1150
1247
|
if rest == "on" || rest == "off" {
|
|
1151
|
-
apply(session, rest == "on");
|
|
1248
|
+
apply(session, ui, rest == "on");
|
|
1152
1249
|
} else if !rest.is_empty() {
|
|
1153
|
-
|
|
1250
|
+
ui.push_notice("Usage: /autorecall [on|off]");
|
|
1154
1251
|
} else {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
&options[0]
|
|
1252
|
+
let options = if session.config.memory_recall {
|
|
1253
|
+
vec![
|
|
1254
|
+
ChoiceItem::new("on (enable)", "on"),
|
|
1255
|
+
ChoiceItem::new("off (disable)", "off"),
|
|
1256
|
+
]
|
|
1161
1257
|
} else {
|
|
1162
|
-
|
|
1258
|
+
vec![
|
|
1259
|
+
ChoiceItem::new("off (disable)", "off"),
|
|
1260
|
+
ChoiceItem::new("on (enable)", "on"),
|
|
1261
|
+
]
|
|
1163
1262
|
};
|
|
1164
|
-
match
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1263
|
+
match ui.prompt_choice(
|
|
1264
|
+
"Memory Recall",
|
|
1265
|
+
"When enabled, each turn full-text-searches this conversation's older messages and injects the few most relevant to what you just said.",
|
|
1266
|
+
&options,
|
|
1267
|
+
) {
|
|
1268
|
+
Ok(Some(idx)) => apply(session, ui, options[idx].value == "on"),
|
|
1269
|
+
Ok(None) => ui.push_notice("Cancelled."),
|
|
1270
|
+
Err(e) => ui.push_notice(format!("Error selecting option: {e}")),
|
|
1168
1271
|
}
|
|
1169
1272
|
}
|
|
1170
1273
|
Some(SlashResult::Handled)
|
|
1171
1274
|
}
|
|
1172
1275
|
|
|
1173
1276
|
"/autofacts" => {
|
|
1174
|
-
let apply = |session: &mut InteractiveSession, on: bool| {
|
|
1277
|
+
let apply = |session: &mut InteractiveSession, ui: &mut dyn CommandUi, on: bool| {
|
|
1175
1278
|
session.config.auto_fact_extraction = on;
|
|
1176
1279
|
match mint_core::save_config(&session.config) {
|
|
1177
|
-
Ok(()) =>
|
|
1178
|
-
"
|
|
1280
|
+
Ok(()) => ui.push_notice(format!(
|
|
1281
|
+
"Auto fact extraction set to: {}",
|
|
1179
1282
|
if on { "Enabled" } else { "Disabled" }
|
|
1180
|
-
),
|
|
1181
|
-
Err(error) =>
|
|
1283
|
+
)),
|
|
1284
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1182
1285
|
}
|
|
1183
1286
|
};
|
|
1184
1287
|
if rest == "on" || rest == "off" {
|
|
1185
|
-
apply(session, rest == "on");
|
|
1288
|
+
apply(session, ui, rest == "on");
|
|
1186
1289
|
} else if !rest.is_empty() {
|
|
1187
|
-
|
|
1290
|
+
ui.push_notice("Usage: /autofacts [on|off]");
|
|
1188
1291
|
} else {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
&options[0]
|
|
1292
|
+
let options = if session.config.auto_fact_extraction {
|
|
1293
|
+
vec![
|
|
1294
|
+
ChoiceItem::new("on (enable)", "on"),
|
|
1295
|
+
ChoiceItem::new("off (disable)", "off"),
|
|
1296
|
+
]
|
|
1195
1297
|
} else {
|
|
1196
|
-
|
|
1298
|
+
vec![
|
|
1299
|
+
ChoiceItem::new("off (disable)", "off"),
|
|
1300
|
+
ChoiceItem::new("on (enable)", "on"),
|
|
1301
|
+
]
|
|
1197
1302
|
};
|
|
1198
|
-
match
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1303
|
+
match ui.prompt_choice(
|
|
1304
|
+
"Auto Fact Extraction",
|
|
1305
|
+
"When enabled, a turn that states a durable preference, naming, or standing instruction triggers a background pass that saves it to long-term memory.",
|
|
1306
|
+
&options,
|
|
1307
|
+
) {
|
|
1308
|
+
Ok(Some(idx)) => apply(session, ui, options[idx].value == "on"),
|
|
1309
|
+
Ok(None) => ui.push_notice("Cancelled."),
|
|
1310
|
+
Err(e) => ui.push_notice(format!("Error selecting option: {e}")),
|
|
1202
1311
|
}
|
|
1203
1312
|
}
|
|
1204
1313
|
Some(SlashResult::Handled)
|
|
1205
1314
|
}
|
|
1206
1315
|
|
|
1207
1316
|
"/factrecall" => {
|
|
1208
|
-
let apply = |session: &mut InteractiveSession, on: bool| {
|
|
1317
|
+
let apply = |session: &mut InteractiveSession, ui: &mut dyn CommandUi, on: bool| {
|
|
1209
1318
|
session.config.semantic_fact_recall = on;
|
|
1210
1319
|
match mint_core::save_config(&session.config) {
|
|
1211
|
-
Ok(()) =>
|
|
1212
|
-
"
|
|
1320
|
+
Ok(()) => ui.push_notice(format!(
|
|
1321
|
+
"Semantic fact recall set to: {}",
|
|
1213
1322
|
if on { "Enabled" } else { "Disabled" }
|
|
1214
|
-
),
|
|
1215
|
-
Err(error) =>
|
|
1323
|
+
)),
|
|
1324
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1216
1325
|
}
|
|
1217
1326
|
};
|
|
1218
1327
|
if rest == "on" || rest == "off" {
|
|
1219
|
-
apply(session, rest == "on");
|
|
1328
|
+
apply(session, ui, rest == "on");
|
|
1220
1329
|
} else if !rest.is_empty() {
|
|
1221
|
-
|
|
1330
|
+
ui.push_notice("Usage: /factrecall [on|off]");
|
|
1222
1331
|
} else {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
&options[0]
|
|
1332
|
+
let options = if session.config.semantic_fact_recall {
|
|
1333
|
+
vec![
|
|
1334
|
+
ChoiceItem::new("on (enable)", "on"),
|
|
1335
|
+
ChoiceItem::new("off (disable)", "off"),
|
|
1336
|
+
]
|
|
1229
1337
|
} else {
|
|
1230
|
-
|
|
1338
|
+
vec![
|
|
1339
|
+
ChoiceItem::new("off (disable)", "off"),
|
|
1340
|
+
ChoiceItem::new("on (enable)", "on"),
|
|
1341
|
+
]
|
|
1231
1342
|
};
|
|
1232
|
-
match
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1343
|
+
match ui.prompt_choice(
|
|
1344
|
+
"Semantic Fact Recall",
|
|
1345
|
+
"When your stored facts overflow their per-turn budget, this fills the overflow with the facts most relevant to your message instead of just the newest ones (the newest few are always kept).",
|
|
1346
|
+
&options,
|
|
1347
|
+
) {
|
|
1348
|
+
Ok(Some(idx)) => apply(session, ui, options[idx].value == "on"),
|
|
1349
|
+
Ok(None) => ui.push_notice("Cancelled."),
|
|
1350
|
+
Err(e) => ui.push_notice(format!("Error selecting option: {e}")),
|
|
1236
1351
|
}
|
|
1237
1352
|
}
|
|
1238
1353
|
Some(SlashResult::Handled)
|
|
@@ -1253,25 +1368,25 @@ pub async fn handle_slash_command(
|
|
|
1253
1368
|
("user", None, rest)
|
|
1254
1369
|
};
|
|
1255
1370
|
if body.is_empty() {
|
|
1256
|
-
|
|
1371
|
+
ui.push_notice("Usage: /remember [here] <text>");
|
|
1257
1372
|
return Some(SlashResult::Handled);
|
|
1258
1373
|
}
|
|
1259
1374
|
match MemoryStore::open_default() {
|
|
1260
1375
|
Ok(memory) => {
|
|
1261
1376
|
match memory.add_fact(scope, project_path.as_deref(), body, None, None) {
|
|
1262
|
-
Ok(Some(_)) =>
|
|
1263
|
-
"
|
|
1377
|
+
Ok(Some(_)) => ui.push_notice(format!(
|
|
1378
|
+
"Remembered{}.",
|
|
1264
1379
|
if scope == "project" {
|
|
1265
1380
|
" (this project)"
|
|
1266
1381
|
} else {
|
|
1267
1382
|
""
|
|
1268
1383
|
}
|
|
1269
|
-
),
|
|
1270
|
-
Ok(None) =>
|
|
1271
|
-
Err(e) =>
|
|
1384
|
+
)),
|
|
1385
|
+
Ok(None) => ui.push_notice("Already remembered."),
|
|
1386
|
+
Err(e) => ui.push_notice(format!("Error: {e}")),
|
|
1272
1387
|
}
|
|
1273
1388
|
}
|
|
1274
|
-
Err(e) =>
|
|
1389
|
+
Err(e) => ui.push_notice(format!("Memory error: {e}")),
|
|
1275
1390
|
}
|
|
1276
1391
|
Some(SlashResult::Handled)
|
|
1277
1392
|
}
|
|
@@ -1301,42 +1416,45 @@ pub async fn handle_slash_command(
|
|
|
1301
1416
|
}
|
|
1302
1417
|
|
|
1303
1418
|
let selected_provider = if rest.is_empty() {
|
|
1304
|
-
let options: Vec<
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1419
|
+
let mut options: Vec<ChoiceItem> =
|
|
1420
|
+
available.iter().map(|s| ChoiceItem::new(*s, *s)).collect();
|
|
1421
|
+
if let Some(pos) = options
|
|
1422
|
+
.iter()
|
|
1423
|
+
.position(|o| o.value == session.config.image_gen_provider)
|
|
1424
|
+
&& pos > 0
|
|
1425
|
+
{
|
|
1426
|
+
let item = options.remove(pos);
|
|
1427
|
+
options.insert(0, item);
|
|
1428
|
+
}
|
|
1429
|
+
match ui.prompt_choice("Select Image Generation provider", "", &options) {
|
|
1430
|
+
Ok(Some(idx)) => Some(options[idx].value.clone()),
|
|
1311
1431
|
Ok(None) => {
|
|
1312
|
-
|
|
1432
|
+
ui.push_notice("Cancelled image provider selection.");
|
|
1313
1433
|
None
|
|
1314
1434
|
}
|
|
1315
1435
|
Err(e) => {
|
|
1316
|
-
|
|
1436
|
+
ui.push_notice(format!("Error selecting provider: {e}"));
|
|
1317
1437
|
None
|
|
1318
1438
|
}
|
|
1319
1439
|
}
|
|
1440
|
+
} else if available.contains(&rest) {
|
|
1441
|
+
Some(rest.to_owned())
|
|
1320
1442
|
} else {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
} else {
|
|
1324
|
-
println!("{ERROR}Provider '{rest}' is not configured or invalid.{RESET}\n");
|
|
1325
|
-
None
|
|
1326
|
-
}
|
|
1443
|
+
ui.push_notice(format!("Provider '{rest}' is not configured or invalid."));
|
|
1444
|
+
None
|
|
1327
1445
|
};
|
|
1328
1446
|
|
|
1329
1447
|
if let Some(provider) = selected_provider {
|
|
1330
|
-
|
|
1331
|
-
|
|
1448
|
+
ui.set_status(vec![format!(
|
|
1449
|
+
"Fetching available image models for {provider}..."
|
|
1450
|
+
)]);
|
|
1332
1451
|
let mut model_options =
|
|
1333
1452
|
mint_core::media::image_models::image_model_options_for_provider_async(
|
|
1334
1453
|
&session.config,
|
|
1335
1454
|
&provider,
|
|
1336
1455
|
)
|
|
1337
1456
|
.await;
|
|
1338
|
-
|
|
1339
|
-
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
1457
|
+
ui.clear_status();
|
|
1340
1458
|
|
|
1341
1459
|
let current_model =
|
|
1342
1460
|
mint_core::media::image_models::active_image_model_for_provider(
|
|
@@ -1349,19 +1467,29 @@ pub async fn handle_slash_command(
|
|
|
1349
1467
|
}
|
|
1350
1468
|
|
|
1351
1469
|
if !model_options.is_empty() {
|
|
1352
|
-
|
|
1470
|
+
let mut choices: Vec<ChoiceItem> = model_options
|
|
1471
|
+
.iter()
|
|
1472
|
+
.map(|m| ChoiceItem::new(m.clone(), m.clone()))
|
|
1473
|
+
.collect();
|
|
1474
|
+
if let Some(pos) = choices.iter().position(|c| c.value == current_model)
|
|
1475
|
+
&& pos > 0
|
|
1476
|
+
{
|
|
1477
|
+
let item = choices.remove(pos);
|
|
1478
|
+
choices.insert(0, item);
|
|
1479
|
+
}
|
|
1480
|
+
match ui.prompt_choice(
|
|
1353
1481
|
&format!(
|
|
1354
1482
|
"Select {} model",
|
|
1355
1483
|
mint_core::media::image_models::image_provider_display_name(&provider)
|
|
1356
1484
|
),
|
|
1357
|
-
|
|
1358
|
-
&
|
|
1485
|
+
"",
|
|
1486
|
+
&choices,
|
|
1359
1487
|
) {
|
|
1360
|
-
Ok(Some(
|
|
1488
|
+
Ok(Some(idx)) => {
|
|
1361
1489
|
mint_core::media::image_models::set_active_image_provider_model(
|
|
1362
1490
|
&mut session.config,
|
|
1363
1491
|
&provider,
|
|
1364
|
-
Some(&
|
|
1492
|
+
Some(&choices[idx].value),
|
|
1365
1493
|
);
|
|
1366
1494
|
}
|
|
1367
1495
|
Ok(None) => {
|
|
@@ -1372,7 +1500,7 @@ pub async fn handle_slash_command(
|
|
|
1372
1500
|
);
|
|
1373
1501
|
}
|
|
1374
1502
|
Err(e) => {
|
|
1375
|
-
|
|
1503
|
+
ui.push_notice(format!("Error selecting image model: {e}"));
|
|
1376
1504
|
}
|
|
1377
1505
|
}
|
|
1378
1506
|
} else {
|
|
@@ -1390,13 +1518,13 @@ pub async fn handle_slash_command(
|
|
|
1390
1518
|
&session.config,
|
|
1391
1519
|
&provider,
|
|
1392
1520
|
);
|
|
1393
|
-
|
|
1394
|
-
"
|
|
1521
|
+
ui.push_notice(format!(
|
|
1522
|
+
"Switched default image provider to: {} โข {}",
|
|
1395
1523
|
mint_core::media::image_models::image_provider_display_name(&provider),
|
|
1396
1524
|
active
|
|
1397
|
-
);
|
|
1525
|
+
));
|
|
1398
1526
|
}
|
|
1399
|
-
Err(error) =>
|
|
1527
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1400
1528
|
}
|
|
1401
1529
|
}
|
|
1402
1530
|
Some(SlashResult::Handled)
|
|
@@ -1412,49 +1540,24 @@ pub async fn handle_slash_command(
|
|
|
1412
1540
|
available.push("veo");
|
|
1413
1541
|
}
|
|
1414
1542
|
|
|
1415
|
-
let current_provider = session
|
|
1416
|
-
.config
|
|
1417
|
-
.extra
|
|
1418
|
-
.get("videoGenProvider")
|
|
1419
|
-
.and_then(|v| v.as_str())
|
|
1420
|
-
.unwrap_or("veo")
|
|
1421
|
-
.to_string();
|
|
1422
|
-
|
|
1423
1543
|
let selected_provider = if rest.is_empty() {
|
|
1424
|
-
let options = vec!["Google Veo (Gemini Videos)"
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
} else {
|
|
1428
|
-
¤t_provider
|
|
1429
|
-
};
|
|
1430
|
-
match prompt_interactive_select(
|
|
1431
|
-
"Select Video Generation provider",
|
|
1432
|
-
&options,
|
|
1433
|
-
current_display,
|
|
1434
|
-
) {
|
|
1435
|
-
Ok(Some(p)) => {
|
|
1436
|
-
if p == "Google Veo (Gemini Videos)" {
|
|
1437
|
-
Some("veo".to_string())
|
|
1438
|
-
} else {
|
|
1439
|
-
Some(p)
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1544
|
+
let options = vec![ChoiceItem::new("Google Veo (Gemini Videos)", "veo")];
|
|
1545
|
+
match ui.prompt_choice("Select Video Generation provider", "", &options) {
|
|
1546
|
+
Ok(Some(idx)) => Some(options[idx].value.clone()),
|
|
1442
1547
|
Ok(None) => {
|
|
1443
|
-
|
|
1548
|
+
ui.push_notice("Cancelled video provider selection.");
|
|
1444
1549
|
None
|
|
1445
1550
|
}
|
|
1446
1551
|
Err(e) => {
|
|
1447
|
-
|
|
1552
|
+
ui.push_notice(format!("Error selecting provider: {e}"));
|
|
1448
1553
|
None
|
|
1449
1554
|
}
|
|
1450
1555
|
}
|
|
1556
|
+
} else if rest == "veo" || rest == "Google Veo (Gemini Videos)" {
|
|
1557
|
+
Some("veo".to_string())
|
|
1451
1558
|
} else {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
} else {
|
|
1455
|
-
println!("{ERROR}Provider '{rest}' is not configured or invalid.{RESET}\n");
|
|
1456
|
-
None
|
|
1457
|
-
}
|
|
1559
|
+
ui.push_notice(format!("Provider '{rest}' is not configured or invalid."));
|
|
1560
|
+
None
|
|
1458
1561
|
};
|
|
1459
1562
|
|
|
1460
1563
|
if let Some(provider) = selected_provider {
|
|
@@ -1469,12 +1572,11 @@ pub async fn handle_slash_command(
|
|
|
1469
1572
|
} else {
|
|
1470
1573
|
&provider
|
|
1471
1574
|
};
|
|
1472
|
-
|
|
1473
|
-
"
|
|
1474
|
-
|
|
1475
|
-
);
|
|
1575
|
+
ui.push_notice(format!(
|
|
1576
|
+
"Switched default video provider to: {display_name}"
|
|
1577
|
+
));
|
|
1476
1578
|
}
|
|
1477
|
-
Err(error) =>
|
|
1579
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1478
1580
|
}
|
|
1479
1581
|
}
|
|
1480
1582
|
Some(SlashResult::Handled)
|
|
@@ -1517,8 +1619,8 @@ pub async fn handle_slash_command(
|
|
|
1517
1619
|
}
|
|
1518
1620
|
|
|
1519
1621
|
if available.is_empty() {
|
|
1520
|
-
|
|
1521
|
-
"
|
|
1622
|
+
ui.push_notice(
|
|
1623
|
+
"No search provider is configured yet. Run onboarding or set a Google/Brave key or SearXNG URL in Settings first.",
|
|
1522
1624
|
);
|
|
1523
1625
|
return Some(SlashResult::Handled);
|
|
1524
1626
|
}
|
|
@@ -1532,30 +1634,24 @@ pub async fn handle_slash_command(
|
|
|
1532
1634
|
.to_string();
|
|
1533
1635
|
|
|
1534
1636
|
let selected_provider = if rest.is_empty() {
|
|
1535
|
-
let options: Vec<
|
|
1637
|
+
let mut options: Vec<ChoiceItem> = available
|
|
1536
1638
|
.iter()
|
|
1537
|
-
.map(|(
|
|
1639
|
+
.map(|(key, label)| ChoiceItem::new(*label, *key))
|
|
1538
1640
|
.collect();
|
|
1539
|
-
let
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
.
|
|
1543
|
-
.
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
current_display,
|
|
1548
|
-
) {
|
|
1549
|
-
Ok(Some(label)) => available
|
|
1550
|
-
.iter()
|
|
1551
|
-
.find(|(_, l)| *l == label)
|
|
1552
|
-
.map(|(key, _)| key.to_string()),
|
|
1641
|
+
if let Some(pos) = options.iter().position(|o| o.value == current_provider)
|
|
1642
|
+
&& pos > 0
|
|
1643
|
+
{
|
|
1644
|
+
let item = options.remove(pos);
|
|
1645
|
+
options.insert(0, item);
|
|
1646
|
+
}
|
|
1647
|
+
match ui.prompt_choice("Select Web Search provider", "", &options) {
|
|
1648
|
+
Ok(Some(idx)) => Some(options[idx].value.clone()),
|
|
1553
1649
|
Ok(None) => {
|
|
1554
|
-
|
|
1650
|
+
ui.push_notice("Cancelled search provider selection.");
|
|
1555
1651
|
None
|
|
1556
1652
|
}
|
|
1557
1653
|
Err(e) => {
|
|
1558
|
-
|
|
1654
|
+
ui.push_notice(format!("Error selecting provider: {e}"));
|
|
1559
1655
|
None
|
|
1560
1656
|
}
|
|
1561
1657
|
}
|
|
@@ -1565,7 +1661,7 @@ pub async fn handle_slash_command(
|
|
|
1565
1661
|
{
|
|
1566
1662
|
Some(key.to_string())
|
|
1567
1663
|
} else {
|
|
1568
|
-
|
|
1664
|
+
ui.push_notice(format!("Provider '{rest}' is not configured or invalid."));
|
|
1569
1665
|
None
|
|
1570
1666
|
};
|
|
1571
1667
|
|
|
@@ -1581,12 +1677,11 @@ pub async fn handle_slash_command(
|
|
|
1581
1677
|
.find(|(key, _)| *key == provider)
|
|
1582
1678
|
.map(|(_, label)| *label)
|
|
1583
1679
|
.unwrap_or(&provider);
|
|
1584
|
-
|
|
1585
|
-
"
|
|
1586
|
-
|
|
1587
|
-
);
|
|
1680
|
+
ui.push_notice(format!(
|
|
1681
|
+
"Switched default web search provider to: {display_name}"
|
|
1682
|
+
));
|
|
1588
1683
|
}
|
|
1589
|
-
Err(error) =>
|
|
1684
|
+
Err(error) => ui.push_notice(format!("Config error: {error}")),
|
|
1590
1685
|
}
|
|
1591
1686
|
}
|
|
1592
1687
|
Some(SlashResult::Handled)
|
|
@@ -2665,11 +2760,9 @@ pub async fn handle_slash_command(
|
|
|
2665
2760
|
Some(SlashResult::Handled)
|
|
2666
2761
|
}
|
|
2667
2762
|
|
|
2668
|
-
"/release-notes"
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
mint_core::release_notes::text()
|
|
2672
|
-
));
|
|
2763
|
+
"/release-notes" | "/release_notes" | "/release-note" | "/release_note"
|
|
2764
|
+
| "/releasenotes" | "/releasenote" => {
|
|
2765
|
+
ui.push_release_notes(&mint_core::release_notes::text());
|
|
2673
2766
|
Some(SlashResult::Handled)
|
|
2674
2767
|
}
|
|
2675
2768
|
|
|
@@ -3101,6 +3194,74 @@ pub async fn handle_slash_command(
|
|
|
3101
3194
|
}
|
|
3102
3195
|
}
|
|
3103
3196
|
|
|
3197
|
+
"/voice" => {
|
|
3198
|
+
let sub = rest.trim().to_ascii_lowercase();
|
|
3199
|
+
if sub == "stop" || sub == "cancel" {
|
|
3200
|
+
let _ = super::stop_cli_voice_recording();
|
|
3201
|
+
ui.push_notice("Voice recording stopped.");
|
|
3202
|
+
return Some(SlashResult::Handled);
|
|
3203
|
+
}
|
|
3204
|
+
if !super::is_cli_voice_recording()
|
|
3205
|
+
&& let Err(err) = super::start_cli_voice_recording()
|
|
3206
|
+
{
|
|
3207
|
+
ui.push_notice(format!("Microphone error: {err}"));
|
|
3208
|
+
return Some(SlashResult::Handled);
|
|
3209
|
+
}
|
|
3210
|
+
let choices = vec![
|
|
3211
|
+
ChoiceItem::with_description(
|
|
3212
|
+
"Transcribe & Send",
|
|
3213
|
+
"Stop recording, transcribe speech, and send prompt to Mint",
|
|
3214
|
+
"send",
|
|
3215
|
+
),
|
|
3216
|
+
ChoiceItem::with_description("Cancel", "Discard current voice recording", "cancel"),
|
|
3217
|
+
];
|
|
3218
|
+
match ui.prompt_choice(
|
|
3219
|
+
"๐ Recording Voice",
|
|
3220
|
+
"Speak into your microphone, then select an action:",
|
|
3221
|
+
&choices,
|
|
3222
|
+
) {
|
|
3223
|
+
Ok(Some(0)) => {
|
|
3224
|
+
let wav_bytes = match super::stop_cli_voice_recording() {
|
|
3225
|
+
Ok(Some(bytes)) => bytes,
|
|
3226
|
+
Ok(None) => {
|
|
3227
|
+
ui.push_notice("No active voice recording found.");
|
|
3228
|
+
return Some(SlashResult::Handled);
|
|
3229
|
+
}
|
|
3230
|
+
Err(err) => {
|
|
3231
|
+
ui.push_notice(format!("Voice recording error: {err}"));
|
|
3232
|
+
return Some(SlashResult::Handled);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
ui.push_notice("Transcribing voice recordingโฆ");
|
|
3236
|
+
match mint_core::mic_transcribe::transcribe_recording(
|
|
3237
|
+
&session.config,
|
|
3238
|
+
wav_bytes,
|
|
3239
|
+
)
|
|
3240
|
+
.await
|
|
3241
|
+
{
|
|
3242
|
+
Ok(text) if !text.trim().is_empty() => {
|
|
3243
|
+
let cleaned = text.trim().to_string();
|
|
3244
|
+
ui.push_notice(format!("๐ Heard: {cleaned}"));
|
|
3245
|
+
Some(SlashResult::ForwardToAgent(cleaned))
|
|
3246
|
+
}
|
|
3247
|
+
Ok(_) => {
|
|
3248
|
+
ui.push_notice("No speech detected in voice recording.");
|
|
3249
|
+
Some(SlashResult::Handled)
|
|
3250
|
+
}
|
|
3251
|
+
Err(err) => {
|
|
3252
|
+
ui.push_notice(format!("Voice transcription failed: {err}"));
|
|
3253
|
+
Some(SlashResult::Handled)
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
_ => {
|
|
3258
|
+
let _ = super::stop_cli_voice_recording();
|
|
3259
|
+
ui.push_notice("Voice recording cancelled.");
|
|
3260
|
+
Some(SlashResult::Handled)
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3104
3265
|
_ => {
|
|
3105
3266
|
// Unknown slash command โ treat as normal message to the agent
|
|
3106
3267
|
None
|
|
@@ -4263,4 +4424,120 @@ mod tests {
|
|
|
4263
4424
|
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
4264
4425
|
assert!(session.fast_mode);
|
|
4265
4426
|
}
|
|
4427
|
+
|
|
4428
|
+
#[tokio::test]
|
|
4429
|
+
async fn slash_release_notes_routes_to_push_release_notes() {
|
|
4430
|
+
let mut session = InteractiveSession {
|
|
4431
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
4432
|
+
config: MintConfig::default(),
|
|
4433
|
+
current_dir: std::env::current_dir().unwrap(),
|
|
4434
|
+
fast_mode: false,
|
|
4435
|
+
plan_mode: false,
|
|
4436
|
+
pending_image: None,
|
|
4437
|
+
history: Vec::new(),
|
|
4438
|
+
jobs: BackgroundJobs::new(),
|
|
4439
|
+
};
|
|
4440
|
+
|
|
4441
|
+
for cmd in [
|
|
4442
|
+
"/release-notes",
|
|
4443
|
+
"/release_notes",
|
|
4444
|
+
"/release-note",
|
|
4445
|
+
"/release_note",
|
|
4446
|
+
] {
|
|
4447
|
+
let mut ui = MockCommandUi::new();
|
|
4448
|
+
let res = handle_slash_command(&mut session, &mut ui, cmd).await;
|
|
4449
|
+
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
4450
|
+
assert!(
|
|
4451
|
+
ui.command_outputs.is_empty(),
|
|
4452
|
+
"must not route {cmd} to push_command_output"
|
|
4453
|
+
);
|
|
4454
|
+
assert_eq!(
|
|
4455
|
+
ui.release_notes.len(),
|
|
4456
|
+
1,
|
|
4457
|
+
"must route {cmd} to push_release_notes"
|
|
4458
|
+
);
|
|
4459
|
+
assert!(ui.release_notes[0].contains("Release Notes"));
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
#[test]
|
|
4464
|
+
fn format_plan_meta_parses_timestamp() {
|
|
4465
|
+
let p1 = Path::new("/tmp/plans/20261102-182231-my-plan.md");
|
|
4466
|
+
assert_eq!(format_plan_meta(p1), "2026-11-02 18:22:31 ยท my-plan");
|
|
4467
|
+
|
|
4468
|
+
let p2 = Path::new("/tmp/plans/20261102-180001.md");
|
|
4469
|
+
assert_eq!(format_plan_meta(p2), "2026-11-02 18:00:01");
|
|
4470
|
+
|
|
4471
|
+
let p3 = Path::new("/tmp/plans/custom-plan.md");
|
|
4472
|
+
assert_eq!(format_plan_meta(p3), "custom-plan.md");
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
#[tokio::test]
|
|
4476
|
+
async fn slash_plan_list_interactive_selection_and_show() {
|
|
4477
|
+
let temp = std::env::temp_dir().join(format!("mint-plan-test-{}", std::process::id()));
|
|
4478
|
+
let plans_dir = temp.join(".agents").join("plans");
|
|
4479
|
+
std::fs::create_dir_all(&plans_dir).unwrap();
|
|
4480
|
+
|
|
4481
|
+
let plan1_content = "# First Plan\n- [ ] Task 1";
|
|
4482
|
+
let plan2_content = "# Second Plan\n- [x] Done";
|
|
4483
|
+
std::fs::write(plans_dir.join("20261102-180000-plan-one.md"), plan1_content).unwrap();
|
|
4484
|
+
std::fs::write(plans_dir.join("20261102-183000-plan-two.md"), plan2_content).unwrap();
|
|
4485
|
+
|
|
4486
|
+
let mut session = InteractiveSession {
|
|
4487
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
4488
|
+
config: MintConfig::default(),
|
|
4489
|
+
current_dir: temp.clone(),
|
|
4490
|
+
fast_mode: false,
|
|
4491
|
+
plan_mode: false,
|
|
4492
|
+
pending_image: None,
|
|
4493
|
+
history: Vec::new(),
|
|
4494
|
+
jobs: BackgroundJobs::new(),
|
|
4495
|
+
};
|
|
4496
|
+
|
|
4497
|
+
// 1. /plan list with interactive selection (pick index 0, which is the newest plan2)
|
|
4498
|
+
let mut ui = MockCommandUi::new();
|
|
4499
|
+
ui.choice_answers.push(Some(0));
|
|
4500
|
+
let res = handle_slash_command(&mut session, &mut ui, "/plan list").await;
|
|
4501
|
+
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
4502
|
+
assert_eq!(ui.plans.len(), 1);
|
|
4503
|
+
assert_eq!(ui.plans[0], plan2_content);
|
|
4504
|
+
|
|
4505
|
+
// 2. /plan list 1 directly views the first plan
|
|
4506
|
+
let mut ui = MockCommandUi::new();
|
|
4507
|
+
let res = handle_slash_command(&mut session, &mut ui, "/plan list 1").await;
|
|
4508
|
+
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
4509
|
+
assert_eq!(ui.plans.len(), 1);
|
|
4510
|
+
assert_eq!(ui.plans[0], plan2_content);
|
|
4511
|
+
|
|
4512
|
+
// 3. /plan show 2 views the second plan
|
|
4513
|
+
let mut ui = MockCommandUi::new();
|
|
4514
|
+
let res = handle_slash_command(&mut session, &mut ui, "/plan show 2").await;
|
|
4515
|
+
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
4516
|
+
assert_eq!(ui.plans.len(), 1);
|
|
4517
|
+
assert_eq!(ui.plans[0], plan1_content);
|
|
4518
|
+
|
|
4519
|
+
let _ = std::fs::remove_dir_all(temp);
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
#[tokio::test]
|
|
4523
|
+
async fn slash_voice_stop_is_handled_cleanly() {
|
|
4524
|
+
let mut session = InteractiveSession {
|
|
4525
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
4526
|
+
config: MintConfig::default(),
|
|
4527
|
+
current_dir: std::env::current_dir().unwrap(),
|
|
4528
|
+
fast_mode: false,
|
|
4529
|
+
plan_mode: false,
|
|
4530
|
+
pending_image: None,
|
|
4531
|
+
history: Vec::new(),
|
|
4532
|
+
jobs: BackgroundJobs::new(),
|
|
4533
|
+
};
|
|
4534
|
+
let mut ui = MockCommandUi::new();
|
|
4535
|
+
let res = handle_slash_command(&mut session, &mut ui, "/voice stop").await;
|
|
4536
|
+
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
4537
|
+
assert!(
|
|
4538
|
+
ui.notices.iter().any(|n| n.contains("Voice recording")),
|
|
4539
|
+
"expected voice notice, got: {:?}",
|
|
4540
|
+
ui.notices
|
|
4541
|
+
);
|
|
4542
|
+
}
|
|
4266
4543
|
}
|