@pheem49/mint 1.15.0 → 1.16.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 (644) hide show
  1. package/Cargo.lock +1012 -69
  2. package/Cargo.toml +5 -2
  3. package/LICENSE +21 -654
  4. package/README.md +92 -817
  5. package/Release_Note.md +53 -560
  6. package/apps/mint-companion/src-tauri/Cargo.toml +20 -0
  7. package/apps/mint-companion/src-tauri/build.rs +3 -0
  8. package/apps/mint-companion/src-tauri/src/main.rs +468 -0
  9. package/apps/mint-companion/tsconfig.json +20 -0
  10. package/apps/mint-companion/vite.config.ts +3 -0
  11. package/components.json +19 -0
  12. package/crates/mint-cli/Cargo.toml +6 -0
  13. package/crates/mint-cli/src/agent/live_status.rs +187 -39
  14. package/crates/mint-cli/src/agent.rs +323 -95
  15. package/crates/mint-cli/src/commands/agent.rs +69 -55
  16. package/crates/mint-cli/src/commands/integrations.rs +7 -0
  17. package/crates/mint-cli/src/commands/knowledge.rs +12 -32
  18. package/crates/mint-cli/src/commands/mod.rs +44 -0
  19. package/crates/mint-cli/src/image.rs +31 -33
  20. package/crates/mint-cli/src/interactive/chat_tui.rs +3594 -483
  21. package/crates/mint-cli/src/interactive/input_box.rs +56 -6
  22. package/crates/mint-cli/src/interactive/slash_commands.rs +208 -56
  23. package/crates/mint-cli/src/interactive/tool_activity.rs +328 -0
  24. package/crates/mint-cli/src/interactive.rs +64 -40
  25. package/crates/mint-cli/src/main.rs +13 -7
  26. package/crates/mint-cli/src/mcp.rs +5 -0
  27. package/crates/mint-cli/src/mod_host.rs +6 -0
  28. package/crates/mint-cli/src/mods_cli.rs +62 -0
  29. package/crates/mint-cli/src/mods_ui.rs +187 -0
  30. package/crates/mint-cli/src/onboard.rs +5 -31
  31. package/crates/mint-cli/src/terminal_theme.rs +3 -1
  32. package/crates/mint-cli/src/updater.rs +56 -2
  33. package/crates/mint-cli/tests/library_intake.rs +77 -0
  34. package/crates/mint-cli/tests/mods_testing.rs +133 -0
  35. package/crates/mint-companion-protocol/Cargo.toml +10 -0
  36. package/crates/mint-companion-protocol/src/lib.rs +163 -0
  37. package/crates/mint-core/Cargo.toml +6 -3
  38. package/crates/mint-core/build.rs +26 -0
  39. package/crates/mint-core/src/agent/chat.rs +167 -39
  40. package/crates/mint-core/src/agent/code_tools.rs +15 -0
  41. package/crates/mint-core/src/agent/memory/sources.rs +240 -0
  42. package/crates/mint-core/src/agent/memory.rs +147 -13
  43. package/crates/mint-core/src/agent/skill_inventory.rs +325 -0
  44. package/crates/mint-core/src/agent/skills.rs +437 -90
  45. package/crates/mint-core/src/agent.rs +1 -0
  46. package/crates/mint-core/src/api_server/routes/background_jobs.rs +310 -0
  47. package/crates/mint-core/src/api_server/routes/chat.rs +216 -199
  48. package/crates/mint-core/src/api_server/routes/html_preview.rs +77 -0
  49. package/crates/mint-core/src/api_server/routes/library.rs +97 -0
  50. package/crates/mint-core/src/api_server/routes/media_gen.rs +10 -57
  51. package/crates/mint-core/src/api_server/routes/misc.rs +104 -107
  52. package/crates/mint-core/src/api_server/routes/mod.rs +6 -0
  53. package/crates/mint-core/src/api_server/routes/mods.rs +31 -0
  54. package/crates/mint-core/src/api_server/routes/sessions.rs +41 -0
  55. package/crates/mint-core/src/api_server/routes/skills_subagents.rs +12 -42
  56. package/crates/mint-core/src/api_server/routes/slash.rs +50 -1
  57. package/crates/mint-core/src/api_server/routes/sources.rs +101 -0
  58. package/crates/mint-core/src/api_server.rs +118 -9
  59. package/crates/mint-core/src/avatar_bridge.rs +11 -2
  60. package/crates/mint-core/src/browser/session.rs +52 -0
  61. package/crates/mint-core/src/companion.rs +716 -0
  62. package/crates/mint-core/src/cron/scheduler.rs +34 -2
  63. package/crates/mint-core/src/integrations/channels.rs +87 -345
  64. package/crates/mint-core/src/integrations/mcp.rs +397 -50
  65. package/crates/mint-core/src/integrations/mcp_calls.rs +823 -0
  66. package/crates/mint-core/src/integrations/mcp_catalog.rs +267 -0
  67. package/crates/mint-core/src/integrations/mcp_device.rs +319 -0
  68. package/crates/mint-core/src/integrations/mcp_result.rs +356 -0
  69. package/crates/mint-core/src/integrations/webhooks.rs +539 -0
  70. package/crates/mint-core/src/integrations.rs +2 -0
  71. package/crates/mint-core/src/lib.rs +85 -26
  72. package/crates/mint-core/src/media/gemini_live.rs +134 -14
  73. package/crates/mint-core/src/media/image_models.rs +3 -27
  74. package/crates/mint-core/src/media/image_workflow.rs +216 -0
  75. package/crates/mint-core/src/media/library/intake.rs +144 -0
  76. package/crates/mint-core/src/media/library/migration.rs +56 -0
  77. package/crates/mint-core/src/media/library/removal.rs +55 -0
  78. package/crates/mint-core/src/media/library/tests.rs +730 -0
  79. package/crates/mint-core/src/media/library/thumbnails.rs +72 -0
  80. package/crates/mint-core/src/media/library.rs +545 -0
  81. package/crates/mint-core/src/media/model_catalog.rs +375 -0
  82. package/crates/mint-core/src/media/pictures.rs +79 -222
  83. package/crates/mint-core/src/media/video_models.rs +3 -22
  84. package/crates/mint-core/src/media.rs +3 -0
  85. package/crates/mint-core/src/mods/manager.rs +1124 -0
  86. package/crates/mint-core/src/mods/mod.rs +569 -0
  87. package/crates/mint-core/src/mods/operations.rs +204 -0
  88. package/crates/mint-core/src/mods/session.rs +146 -0
  89. package/crates/mint-core/src/mods/slash.rs +59 -0
  90. package/crates/mint-core/src/mods/telemetry.rs +306 -0
  91. package/crates/mint-core/src/orchestration/approvals.rs +344 -0
  92. package/crates/mint-core/src/orchestration/context_render.rs +104 -0
  93. package/crates/mint-core/src/orchestration/mcp_review.rs +153 -0
  94. package/crates/mint-core/src/orchestration/memory_skill.rs +104 -32
  95. package/crates/mint-core/src/orchestration/mod.rs +2139 -244
  96. package/crates/mint-core/src/orchestration/mod_authoring_regressions.rs +209 -0
  97. package/crates/mint-core/src/orchestration/progress_clock.rs +80 -0
  98. package/crates/mint-core/src/orchestration/repeated_failures.rs +116 -0
  99. package/crates/mint-core/src/orchestration/review_regressions.rs +449 -0
  100. package/crates/mint-core/src/orchestration/run_control.rs +196 -0
  101. package/crates/mint-core/src/orchestration/tools/files.rs +161 -14
  102. package/crates/mint-core/src/orchestration/tools/media.rs +37 -41
  103. package/crates/mint-core/src/orchestration/tools/misc.rs +3 -2
  104. package/crates/mint-core/src/orchestration/tools/mod.rs +2 -0
  105. package/crates/mint-core/src/orchestration/tools/mods.rs +161 -0
  106. package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +303 -71
  107. package/crates/mint-core/src/orchestration/tools/shell.rs +95 -2
  108. package/crates/mint-core/src/orchestration/workspace_helpers.rs +4 -1
  109. package/crates/mint-core/src/prompts/agent.rs +17 -2
  110. package/crates/mint-core/src/prompts/tool_catalog.rs +28 -2
  111. package/crates/mint-core/src/search/knowledge.rs +7 -1
  112. package/crates/mint-core/src/search/linked_folders/index.rs +177 -13
  113. package/crates/mint-core/src/search/linked_folders.rs +83 -2
  114. package/crates/mint-core/src/slash/catalog.rs +1 -1
  115. package/crates/mint-core/src/slash/mod.rs +174 -11
  116. package/crates/mint-core/src/slash/mods.rs +155 -0
  117. package/crates/mint-core/src/system/bg_shell.rs +1129 -95
  118. package/crates/mint-core/src/system/config.rs +5 -10
  119. package/crates/mint-core/src/system/html_preview.rs +398 -0
  120. package/crates/mint-core/src/system/mint_web.rs +164 -0
  121. package/crates/mint-core/src/system/release_notes.rs +18 -0
  122. package/crates/mint-core/src/system/shell.rs +21 -6
  123. package/crates/mint-core/src/system/workspace.rs +72 -8
  124. package/crates/mint-core/src/system/workspace_history.rs +604 -0
  125. package/crates/mint-core/src/system.rs +4 -0
  126. package/crates/mint-core/tests/background_retention.rs +34 -0
  127. package/crates/mint-core/tests/background_shutdown.rs +24 -0
  128. package/crates/mint-core/tests/library_http.rs +209 -0
  129. package/crates/mint-core/tests/mcp_contract.rs +283 -0
  130. package/crates/mint-core/tests/mcp_stdio.rs +11 -10
  131. package/crates/mint-core/tests/media_model_catalog_http.rs +403 -0
  132. package/crates/mint-core/tests/mods_http.rs +215 -0
  133. package/crates/mint-core/tests/terminal_shutdown.rs +109 -0
  134. package/crates/mint-mod-runtime/Cargo.toml +18 -0
  135. package/crates/mint-mod-runtime/src/lib.rs +507 -0
  136. package/crates/mint-mod-runtime/src/sdk.js +56 -0
  137. package/crates/mint-mod-runtime/src/test_sdk.js +32 -0
  138. package/crates/mint-mod-runtime/src/testing.rs +94 -0
  139. package/crates/mint-mod-runtime/tests/runtime.rs +156 -0
  140. package/docs/mods/AUTHORING.md +61 -0
  141. package/docs/mods/README.md +139 -0
  142. package/docs/mods/TESTING.md +56 -0
  143. package/docs/mods/mint-mods.d.ts +66 -0
  144. package/examples/device-mcp/README.md +22 -0
  145. package/examples/device-mcp/contract.md +49 -0
  146. package/examples/device-mcp/mock-device.mjs +66 -0
  147. package/examples/device-mcp/server.mjs +48 -0
  148. package/examples/device-mcp/server.test.mjs +78 -0
  149. package/examples/mods/task-checklist/README.md +1 -0
  150. package/examples/mods/task-checklist/main.ts +17 -0
  151. package/examples/mods/task-checklist/manifest.json +1 -0
  152. package/examples/mods/task-checklist/tests/checklist.test.ts +15 -0
  153. package/examples/mods/token-meter/README.md +1 -0
  154. package/examples/mods/token-meter/main.ts +8 -0
  155. package/examples/mods/token-meter/manifest.json +1 -0
  156. package/examples/mods/tool-guard/README.md +1 -0
  157. package/examples/mods/tool-guard/main.ts +9 -0
  158. package/examples/mods/tool-guard/manifest.json +1 -0
  159. package/out/web/assets/CliSessionsView-CtzUJpK_.css +1 -0
  160. package/out/web/assets/CliSessionsView-D7hw0Mgj.js +1 -0
  161. package/out/web/assets/CodeReviewPage-C7bEOJfd.js +4 -0
  162. package/out/web/assets/CodeReviewPage-Dte9uLiL.css +1 -0
  163. package/out/web/assets/CommandPalette-0RrF-AOT.js +1 -0
  164. package/out/web/assets/ImageStudioPanel-CdxkAf_r.js +1 -0
  165. package/out/web/assets/LibraryPage--Qfisp6D.js +2 -0
  166. package/out/web/assets/LibraryPage-DAxUTAg7.css +1 -0
  167. package/out/web/assets/LinkedFoldersView-2nfZbDgN.js +1 -0
  168. package/out/web/assets/McpRegistryPicker-h9uS8_wT.js +1 -0
  169. package/out/web/assets/McpServersView-slfQJzNB.js +1 -0
  170. package/out/web/assets/MintDashboard-BSR5XpXh.js +25 -0
  171. package/out/web/assets/MintDashboard-CWUcCGhd.css +1 -0
  172. package/out/web/assets/PluginsView-l7LSm503.js +1 -0
  173. package/out/web/assets/ScheduledTasksView-CttmLbq1.js +1 -0
  174. package/out/web/assets/SettingsWindow-BCMLYvEV.js +4 -0
  175. package/out/web/assets/SettingsWindow-DcMVmU9u.css +1 -0
  176. package/out/web/assets/SkillsView-rNs1qr_U.js +2 -0
  177. package/out/web/assets/ToolSurfacePage-B9DzEPWN.js +37 -0
  178. package/out/web/assets/ToolSurfacePage-CTfgggF2.css +1 -0
  179. package/out/web/assets/VeoStudioPanel-DiJs_Cg2.js +4 -0
  180. package/out/web/assets/WorkspaceSelector-B2xIp04D.js +1 -0
  181. package/out/web/assets/WorkspaceSelector-Ca8uJOsS.css +1 -0
  182. package/out/web/assets/{abnfDiagram-VCTEODGH-ByMyfanq.js → abnfDiagram-VCTEODGH-CD0CjoGl.js} +1 -1
  183. package/out/web/assets/{architecture-7GRP2DOG-CLq2kEOB.js → architecture-7GRP2DOG-RKV83OeZ.js} +1 -1
  184. package/out/web/assets/{architectureDiagram-5GKGNRK7-6PoVSmNG.js → architectureDiagram-5GKGNRK7-nN_HAFct.js} +1 -1
  185. package/out/web/assets/{blockDiagram-NRAW4CY4-cKw9mAKi.js → blockDiagram-NRAW4CY4-BrrqzHW5.js} +1 -1
  186. package/out/web/assets/{c4Diagram-UCG6FXSJ-Cqy8N5Ge.js → c4Diagram-UCG6FXSJ-DTdV8_zE.js} +1 -1
  187. package/out/web/assets/{chunk-75Z2AOVW-3qybijF5.js → chunk-75Z2AOVW-_M0z5olV.js} +1 -1
  188. package/out/web/assets/chunk-CKBBP62Z-BpQEgeHF.js +2 -0
  189. package/out/web/assets/{chunk-CLGD4ZFX-CndDQkww.js → chunk-CLGD4ZFX-D54yGvGS.js} +1 -1
  190. package/out/web/assets/{chunk-DSNCTWBM-Cc4NxvgD.js → chunk-DSNCTWBM-C-gvT2ru.js} +1 -1
  191. package/out/web/assets/{chunk-DU6HZSFF-drzh6GsU.js → chunk-DU6HZSFF-CnFngIhW.js} +1 -1
  192. package/out/web/assets/{chunk-F27PBJKO-FiY35l4v.js → chunk-F27PBJKO-BqtWrnEO.js} +1 -1
  193. package/out/web/assets/{chunk-G27WJ6UU-Bjt9Il4A.js → chunk-G27WJ6UU-Ckoq5mi2.js} +1 -1
  194. package/out/web/assets/{chunk-GMAD6QVW-Bppi3Ctd.js → chunk-GMAD6QVW-BuhEcgHq.js} +1 -1
  195. package/out/web/assets/{chunk-JQ64N6SF-DGg5kcEV.js → chunk-JQ64N6SF-CF8T7GiZ.js} +1 -1
  196. package/out/web/assets/{chunk-LCL6LL3I-CCYcjd7p.js → chunk-LCL6LL3I-BhXV9zyH.js} +1 -1
  197. package/out/web/assets/{chunk-OBVCFTLP-DJeNil5M.js → chunk-OBVCFTLP-CRIkunUB.js} +1 -1
  198. package/out/web/assets/{chunk-P2QGCYS3-lRzkRjBB.js → chunk-P2QGCYS3-UFEU6BxZ.js} +1 -1
  199. package/out/web/assets/{chunk-POPQ4Y6H-DouASMBW.js → chunk-POPQ4Y6H-CQcrNbjr.js} +1 -1
  200. package/out/web/assets/{chunk-PWAF6VOD-DMmD1vVf.js → chunk-PWAF6VOD-Cc2uVDsG.js} +1 -1
  201. package/out/web/assets/{chunk-R7TYR2AO-CYLA5UC5.js → chunk-R7TYR2AO-DiCxzdAY.js} +1 -1
  202. package/out/web/assets/{chunk-RHFEMEQ7-BiXwEcUT.js → chunk-RHFEMEQ7-b7hnPmrR.js} +1 -1
  203. package/out/web/assets/{chunk-SVP7TREG-BV-zPslF.js → chunk-SVP7TREG-Bu_yYb-V.js} +1 -1
  204. package/out/web/assets/{chunk-TEH6E4GO-B2RGEohZ.js → chunk-TEH6E4GO-G4Cdlyrt.js} +1 -1
  205. package/out/web/assets/classDiagram-DTDB5LWJ-RnC2mYO4.js +1 -0
  206. package/out/web/assets/classDiagram-v2-JRS7N3AN-RnC2mYO4.js +1 -0
  207. package/out/web/assets/config-CnvCChUM.js +2 -0
  208. package/out/web/assets/config-DM6oIVO3.css +1 -0
  209. package/out/web/assets/core-DsKkQ7ig.js +1 -0
  210. package/out/web/assets/{cynefin-OW5HDTMX-Cas4csHM.js → cynefin-OW5HDTMX-CnzQfOGp.js} +1 -1
  211. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-Bbpj66Y3.js → cynefinDiagram-5FMLGOSQ-DVtQnfpm.js} +1 -1
  212. package/out/web/assets/{dagre-3AP2YEHR-B0-2RDEd.js → dagre-3AP2YEHR-DG76_k5_.js} +1 -1
  213. package/out/web/assets/{diagram-S7CK7UJ4-BdzEjUyH.js → diagram-S7CK7UJ4-CaouUoze.js} +1 -1
  214. package/out/web/assets/{diagram-UQ7AKVKN-CJDwyPxF.js → diagram-UQ7AKVKN-B4bEc26B.js} +1 -1
  215. package/out/web/assets/{diagram-VSXAHHWV-BxaG1r_B.js → diagram-VSXAHHWV-BJ86Ov2Y.js} +1 -1
  216. package/out/web/assets/{diagram-VX7I27RA-BMmUqm8f.js → diagram-VX7I27RA-7hH5IPYf.js} +1 -1
  217. package/out/web/assets/{diagram-Z3DM3KII-CFtd03eE.js → diagram-Z3DM3KII-CCM4clo8.js} +1 -1
  218. package/out/web/assets/dist-js-BkYNznHX.js +1 -0
  219. package/out/web/assets/{ebnfDiagram-PWID7BFC-DwAen1hO.js → ebnfDiagram-PWID7BFC-Da7WAM3k.js} +1 -1
  220. package/out/web/assets/{erDiagram-SSCWMZ5O-BCZSHACj.js → erDiagram-SSCWMZ5O-CzzmnZWT.js} +1 -1
  221. package/out/web/assets/{event-ChBVpRy0.js → event-Cdt7Byyj.js} +1 -1
  222. package/out/web/assets/{eventmodeling-NTZA5JFV-XHStE4E_.js → eventmodeling-NTZA5JFV-xcfsA0XD.js} +1 -1
  223. package/out/web/assets/flowDiagram-A5DVABFB-JdtH1673.js +1 -0
  224. package/out/web/assets/{ganttDiagram-EL5Y4UJY-BAEu1Cql.js → ganttDiagram-EL5Y4UJY-B3ClFGmL.js} +1 -1
  225. package/out/web/assets/{gitGraph-4MIJSDKK-r32Vyper.js → gitGraph-4MIJSDKK-BZ_y_cDU.js} +1 -1
  226. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-z9o9qt6V.js → gitGraphDiagram-WWUBYQGX-drhRSYm3.js} +1 -1
  227. package/out/web/assets/index-BZ9xgmMP.css +1 -0
  228. package/out/web/assets/index-Ca8cCS4h.js +168 -0
  229. package/out/web/assets/{info-A6RAGUB7-BvLaC2cw.js → info-A6RAGUB7-B7I3K0F1.js} +1 -1
  230. package/out/web/assets/{infoDiagram-RXCK75RN-Be6WAXXw.js → infoDiagram-RXCK75RN-deX_1MPh.js} +1 -1
  231. package/out/web/assets/{ishikawaDiagram-5VMMS53U-DZjoHJHC.js → ishikawaDiagram-5VMMS53U-s38yo28y.js} +1 -1
  232. package/out/web/assets/{journeyDiagram-EYS64GPL-CtVkjkGm.js → journeyDiagram-EYS64GPL-CZn2kB8b.js} +1 -1
  233. package/out/web/assets/jsx-runtime-DN4NhQUK.js +1 -0
  234. package/out/web/assets/{kanban-definition-3QL26DDD-CW5FMv1w.js → kanban-definition-3QL26DDD-DodjgySL.js} +1 -1
  235. package/out/web/assets/markdown-CMhYIcWB.js +39 -0
  236. package/out/web/assets/{mermaid-parser.core-aUgCqsZ5.js → mermaid-parser.core-Whu2rlJC.js} +3 -3
  237. package/out/web/assets/{mermaid.core-D57sEiSL.js → mermaid.core-CKyPo8VH.js} +4 -4
  238. package/out/web/assets/{mindmap-definition-FBJOCRG2-DAmSpM3a.js → mindmap-definition-FBJOCRG2-260WBDxZ.js} +1 -1
  239. package/out/web/assets/modelManager-CUaGWMWu.js +2 -0
  240. package/out/web/assets/{oauthManager-9jxicDWJ.js → oauthManager-DlNhXJr6.js} +1 -1
  241. package/out/web/assets/{packet-AYTQ26CC-DJ27iIoJ.js → packet-AYTQ26CC-CEeIRKRG.js} +1 -1
  242. package/out/web/assets/{pegDiagram-XKGWAZYB-Bj3ii-gS.js → pegDiagram-XKGWAZYB-BYadI-KO.js} +1 -1
  243. package/out/web/assets/{pie-WAS4IAKB-DG5mCUbc.js → pie-WAS4IAKB-D8uf4dh5.js} +1 -1
  244. package/out/web/assets/{pieDiagram-E7YTZNPT-BrJLPnDA.js → pieDiagram-E7YTZNPT-CjSDy4hO.js} +1 -1
  245. package/out/web/assets/{quadrantDiagram-AXDQQJYC-3x_TmF3j.js → quadrantDiagram-AXDQQJYC-B8D3gU5d.js} +1 -1
  246. package/out/web/assets/{radar-RG4KPBEZ-CrXaTTuV.js → radar-RG4KPBEZ-DZJxnokY.js} +1 -1
  247. package/out/web/assets/{railroad-74A4TZTK-BpqMH9BE.js → railroad-74A4TZTK-DBysUiUi.js} +1 -1
  248. package/out/web/assets/{railroad-abnf-HS5TGJTU-DHI0uLlV.js → railroad-abnf-HS5TGJTU-CHWxoJYg.js} +1 -1
  249. package/out/web/assets/{railroad-ebnf-LZEXJU2U-CQCb316y.js → railroad-ebnf-LZEXJU2U-DDv7X6bY.js} +1 -1
  250. package/out/web/assets/{railroad-peg-WCYAUIDC-Ph1giKkH.js → railroad-peg-WCYAUIDC-DpfcYYNJ.js} +1 -1
  251. package/out/web/assets/{railroadDiagram-O6MQD6OU-BRq6wIxx.js → railroadDiagram-O6MQD6OU-CYtyv0t3.js} +1 -1
  252. package/out/web/assets/{requirementDiagram-EFPCY7ZU-DIJj6G1g.js → requirementDiagram-EFPCY7ZU-D2X-Ewik.js} +1 -1
  253. package/out/web/assets/{sankeyDiagram-P5KCCOFB-DZajSryq.js → sankeyDiagram-P5KCCOFB-Bn2x6C4Y.js} +1 -1
  254. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-DxJCWyZS.js → sequenceDiagram-WJ2MYXX4-BvFLKtPd.js} +1 -1
  255. package/out/web/assets/{stateDiagram-HBIQ2CUA-k6voi8Vf.js → stateDiagram-HBIQ2CUA-Bx0GWeaY.js} +1 -1
  256. package/out/web/assets/stateDiagram-v2-4QOOHH4V--hol1yg_.js +1 -0
  257. package/out/web/assets/{swimlanes-XN3QIQJK-igYhTjXB.js → swimlanes-XN3QIQJK-CCma0T7L.js} +1 -1
  258. package/out/web/assets/swimlanesDiagram-VK2B7HYN-BkohoVsU.js +8 -0
  259. package/out/web/assets/{timeline-definition-24CTP7MA-Dke2p93p.js → timeline-definition-24CTP7MA-iCRhhJ7C.js} +1 -1
  260. package/out/web/assets/{treeView-Q6P3EWNA-hNELnXUs.js → treeView-Q6P3EWNA-DZ-y08qp.js} +1 -1
  261. package/out/web/assets/{treemap-WGGIJYW6-D2B6wDsx.js → treemap-WGGIJYW6-De0CxzMx.js} +1 -1
  262. package/out/web/assets/{vennDiagram-4TSXK5OY-CDkW7rUP.js → vennDiagram-4TSXK5OY-w_7aZ6xe.js} +1 -1
  263. package/out/web/assets/{wardley-WFR3VGLG-BECdXETi.js → wardley-WFR3VGLG-DT0y0xJ4.js} +1 -1
  264. package/out/web/assets/{wardleyDiagram-VM6X3IG4-B5BycpQ4.js → wardleyDiagram-VM6X3IG4-DAdXELbQ.js} +1 -1
  265. package/out/web/assets/window-BJCVtyET.js +1 -0
  266. package/out/web/assets/{xychartDiagram-S5SC5T6Z-CtJS3b--.js → xychartDiagram-S5SC5T6Z-IZIXf3Vo.js} +1 -1
  267. package/out/web/index-web.html +3 -2
  268. package/out/web/index.html +3 -2
  269. package/package.json +71 -22
  270. package/shared-modules/.github/workflows/ci.yml +120 -0
  271. package/shared-modules/.github/workflows/update.yaml +28 -0
  272. package/slash-commands.json +7 -2
  273. package/src/bin/auto-build.js +66 -0
  274. package/src/bin/desktop.js +113 -0
  275. package/src/bin/desktop.test.js +113 -0
  276. package/src/bin/index.js +18 -25
  277. package/src/bin/index.test.js +89 -0
  278. package/src/bin/measure-effects.mjs +53 -0
  279. package/src/bin/package.test.js +28 -0
  280. package/src/bin/release-notes.test.js +84 -0
  281. package/src/bin/styles.test.js +20 -0
  282. package/src/bin/web.js +5 -0
  283. package/src/bin/web.test.js +39 -0
  284. package/src/renderer/index.html +0 -2
  285. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  286. package/src/renderer/shared/agentProgress.ts +33 -0
  287. package/src/renderer/shared/components/AgentActivityDrawer.tsx +3 -2
  288. package/src/renderer/shared/components/AgentActivityTable.tsx +1 -1
  289. package/src/renderer/shared/components/ApprovalCard.test.tsx +24 -0
  290. package/src/renderer/shared/components/ApprovalCard.tsx +10 -3
  291. package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +88 -37
  292. package/src/renderer/shared/components/AuthGate.tsx +75 -39
  293. package/src/renderer/shared/components/BackgroundTerminals.tsx +181 -0
  294. package/src/renderer/shared/components/ChatCodeBlock.tsx +8 -4
  295. package/src/renderer/shared/components/ChatMessageItem.test.tsx +68 -0
  296. package/src/renderer/shared/components/ChatMessageItem.tsx +36 -16
  297. package/src/renderer/shared/components/ChatPanel.test.tsx +21 -0
  298. package/src/renderer/shared/components/ChatPanel.tsx +294 -272
  299. package/src/renderer/shared/components/ChunkErrorBoundary.tsx +4 -102
  300. package/src/renderer/shared/components/CliSessionsView.test.tsx +125 -0
  301. package/src/renderer/shared/components/CliSessionsView.tsx +61 -13
  302. package/src/renderer/shared/components/CodeReviewPage.tsx +111 -8
  303. package/src/renderer/shared/components/CollapsiblePasteInput.tsx +90 -0
  304. package/src/renderer/shared/components/ContextCompactionStatus.tsx +17 -0
  305. package/src/renderer/shared/components/DashboardSidebar.tsx +15 -128
  306. package/src/renderer/shared/components/DesktopTitlebar.tsx +45 -19
  307. package/src/renderer/shared/components/ErrorNotice.tsx +29 -0
  308. package/src/renderer/shared/components/FormattedMessage.tsx +11 -0
  309. package/src/renderer/shared/components/GeminiLiveOverlay.css +86 -0
  310. package/src/renderer/shared/components/GeminiLiveOverlay.test.tsx +24 -0
  311. package/src/renderer/shared/components/GeminiLiveOverlay.tsx +85 -101
  312. package/src/renderer/shared/components/GlideSelect.css +138 -0
  313. package/src/renderer/shared/components/GlideSelect.d.ts +31 -0
  314. package/src/renderer/shared/components/GlideSelect.jsx +266 -0
  315. package/src/renderer/shared/components/ImageSearchCard.tsx +46 -56
  316. package/src/renderer/shared/components/ImageStudioPanel.tsx +9 -8
  317. package/src/renderer/shared/components/LibraryItemCard.tsx +72 -0
  318. package/src/renderer/shared/components/LibraryPage.tsx +165 -0
  319. package/src/renderer/shared/components/LibraryPreview.tsx +59 -0
  320. package/src/renderer/shared/components/McpServersView.tsx +27 -8
  321. package/src/renderer/shared/components/McpToolAllowlist.tsx +2 -4
  322. package/src/renderer/shared/components/McpToolResults.tsx +99 -0
  323. package/src/renderer/shared/components/MintDashboard.tsx +308 -770
  324. package/src/renderer/shared/components/ModSessionControls.tsx +8 -0
  325. package/src/renderer/shared/components/ModUi.test.tsx +65 -0
  326. package/src/renderer/shared/components/ModUi.tsx +26 -0
  327. package/src/renderer/shared/components/ModelSelectorPopover.tsx +14 -7
  328. package/src/renderer/shared/components/ModsPanel.tsx +156 -0
  329. package/src/renderer/shared/components/ProgressActivity.tsx +17 -0
  330. package/src/renderer/shared/components/ReviewHistory.tsx +42 -0
  331. package/src/renderer/shared/components/RewindModal.tsx +5 -12
  332. package/src/renderer/shared/components/RunSummaryDashboard.tsx +6 -12
  333. package/src/renderer/shared/components/ScheduledTasksView.tsx +7 -6
  334. package/src/renderer/shared/components/SelectField.test.tsx +27 -0
  335. package/src/renderer/shared/components/SelectField.tsx +38 -0
  336. package/src/renderer/shared/components/SessionSources.tsx +48 -0
  337. package/src/renderer/shared/components/SessionSourcesPopover.tsx +61 -0
  338. package/src/renderer/shared/components/Settings/AgentsTab.tsx +7 -6
  339. package/src/renderer/shared/components/Settings/AudioTab.tsx +8 -7
  340. package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -2
  341. package/src/renderer/shared/components/Settings/GeneralTab.tsx +33 -32
  342. package/src/renderer/shared/components/Settings/MemoryTab.test.tsx +37 -0
  343. package/src/renderer/shared/components/Settings/MemoryTab.tsx +71 -31
  344. package/src/renderer/shared/components/Settings/NotificationsTab.test.tsx +15 -0
  345. package/src/renderer/shared/components/Settings/NotificationsTab.tsx +84 -0
  346. package/src/renderer/shared/components/Settings/ThemeTab.tsx +19 -11
  347. package/src/renderer/shared/components/SlashChoicePanel.tsx +88 -0
  348. package/src/renderer/shared/components/SlashSuggestions.test.tsx +33 -0
  349. package/src/renderer/shared/components/SlashSuggestions.tsx +38 -10
  350. package/src/renderer/shared/components/SourcesBlock.tsx +12 -20
  351. package/src/renderer/shared/components/ToolSurfacePage.test.tsx +32 -0
  352. package/src/renderer/shared/components/ToolSurfacePage.tsx +59 -11
  353. package/src/renderer/shared/components/ToolsPanelToggle.tsx +13 -0
  354. package/src/renderer/shared/components/VeoStudioPanel.tsx +9 -8
  355. package/src/renderer/shared/components/ViewedImages.tsx +36 -0
  356. package/src/renderer/shared/components/VirtualChatHistory.tsx +82 -0
  357. package/src/renderer/shared/components/VoicePill.css +85 -0
  358. package/src/renderer/shared/components/VoicePill.d.ts +12 -0
  359. package/src/renderer/shared/components/VoicePill.jsx +190 -0
  360. package/src/renderer/shared/components/VoicePill.test.tsx +22 -0
  361. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +20 -15
  362. package/src/renderer/shared/components/WorkspacePaneResizer.tsx +92 -0
  363. package/src/renderer/shared/components/WorkspacePreviewActions.test.tsx +44 -0
  364. package/src/renderer/shared/components/WorkspacePreviewActions.tsx +19 -0
  365. package/src/renderer/shared/components/WorkspaceSelector.tsx +13 -8
  366. package/src/renderer/shared/components/background-terminals.css +292 -0
  367. package/src/renderer/shared/components/ui/README.md +52 -0
  368. package/src/renderer/shared/components/ui/button.tsx +37 -0
  369. package/src/renderer/shared/components/ui/demo.tsx +26 -0
  370. package/src/renderer/shared/components/ui/error-recovery.css +35 -0
  371. package/src/renderer/shared/components/ui/error-recovery.tsx +35 -0
  372. package/src/renderer/shared/components/ui/voice-powered-orb.css +6 -0
  373. package/src/renderer/shared/components/ui/voice-powered-orb.tsx +374 -0
  374. package/src/renderer/shared/components/workspace-selector.css +248 -0
  375. package/src/renderer/shared/constants/config.ts +30 -1
  376. package/src/renderer/shared/constants/slashCommands.ts +1 -1
  377. package/src/renderer/shared/conversation/lifecycle.test.ts +362 -0
  378. package/src/renderer/shared/conversation/lifecycle.ts +418 -0
  379. package/src/renderer/shared/conversation/projectNavigation.test.ts +105 -0
  380. package/src/renderer/shared/conversation/projectNavigation.ts +39 -0
  381. package/src/renderer/shared/conversation/sourcesTransport.ts +27 -0
  382. package/src/renderer/shared/conversation/syncView.test.ts +1 -34
  383. package/src/renderer/shared/conversation/syncView.ts +0 -37
  384. package/src/renderer/shared/conversation/transport.test.ts +163 -0
  385. package/src/renderer/shared/conversation/transport.ts +159 -0
  386. package/src/renderer/shared/conversation/useConversationCoordinator.ts +30 -72
  387. package/src/renderer/shared/css/agent-observability.css +20 -21
  388. package/src/renderer/shared/css/auth-gate.css +112 -13
  389. package/src/renderer/shared/css/code-review.css +33 -0
  390. package/src/renderer/shared/css/code-sessions.css +27 -0
  391. package/src/renderer/shared/css/collapsed-paste.css +35 -0
  392. package/src/renderer/shared/css/effects.css +15 -0
  393. package/src/renderer/shared/css/error-notice.css +60 -0
  394. package/src/renderer/shared/css/library.css +476 -0
  395. package/src/renderer/shared/css/management-views.css +138 -0
  396. package/src/renderer/shared/css/mcp-results.css +11 -0
  397. package/src/renderer/shared/css/model-selector.css +163 -105
  398. package/src/renderer/shared/css/mods.css +77 -0
  399. package/src/renderer/shared/css/notification-notice.css +28 -0
  400. package/src/renderer/shared/css/session-sources-popover.css +9 -0
  401. package/src/renderer/shared/css/session-sources.css +23 -0
  402. package/src/renderer/shared/css/settings/memory.css +154 -0
  403. package/src/renderer/shared/css/settings/notifications.css +11 -0
  404. package/src/renderer/shared/css/settings/theme.css +5 -0
  405. package/src/renderer/shared/css/slash-choice.css +95 -0
  406. package/src/renderer/shared/css/slash-suggestions.css +68 -0
  407. package/src/renderer/shared/css/sources-block.css +91 -0
  408. package/src/renderer/shared/css/thinking-status.css +46 -0
  409. package/src/renderer/shared/css/tools-panel.css +7 -0
  410. package/src/renderer/shared/css/ui-tailwind.css +19 -0
  411. package/src/renderer/shared/css/viewed-images.css +15 -0
  412. package/src/renderer/shared/fonts.css +46 -0
  413. package/src/renderer/shared/fonts.ts +3 -12
  414. package/src/renderer/shared/lib/utils.ts +6 -0
  415. package/src/renderer/shared/library/controller.test.ts +61 -0
  416. package/src/renderer/shared/library/controller.ts +57 -0
  417. package/src/renderer/shared/library/transport.test.ts +26 -0
  418. package/src/renderer/shared/library/transport.ts +60 -0
  419. package/src/renderer/shared/library/useLibraryFile.ts +38 -0
  420. package/src/renderer/shared/mods.ts +58 -0
  421. package/src/renderer/shared/platform.test.ts +57 -1
  422. package/src/renderer/shared/platform.ts +46 -1
  423. package/src/renderer/shared/theme/themeManager.test.ts +43 -0
  424. package/src/renderer/shared/theme/themeManager.ts +17 -4
  425. package/src/renderer/shared/types.ts +56 -2
  426. package/src/renderer/shared/utils/agentActivity.test.ts +75 -0
  427. package/src/renderer/shared/utils/agentActivity.ts +42 -3
  428. package/src/renderer/shared/utils/approval.tsx +7 -0
  429. package/src/renderer/shared/utils/artifactBrowser.test.ts +80 -0
  430. package/src/renderer/shared/utils/artifactBrowser.ts +41 -0
  431. package/src/renderer/shared/utils/contextCompaction.test.tsx +48 -0
  432. package/src/renderer/shared/utils/contextCompaction.ts +21 -0
  433. package/src/renderer/shared/utils/effectVisibility.ts +9 -0
  434. package/src/renderer/shared/utils/markdown.tsx +3 -3
  435. package/src/renderer/shared/utils/mcpResults.test.tsx +91 -0
  436. package/src/renderer/shared/utils/modViewport.ts +7 -0
  437. package/src/renderer/shared/utils/nativeRecordingSession.test.ts +61 -0
  438. package/src/renderer/shared/utils/nativeRecordingSession.ts +46 -0
  439. package/src/renderer/shared/utils/notificationSettings.test.ts +14 -0
  440. package/src/renderer/shared/utils/notificationSettings.ts +23 -0
  441. package/src/renderer/shared/utils/notifications.test.ts +51 -0
  442. package/src/renderer/shared/utils/notifications.ts +54 -0
  443. package/src/renderer/shared/utils/pastedText.test.ts +20 -0
  444. package/src/renderer/shared/utils/pastedText.ts +13 -0
  445. package/src/renderer/shared/utils/progressCache.test.ts +32 -0
  446. package/src/renderer/shared/utils/sessionSources.test.ts +31 -0
  447. package/src/renderer/shared/utils/sessionSources.ts +34 -0
  448. package/src/renderer/shared/utils/slashCommandProcessor.ts +1 -1
  449. package/src/renderer/shared/utils/sourceAttachments.ts +28 -0
  450. package/src/renderer/shared/utils/speech.ts +51 -9
  451. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +22 -0
  452. package/src/renderer/shared/utils/syntaxHighlight.tsx +26 -0
  453. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +49 -5
  454. package/src/renderer/shared/utils/useLatestCallback.ts +9 -0
  455. package/src/renderer/shared/utils/useNativeVoiceInput.ts +80 -72
  456. package/src/renderer/shared/utils/useSessionSources.ts +33 -0
  457. package/src/renderer/shared/utils/viewedImages.ts +26 -0
  458. package/src/renderer/shared/utils/workspacePane.test.ts +33 -0
  459. package/src/renderer/shared/utils/workspacePane.ts +20 -0
  460. package/src/renderer/shared/utils/workspacePreview.test.ts +24 -0
  461. package/src/renderer/shared/utils/workspacePreview.ts +16 -0
  462. package/src/renderer/shared/utils/workspaceSnapshot.test.ts +49 -0
  463. package/src/renderer/shared/utils/workspaceSnapshot.ts +26 -0
  464. package/src/renderer/shared/utils/workspaces.ts +10 -0
  465. package/src/renderer/shared/workspaceUndo.ts +88 -0
  466. package/src/renderer/src/App.tsx +9 -1
  467. package/src/renderer/src/components/LiveTranslateControls.tsx +65 -0
  468. package/src/renderer/src/components/PicturesLibrary.tsx +2 -288
  469. package/src/renderer/src/components/ScreenPicker.css +67 -30
  470. package/src/renderer/src/components/ScreenPicker.tsx +201 -60
  471. package/src/renderer/src/components/SettingsWindow.tsx +10 -5
  472. package/src/renderer/src/components/WorkspacePanel.tsx +222 -32
  473. package/src/renderer/src/components/browser-shell.css +1 -1
  474. package/src/renderer/src/components/browser-suggestions-popup.css +1 -1
  475. package/src/renderer/src/components/liveTranslateFrames.test.ts +26 -5
  476. package/src/renderer/src/components/liveTranslateFrames.ts +18 -7
  477. package/src/renderer/src/components/liveTranslateLanguage.ts +6 -0
  478. package/src/renderer/src/css/app-shell.css +52 -137
  479. package/src/renderer/src/css/chat.css +21 -495
  480. package/src/renderer/src/css/desktop-tools.css +199 -1
  481. package/src/renderer/src/css/misc.css +0 -52
  482. package/src/renderer/src/css/pictures.css +2 -415
  483. package/src/renderer/src/css/settings/base.css +50 -47
  484. package/src/renderer/src/css/sidebar-workspace.css +151 -94
  485. package/src/renderer/src/css/tool-surfaces.css +65 -9
  486. package/src/renderer/src/css/widget.css +1 -1
  487. package/src/renderer/src/env.d.ts +1 -9
  488. package/src/renderer/src/index.css +1 -1
  489. package/src/renderer/src/main.tsx +2 -1
  490. package/src/renderer/src/proactiveSuggestions.ts +29 -0
  491. package/src/renderer/src/tauri.ts +122 -330
  492. package/src/renderer/src/voiceInput.ts +2 -1
  493. package/src/renderer/src-web/components/PicturesLibrary.tsx +2 -285
  494. package/src/renderer/src-web/components/PullToRefresh.tsx +1 -1
  495. package/src/renderer/src-web/components/SettingsWindow.tsx +10 -5
  496. package/src/renderer/src-web/css/app-shell.css +24 -121
  497. package/src/renderer/src-web/css/chat.css +21 -495
  498. package/src/renderer/src-web/css/misc.css +0 -52
  499. package/src/renderer/src-web/css/mobile.css +15 -14
  500. package/src/renderer/src-web/css/pictures.css +2 -462
  501. package/src/renderer/src-web/css/settings/base.css +50 -47
  502. package/src/renderer/src-web/css/sidebar-workspace.css +9 -95
  503. package/src/renderer/src-web/css/widget.css +1 -1
  504. package/src/renderer/src-web/env.d.ts +1 -12
  505. package/src/renderer/src-web/index.css +1 -1
  506. package/src/renderer/src-web/main.tsx +2 -1
  507. package/src/renderer/src-web/proactiveSuggestions.ts +3 -0
  508. package/src/renderer/src-web/tauri.ts +129 -354
  509. package/src/renderer/tests/backgroundTerminals.browser.html +81 -0
  510. package/src/renderer/tests/chatPanelFixture.ts +23 -0
  511. package/src/renderer/tests/chatPerformance.browser.html +71 -0
  512. package/src/renderer/tests/cliSessions.browser.html +82 -0
  513. package/src/renderer/tests/collapsedPaste.browser.html +70 -0
  514. package/src/renderer/tests/composerMenus.browser.html +116 -0
  515. package/src/renderer/tests/contextCompaction.browser.html +17 -0
  516. package/src/renderer/tests/conversationLifecycle.browser.html +70 -0
  517. package/src/renderer/tests/effectsPerformance.browser.html +13 -0
  518. package/src/renderer/tests/errorRecovery.browser.html +21 -0
  519. package/src/renderer/tests/glideSelect.browser.html +71 -0
  520. package/src/renderer/tests/glideSelect.preview.html +27 -0
  521. package/src/renderer/tests/library.browser.html +1 -0
  522. package/src/renderer/tests/library.browser.tsx +78 -0
  523. package/src/renderer/tests/liveOrb.browser.html +49 -0
  524. package/src/renderer/tests/liveVoice.browser.html +29 -0
  525. package/src/renderer/tests/modInput.browser.html +22 -0
  526. package/src/renderer/tests/sessionSources.browser.html +1 -0
  527. package/src/renderer/tests/sessionSources.browser.tsx +107 -0
  528. package/src/renderer/tests/skills.browser.html +24 -0
  529. package/src/renderer/tests/toolsPanel.browser.html +1 -0
  530. package/src/renderer/tests/toolsPanel.browser.tsx +113 -0
  531. package/src/renderer/tests/viewedImages.browser.html +1 -0
  532. package/src/renderer/tests/viewedImages.browser.tsx +93 -0
  533. package/src/renderer/tests/voicePill.browser.html +80 -0
  534. package/src/renderer/tests/voicePill.preview.html +34 -0
  535. package/src/renderer/tests/webSources.browser.html +1 -0
  536. package/src/renderer/tests/webSources.browser.tsx +98 -0
  537. package/src/renderer/tests/workspaceUndo.browser.html +92 -0
  538. package/src-tauri/Cargo.toml +40 -0
  539. package/src-tauri/build.rs +3 -0
  540. package/src-tauri/src/app/lifecycle.rs +277 -0
  541. package/src-tauri/src/app/mod.rs +4 -0
  542. package/src-tauri/src/app/shortcuts.rs +40 -0
  543. package/src-tauri/src/app/tray.rs +52 -0
  544. package/src-tauri/src/background/events.rs +70 -0
  545. package/src-tauri/src/background/headless.rs +230 -0
  546. package/src-tauri/src/background/mod.rs +4 -0
  547. package/src-tauri/src/background/proactive.rs +262 -0
  548. package/src-tauri/src/commands/approvals.rs +20 -0
  549. package/src-tauri/src/commands/auth.rs +127 -0
  550. package/src-tauri/src/commands/conversation.rs +642 -0
  551. package/src-tauri/src/commands/integrations.rs +157 -0
  552. package/src-tauri/src/commands/media.rs +179 -0
  553. package/src-tauri/src/commands/mod.rs +11 -0
  554. package/src-tauri/src/commands/mods.rs +6 -0
  555. package/src-tauri/src/commands/settings.rs +141 -0
  556. package/src-tauri/src/commands/voice.rs +153 -0
  557. package/src-tauri/src/commands/workspace.rs +501 -0
  558. package/src-tauri/src/lib.rs +10 -0
  559. package/src-tauri/src/main.rs +3 -0
  560. package/src-tauri/src/native/actions.rs +206 -0
  561. package/src-tauri/src/native/browser.rs +252 -0
  562. package/src-tauri/src/native/capture.rs +695 -0
  563. package/src-tauri/src/native/discord_rpc.rs +99 -0
  564. package/src-tauri/src/native/integrations.rs +126 -0
  565. package/src-tauri/src/native/mod.rs +12 -0
  566. package/src-tauri/src/native/notifications.rs +51 -0
  567. package/src-tauri/src/native/plugins.rs +14 -0
  568. package/src-tauri/src/native/system.rs +250 -0
  569. package/src-tauri/src/native/terminal.rs +201 -0
  570. package/src-tauri/src/native/updater.rs +148 -0
  571. package/src-tauri/src/native/windows.rs +143 -0
  572. package/tsconfig.json +2 -0
  573. package/vite.config.ts +4 -1
  574. package/vite.config.web.ts +4 -0
  575. package/.cargo/config.toml +0 -13
  576. package/.claude/settings.local.json +0 -182
  577. package/.codex +0 -0
  578. package/CLAUDE.md +0 -102
  579. package/ROADMAP.md +0 -30
  580. package/mint-presentation-demo.mp4 +0 -0
  581. package/out/web/Live2DCubismCore.js +0 -9
  582. package/out/web/assets/CliSessionsView-BxUVWEEL.js +0 -1
  583. package/out/web/assets/CliSessionsView-CBDTXuQv.css +0 -1
  584. package/out/web/assets/CodeReviewPage-DkMgfcos.css +0 -1
  585. package/out/web/assets/CodeReviewPage-jKlmLJwT.js +0 -3
  586. package/out/web/assets/CommandPalette-DnwPA5hy.js +0 -1
  587. package/out/web/assets/ImageStudioPanel-BR1XFwhR.js +0 -1
  588. package/out/web/assets/LinkedFoldersView-bi-CWQ-I.js +0 -1
  589. package/out/web/assets/McpRegistryPicker-D5tEFnyq.js +0 -1
  590. package/out/web/assets/McpServersView-BGG1dhFM.js +0 -1
  591. package/out/web/assets/MintDashboard-BIglDEWX.js +0 -20
  592. package/out/web/assets/MintDashboard-DSMywSgs.css +0 -1
  593. package/out/web/assets/ModelPanel-CbJ1PFoZ.js +0 -1
  594. package/out/web/assets/PicturesLibrary-CQP0fLP1.css +0 -1
  595. package/out/web/assets/PicturesLibrary-DoYN7T1p.js +0 -1
  596. package/out/web/assets/PluginsView-CU1HWXGk.js +0 -1
  597. package/out/web/assets/ScheduledTasksView-Bnl36y2N.js +0 -1
  598. package/out/web/assets/SettingsWindow-DhWLAn9T.css +0 -1
  599. package/out/web/assets/SettingsWindow-Dw8L01pw.js +0 -2
  600. package/out/web/assets/SkillsView-cnl8aY5p.js +0 -2
  601. package/out/web/assets/ToolSurfacePage-CgsQw6l5.css +0 -1
  602. package/out/web/assets/ToolSurfacePage-Cz2VJAPZ.js +0 -37
  603. package/out/web/assets/VeoStudioPanel-D4JnERWR.js +0 -4
  604. package/out/web/assets/chunk-CKBBP62Z-cIrXq6jN.js +0 -2
  605. package/out/web/assets/classDiagram-DTDB5LWJ-Dq0smRWk.js +0 -1
  606. package/out/web/assets/classDiagram-v2-JRS7N3AN-Dq0smRWk.js +0 -1
  607. package/out/web/assets/config-C-ZFtipU.css +0 -1
  608. package/out/web/assets/config-eEDngJXO.js +0 -1
  609. package/out/web/assets/core-hQ2VA9Ti.js +0 -1
  610. package/out/web/assets/dist-js-Bf2bhoku.js +0 -1
  611. package/out/web/assets/flowDiagram-A5DVABFB-BLg7nEcd.js +0 -1
  612. package/out/web/assets/index-Btsf4L8M.css +0 -1
  613. package/out/web/assets/index-D8D9MeRA.js +0 -11
  614. package/out/web/assets/lucide-react-Ip3L6Jtn.js +0 -1
  615. package/out/web/assets/markdown-BSeeUbJH.js +0 -38
  616. package/out/web/assets/modelManager-tMVmJVAW.js +0 -2
  617. package/out/web/assets/prompt-latin-400-normal-BQ9zjSN8.woff2 +0 -0
  618. package/out/web/assets/prompt-latin-400-normal-CxU4ec_r.woff +0 -0
  619. package/out/web/assets/prompt-latin-500-normal-B1edBDQ9.woff +0 -0
  620. package/out/web/assets/prompt-latin-500-normal-CxzxEHZc.woff2 +0 -0
  621. package/out/web/assets/prompt-latin-600-normal-CSDDLorv.woff +0 -0
  622. package/out/web/assets/prompt-latin-600-normal-hKZWXsc1.woff2 +0 -0
  623. package/out/web/assets/prompt-latin-700-normal-BfbftDSq.woff +0 -0
  624. package/out/web/assets/prompt-latin-700-normal-I2gc831J.woff2 +0 -0
  625. package/out/web/assets/prompt-thai-400-normal-BrkKv8cO.woff2 +0 -0
  626. package/out/web/assets/prompt-thai-400-normal-DJypnfr3.woff +0 -0
  627. package/out/web/assets/prompt-thai-500-normal-C18pDUoL.woff2 +0 -0
  628. package/out/web/assets/prompt-thai-500-normal-D7YNqUJD.woff +0 -0
  629. package/out/web/assets/prompt-thai-600-normal-CYPViXds.woff +0 -0
  630. package/out/web/assets/prompt-thai-600-normal-MrdfU7zR.woff2 +0 -0
  631. package/out/web/assets/prompt-thai-700-normal-Cb5ilR-c.woff +0 -0
  632. package/out/web/assets/prompt-thai-700-normal-Cg4aQ0Nn.woff2 +0 -0
  633. package/out/web/assets/stateDiagram-v2-4QOOHH4V-DeEc0lGZ.js +0 -1
  634. package/out/web/assets/swimlanesDiagram-VK2B7HYN-DpVik9v7.js +0 -8
  635. package/out/web/assets/window-5qYXKoW7.js +0 -1
  636. package/skills-lock.json +0 -77
  637. package/src/renderer/public/Live2DCubismCore.js +0 -9
  638. package/src/renderer/src/companionWidget.ts +0 -95
  639. package/src/renderer/src/components/Live2DStage.tsx +0 -412
  640. package/src/renderer/src/components/ModelPanel.tsx +0 -155
  641. package/src/renderer/src/css/model.css +0 -628
  642. package/src/renderer/src-web/companionWidget.ts +0 -37
  643. package/src/renderer/src-web/components/ModelPanel.tsx +0 -12
  644. package/src/renderer/src-web/css/model.css +0 -628
@@ -1,21 +1,22 @@
1
- import { lazy, Suspense, type ChangeEvent, type CSSProperties, type FormEvent, useCallback, useEffect, useRef, useState } from 'react'
1
+ import { createWorkspacePreview } from '../utils/workspacePreview'
2
+ import { shouldNotify } from '../utils/notificationSettings'
3
+ import { deliverNotification, receiveProactiveNotification } from '../utils/notifications'
4
+ import '../css/notification-notice.css'
5
+ import { lazy, Suspense, type ChangeEvent, type CSSProperties, type FormEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
6
  import {
3
- compactAgentProgressForPersistence,
4
- mergeActivitySnapshots,
5
7
  mergeFileChanges,
6
8
  parseFileChangesFromProgress,
7
9
  } from '../agentProgress'
8
- import { catalogPlatform, conversationPlatform, mediaPlatform, runtimePlatform, type SlashResponse } from '../platform'
9
- import type { AgentProgress, ChatResponse, ChatSession, DocumentAttachment, PictureEntry, RuntimeStatus } from '../types'
10
+ import { catalogPlatform, conversationPlatform, mediaPlatform, runtimePlatform, workspacePlatform, type SlashResponse } from '../platform'
11
+ import type { ChatSession, DocumentAttachment, RuntimeStatus } from '../types'
10
12
 
11
13
  const {
12
14
  clearChatHistory, deleteChatSession, renameChatSession,
13
- getConversationSnapshot, getConversationChanges,
14
- saveSystemInteraction, listChatSessions, updateChatSessionWorkspace, saveInteractionAgentActivity, streamChatMessage,
15
- cancelChatMessage, submitToolApproval, listen, readClipboardImage,
15
+ saveSystemInteraction, updateChatSessionWorkspace,
16
+ listen, readClipboardImage,
16
17
  } = conversationPlatform
17
18
  const { getRuntimeStatus, setActiveModel, selectWorkspaceDirectory, selectLinkedFolderPath, isTauriRuntime } = runtimePlatform
18
- const { listSavedPictures, convertFileSrc } = mediaPlatform
19
+ const { convertFileSrc } = mediaPlatform
19
20
  const {
20
21
  listLearnedSkills, addLearnedSkill, deleteLearnedSkill, detectSystemTools, reauthMcpServer,
21
22
  listMcpServerTools, setProfileValue, listCronJobs, addCronJob, removeCronJob,
@@ -27,7 +28,7 @@ import ChatPanel, { type ConversationActions, type ConversationViewModel } from
27
28
  import ScreenCaptureDialog from './ScreenCaptureDialog'
28
29
  import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
29
30
  import DesktopTitlebar from './DesktopTitlebar'
30
- import type { ModelInteraction } from '@/components/ModelPanel'
31
+ import ErrorNotice from './ErrorNotice'
31
32
  import type { ToolSurface } from './ToolSurfacePage'
32
33
  import {
33
34
  errorMessage,
@@ -38,29 +39,17 @@ import {
38
39
  applyThemeStyles,
39
40
  parseUtcDate,
40
41
  } from '../utils/ui'
42
+ import { workspacePaths } from '../utils/workspaces'
41
43
  import { executeSlashCommand } from '../utils/slashCommandProcessor'
42
44
  import { captureScreenForChat } from '../utils/screenCapture'
43
45
  import { useConversationCoordinator } from '../conversation/useConversationCoordinator'
44
- import { matchesActiveRun, matchesActiveSession } from '../conversation/syncView'
46
+ import { isPersistedConversation } from '../conversation/projectNavigation'
47
+ import type { PromptOptions } from '../conversation/lifecycle'
45
48
 
46
49
 
47
- const EXPRESSIONS = [
48
- "Default",
49
- "Dumb Cat",
50
- "Dumb Cat Eye Roll",
51
- "Take Photo",
52
- "Poke",
53
- "Cat Filter",
54
- ]
55
50
 
56
- const ACCESSORIES = [
57
- "None",
58
- "Apron",
59
- "Glasses",
60
- "Hold Pen",
61
- ]
62
-
63
- import { DEFAULT_CONFIG, migrateTypographyScale } from '../constants/config'
51
+ import { DEFAULT_CONFIG, migrateFontFamily, migrateTypographyScale } from '../constants/config'
52
+ import { BackgroundTerminals } from './BackgroundTerminals'
64
53
 
65
54
  const LAST_WORKSPACE_PATH_KEY = 'mint:last-workspace-path'
66
55
  const RECENT_WORKSPACE_PATHS_KEY = 'mint:recent-workspace-paths'
@@ -238,9 +227,8 @@ const MOCK_WELCOME_INTERACTION = {
238
227
 
239
228
 
240
229
  import { isSupportedDocument } from '../utils/documentTypes'
241
- import { useCompanionWidget } from '@/companionWidget'
230
+ import { useProactiveSuggestions } from '@/proactiveSuggestions'
242
231
 
243
- const ModelPanel = lazy(() => import('@/components/ModelPanel'))
244
232
  const WorkspacePanel = lazy(() => import('@/components/WorkspacePanel'))
245
233
  const ToolSurfacePage = lazy(() => import('./ToolSurfacePage'))
246
234
  const SkillsView = lazy(() => import('./SkillsView'))
@@ -249,7 +237,7 @@ const LinkedFoldersView = lazy(() => import('./LinkedFoldersView'))
249
237
  const McpServersView = lazy(() => import('./McpServersView'))
250
238
  const PluginsView = lazy(() => import('./PluginsView'))
251
239
  const CliSessionsView = lazy(() => import('./CliSessionsView'))
252
- const PicturesLibrary = lazy(() => import('@/components/PicturesLibrary'))
240
+ const LibraryPage = lazy(() => import('./LibraryPage'))
253
241
  const ImageStudioPanel = lazy(() => import('./ImageStudioPanel'))
254
242
  const VeoStudioPanel = lazy(() => import('./VeoStudioPanel'))
255
243
  const CommandPalette = lazy(() => import('./CommandPalette').then(({ CommandPalette: Component }) => ({ default: Component })))
@@ -267,7 +255,7 @@ function getInitialViewFromUrl(): DashboardView {
267
255
  if (target.includes('skills')) return 'skills'
268
256
  if (target.includes('mcp')) return 'mcp'
269
257
  if (target.includes('plugins')) return 'plugins'
270
- if (target.includes('picture')) return 'pictures'
258
+ if (target.includes('picture') || target.includes('library')) return 'pictures'
271
259
  if (target.includes('image-studio') || target.includes('imagine')) return 'imagine'
272
260
  if (target.includes('veo-studio') || target.includes('veo')) return 'veo'
273
261
  if (target.includes('code') || target === '/cli' || target === 'cli' || target.includes('cli-sessions')) return 'code'
@@ -278,7 +266,7 @@ function getCleanPathForView(v: string, activeId?: string): string {
278
266
  if (v === 'skills') return '/skills'
279
267
  if (v === 'mcp') return '/mcp'
280
268
  if (v === 'plugins') return '/plugins'
281
- if (v === 'pictures') return '/pictures'
269
+ if (v === 'pictures') return '/library'
282
270
  if (v === 'imagine') return '/image-studio'
283
271
  if (v === 'veo' || v === 'veo_studio') return '/veo-studio'
284
272
  if (v === 'code') return '/code'
@@ -317,87 +305,56 @@ function describeApproval(payload: any): string {
317
305
  }
318
306
  }
319
307
 
320
- /** Fires the same notification mechanism as a finished AI reply, but only
321
- * while the window is unfocused — reused so both surfaces get identical
322
- * title/permission/dedupe behavior, just a different body string. */
323
- function notifyPendingApproval(payload: any) {
324
- if (document.hidden || !document.hasFocus()) {
325
- window.api?.notifyAiResponse?.(truncateForNotification(describeApproval(payload)))
326
- }
327
- }
328
-
329
308
  export default function MintDashboard() {
330
309
  const isDesktopApp = isTauriRuntime()
331
310
  const [view, setViewState] = useState<DashboardView>(getInitialViewFromUrl)
332
- const [conversationId, setConversationId] = useState(activeConversationId)
311
+ const [initialConversationId] = useState(activeConversationId)
333
312
  // Declared here (not further down with the rest of the workspace-related
334
313
  // state) so the URL-change effect below can read it.
335
314
  const { conversation, actions: conversationActions } = useConversationCoordinator(
336
315
  {
337
316
  workspacePath: window.localStorage.getItem(LAST_WORKSPACE_PATH_KEY) || '',
338
- message: readDraft(conversationId),
317
+ message: readDraft(initialConversationId),
339
318
  },
340
319
  {
341
- conversationId,
320
+ conversationId: initialConversationId,
342
321
  draftKeyPrefix: DRAFT_KEY_PREFIX,
343
322
  workspaceStorageKey: LAST_WORKSPACE_PATH_KEY,
323
+ historyVisible: view === 'chat',
324
+ onError: reason => setError(errorMessage(reason)),
325
+ onApproval: (payload, chatId) => notifyPendingApproval(payload, chatId),
326
+ onReply: (response, chatId) => {
327
+ if (document.hidden || !document.hasFocus()) return sendNotification(response.text || 'Mint finished your request.', chatId)
328
+ },
329
+ onActivityPersisted: id => {
330
+ const key = String(id)
331
+ setThinkingExpanded(current => ({ ...current,
332
+ ...(liveThinkingOpenRef.current ? { [key]: true } : {}),
333
+ ...(liveExtendedThinkingOpenRef.current ? { [`extended-${key}`]: true } : {}),
334
+ }))
335
+ },
336
+ onRunCompleted: async () => {
337
+ getRuntimeStatus().then(setStatus).catch(() => {})
338
+ },
339
+ onVisibleRunCompleted: chatId => {
340
+ const pathname = (window.location.pathname || '').replace(/\/+$/, '')
341
+ const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
342
+ const target = (pathname || hash).toLowerCase()
343
+ if (!target || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html') {
344
+ window.history.replaceState({}, '', `/chat/${encodeURIComponent(chatId)}`)
345
+ }
346
+ },
344
347
  },
345
348
  )
346
349
  const {
347
- workspacePath, message, imageAttachments, videoAttachments, documentAttachment,
350
+ conversationId, workspacePath, message, imageAttachments, videoAttachments, documentAttachment,
348
351
  sending, sendingMessage, sendingImageCount, sendingVideoCount, streamedReply,
349
- streamedResponse, agentProgress,
352
+ streamedResponse, agentProgress, interactions, hasOlderInteractions,
353
+ streamingConversationId, activeTurnId, agentActivitySnapshots, pendingApprovals,
354
+ sessionAutoApproved, chatSessions,
350
355
  } = conversation
351
- const activeConversationRef = useRef(conversationId)
352
- const sessionGenerationRef = useRef(0)
353
- const runGenerationRef = useRef(0)
354
- const conversationCursorRef = useRef<{ chatId: string; cursor: number } | null>(null)
355
- const historyLoadSerialRef = useRef(0)
356
- const [pendingApprovals, setPendingApprovals] = useState<Record<string, { token: string; approval: any }>>({})
357
- const autoApprovedChatIdRef = useRef<string | null>(null)
358
- const isCurrentSession = (chatId: string, generation: number) => matchesActiveSession(
359
- { chatId, generation },
360
- { chatId: activeConversationRef.current, generation: sessionGenerationRef.current },
361
- )
362
356
  const activateConversation = (id: string) => {
363
- if (activeConversationRef.current !== id) {
364
- activeConversationRef.current = id
365
- sessionGenerationRef.current += 1
366
- runGenerationRef.current += 1
367
- conversationCursorRef.current = null
368
- setInteractions([])
369
- setHasOlderInteractions(false)
370
- setAgentActivitySnapshots({})
371
- setStreamingConversationId(null)
372
- setActiveTurnId(null)
373
- }
374
- setConversationId(id)
375
- }
376
- async function loadConversationHistory(chatId: string, generation: number) {
377
- const serial = ++historyLoadSerialRef.current
378
- const snapshot = await getConversationSnapshot(chatId, null, 50)
379
- if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return
380
- const currentCursor = conversationCursorRef.current
381
- if (currentCursor?.chatId === chatId && currentCursor.cursor > snapshot.cursor) return
382
- conversationCursorRef.current = { chatId, cursor: snapshot.cursor }
383
- setInteractions((current) => {
384
- if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return current
385
- if (conversationCursorRef.current?.chatId === chatId
386
- && conversationCursorRef.current.cursor > snapshot.cursor) return current
387
- return snapshot.interactions
388
- })
389
- setHasOlderInteractions((current) => {
390
- if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return current
391
- if (conversationCursorRef.current?.chatId === chatId
392
- && conversationCursorRef.current.cursor > snapshot.cursor) return current
393
- return snapshot.hasOlder
394
- })
395
- setAgentActivitySnapshots((current) => {
396
- if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return current
397
- if (conversationCursorRef.current?.chatId === chatId
398
- && conversationCursorRef.current.cursor > snapshot.cursor) return current
399
- return mergeActivitySnapshots(current, snapshot.interactions)
400
- })
357
+ conversationActions.activate(id)
401
358
  }
402
359
  // Web only in practice (desktop's window has no mobile-width breakpoint,
403
360
  // so nothing ever sets this true there) — declared unconditionally so
@@ -416,19 +373,26 @@ export default function MintDashboard() {
416
373
  const [workspaceRefreshRevision, setWorkspaceRefreshRevision] = useState(0)
417
374
  const [recentWorkspacePaths, setRecentWorkspacePaths] = useState<string[]>(readRecentWorkspacePaths)
418
375
 
419
- useEffect(() => {
420
- const currentPath = workspacePath.trim()
421
- if (!currentPath) return
422
- const recent = [currentPath, ...readRecentWorkspacePaths().filter((path) => path !== currentPath)]
423
- .slice(0, MAX_RECENT_WORKSPACES)
376
+ function rememberWorkspace(path: string) {
377
+ const recent = workspacePaths([path, ...readRecentWorkspacePaths()]).slice(0, MAX_RECENT_WORKSPACES)
424
378
  try {
425
379
  window.localStorage.setItem(RECENT_WORKSPACE_PATHS_KEY, JSON.stringify(recent))
426
380
  } catch {
427
381
  /* Keep the in-memory recent list usable when storage is unavailable. */
428
382
  }
429
383
  setRecentWorkspacePaths(recent)
384
+ }
385
+
386
+ useEffect(() => {
387
+ if (workspacePath.trim()) rememberWorkspace(workspacePath)
430
388
  }, [workspacePath])
431
389
 
390
+ async function selectCodeHubFolder() {
391
+ const selected = await selectLinkedFolderPath()
392
+ if (selected) rememberWorkspace(selected)
393
+ return selected
394
+ }
395
+
432
396
  const changeView = (newView: any, targetConversationId?: string) => {
433
397
  setMobileSidebarOpen(false)
434
398
  setViewState((prev) => {
@@ -443,7 +407,10 @@ export default function MintDashboard() {
443
407
  }
444
408
  return prev
445
409
  }
446
- const mappedView: DashboardView = (next === 'veo_studio' ? 'veo' : next) as DashboardView
410
+ const requestedView: DashboardView = (next === 'veo_studio' ? 'veo' : next) as DashboardView
411
+ // The Web app can select a project for chat context, but its local file
412
+ // browser is Desktop-only. Keep any Workspace navigation on Web in chat.
413
+ const mappedView: DashboardView = !isDesktopApp && requestedView === 'workspace' ? 'chat' : requestedView
447
414
  const targetPath = getCleanPathForView(mappedView, targetConversationId || conversationId)
448
415
  if (typeof window !== 'undefined' && window.location.pathname !== targetPath) {
449
416
  window.history.pushState({}, '', targetPath)
@@ -467,16 +434,12 @@ export default function MintDashboard() {
467
434
  if (urlSessionId && urlSessionId !== conversationId) {
468
435
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, urlSessionId)
469
436
  activateConversation(urlSessionId)
470
- const generation = sessionGenerationRef.current
471
- loadConversationHistory(urlSessionId, generation).catch((error) => {
437
+ conversationActions.refreshHistory().catch((error) => {
472
438
  console.error('Failed to load conversation history:', error)
473
439
  })
474
440
  } else if (!urlSessionId && isRootOrNewChatRoute()) {
475
441
  const next = createConversationId()
476
442
  activateConversation(next)
477
- setInteractions([])
478
- setAgentActivitySnapshots({})
479
- conversationActions.switchSession()
480
443
  }
481
444
  }
482
445
  window.addEventListener('popstate', handleUrlChange)
@@ -501,24 +464,62 @@ export default function MintDashboard() {
501
464
  }, [])
502
465
  const [status, setStatus] = useState<RuntimeStatus | null>(null)
503
466
  const [error, setError] = useState('')
504
- // When `conversationId` changes we swap `message` to that chat's saved draft;
505
- // this ref tells the persist effect to skip the render right after that swap,
506
- // so the outgoing chat's text is never written under the incoming chat's key.
507
- const [interactions, setInteractions] = useState<any[]>([])
508
- const [hasOlderInteractions, setHasOlderInteractions] = useState(false)
509
- const [pictures, setPictures] = useState<PictureEntry[]>([])
510
- const [streamingConversationId, setStreamingConversationId] = useState<string | null>(null)
511
- const [activeTurnId, setActiveTurnId] = useState<number | null>(null)
512
- const [agentActivitySnapshots, setAgentActivitySnapshots] = useState<Record<string, AgentProgress[]>>({})
467
+ const [notificationNotice, setNotificationNotice] = useState<{ message: string; chatId?: string; type?: string } | null>(null)
468
+ const notificationNavigationRef = useRef(selectConversation)
469
+ notificationNavigationRef.current = selectConversation
470
+ const sendNotification = (body: string, chatId?: string, kind: 'replies' | 'approvals' | 'system' = 'replies') => deliverNotification(
471
+ () => shouldNotify(notificationConfigRef.current?.notificationSettings, kind, 'os')
472
+ ? window.api?.notifyAiResponse?.(truncateForNotification(body), chatId) : undefined,
473
+ setError,
474
+ )
475
+ const notifyPendingApproval = (payload: any, chatId?: string) => {
476
+ if (document.hidden || !document.hasFocus()) {
477
+ void sendNotification(describeApproval(payload), chatId ?? payload.chatId, 'approvals')
478
+ }
479
+ }
480
+ useEffect(() => {
481
+ let disposed = false
482
+ const cleanups: Array<() => void> = []
483
+ const register = (subscription: Promise<any> | undefined) => {
484
+ subscription?.then(unlisten => {
485
+ if (typeof unlisten !== 'function') return
486
+ if (disposed) unlisten()
487
+ else cleanups.push(unlisten)
488
+ }).catch(reason => { if (!disposed) setError(errorMessage(reason)) })
489
+ }
490
+ const openChat = (chatId?: string) => {
491
+ if (chatId) void notificationNavigationRef.current(chatId).catch(reason => setError(errorMessage(reason)))
492
+ }
493
+ const clicked = (event: Event) => openChat((event as CustomEvent).detail?.chatId)
494
+ window.addEventListener('mint:notification-click', clicked)
495
+ register(listen('notification-clicked', (event: { payload: { chatId?: string } }) => openChat(event.payload?.chatId)))
496
+ register(Promise.resolve(window.api?.onProactiveNotification?.((payload: any) => {
497
+ if (disposed) return
498
+ void receiveProactiveNotification(payload, document.hidden || !document.hasFocus(),
499
+ notice => { if (shouldNotify(notificationConfigRef.current?.notificationSettings, 'system', 'inApp')) setNotificationNotice(notice) },
500
+ (body, chatId) => sendNotification(body, chatId, 'system'))
501
+ .catch(reason => setError(errorMessage(reason)))
502
+ })))
503
+ const visible = () => { if (!document.hidden) window.api?.clearAiNotifications?.() }
504
+ document.addEventListener('visibilitychange', visible)
505
+ return () => {
506
+ disposed = true
507
+ cleanups.forEach(unlisten => unlisten())
508
+ window.removeEventListener('mint:notification-click', clicked)
509
+ document.removeEventListener('visibilitychange', visible)
510
+ }
511
+ }, [])
512
+
513
513
  const [thinkingExpanded, setThinkingExpanded] = useState<Record<string, boolean>>({})
514
514
  const liveThinkingOpenRef = useRef(true)
515
515
  const liveExtendedThinkingOpenRef = useRef(true)
516
- const [sessionAutoApproved, setSessionAutoApproved] = useState(false)
517
- const sessionAutoApprovedRef = useRef(false)
518
516
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => window.localStorage.getItem('mint:sidebar-collapsed') === 'true')
519
517
  const [toolSurfaces, setToolSurfaces] = useState<ToolSurface[]>([])
520
518
  const [activeSurfaceId, setActiveSurfaceId] = useState<string | null>(null)
521
519
  const [toolsPanelOpen, setToolsPanelOpen] = useState(false)
520
+ const [modsPanelTarget, setModsPanelTarget] = useState<HTMLDivElement | null>(null)
521
+ const [backgroundPanelTarget, setBackgroundPanelTarget] = useState<HTMLDivElement | null>(null)
522
+ const [backgroundTaskCount, setBackgroundTaskCount] = useState(0)
522
523
  const [terminalRightWidth, setTerminalRightWidth] = useState(() => {
523
524
  const saved = Number(window.localStorage.getItem('mint:terminal-right-width'))
524
525
  return saved >= 280 && saved <= 900 ? saved : 440
@@ -536,11 +537,6 @@ export default function MintDashboard() {
536
537
  }),
537
538
  parseFileChangesFromProgress(agentProgress),
538
539
  ])
539
- const workspaceSourceNames = [
540
- ...imageAttachments.map((attachment) => attachment.name),
541
- ...videoAttachments.map((attachment) => attachment.name),
542
- documentAttachment?.filename || '',
543
- ]
544
540
 
545
541
  const [agentMode, setAgentMode] = useState(() => window.localStorage.getItem('mint:agent-mode') === 'true')
546
542
  const [planMode, setPlanMode] = useState(() => window.localStorage.getItem('mint:plan-mode') === 'true')
@@ -548,33 +544,19 @@ export default function MintDashboard() {
548
544
  const [dashboardDataReady, setDashboardDataReady] = useState(false)
549
545
  const [startupTimedOut, setStartupTimedOut] = useState(false)
550
546
  const [settingsConfig, setSettingsConfig] = useState<any>(null)
551
- const [chatSessions, setChatSessions] = useState<ChatSession[]>([])
547
+ const notificationConfigRef = useRef(settingsConfig)
548
+ notificationConfigRef.current = settingsConfig
549
+ useEffect(() => {
550
+ if (!shouldNotify(settingsConfig?.notificationSettings, 'system', 'inApp')) setNotificationNotice(null)
551
+ if (settingsConfig?.notificationSettings?.enabled === false || settingsConfig?.notificationSettings?.os === false) window.api?.clearAiNotifications?.()
552
+ }, [settingsConfig])
553
+ const availableWorkspacePaths = useMemo(() => workspacePaths(
554
+ recentWorkspacePaths, workspacePath, chatSessions.map(session => session.workspacePath),
555
+ ), [recentWorkspacePaths, workspacePath, chatSessions])
552
556
  const chatEnd = useRef<HTMLDivElement | null>(null)
553
557
  const lastNativePasteTimeRef = useRef(0)
554
- const {
555
- modelVisible,
556
- scale,
557
- interactionEnabled,
558
- showInteractionGuide,
559
- isLocked,
560
- layoutPreset,
561
- expressionIndex,
562
- accessoryIndex,
563
- modelReady,
564
- proactiveSuggestion,
565
- setScale,
566
- setIsLocked,
567
- setExpressionIndex,
568
- setAccessoryIndex,
569
- setModelReady,
570
- toggleModel,
571
- changeLayoutPreset,
572
- updateInteractionEnabled,
573
- updateInteractionGuide,
574
- dismissProactiveSuggestion,
575
- handleProactiveAction,
576
- } = useCompanionWidget((message) => setError(message))
577
- const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
558
+ const { proactiveSuggestion, dismissProactiveSuggestion, handleProactiveAction } = useProactiveSuggestions((message) => setError(message))
559
+ const startupReady = dashboardDataReady || startupTimedOut
578
560
 
579
561
  const [mcpName, setMcpName] = useState('')
580
562
  const [mcpCmd, setMcpCmd] = useState('')
@@ -685,118 +667,6 @@ export default function MintDashboard() {
685
667
  }
686
668
  }, [isSearchOpen])
687
669
 
688
- // Another process — the CLI's interactive chat, a scheduled task, a
689
- // messaging bridge — can add messages to this same conversation while
690
- // this page is open, and nothing here would otherwise notice: Mint's
691
- // surfaces only share a SQLite file, not a live event bus, so this polls
692
- // for changes instead of requiring a manual refresh. Kept in a ref rather
693
- // than the effect's own dependency array so a poll landing doesn't tear
694
- // down and recreate the interval on every tick.
695
- const interactionsRef = useRef(interactions)
696
- const chatPollInFlightRef = useRef<{ chatId: string; generation: number } | null>(null)
697
- useEffect(() => {
698
- interactionsRef.current = interactions
699
- }, [interactions])
700
- useEffect(() => {
701
- if (interactions.length === 0) setHasOlderInteractions(false)
702
- }, [interactions.length])
703
-
704
- // Load the saved draft whenever the active conversation changes. Declared
705
- // before the persist effect so it runs first within the same commit. The
706
- // first run is a no-op: `message`'s initial value already came from the
707
- // lazy `useState` initializer above.
708
- useEffect(() => {
709
- if (view !== 'chat' || !conversationId) return
710
- let disposed = false
711
- const generation = sessionGenerationRef.current
712
- const isCurrent = () => !disposed && isCurrentSession(conversationId, generation)
713
- const syncHistory = async () => {
714
- // A shared SQLite database has no cross-process event bus. Refresh as
715
- // soon as the user returns, but avoid work while hidden or overlapping
716
- // a slow read with the next polling tick.
717
- if (
718
- !isCurrent() ||
719
- document.visibilityState !== 'visible' ||
720
- (chatPollInFlightRef.current?.chatId === conversationId
721
- && chatPollInFlightRef.current.generation === generation)
722
- ) return
723
-
724
- const poll = { chatId: conversationId, generation }
725
- chatPollInFlightRef.current = poll
726
- try {
727
- if (conversationCursorRef.current?.chatId !== conversationId) {
728
- await loadConversationHistory(conversationId, generation)
729
- return
730
- }
731
- let cursor = conversationCursorRef.current.cursor
732
- let changed = false
733
- for (let page = 0; page < 10; page++) {
734
- const batch = await getConversationChanges(conversationId, cursor, 100)
735
- if (!isCurrent()) return
736
- if (conversationCursorRef.current?.chatId === conversationId
737
- && conversationCursorRef.current.cursor > batch.cursor) return
738
- cursor = batch.cursor
739
- if (batch.changes.length > 0) {
740
- changed = true
741
- setInteractions((current) => {
742
- if (!isCurrent()) return current
743
- if (conversationCursorRef.current?.chatId === conversationId
744
- && conversationCursorRef.current.cursor > batch.cursor) return current
745
- const byId = new Map(current.map((interaction) => [interaction.id, interaction]))
746
- for (const change of batch.changes) {
747
- if (change.interaction) byId.set(change.interactionId, change.interaction)
748
- else byId.delete(change.interactionId)
749
- }
750
- return Array.from(byId.values()).sort((a, b) => a.id - b.id)
751
- })
752
- setAgentActivitySnapshots((current) => {
753
- if (!isCurrent()) return current
754
- if (conversationCursorRef.current?.chatId === conversationId
755
- && conversationCursorRef.current.cursor > batch.cursor) return current
756
- const merged = { ...current }
757
- for (const change of batch.changes) {
758
- const key = String(change.interactionId)
759
- if (change.interaction) {
760
- if (change.interaction.agentActivity && Array.isArray(change.interaction.agentActivity)) {
761
- merged[key] = change.interaction.agentActivity
762
- }
763
- } else {
764
- delete merged[key]
765
- }
766
- }
767
- return merged
768
- })
769
- }
770
- if (!batch.hasMore) break
771
- }
772
- if (conversationCursorRef.current?.chatId === conversationId
773
- && conversationCursorRef.current.cursor > cursor) return
774
- conversationCursorRef.current = { chatId: conversationId, cursor }
775
- if (changed) {
776
- const sessions = await listChatSessions()
777
- if (isCurrent()) setChatSessions(sessions)
778
- }
779
- } catch {
780
- // Best-effort — a transient fetch failure just waits for the next tick.
781
- } finally {
782
- if (chatPollInFlightRef.current === poll) chatPollInFlightRef.current = null
783
- }
784
- }
785
-
786
- const syncWhenActive = () => { void syncHistory() }
787
- syncWhenActive()
788
- window.addEventListener('focus', syncWhenActive)
789
- document.addEventListener('visibilitychange', syncWhenActive)
790
- const interval = window.setInterval(syncWhenActive, 1500)
791
-
792
- return () => {
793
- disposed = true
794
- window.clearInterval(interval)
795
- window.removeEventListener('focus', syncWhenActive)
796
- document.removeEventListener('visibilitychange', syncWhenActive)
797
- }
798
- }, [view, conversationId, workspacePath])
799
-
800
670
  const filteredSessions = chatSessions.filter((session) => {
801
671
  if (session.kind === 'cli' || session.id === 'conversation-default') return false
802
672
  return session.title.toLowerCase().includes(searchQuery.toLowerCase())
@@ -848,71 +718,31 @@ export default function MintDashboard() {
848
718
  const groupedSearchSessions = groupSessionsByDate(filteredSessions)
849
719
 
850
720
 
851
- async function refreshHistory() {
852
- const chatId = conversationId
853
- const generation = sessionGenerationRef.current
854
- await loadConversationHistory(chatId, generation)
855
- }
856
-
721
+ const refreshHistory = conversationActions.refreshHistory
857
722
  const refreshChatSessions = useCallback(async () => {
858
- try {
859
- const sessions = await listChatSessions()
860
- setChatSessions((prev) => {
861
- if (
862
- prev.length === sessions.length &&
863
- prev.every(
864
- (s, i) =>
865
- s.id === sessions[i]?.id &&
866
- s.updatedAt === sessions[i]?.updatedAt &&
867
- s.title === sessions[i]?.title &&
868
- s.messageCount === sessions[i]?.messageCount &&
869
- s.totalBytes === sessions[i]?.totalBytes
870
- )
871
- ) {
872
- return prev
873
- }
874
- return sessions
875
- })
876
- } catch (e) {
877
- console.error('Failed to refresh chat sessions:', e)
878
- }
879
- }, [])
880
-
881
- useEffect(() => {
882
- const refreshVisible = () => {
883
- if (document.visibilityState === 'visible') void refreshChatSessions()
884
- }
885
- const interval = window.setInterval(refreshVisible, 5000)
886
- window.addEventListener('focus', refreshVisible)
887
- document.addEventListener('visibilitychange', refreshVisible)
888
- return () => {
889
- window.clearInterval(interval)
890
- window.removeEventListener('focus', refreshVisible)
891
- document.removeEventListener('visibilitychange', refreshVisible)
892
- }
893
- }, [refreshChatSessions])
894
-
895
- const loadOlderInteractions = useCallback(async () => {
896
- const generation = sessionGenerationRef.current
897
- const firstId = interactionsRef.current[0]?.id
898
- if (firstId == null) return
899
- const page = await getConversationSnapshot(conversationId, firstId, 50)
900
- if (!isCurrentSession(conversationId, generation)) return
901
- setHasOlderInteractions(page.hasOlder)
902
- if (page.interactions.length === 0) return
903
- setInteractions((current) => {
904
- if (!isCurrentSession(conversationId, generation)) return current
905
- const byId = new Map([...page.interactions, ...current].map((interaction) => [interaction.id, interaction]))
906
- return Array.from(byId.values()).sort((a, b) => a.id - b.id)
907
- })
908
- }, [conversationId])
723
+ try { await conversationActions.refreshSessions() }
724
+ catch (error) { console.error('Failed to refresh chat sessions:', error) }
725
+ }, [conversationActions])
726
+ const loadOlderInteractions = conversationActions.loadOlder
909
727
 
728
+ const workspaceUpdateQueues = useRef(new Map<string, Promise<void>>())
910
729
  const handleUpdateSessionWorkspace = useCallback(async (sessionId: string, targetPath: string | null) => {
911
- try {
730
+ // Preserve selection order if the user switches folders quickly.
731
+ const previous = workspaceUpdateQueues.current.get(sessionId) ?? Promise.resolve()
732
+ const update = previous.catch(() => {}).then(async () => {
912
733
  await updateChatSessionWorkspace(sessionId, targetPath)
913
734
  await refreshChatSessions()
735
+ })
736
+ workspaceUpdateQueues.current.set(sessionId, update)
737
+ try {
738
+ await update
914
739
  } catch (e) {
915
740
  console.error('Failed to update session workspace:', e)
741
+ setError(errorMessage(e))
742
+ } finally {
743
+ if (workspaceUpdateQueues.current.get(sessionId) === update) {
744
+ workspaceUpdateQueues.current.delete(sessionId)
745
+ }
916
746
  }
917
747
  }, [refreshChatSessions])
918
748
 
@@ -925,17 +755,6 @@ export default function MintDashboard() {
925
755
  }
926
756
  }, [])
927
757
 
928
- const [picturesRefreshing, setPicturesRefreshing] = useState(false)
929
-
930
- async function refreshPictures() {
931
- setPicturesRefreshing(true)
932
- try {
933
- setPictures(await listSavedPictures())
934
- } finally {
935
- setPicturesRefreshing(false)
936
- }
937
- }
938
-
939
758
  useEffect(() => {
940
759
  Promise.allSettled([
941
760
  getRuntimeStatus().then(setStatus),
@@ -943,7 +762,7 @@ export default function MintDashboard() {
943
762
  refreshChatSessions(),
944
763
  window.settingsApi?.getSettings()
945
764
  .then((loaded: any) => {
946
- const migrated = migrateTypographyScale(loaded)
765
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
947
766
  setSettingsConfig(migrated)
948
767
  applyThemeStyles({ ...DEFAULT_CONFIG, ...migrated })
949
768
  }),
@@ -956,18 +775,11 @@ export default function MintDashboard() {
956
775
  changeView('chat')
957
776
  conversationActions.compose(query)
958
777
  })
959
- const unlistenVision = window.api.onVisionReady((image) => {
960
- createTrimmedImagePreview(image)
961
- .catch(() => image)
962
- .then((previewDataUri) => {
963
- conversationActions.attachImage({ dataUri: image, previewDataUri, name: 'Screen capture' })
964
- })
965
- })
966
778
  const handleWindowFocus = () => {
967
779
  getRuntimeStatus().then(setStatus).catch(() => {})
968
780
  window.settingsApi?.getSettings?.().then((loaded: any) => {
969
781
  if (loaded) {
970
- const migrated = migrateTypographyScale(loaded)
782
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
971
783
  setSettingsConfig(migrated)
972
784
  applyThemeStyles(migrated)
973
785
  }
@@ -977,7 +789,7 @@ export default function MintDashboard() {
977
789
  window.addEventListener('focus', handleWindowFocus)
978
790
 
979
791
  window.api?.onSettingsChanged?.((loaded: any) => {
980
- const migrated = migrateTypographyScale(loaded)
792
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
981
793
  setSettingsConfig(migrated)
982
794
  applyThemeStyles(migrated)
983
795
  getRuntimeStatus().then(setStatus).catch(() => {})
@@ -985,21 +797,12 @@ export default function MintDashboard() {
985
797
 
986
798
  const unlistenPromise = listen('tool-approval-requested', (event: { payload: any }) => {
987
799
  const chatId = event.payload.chatId as string | undefined
988
- if (!chatId) return
989
- if (sessionAutoApprovedRef.current && autoApprovedChatIdRef.current === chatId) {
990
- submitToolApproval(event.payload.token, true).catch((err) => {
991
- console.error("Auto approval failed:", err)
992
- })
993
- } else {
994
- setPendingApprovals((current) => ({ ...current, [chatId]: event.payload }))
995
- notifyPendingApproval(event.payload)
996
- }
800
+ if (chatId) conversationActions.receiveApproval(chatId, event.payload)
997
801
  })
998
802
  return () => {
999
803
  window.removeEventListener('focus', handleWindowFocus)
1000
804
  unlistenPromise?.then?.((unlisten) => unlisten?.())
1001
805
  unlistenSpotlight?.then?.((unlisten) => unlisten?.())
1002
- unlistenVision?.then?.((unlisten) => unlisten?.())
1003
806
  }
1004
807
  }, [])
1005
808
 
@@ -1008,10 +811,6 @@ export default function MintDashboard() {
1008
811
  return () => window.clearTimeout(timer)
1009
812
  }, [])
1010
813
 
1011
- useEffect(() => {
1012
- if (view === 'pictures' || view === 'imagine') refreshPictures().catch((reason: unknown) => setError(errorMessage(reason)))
1013
- }, [view])
1014
-
1015
814
  useEffect(() => {
1016
815
  if (view === 'workspace' && !agentMode) updateAgentMode(true)
1017
816
  }, [view, agentMode])
@@ -1021,7 +820,7 @@ export default function MintDashboard() {
1021
820
  setTimeout(() => setToastMessage((current) => current === nextMessage ? '' : current), 3000)
1022
821
  }
1023
822
 
1024
- const openMintBrowser = async (url: string) => {
823
+ const openMintBrowser = async (url: string, throwOnError = false) => {
1025
824
  if (!isDesktopApp) {
1026
825
  window.open(url, '_blank', 'noopener,noreferrer')
1027
826
  return
@@ -1031,10 +830,35 @@ export default function MintDashboard() {
1031
830
  const { invoke } = await import('@tauri-apps/api/core')
1032
831
  await invoke('open_mint_browser', { url })
1033
832
  } catch (error) {
833
+ if (throwOnError) throw error
1034
834
  showToast(`Could not open browser: ${String(error)}`)
1035
835
  }
1036
836
  }
1037
837
 
838
+ const openHtmlFile = async (root: string, relativePath: string, mode: 'external' | 'mint') => {
839
+ if (!isDesktopApp && !['localhost', '127.0.0.1', '[::1]', '::1'].includes(window.location.hostname)) {
840
+ throw new Error('HTML preview requires a browser on the same machine as the Mint backend.')
841
+ }
842
+ const popup = !isDesktopApp && mode === 'external' ? window.open('about:blank', '_blank') : null
843
+ if (popup) popup.opener = null
844
+ try {
845
+ const preview = await workspacePlatform.startHtmlPreview(root, relativePath, !isDesktopApp && mode === 'mint')
846
+ if (mode === 'mint') {
847
+ if (isDesktopApp) await openMintBrowser(preview.url, true)
848
+ } else if (isDesktopApp) {
849
+ const { invoke } = await import('@tauri-apps/api/core')
850
+ await invoke('run_desktop_action', { action: { type: 'open_url', target: preview.url } })
851
+ } else {
852
+ if (!popup) throw new Error('Allow browser popups, then open the HTML file again.')
853
+ popup.location.replace(preview.url)
854
+ }
855
+ showToast('HTML preview is running. Open Tools → Background tasks to view or stop the server.')
856
+ } catch (error) {
857
+ popup?.close()
858
+ throw error
859
+ }
860
+ }
861
+
1038
862
  const openTerminalSurface = () => {
1039
863
  setToolsPanelOpen(true)
1040
864
  const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
@@ -1053,6 +877,17 @@ export default function MintDashboard() {
1053
877
  })
1054
878
  }
1055
879
 
880
+ const openUtilitySurface = (kind: 'mods' | 'background') => {
881
+ setToolsPanelOpen(true)
882
+ setToolSurfaces(current => current.some(surface => surface.id === kind) ? current : [...current, { id: kind, kind, title: kind === 'mods' ? 'Mods' : 'Background tasks' }])
883
+ setActiveSurfaceId(kind)
884
+ }
885
+
886
+ const closeToolsPanel = () => {
887
+ setToolsPanelOpen(false)
888
+ requestAnimationFrame(() => document.querySelector<HTMLButtonElement>('.tools-panel-toggle')?.focus())
889
+ }
890
+
1056
891
  const openNativeBrowser = (url = 'https://www.google.com') => {
1057
892
  void openMintBrowser(url)
1058
893
  }
@@ -1061,6 +896,7 @@ export default function MintDashboard() {
1061
896
  setToolSurfaces((current) => {
1062
897
  const source = current.find((surface) => surface.id === id)
1063
898
  if (!source) return current
899
+ if (source.kind === 'mods' || source.kind === 'background') return current
1064
900
  const duplicateId = `${source.kind}:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
1065
901
  const duplicate = { ...source, id: duplicateId, title: `${source.title} copy` } as ToolSurface
1066
902
  setActiveSurfaceId(duplicateId)
@@ -1106,7 +942,8 @@ export default function MintDashboard() {
1106
942
  const openArtifactSurface = (artifact: import('./ArtifactPreviewPanel').ArtifactFile) => {
1107
943
  setToolsPanelOpen(true)
1108
944
  const fileName = artifact.path.replace(/\\/g, '/').split('/').pop() || artifact.path
1109
- const id = `preview:${artifact.path}`
945
+ artifact = { ...artifact, workspacePath: artifact.workspacePath ?? workspacePath }
946
+ const id = `preview:${JSON.stringify([artifact.workspacePath, artifact.path])}`
1110
947
  setToolSurfaces((current) => {
1111
948
  const next = current.filter((surface) => surface.id !== id)
1112
949
  return [...next, { id, kind: 'preview', title: fileName, artifact }]
@@ -1114,6 +951,14 @@ export default function MintDashboard() {
1114
951
  setActiveSurfaceId(id)
1115
952
  }
1116
953
 
954
+ const openWorkspacePreview = async (root: string, path: string) => {
955
+ const artifact = await createWorkspacePreview(root, path, {
956
+ readWorkspaceFile: (file, workspace) => runtimePlatform.readWorkspaceFile(file, workspace),
957
+ startHtmlPreview: (workspace, file) => workspacePlatform.startHtmlPreview(workspace, file),
958
+ })
959
+ openArtifactSurface(artifact)
960
+ }
961
+
1117
962
  const openReviewSurface = (review: Extract<ToolSurface, { kind: 'review' }>) => {
1118
963
  setToolsPanelOpen(true)
1119
964
  setToolSurfaces((current) => [...current.filter((surface) => surface.id !== review.id), review])
@@ -1194,217 +1039,25 @@ export default function MintDashboard() {
1194
1039
  setPlanMode(enabled)
1195
1040
  }
1196
1041
 
1197
- const updateWorkspacePath = (path: string) => {
1042
+ const updateWorkspacePath = (path: string, persist = true) => {
1198
1043
  const next = path.trim()
1199
1044
  conversationActions.selectWorkspace(next)
1200
- }
1201
-
1202
- useEffect(() => {
1203
- if (!sending) {
1204
- sessionAutoApprovedRef.current = false
1205
- autoApprovedChatIdRef.current = null
1206
- setSessionAutoApproved(false)
1207
- }
1208
- }, [sending])
1209
-
1210
- async function handleApproval(approved: boolean, autoApproveSession = false, answer?: string) {
1211
- const chatId = conversationId
1212
- const pendingApproval = pendingApprovals[chatId]
1213
- if (!pendingApproval) return
1214
- try {
1215
- if (autoApproveSession) {
1216
- sessionAutoApprovedRef.current = true
1217
- autoApprovedChatIdRef.current = chatId
1218
- setSessionAutoApproved(true)
1219
- }
1220
- await submitToolApproval(pendingApproval.token, approved, answer)
1221
- setPendingApprovals((current) => {
1222
- if (current[chatId]?.token !== pendingApproval.token) return current
1223
- const next = { ...current }
1224
- delete next[chatId]
1225
- return next
1226
- })
1227
- } catch (reason) {
1228
- setError(errorMessage(reason))
1045
+ const activeId = conversationActions.getSnapshot().conversationId
1046
+ if (persist && isPersistedConversation(activeId, chatSessions)) {
1047
+ void handleUpdateSessionWorkspace(activeId, next || null)
1229
1048
  }
1230
1049
  }
1231
1050
 
1232
- async function handleCancelMessage() {
1233
- if (!sending || !streamingConversationId) return
1234
- runGenerationRef.current += 1
1235
- try {
1236
- await conversationActions.executeCancellation(() =>
1237
- cancelChatMessage(streamingConversationId),
1238
- )
1239
- } catch (e) {
1240
- console.error("Failed to cancel message stream:", e)
1241
- } finally {
1242
- setPendingApprovals((current) => {
1243
- const next = { ...current }
1244
- delete next[streamingConversationId]
1245
- return next
1246
- })
1247
- setStreamingConversationId(null)
1248
- setActiveTurnId(null)
1249
- }
1250
- }
1051
+ const handleApproval = conversationActions.resolveApproval
1052
+ const handleCancelMessage = conversationActions.cancel
1251
1053
 
1252
- async function sendPrompt(
1253
- promptText: string,
1254
- options: {
1255
- imageAttachments?: Array<{ dataUri: string; name: string; previewDataUri?: string }>
1256
- videoAttachments?: Array<{ dataUri: string; name: string }>
1257
- audioDataUri?: string | null
1258
- documentAttachment?: DocumentAttachment | null
1259
- systemInstruction?: string
1260
- clearComposer?: boolean
1261
- pinnedMcpServer?: string
1262
- forceAgentMode?: boolean
1263
- } = {},
1264
- ) {
1265
- if (sending) return
1266
- const originChatId = conversationId
1267
- const originWorkspacePath = workspacePath || null
1268
- const sessionGeneration = sessionGenerationRef.current
1269
- const runGeneration = ++runGenerationRef.current
1270
- const originRun = { chatId: originChatId, sessionGeneration, runGeneration }
1271
- const isCurrentRun = () => matchesActiveRun(originRun, {
1272
- chatId: activeConversationRef.current,
1273
- sessionGeneration: sessionGenerationRef.current,
1274
- runGeneration: runGenerationRef.current,
1275
- })
1276
- let turnId: number | null = null
1277
- const outgoingImages = options.imageAttachments ?? []
1278
- const outgoingVideos = options.videoAttachments ?? []
1279
- const outgoingDocument = options.documentAttachment ?? null
1280
- const shouldUseAgentMode = agentMode || options.forceAgentMode || promptText.toLowerCase().startsWith('search web:')
1281
- const outgoingImage = outgoingImages.map((img) => img.dataUri).join(' ')
1282
- const outgoingVideo = outgoingVideos.map((vid) => vid.dataUri).join(' ')
1283
- const outgoingImageCount = outgoingImages.length
1284
- conversationActions.startRun(promptText, outgoingImageCount, outgoingVideos.length)
1285
- setStreamingConversationId(conversationId)
1286
- setActiveTurnId(null)
1054
+ async function sendPrompt(promptText: string, options: PromptOptions = {}) {
1055
+ if (conversationActions.getSnapshot().sending) return
1287
1056
  setError('')
1288
1057
  liveThinkingOpenRef.current = true
1289
1058
  liveExtendedThinkingOpenRef.current = true
1290
- setThinkingExpanded((current) => ({ ...current, live: true, 'live-extended': true }))
1291
- const progressSnapshot: AgentProgress[] = []
1292
- let pendingProgress: AgentProgress[] = []
1293
- let progressFlushTimer: ReturnType<typeof setTimeout> | null = null
1294
- const flushPendingProgress = () => {
1295
- if (progressFlushTimer) {
1296
- clearTimeout(progressFlushTimer)
1297
- progressFlushTimer = null
1298
- }
1299
- if (pendingProgress.length === 0) return
1300
- const batch = pendingProgress
1301
- pendingProgress = []
1302
- if (isCurrentRun()) conversationActions.receiveProgress(batch)
1303
- }
1304
- const queueProgress = (progress: AgentProgress) => {
1305
- pendingProgress.push(progress)
1306
- if (progress.type !== 'ThinkingDelta') {
1307
- flushPendingProgress()
1308
- } else if (!progressFlushTimer) {
1309
- progressFlushTimer = setTimeout(flushPendingProgress, 75)
1310
- }
1311
- }
1312
- if (options.clearComposer) {
1313
- conversationActions.clearComposer()
1314
- }
1315
-
1316
- try {
1317
- const response = await conversationActions.executeStream((onChunk) => streamChatMessage(
1318
- shouldUseAgentMode ? promptText : `/chat ${promptText}`,
1319
- onChunk,
1320
- outgoingImage,
1321
- options.audioDataUri ?? null,
1322
- outgoingVideo,
1323
- options.systemInstruction ?? '',
1324
- (progress) => {
1325
- progressSnapshot.push(progress)
1326
- queueProgress(progress)
1327
- },
1328
- outgoingDocument,
1329
- originWorkspacePath,
1330
- originChatId,
1331
- undefined,
1332
- shouldUseAgentMode ? planMode : false,
1333
- options.pinnedMcpServer ?? null,
1334
- (payload) => {
1335
- if (sessionAutoApprovedRef.current && autoApprovedChatIdRef.current === originChatId) {
1336
- submitToolApproval(payload.token, true).catch((err) => {
1337
- console.error("Auto approval failed:", err)
1338
- })
1339
- } else {
1340
- setPendingApprovals((current) => ({ ...current, [originChatId]: payload }))
1341
- notifyPendingApproval(payload)
1342
- }
1343
- },
1344
- (id) => {
1345
- turnId = id
1346
- if (isCurrentRun()) setActiveTurnId(id)
1347
- },
1348
- ), isCurrentRun)
1349
- flushPendingProgress()
1350
- if (document.hidden || !document.hasFocus()) {
1351
- window.api?.notifyAiResponse?.(truncateForNotification(response.text))
1352
- }
1353
- const persistedProgress = compactAgentProgressForPersistence(progressSnapshot)
1354
- if (persistedProgress.length > 0) {
1355
- if (turnId != null) {
1356
- const interactionKey = String(turnId)
1357
- await saveInteractionAgentActivity(turnId, persistedProgress)
1358
- if (isCurrentRun()) {
1359
- setAgentActivitySnapshots((current) => ({
1360
- ...current,
1361
- [interactionKey]: persistedProgress.slice(),
1362
- }))
1363
- setThinkingExpanded((current) => ({
1364
- ...current,
1365
- ...(liveThinkingOpenRef.current ? { [interactionKey]: true } : {}),
1366
- ...(liveExtendedThinkingOpenRef.current ? { [`extended-${interactionKey}`]: true } : {}),
1367
- }))
1368
- }
1369
- } else {
1370
- console.warn('Agent activity was not saved because the stream did not provide a turn ID')
1371
- }
1372
- }
1373
- if (isCurrentRun()) {
1374
- try {
1375
- await loadConversationHistory(originChatId, sessionGeneration)
1376
- } catch (error) {
1377
- console.error('Failed to refresh completed conversation:', error)
1378
- }
1379
- }
1380
- await refreshChatSessions()
1381
- await refreshPictures()
1382
- if (isCurrentRun() && typeof window !== 'undefined') {
1383
- const pathname = (window.location.pathname || '').replace(/\/+$/, '')
1384
- const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
1385
- const target = (pathname || hash).toLowerCase()
1386
- if (!target || target === '' || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html') {
1387
- window.history.replaceState({}, '', `/chat/${encodeURIComponent(originChatId)}`)
1388
- }
1389
- }
1390
- getRuntimeStatus().then(setStatus).catch(() => {})
1391
- if (isCurrentRun()) conversationActions.clearStream()
1392
- } catch (reason) {
1393
- if (isCurrentRun()) setError(errorMessage(reason))
1394
- } finally {
1395
- flushPendingProgress()
1396
- setPendingApprovals((current) => {
1397
- if (!current[originChatId]) return current
1398
- const next = { ...current }
1399
- delete next[originChatId]
1400
- return next
1401
- })
1402
- if (isCurrentRun()) {
1403
- conversationActions.finishRun()
1404
- setStreamingConversationId(null)
1405
- setActiveTurnId(null)
1406
- }
1407
- }
1059
+ setThinkingExpanded(current => ({ ...current, live: true, 'live-extended': true }))
1060
+ await conversationActions.sendPrompt(promptText, { ...options, agentMode, planMode })
1408
1061
  }
1409
1062
 
1410
1063
  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
@@ -1421,7 +1074,7 @@ export default function MintDashboard() {
1421
1074
  // comes back as `not_handled` and falls through to the TS processor below.
1422
1075
  let engineResp: SlashResponse | null = null
1423
1076
  try {
1424
- engineResp = await runSlashCommand(trimmed, workspacePath || null)
1077
+ engineResp = await runSlashCommand(trimmed, workspacePath || null, conversationId)
1425
1078
  } catch {
1426
1079
  engineResp = null
1427
1080
  }
@@ -1464,7 +1117,7 @@ export default function MintDashboard() {
1464
1117
  provider: 'system',
1465
1118
  model: 'mint-cli',
1466
1119
  }
1467
- setInteractions((prev) => [...prev, systemMsg])
1120
+ conversationActions.appendInteraction(systemMsg)
1468
1121
  } else {
1469
1122
  const systemMsg = {
1470
1123
  id: Date.now(),
@@ -1474,7 +1127,7 @@ export default function MintDashboard() {
1474
1127
  provider: 'system',
1475
1128
  model: 'mint-cli',
1476
1129
  }
1477
- setInteractions((prev) => [...prev, systemMsg])
1130
+ conversationActions.appendInteraction(systemMsg)
1478
1131
  }
1479
1132
  return
1480
1133
  }
@@ -1488,7 +1141,7 @@ export default function MintDashboard() {
1488
1141
  provider: 'system',
1489
1142
  model: 'mint-cli',
1490
1143
  }
1491
- setInteractions((prev) => [...prev, systemMsg])
1144
+ conversationActions.appendInteraction(systemMsg)
1492
1145
  saveSystemInteraction(conversationId, trimmed, slashResult.systemText || '', 'system', 'mint-cli').catch(() => {})
1493
1146
  }
1494
1147
  return
@@ -1657,16 +1310,16 @@ export default function MintDashboard() {
1657
1310
  conversationActions.compose(message.trim() ? `Search web: ${message.trim()}` : 'Search web: ')
1658
1311
  }
1659
1312
 
1660
- async function selectWorkspace(path?: string) {
1661
- try {
1662
- const selected = path || await selectWorkspaceDirectory()
1663
- if (selected) {
1664
- updateWorkspacePath(selected)
1665
- changeView('workspace')
1666
- }
1667
- } catch (reason) {
1668
- setError(errorMessage(reason))
1669
- }
1313
+ async function selectWorkspace(path?: string, showWorkspace = false) {
1314
+ const targetView = isDesktopApp && (showWorkspace || view === 'workspace') ? 'workspace' : 'chat'
1315
+ await conversationActions.selectProject(
1316
+ async () => path || await selectLinkedFolderPath(),
1317
+ {
1318
+ openSession: (session, nextView) => selectConversation(session.id, session, nextView),
1319
+ newChat: (targetPath, nextView) => clearHistory('New chat', targetPath, nextView),
1320
+ showCurrentView: nextView => changeView(nextView, conversationActions.getSnapshot().conversationId),
1321
+ }, targetView,
1322
+ )
1670
1323
  }
1671
1324
 
1672
1325
  function captureScreen() {
@@ -1692,27 +1345,17 @@ export default function MintDashboard() {
1692
1345
  })
1693
1346
  }
1694
1347
 
1695
- async function clearHistory(action: 'New chat' | 'Clear history', targetWorkspacePath?: string | null) {
1696
- const originChatId = conversationId
1697
- const generation = sessionGenerationRef.current
1348
+ async function clearHistory(action: 'New chat' | 'Clear history', targetWorkspacePath?: string | null, targetView: 'chat' | 'workspace' = 'chat') {
1698
1349
  try {
1699
1350
  if (action === 'New chat') {
1700
1351
  const next = createConversationId()
1701
1352
  activateConversation(next)
1702
- setInteractions([])
1703
- setAgentActivitySnapshots({})
1704
- conversationActions.switchSession()
1705
- setViewState('chat')
1353
+ setViewState(targetView)
1706
1354
 
1707
1355
  if (targetWorkspacePath !== undefined) {
1708
1356
  updateWorkspacePath(targetWorkspacePath || '')
1709
1357
  } else {
1710
- const currentSession = chatSessions.find((s) => s.id === conversationId)
1711
- if (!currentSession || !currentSession.workspacePath) {
1712
- updateWorkspacePath('')
1713
- } else {
1714
- updateWorkspacePath(currentSession.workspacePath)
1715
- }
1358
+ updateWorkspacePath(workspacePath)
1716
1359
  }
1717
1360
 
1718
1361
  if (typeof window !== 'undefined') {
@@ -1724,12 +1367,8 @@ export default function MintDashboard() {
1724
1367
  return
1725
1368
  } else {
1726
1369
  if (!window.confirm(`${action} will clear the current conversation history. Continue?`)) return
1727
- await clearChatHistory(originChatId)
1370
+ await conversationActions.clearHistory()
1728
1371
  }
1729
- if (!isCurrentSession(originChatId, generation)) return
1730
- setInteractions([])
1731
- setAgentActivitySnapshots({})
1732
- conversationActions.switchSession()
1733
1372
  } catch (reason) {
1734
1373
  setError(errorMessage(reason))
1735
1374
  }
@@ -1747,7 +1386,7 @@ export default function MintDashboard() {
1747
1386
  provider: 'system',
1748
1387
  model: 'mint-cli',
1749
1388
  }
1750
- setInteractions((prev) => [...prev, systemMsg])
1389
+ conversationActions.appendInteraction(systemMsg)
1751
1390
  saveSystemInteraction(conversationId, userText, aiText, 'system', 'mint-cli').catch(() => {})
1752
1391
  }
1753
1392
 
@@ -1775,13 +1414,19 @@ export default function MintDashboard() {
1775
1414
  async function handleEngineSlashResponse(userText: string, resp: SlashResponse) {
1776
1415
  switch (resp.kind) {
1777
1416
  case 'message':
1778
- pushSystemMessage(userText, resp.markdown)
1417
+ pushSystemMessage(userText.startsWith('/mods set ') ? '/mods' : userText, resp.markdown)
1418
+ if (userText.startsWith('/mods set ')) {
1419
+ requestAnimationFrame(() => document.getElementById('chat-input')?.focus())
1420
+ }
1779
1421
  break
1780
1422
  case 'applied': {
1781
1423
  const wsChange = resp.effects.find((e) => e.kind === 'workspace_changed') as
1782
1424
  | { kind: 'workspace_changed'; path: string }
1783
1425
  | undefined
1784
- if (wsChange) conversationActions.selectWorkspace(wsChange.path)
1426
+ if (wsChange) updateWorkspacePath(wsChange.path)
1427
+ if (resp.effects.some((effect) => effect.kind === 'workspace_files_changed')) {
1428
+ setWorkspaceRefreshRevision((revision) => revision + 1)
1429
+ }
1785
1430
  const planModeChange = resp.effects.find((e) => e.kind === 'plan_mode_changed') as
1786
1431
  | { kind: 'plan_mode_changed'; enabled: boolean }
1787
1432
  | undefined
@@ -1793,25 +1438,17 @@ export default function MintDashboard() {
1793
1438
  // Render the same "✨ Gemini • model" chip the GUI's own /models shows.
1794
1439
  // The engine already logged the `provider_change` row into the shared
1795
1440
  // "cli" conversation, so this is just the immediate local echo.
1796
- setInteractions((prev) => [
1797
- ...prev,
1798
- {
1799
- id: Date.now(),
1800
- userText: providerChange.display,
1801
- aiText: '',
1802
- createdAt: new Date().toISOString(),
1803
- provider: 'system',
1804
- model: 'provider_change',
1805
- },
1806
- ])
1441
+ conversationActions.appendInteraction({
1442
+ id: Date.now(),
1443
+ userText: providerChange.display,
1444
+ aiText: '',
1445
+ createdAt: new Date().toISOString(),
1446
+ provider: 'system',
1447
+ model: 'provider_change',
1448
+ })
1807
1449
  }
1808
1450
  if (resp.effects.some((e) => e.kind === 'history_cleared')) {
1809
- const chatId = conversationId
1810
- const generation = sessionGenerationRef.current
1811
- await clearChatHistory(chatId).catch(() => {})
1812
- if (isCurrentSession(chatId, generation)) {
1813
- setInteractions([])
1814
- }
1451
+ await conversationActions.clearHistory().catch(() => {})
1815
1452
  }
1816
1453
  if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1817
1454
  if (resp.effects.some((e) => e.kind !== 'history_cleared' && e.kind !== 'workspace_changed')) {
@@ -1844,35 +1481,33 @@ export default function MintDashboard() {
1844
1481
  setThinkingExpanded((current) => ({ ...current, [key]: open }))
1845
1482
  }
1846
1483
 
1847
- async function selectConversation(id: string) {
1484
+ async function selectConversation(id: string, selectedSession?: ChatSession, targetView: 'chat' | 'workspace' = 'chat') {
1848
1485
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, id)
1849
1486
  touchActiveTimestamp()
1850
1487
  activateConversation(id)
1851
- const generation = sessionGenerationRef.current
1852
- changeView('chat', id)
1853
- conversationActions.switchSession()
1854
- const session = chatSessions.find((item) => item.id === id)
1488
+ changeView(targetView, id)
1489
+ const session = selectedSession ?? chatSessions.find((item) => item.id === id)
1855
1490
  if (session) {
1856
1491
  if (session.workspacePath) {
1857
1492
  if (session.workspacePath !== workspacePath) {
1858
- updateWorkspacePath(session.workspacePath)
1493
+ updateWorkspacePath(session.workspacePath, false)
1859
1494
  }
1860
1495
  } else {
1861
1496
  if (workspacePath) {
1862
- updateWorkspacePath('')
1497
+ updateWorkspacePath('', false)
1863
1498
  }
1864
1499
  }
1865
1500
  }
1866
1501
  try {
1867
- await loadConversationHistory(id, generation)
1502
+ await conversationActions.refreshHistory()
1868
1503
  } catch (reason) {
1869
- if (isCurrentSession(id, generation)) setError(errorMessage(reason))
1504
+ setError(errorMessage(reason))
1870
1505
  }
1871
1506
  }
1872
1507
 
1873
- const handleNewChatInProject = useCallback((targetPath: string) => {
1874
- clearHistory('New chat', targetPath)
1875
- }, [])
1508
+ const handleNewChatInProject = (targetPath: string) => {
1509
+ void clearHistory('New chat', targetPath)
1510
+ }
1876
1511
 
1877
1512
  async function deleteConversation(id: string) {
1878
1513
  if (id === 'cli') {
@@ -1894,7 +1529,7 @@ export default function MintDashboard() {
1894
1529
  try {
1895
1530
  await deleteChatSession(id)
1896
1531
  const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && !item.id.startsWith('cli') && item.id !== 'conversation-default')
1897
- const currentChatId = activeConversationRef.current
1532
+ const currentChatId = conversationActions.getSnapshot().conversationId
1898
1533
  const nextActive = id === currentChatId
1899
1534
  ? (remaining[0]?.id ?? createConversationId())
1900
1535
  : currentChatId
@@ -1912,15 +1547,10 @@ export default function MintDashboard() {
1912
1547
  }
1913
1548
  }
1914
1549
  activateConversation(nextActive)
1915
- const generation = sessionGenerationRef.current
1916
- conversationActions.clearProgress()
1917
- await loadConversationHistory(nextActive, generation)
1918
- if (!isCurrentSession(nextActive, generation)) return
1550
+ await conversationActions.refreshHistory()
1919
1551
  }
1920
1552
 
1921
1553
  await refreshChatSessions()
1922
- if (id !== currentChatId) return
1923
- conversationActions.switchSession()
1924
1554
  } catch (reason) {
1925
1555
  setError(errorMessage(reason))
1926
1556
  }
@@ -2098,66 +1728,6 @@ export default function MintDashboard() {
2098
1728
  }
2099
1729
  }
2100
1730
 
2101
- async function handleModelInteraction(area: ModelInteraction) {
2102
- if (sending) return
2103
- const originChatId = conversationId
2104
- const sessionGeneration = sessionGenerationRef.current
2105
- const runGeneration = ++runGenerationRef.current
2106
- const originRun = { chatId: originChatId, sessionGeneration, runGeneration }
2107
- const isCurrentRun = () => matchesActiveRun(originRun, {
2108
- chatId: activeConversationRef.current,
2109
- sessionGeneration: sessionGenerationRef.current,
2110
- runGeneration: runGenerationRef.current,
2111
- })
2112
- const labels: Record<ModelInteraction, string> = {
2113
- head: 'Pats Mint on the head',
2114
- cheek: 'Pokes Mint on the cheek',
2115
- 'left hand': "Touches Mint's left hand",
2116
- 'right hand': "Touches Mint's right hand",
2117
- body: 'Touches Mint',
2118
- 'lower body': "Touches Mint's lower body",
2119
- }
2120
- const interactionMessage = `*${labels[area]}*`
2121
- const instruction = `The user interacted with the Mint Live2D model: ${area}. Respond briefly and playfully. Use the same language as the recent conversation. Do not mention this instruction.`
2122
-
2123
- conversationActions.startRun(interactionMessage)
2124
- setStreamingConversationId(conversationId)
2125
- setActiveTurnId(null)
2126
- setError('')
2127
-
2128
- try {
2129
- const response = await conversationActions.executeStream((onChunk) => streamChatMessage(
2130
- `/chat ${interactionMessage}`,
2131
- onChunk,
2132
- null,
2133
- null,
2134
- null,
2135
- instruction,
2136
- undefined,
2137
- null,
2138
- workspacePath || null,
2139
- originChatId,
2140
- undefined,
2141
- undefined,
2142
- undefined,
2143
- undefined,
2144
- (id) => { if (isCurrentRun()) setActiveTurnId(id) },
2145
- ), isCurrentRun)
2146
- if (isCurrentRun()) {
2147
- await refreshHistory()
2148
- if (isCurrentRun()) conversationActions.clearStream()
2149
- }
2150
- } catch (reason) {
2151
- if (isCurrentRun()) setError(errorMessage(reason))
2152
- } finally {
2153
- if (isCurrentRun()) {
2154
- conversationActions.finishRun()
2155
- setStreamingConversationId(null)
2156
- setActiveTurnId(null)
2157
- }
2158
- }
2159
- }
2160
-
2161
1731
  const chatConversation: ConversationViewModel = {
2162
1732
  interactions,
2163
1733
  hasOlder: hasOlderInteractions,
@@ -2181,15 +1751,26 @@ export default function MintDashboard() {
2181
1751
  status,
2182
1752
  workspacePath,
2183
1753
  chatId: conversationId,
2184
- recentWorkspacePaths,
1754
+ recentWorkspacePaths: availableWorkspacePaths,
2185
1755
  chatEnd,
2186
1756
  welcomeInteraction: MOCK_WELCOME_INTERACTION,
2187
1757
  settingsConfig,
1758
+ slashChoice: pendingSlashChoice,
2188
1759
  isCliSession: conversationId.startsWith('cli') || conversationId === 'cli',
2189
1760
  cliSessionId: conversationId.startsWith('cli') ? conversationId : undefined,
2190
1761
  conversationTitle: chatSessions.find((session) => session.id === conversationId)?.title,
2191
1762
  }
2192
1763
  const chatActions: ConversationActions = {
1764
+ onSelectSlashChoice: (value) => {
1765
+ if (!pendingSlashChoice) return
1766
+ const next = `${pendingSlashChoice.command} ${value}`
1767
+ setPendingSlashChoice(null)
1768
+ requestAnimationFrame(() => document.getElementById('chat-input')?.focus())
1769
+ runSlashCommand(next, workspacePath || null, conversationId)
1770
+ .then((response) => handleEngineSlashResponse(next, response))
1771
+ .catch(() => {})
1772
+ },
1773
+ onDismissSlashChoice: () => setPendingSlashChoice(null),
2193
1774
  onLoadOlder: loadOlderInteractions,
2194
1775
  onThinkingExpandedChange: handleThinkingExpandedChange,
2195
1776
  onSubmit: handleSubmit,
@@ -2207,7 +1788,7 @@ export default function MintDashboard() {
2207
1788
  onSetAgentMode: updateAgentMode,
2208
1789
  onSetPlanMode: isDesktopApp ? updatePlanMode : undefined,
2209
1790
  onSetProvider: changeProvider,
2210
- onSelectWorkspace: isDesktopApp ? selectWorkspace : undefined,
1791
+ onSelectWorkspace: selectWorkspace,
2211
1792
  onWorkspaceChanged: () => setWorkspaceRefreshRevision((revision) => revision + 1),
2212
1793
  onApproval: handleApproval,
2213
1794
  onUpdateSettings: (updated) => setSettingsConfig(updated),
@@ -2237,23 +1818,37 @@ export default function MintDashboard() {
2237
1818
  sidebarCollapsed={sidebarCollapsed}
2238
1819
  onToggleSidebar={toggleSidebar}
2239
1820
  onNewChat={() => clearHistory('New chat')}
2240
- onOpenWorkspace={() => selectWorkspace()}
1821
+ onOpenWorkspace={() => selectWorkspace(undefined, true)}
2241
1822
  onOpenTerminal={openTerminalSurface}
2242
1823
  onToggleTerminal={toggleTerminalSurface}
2243
1824
  onOpenBrowser={() => openNativeBrowser()}
2244
1825
  toolsPanelOpen={toolsPanelOpen}
1826
+ backgroundTaskCount={backgroundTaskCount}
2245
1827
  onToggleToolsPanel={() => setToolsPanelOpen((open) => !open)}
2246
1828
  onOpenSettings={() => changeView('settings')}
2247
1829
  onCheckForUpdates={checkForUpdatesFromMenu}
2248
1830
  onShowAbout={() => showToast('Mint Agent — AI workspace')}
2249
1831
  workspacePath={workspacePath}
1832
+ workspaceActive={view === 'workspace' && !toolsPanelOpen}
1833
+ workspaceRevision={workspaceRefreshRevision}
1834
+ sending={sending}
1835
+ onWorkspaceUndoError={showToast}
2250
1836
  terminalCount={toolSurfaces.filter((surface) => surface.kind === 'terminal').length}
2251
- sourceNames={workspaceSourceNames}
1837
+ chatId={conversationId}
1838
+ liveProgress={streamingConversationId === conversationId ? agentProgress : []}
2252
1839
  recentChanges={recentAgentChanges}
2253
1840
  onOpenReview={(changes) => openReviewSurface({ id: `review:workspace:${Date.now()}`, kind: 'review', title: 'Review', reviewTitle: 'Recent agent changes', changes })}
2254
1841
  onRefreshWorkspace={() => setWorkspaceRefreshRevision((revision) => revision + 1)}
2255
1842
  />
2256
1843
  )}
1844
+ {notificationNotice && (
1845
+ <section className="mint-notification-notice" role="status" data-kind={notificationNotice.type}>
1846
+ <span>{notificationNotice.message}</span>
1847
+ {notificationNotice.chatId && <button type="button" onClick={() => { void notificationNavigationRef.current(notificationNotice.chatId!); setNotificationNotice(null) }}>Open chat</button>}
1848
+ <button type="button" aria-label="Dismiss notification" onClick={() => setNotificationNotice(null)}>×</button>
1849
+ </section>
1850
+ )}
1851
+ {error && <ErrorNotice key={error} message={error} onDismiss={() => setError('')} />}
2257
1852
  <div
2258
1853
  className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''} ${toolsPanelOpen ? 'tool-surface-active' : ''}`}
2259
1854
  style={{
@@ -2298,17 +1893,11 @@ export default function MintDashboard() {
2298
1893
  }}
2299
1894
  />
2300
1895
  )}
1896
+ {toastMessage && <div role="status" style={{ position: 'fixed', bottom: 22, right: 22, zIndex: 10000, padding: '12px 18px', borderRadius: 10, background: 'var(--bg-surface)', color: 'var(--text-main)', border: '1px solid var(--border-color)' }}>{toastMessage}</div>}
2301
1897
  <DashboardSidebar
2302
1898
  view={view}
2303
1899
  sidebarCollapsed={sidebarCollapsed}
2304
- modelVisible={modelVisible}
2305
1900
  sending={sending}
2306
- expressionIndex={expressionIndex}
2307
- accessoryIndex={accessoryIndex}
2308
- expressions={EXPRESSIONS}
2309
- accessories={ACCESSORIES}
2310
- interactionEnabled={interactionEnabled}
2311
- showInteractionGuide={showInteractionGuide}
2312
1901
  onToggleSidebar={toggleSidebar}
2313
1902
  onSidebarResize={handleSidebarResize}
2314
1903
  onSidebarResizeEnd={handleSidebarResizeEnd}
@@ -2319,12 +1908,6 @@ export default function MintDashboard() {
2319
1908
  onDeleteConversation={deleteConversation}
2320
1909
  onRenameConversation={renameConversation}
2321
1910
  onSetView={changeView}
2322
- onToggleModel={toggleModel}
2323
- onSetExpressionIndex={setExpressionIndex}
2324
- onSetAccessoryIndex={setAccessoryIndex}
2325
- onSetInteractionEnabled={updateInteractionEnabled}
2326
- onSetShowInteractionGuide={updateInteractionGuide}
2327
- onShowToast={showToast}
2328
1911
  isSearchOpen={isSearchOpen}
2329
1912
  onSetSearchOpen={setIsSearchOpen}
2330
1913
  showWorkspaceTab={isDesktopApp}
@@ -2335,7 +1918,7 @@ export default function MintDashboard() {
2335
1918
  onUpdateSessionWorkspace={handleUpdateSessionWorkspace}
2336
1919
  onNewChatInProject={handleNewChatInProject}
2337
1920
  />
2338
- <main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === 'workspace' ? '' : 'model-hidden'} ${view === 'workspace' ? 'workspace-open' : ''}`} style={(view === 'skills' || view === 'mcp' || view === 'plugins' || view === 'cron' || view === 'link' || view === 'pictures' || view === 'imagine' || view === 'veo' || view === 'code') ? { display: 'none' } : undefined}>
1921
+ <main className={`assistant-workspace ${view === 'workspace' ? '' : 'chat-only'} ${view === 'workspace' ? 'workspace-open' : ''}`} style={(view === 'skills' || view === 'mcp' || view === 'plugins' || view === 'cron' || view === 'link' || view === 'pictures' || view === 'imagine' || view === 'veo' || view === 'code') ? { display: 'none' } : undefined}>
2339
1922
  {proactiveSuggestion && (
2340
1923
  <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
2341
1924
  <div className="proactive-header">
@@ -2358,37 +1941,10 @@ export default function MintDashboard() {
2358
1941
  </div>
2359
1942
  </div>
2360
1943
  )}
2361
- {pendingSlashChoice && (
2362
- <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
2363
- <div className="proactive-header">
2364
- <span className="proactive-icon">⚡</span>
2365
- <div className="proactive-message">{pendingSlashChoice.title}</div>
2366
- <button className="proactive-dismiss-btn" onClick={() => setPendingSlashChoice(null)}>
2367
- Dismiss
2368
- </button>
2369
- </div>
2370
- <div className="proactive-chips">
2371
- {pendingSlashChoice.options.map((opt) => (
2372
- <button
2373
- key={opt.value}
2374
- className="suggestion-chip"
2375
- onClick={() => {
2376
- const next = `${pendingSlashChoice.command} ${opt.value}`
2377
- setPendingSlashChoice(null)
2378
- runSlashCommand(next, workspacePath || null)
2379
- .then((r) => handleEngineSlashResponse(next, r))
2380
- .catch(() => {})
2381
- }}
2382
- >
2383
- {opt.label}
2384
- </button>
2385
- ))}
2386
- </div>
2387
- </div>
2388
- )}
2389
1944
  {view === 'workspace' && (
2390
1945
  <Suspense fallback={<LazyPanelFallback />}>
2391
1946
  <WorkspacePanel
1947
+ key={workspacePath}
2392
1948
  agentMode={agentMode}
2393
1949
  sending={sending}
2394
1950
  workspacePath={workspacePath}
@@ -2396,38 +1952,29 @@ export default function MintDashboard() {
2396
1952
  onSetMessage={conversationActions.compose}
2397
1953
  onWorkspaceReady={updateWorkspacePath}
2398
1954
  refreshRevision={workspaceRefreshRevision}
1955
+ onOpenHtml={openHtmlFile}
1956
+ onOpenPreview={openWorkspacePreview}
2399
1957
  />
2400
1958
  </Suspense>
2401
1959
  )}
2402
- <Suspense fallback={null}>
2403
- <ModelPanel
2404
- scale={scale}
2405
- expressionIndex={expressionIndex}
2406
- accessoryIndex={accessoryIndex}
2407
- isLocked={isLocked}
2408
- isActive={modelVisible && view !== 'pictures' && view !== 'workspace' && view !== 'imagine' && view !== 'veo' && view !== 'skills' && view !== 'mcp' && view !== 'plugins'}
2409
- layoutPreset={layoutPreset}
2410
- sending={sending}
2411
- interactionEnabled={interactionEnabled}
2412
- showInteractionGuide={showInteractionGuide}
2413
- toastMessage={toastMessage}
2414
- onSetScale={setScale}
2415
- onSetLocked={setIsLocked}
2416
- onSetView={changeView}
2417
- onChangeLayoutPreset={changeLayoutPreset}
2418
- onDismissToast={() => setToastMessage('')}
2419
- onInteract={handleModelInteraction}
2420
- onModelLoadComplete={() => setModelReady(true)}
2421
- />
2422
- </Suspense>
2423
- <ChatPanel conversation={chatConversation} actions={chatActions} />
1960
+ <ChatPanel conversation={chatConversation} actions={chatActions} showSessionSources={!isDesktopApp} tools={{
1961
+ modsPanelTarget, onOpenMods: () => openUtilitySurface('mods'),
1962
+ open: toolsPanelOpen, backgroundTaskCount,
1963
+ onToggle: isDesktopApp ? undefined : () => setToolsPanelOpen(open => !open),
1964
+ }} />
2424
1965
  </main>
2425
- {isDesktopApp && toolsPanelOpen && (
1966
+ {toolsPanelOpen && (
2426
1967
  <Suspense fallback={<LazyPanelFallback />}>
2427
1968
  <ToolSurfacePage
2428
1969
  surfaces={toolSurfaces}
2429
1970
  activeSurfaceId={activeSurfaceId}
2430
1971
  workspacePath={workspacePath}
1972
+ desktopTools={isDesktopApp}
1973
+ backgroundTaskCount={backgroundTaskCount}
1974
+ onOpenMods={() => openUtilitySurface('mods')}
1975
+ onOpenBackgroundTasks={() => openUtilitySurface('background')}
1976
+ onModsPanelTarget={setModsPanelTarget}
1977
+ onBackgroundPanelTarget={setBackgroundPanelTarget}
2431
1978
  terminalSize={terminalRightWidth}
2432
1979
  onSelect={setActiveSurfaceId}
2433
1980
  onClose={closeToolSurface}
@@ -2444,7 +1991,7 @@ export default function MintDashboard() {
2444
1991
  changeView('workspace')
2445
1992
  }}
2446
1993
  onOpenSideChat={() => { setActiveSurfaceId(null); setToolsPanelOpen(false) }}
2447
- onClosePanel={() => setToolsPanelOpen(false)}
1994
+ onClosePanel={closeToolsPanel}
2448
1995
  onResizeTerminal={(size) => {
2449
1996
  setTerminalRightWidth(size)
2450
1997
  if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
@@ -2462,6 +2009,7 @@ export default function MintDashboard() {
2462
2009
  />
2463
2010
  </Suspense>
2464
2011
  )}
2012
+ <BackgroundTerminals workspacePath={workspacePath} panelTarget={backgroundPanelTarget} onCountChange={setBackgroundTaskCount} />
2465
2013
  {view === 'skills' && (
2466
2014
  <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2467
2015
  <Suspense fallback={<LazyPanelFallback />}>
@@ -2550,6 +2098,8 @@ export default function MintDashboard() {
2550
2098
  chatSessions={chatSessions}
2551
2099
  activeConversationId={conversationId}
2552
2100
  workspacePath={workspacePath}
2101
+ recentWorkspacePaths={availableWorkspacePaths}
2102
+ onSelectFolder={selectCodeHubFolder}
2553
2103
  onSelectSession={(id) => {
2554
2104
  selectConversation(id)
2555
2105
  }}
@@ -2563,14 +2113,13 @@ export default function MintDashboard() {
2563
2113
  )}
2564
2114
  {view === 'pictures' && (
2565
2115
  <Suspense fallback={<LazyPanelFallback />}>
2566
- <PicturesLibrary view={view} pictures={pictures} onSetView={changeView} onRefreshPictures={refreshPictures} />
2116
+ <LibraryPage chatId={conversationId} onClose={() => changeView('chat')} onOpenConversation={id => { void selectConversation(id) }} />
2567
2117
  </Suspense>
2568
2118
  )}
2569
2119
  {view === 'imagine' && (
2570
2120
  <Suspense fallback={<LazyPanelFallback />}>
2571
2121
  <ImageStudioPanel
2572
2122
  view={view}
2573
- onRefreshPictures={refreshPictures}
2574
2123
  onSendToChat={async (imagePath, imgPrompt) => {
2575
2124
  try {
2576
2125
  const response = await fetch(convertFileSrc(imagePath))
@@ -2608,17 +2157,6 @@ export default function MintDashboard() {
2608
2157
  <div className="startup-loading-text">Loading Mint Agent</div>
2609
2158
  </div>
2610
2159
  </div>
2611
- {error && (
2612
- <div className="mint-error" style={{ position: 'absolute', bottom: '20px', right: '20px', zIndex: 100, margin: 0, boxShadow: '0 8px 24px rgba(0,0,0,0.3)', display: 'flex', alignItems: 'center', gap: '8px' }}>
2613
- <span>{error}</span>
2614
- <button onClick={() => setError('')} style={{ background: 'transparent', border: 0, color: 'white', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', padding: 0 }}>
2615
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
2616
- <line x1="18" y1="6" x2="6" y2="18"></line>
2617
- <line x1="6" y1="6" x2="18" y2="18"></line>
2618
- </svg>
2619
- </button>
2620
- </div>
2621
- )}
2622
2160
 
2623
2161
  {isSearchOpen && (
2624
2162
  <Suspense fallback={null}>