@pheem49/mint 1.14.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 (925) hide show
  1. package/Cargo.lock +1258 -35
  2. package/Cargo.toml +5 -2
  3. package/LICENSE +21 -654
  4. package/README.md +93 -756
  5. package/Release_Note.md +53 -446
  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 +9 -1
  13. package/crates/mint-cli/src/agent/approval_prompts.rs +16 -3
  14. package/crates/mint-cli/src/agent/live_status.rs +545 -148
  15. package/crates/mint-cli/src/agent/markdown_render.rs +601 -61
  16. package/crates/mint-cli/src/agent.rs +723 -258
  17. package/crates/mint-cli/src/commands/agent.rs +83 -74
  18. package/crates/mint-cli/src/commands/eval.rs +9 -5
  19. package/crates/mint-cli/src/commands/git.rs +204 -0
  20. package/crates/mint-cli/src/commands/integrations.rs +35 -0
  21. package/crates/mint-cli/src/commands/knowledge.rs +13 -32
  22. package/crates/mint-cli/src/commands/mod.rs +81 -2
  23. package/crates/mint-cli/src/commands/tailnet_web.rs +235 -0
  24. package/crates/mint-cli/src/image.rs +32 -11
  25. package/crates/mint-cli/src/interactive/chat_tui.rs +7853 -0
  26. package/crates/mint-cli/src/interactive/command_ui.rs +477 -0
  27. package/crates/mint-cli/src/interactive/format.rs +8 -0
  28. package/crates/mint-cli/src/interactive/input_box.rs +94 -8
  29. package/crates/mint-cli/src/interactive/picker.rs +180 -37
  30. package/crates/mint-cli/src/interactive/resume_picker.rs +686 -0
  31. package/crates/mint-cli/src/interactive/slash_commands.rs +1071 -501
  32. package/crates/mint-cli/src/interactive/thought_viewer.rs +286 -0
  33. package/crates/mint-cli/src/interactive/tool_activity.rs +328 -0
  34. package/crates/mint-cli/src/interactive.rs +454 -166
  35. package/crates/mint-cli/src/main.rs +93 -26
  36. package/crates/mint-cli/src/markdown.rs +12 -5
  37. package/crates/mint-cli/src/mcp.rs +5 -0
  38. package/crates/mint-cli/src/mod_host.rs +6 -0
  39. package/crates/mint-cli/src/mods_cli.rs +62 -0
  40. package/crates/mint-cli/src/mods_ui.rs +187 -0
  41. package/crates/mint-cli/src/onboard.rs +37 -61
  42. package/crates/mint-cli/src/plugins_cli.rs +8 -5
  43. package/crates/mint-cli/src/setup.rs +10 -8
  44. package/crates/mint-cli/src/terminal_theme.rs +189 -0
  45. package/crates/mint-cli/src/updater.rs +112 -23
  46. package/crates/mint-cli/tests/library_intake.rs +77 -0
  47. package/crates/mint-cli/tests/mods_testing.rs +133 -0
  48. package/crates/mint-companion-protocol/Cargo.toml +10 -0
  49. package/crates/mint-companion-protocol/src/lib.rs +163 -0
  50. package/crates/mint-core/Cargo.toml +6 -3
  51. package/crates/mint-core/build.rs +26 -0
  52. package/crates/mint-core/src/agent/agent_loop.rs +1 -0
  53. package/crates/mint-core/src/agent/chat.rs +1003 -209
  54. package/crates/mint-core/src/agent/code_tools.rs +98 -2
  55. package/crates/mint-core/src/agent/memory/sources.rs +240 -0
  56. package/crates/mint-core/src/agent/memory.rs +805 -33
  57. package/crates/mint-core/src/agent/skill_inventory.rs +325 -0
  58. package/crates/mint-core/src/agent/skills.rs +437 -90
  59. package/crates/mint-core/src/agent.rs +1 -0
  60. package/crates/mint-core/src/api_server/routes/background_jobs.rs +310 -0
  61. package/crates/mint-core/src/api_server/routes/chat.rs +233 -190
  62. package/crates/mint-core/src/api_server/routes/html_preview.rs +77 -0
  63. package/crates/mint-core/src/api_server/routes/library.rs +97 -0
  64. package/crates/mint-core/src/api_server/routes/linked_folders.rs +118 -3
  65. package/crates/mint-core/src/api_server/routes/media_gen.rs +10 -57
  66. package/crates/mint-core/src/api_server/routes/misc.rs +114 -121
  67. package/crates/mint-core/src/api_server/routes/mod.rs +6 -0
  68. package/crates/mint-core/src/api_server/routes/mods.rs +31 -0
  69. package/crates/mint-core/src/api_server/routes/sessions.rs +85 -0
  70. package/crates/mint-core/src/api_server/routes/skills_subagents.rs +12 -42
  71. package/crates/mint-core/src/api_server/routes/slash.rs +50 -1
  72. package/crates/mint-core/src/api_server/routes/sources.rs +101 -0
  73. package/crates/mint-core/src/api_server.rs +140 -12
  74. package/crates/mint-core/src/avatar_bridge.rs +13 -2
  75. package/crates/mint-core/src/browser/cdp.rs +74 -83
  76. package/crates/mint-core/src/browser/evidence.rs +260 -0
  77. package/crates/mint-core/src/browser/input.rs +1 -1
  78. package/crates/mint-core/src/browser/interact.rs +98 -124
  79. package/crates/mint-core/src/browser/lifecycle.rs +75 -4
  80. package/crates/mint-core/src/browser/mod.rs +16 -2
  81. package/crates/mint-core/src/browser/navigate.rs +27 -19
  82. package/crates/mint-core/src/browser/observe.js +40 -0
  83. package/crates/mint-core/src/browser/session.rs +1610 -0
  84. package/crates/mint-core/src/companion.rs +716 -0
  85. package/crates/mint-core/src/cron/scheduler.rs +34 -2
  86. package/crates/mint-core/src/git/branches.rs +348 -0
  87. package/crates/mint-core/src/git/checkpoint.rs +40 -0
  88. package/crates/mint-core/src/git/diff.rs +206 -0
  89. package/crates/mint-core/src/git/mod.rs +10 -2
  90. package/crates/mint-core/src/integrations/channels.rs +88 -345
  91. package/crates/mint-core/src/integrations/mcp.rs +397 -50
  92. package/crates/mint-core/src/integrations/mcp_calls.rs +823 -0
  93. package/crates/mint-core/src/integrations/mcp_catalog.rs +267 -0
  94. package/crates/mint-core/src/integrations/mcp_device.rs +319 -0
  95. package/crates/mint-core/src/integrations/mcp_result.rs +356 -0
  96. package/crates/mint-core/src/integrations/webhooks.rs +539 -0
  97. package/crates/mint-core/src/integrations.rs +2 -0
  98. package/crates/mint-core/src/lib.rs +107 -40
  99. package/crates/mint-core/src/live_sync.rs +28 -9
  100. package/crates/mint-core/src/media/auto_shorts.rs +1 -0
  101. package/crates/mint-core/src/media/gemini_live.rs +145 -14
  102. package/crates/mint-core/src/media/image_models.rs +3 -27
  103. package/crates/mint-core/src/media/image_workflow.rs +216 -0
  104. package/crates/mint-core/src/media/library/intake.rs +144 -0
  105. package/crates/mint-core/src/media/library/migration.rs +56 -0
  106. package/crates/mint-core/src/media/library/removal.rs +55 -0
  107. package/crates/mint-core/src/media/library/tests.rs +730 -0
  108. package/crates/mint-core/src/media/library/thumbnails.rs +72 -0
  109. package/crates/mint-core/src/media/library.rs +545 -0
  110. package/crates/mint-core/src/media/mic_transcribe.rs +1 -0
  111. package/crates/mint-core/src/media/model_catalog.rs +375 -0
  112. package/crates/mint-core/src/media/pictures.rs +79 -222
  113. package/crates/mint-core/src/media/subtitle.rs +1 -0
  114. package/crates/mint-core/src/media/video_models.rs +3 -22
  115. package/crates/mint-core/src/media.rs +3 -0
  116. package/crates/mint-core/src/mods/manager.rs +1124 -0
  117. package/crates/mint-core/src/mods/mod.rs +569 -0
  118. package/crates/mint-core/src/mods/operations.rs +204 -0
  119. package/crates/mint-core/src/mods/session.rs +146 -0
  120. package/crates/mint-core/src/mods/slash.rs +59 -0
  121. package/crates/mint-core/src/mods/telemetry.rs +306 -0
  122. package/crates/mint-core/src/orchestration/approvals.rs +344 -0
  123. package/crates/mint-core/src/orchestration/context_render.rs +105 -0
  124. package/crates/mint-core/src/orchestration/mcp_review.rs +153 -0
  125. package/crates/mint-core/src/orchestration/memory_skill.rs +110 -33
  126. package/crates/mint-core/src/orchestration/mod.rs +3129 -481
  127. package/crates/mint-core/src/orchestration/mod_authoring_regressions.rs +209 -0
  128. package/crates/mint-core/src/orchestration/progress_clock.rs +80 -0
  129. package/crates/mint-core/src/orchestration/repeated_failures.rs +116 -0
  130. package/crates/mint-core/src/orchestration/review_regressions.rs +449 -0
  131. package/crates/mint-core/src/orchestration/run_control.rs +196 -0
  132. package/crates/mint-core/src/orchestration/tools/browser.rs +16 -139
  133. package/crates/mint-core/src/orchestration/tools/files.rs +161 -14
  134. package/crates/mint-core/src/orchestration/tools/media.rs +37 -41
  135. package/crates/mint-core/src/orchestration/tools/misc.rs +5 -3
  136. package/crates/mint-core/src/orchestration/tools/mod.rs +2 -0
  137. package/crates/mint-core/src/orchestration/tools/mods.rs +161 -0
  138. package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +303 -71
  139. package/crates/mint-core/src/orchestration/tools/shell.rs +95 -2
  140. package/crates/mint-core/src/orchestration/tools/web.rs +1 -1
  141. package/crates/mint-core/src/orchestration/workspace_helpers.rs +4 -1
  142. package/crates/mint-core/src/prompts/agent.rs +75 -31
  143. package/crates/mint-core/src/prompts/chat.rs +1 -1
  144. package/crates/mint-core/src/prompts/persona.rs +1 -1
  145. package/crates/mint-core/src/prompts/tool_catalog.rs +121 -27
  146. package/crates/mint-core/src/search/knowledge.rs +7 -1
  147. package/crates/mint-core/src/search/linked_folders/index.rs +896 -0
  148. package/crates/mint-core/src/search/linked_folders.rs +473 -131
  149. package/crates/mint-core/src/slash/catalog.rs +1 -1
  150. package/crates/mint-core/src/slash/mod.rs +644 -33
  151. package/crates/mint-core/src/slash/model_fetcher.rs +12 -3
  152. package/crates/mint-core/src/slash/models.rs +66 -10
  153. package/crates/mint-core/src/slash/mods.rs +155 -0
  154. package/crates/mint-core/src/system/bg_shell.rs +1129 -95
  155. package/crates/mint-core/src/system/config.rs +235 -10
  156. package/crates/mint-core/src/system/html_preview.rs +398 -0
  157. package/crates/mint-core/src/system/mint_web.rs +164 -0
  158. package/crates/mint-core/src/system/release_notes.rs +18 -0
  159. package/crates/mint-core/src/system/shell.rs +21 -6
  160. package/crates/mint-core/src/system/workspace.rs +292 -0
  161. package/crates/mint-core/src/system/workspace_history.rs +604 -0
  162. package/crates/mint-core/src/system.rs +5 -0
  163. package/crates/mint-core/tests/background_retention.rs +34 -0
  164. package/crates/mint-core/tests/background_shutdown.rs +24 -0
  165. package/crates/mint-core/tests/library_http.rs +209 -0
  166. package/crates/mint-core/tests/mcp_contract.rs +283 -0
  167. package/crates/mint-core/tests/mcp_stdio.rs +11 -10
  168. package/crates/mint-core/tests/media_model_catalog_http.rs +403 -0
  169. package/crates/mint-core/tests/memory_persistence.rs +39 -0
  170. package/crates/mint-core/tests/mods_http.rs +215 -0
  171. package/crates/mint-core/tests/terminal_shutdown.rs +109 -0
  172. package/crates/mint-mod-runtime/Cargo.toml +18 -0
  173. package/crates/mint-mod-runtime/src/lib.rs +507 -0
  174. package/crates/mint-mod-runtime/src/sdk.js +56 -0
  175. package/crates/mint-mod-runtime/src/test_sdk.js +32 -0
  176. package/crates/mint-mod-runtime/src/testing.rs +94 -0
  177. package/crates/mint-mod-runtime/tests/runtime.rs +156 -0
  178. package/docs/mods/AUTHORING.md +61 -0
  179. package/docs/mods/README.md +139 -0
  180. package/docs/mods/TESTING.md +56 -0
  181. package/docs/mods/mint-mods.d.ts +66 -0
  182. package/examples/device-mcp/README.md +22 -0
  183. package/examples/device-mcp/contract.md +49 -0
  184. package/examples/device-mcp/mock-device.mjs +66 -0
  185. package/examples/device-mcp/server.mjs +48 -0
  186. package/examples/device-mcp/server.test.mjs +78 -0
  187. package/examples/mods/task-checklist/README.md +1 -0
  188. package/examples/mods/task-checklist/main.ts +17 -0
  189. package/examples/mods/task-checklist/manifest.json +1 -0
  190. package/examples/mods/task-checklist/tests/checklist.test.ts +15 -0
  191. package/examples/mods/token-meter/README.md +1 -0
  192. package/examples/mods/token-meter/main.ts +8 -0
  193. package/examples/mods/token-meter/manifest.json +1 -0
  194. package/examples/mods/tool-guard/README.md +1 -0
  195. package/examples/mods/tool-guard/main.ts +9 -0
  196. package/examples/mods/tool-guard/manifest.json +1 -0
  197. package/out/web/assets/CliSessionsView-CtzUJpK_.css +1 -0
  198. package/out/web/assets/CliSessionsView-D7hw0Mgj.js +1 -0
  199. package/out/web/assets/CodeReviewPage-C7bEOJfd.js +4 -0
  200. package/out/web/assets/CodeReviewPage-Dte9uLiL.css +1 -0
  201. package/out/web/assets/CommandPalette-0RrF-AOT.js +1 -0
  202. package/out/web/assets/ImageStudioPanel-CdxkAf_r.js +1 -0
  203. package/out/web/assets/ImageStudioPanel-D0lnhQV5.css +1 -0
  204. package/out/web/assets/LibraryPage--Qfisp6D.js +2 -0
  205. package/out/web/assets/LibraryPage-DAxUTAg7.css +1 -0
  206. package/out/web/assets/LinkedFoldersView-2nfZbDgN.js +1 -0
  207. package/out/web/assets/McpRegistryPicker-h9uS8_wT.js +1 -0
  208. package/out/web/assets/McpServersView-slfQJzNB.js +1 -0
  209. package/out/web/assets/MintDashboard-BSR5XpXh.js +25 -0
  210. package/out/web/assets/MintDashboard-CWUcCGhd.css +1 -0
  211. package/out/web/assets/PluginsView-l7LSm503.js +1 -0
  212. package/out/web/assets/ScheduledTasksView-CttmLbq1.js +1 -0
  213. package/out/web/assets/SettingsWindow-BCMLYvEV.js +4 -0
  214. package/out/web/assets/SettingsWindow-DcMVmU9u.css +1 -0
  215. package/out/web/assets/SkillsView-rNs1qr_U.js +2 -0
  216. package/out/web/assets/ToolSurfacePage-B9DzEPWN.js +37 -0
  217. package/out/web/assets/ToolSurfacePage-CTfgggF2.css +1 -0
  218. package/out/web/assets/VeoStudioPanel-DOj7_WR3.css +1 -0
  219. package/out/web/assets/VeoStudioPanel-DiJs_Cg2.js +4 -0
  220. package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +1 -0
  221. package/out/web/assets/WorkspaceSelector-B2xIp04D.js +1 -0
  222. package/out/web/assets/WorkspaceSelector-Ca8uJOsS.css +1 -0
  223. package/out/web/assets/{abnfDiagram-VCTEODGH-DAOBA7ZG.js → abnfDiagram-VCTEODGH-CD0CjoGl.js} +1 -1
  224. package/out/web/assets/{architecture-7GRP2DOG-CgSwQOPQ.js → architecture-7GRP2DOG-RKV83OeZ.js} +1 -1
  225. package/out/web/assets/{architectureDiagram-5GKGNRK7-CYp3yupP.js → architectureDiagram-5GKGNRK7-nN_HAFct.js} +1 -1
  226. package/out/web/assets/{blockDiagram-NRAW4CY4-CyWybDWr.js → blockDiagram-NRAW4CY4-BrrqzHW5.js} +1 -1
  227. package/out/web/assets/{c4Diagram-UCG6FXSJ-BvY3U709.js → c4Diagram-UCG6FXSJ-DTdV8_zE.js} +1 -1
  228. package/out/web/assets/{chunk-75Z2AOVW-TLXCoZYv.js → chunk-75Z2AOVW-_M0z5olV.js} +1 -1
  229. package/out/web/assets/chunk-CKBBP62Z-BpQEgeHF.js +2 -0
  230. package/out/web/assets/{chunk-CLGD4ZFX-xjsKtNS7.js → chunk-CLGD4ZFX-D54yGvGS.js} +1 -1
  231. package/out/web/assets/chunk-DSNCTWBM-C-gvT2ru.js +2 -0
  232. package/out/web/assets/chunk-DU6HZSFF-CnFngIhW.js +128 -0
  233. package/out/web/assets/{chunk-F27PBJKO-BFpgF55A.js → chunk-F27PBJKO-BqtWrnEO.js} +1 -1
  234. package/out/web/assets/{chunk-G27WJ6UU-DfF7qW-C.js → chunk-G27WJ6UU-Ckoq5mi2.js} +1 -1
  235. package/out/web/assets/{chunk-GMAD6QVW-C-558kh_.js → chunk-GMAD6QVW-BuhEcgHq.js} +1 -1
  236. package/out/web/assets/{chunk-JQ64N6SF-wJmeGKAw.js → chunk-JQ64N6SF-CF8T7GiZ.js} +1 -1
  237. package/out/web/assets/{chunk-LCL6LL3I-C1wHD8Ie.js → chunk-LCL6LL3I-BhXV9zyH.js} +1 -1
  238. package/out/web/assets/{chunk-OBVCFTLP-BsRTPBqs.js → chunk-OBVCFTLP-CRIkunUB.js} +1 -1
  239. package/out/web/assets/{chunk-P2QGCYS3-HZIiM7Ni.js → chunk-P2QGCYS3-UFEU6BxZ.js} +1 -1
  240. package/out/web/assets/{chunk-POPQ4Y6H-Dk9wvVb0.js → chunk-POPQ4Y6H-CQcrNbjr.js} +1 -1
  241. package/out/web/assets/{chunk-PWAF6VOD-BmjYLNbq.js → chunk-PWAF6VOD-Cc2uVDsG.js} +1 -1
  242. package/out/web/assets/{chunk-R7TYR2AO-CaHIf-lK.js → chunk-R7TYR2AO-DiCxzdAY.js} +1 -1
  243. package/out/web/assets/{chunk-RHFEMEQ7-_T561hOU.js → chunk-RHFEMEQ7-b7hnPmrR.js} +1 -1
  244. package/out/web/assets/{chunk-SVP7TREG-CM-GGYoY.js → chunk-SVP7TREG-Bu_yYb-V.js} +1 -1
  245. package/out/web/assets/{chunk-TEH6E4GO-K2A8Vg2C.js → chunk-TEH6E4GO-G4Cdlyrt.js} +1 -1
  246. package/out/web/assets/classDiagram-DTDB5LWJ-RnC2mYO4.js +1 -0
  247. package/out/web/assets/classDiagram-v2-JRS7N3AN-RnC2mYO4.js +1 -0
  248. package/out/web/assets/config-CnvCChUM.js +2 -0
  249. package/out/web/assets/config-DM6oIVO3.css +1 -0
  250. package/out/web/assets/core-DsKkQ7ig.js +1 -0
  251. package/out/web/assets/{cynefin-OW5HDTMX-ClzaB6cc.js → cynefin-OW5HDTMX-CnzQfOGp.js} +1 -1
  252. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-D_JzZBzf.js → cynefinDiagram-5FMLGOSQ-DVtQnfpm.js} +1 -1
  253. package/out/web/assets/{dagre-3AP2YEHR-BDuUeMHj.js → dagre-3AP2YEHR-DG76_k5_.js} +1 -1
  254. package/out/web/assets/{diagram-S7CK7UJ4-DgtcfE8P.js → diagram-S7CK7UJ4-CaouUoze.js} +1 -1
  255. package/out/web/assets/{diagram-UQ7AKVKN-DTLoEYti.js → diagram-UQ7AKVKN-B4bEc26B.js} +1 -1
  256. package/out/web/assets/{diagram-VSXAHHWV-ChGl9pXz.js → diagram-VSXAHHWV-BJ86Ov2Y.js} +1 -1
  257. package/out/web/assets/{diagram-VX7I27RA-Be8DWz0S.js → diagram-VX7I27RA-7hH5IPYf.js} +1 -1
  258. package/out/web/assets/{diagram-Z3DM3KII-BMXjRTOF.js → diagram-Z3DM3KII-CCM4clo8.js} +1 -1
  259. package/out/web/assets/dist-js-BkYNznHX.js +1 -0
  260. package/out/web/assets/{ebnfDiagram-PWID7BFC-8Q9pl1CJ.js → ebnfDiagram-PWID7BFC-Da7WAM3k.js} +1 -1
  261. package/out/web/assets/{erDiagram-SSCWMZ5O-DjH8Lnkn.js → erDiagram-SSCWMZ5O-CzzmnZWT.js} +1 -1
  262. package/out/web/assets/event-Cdt7Byyj.js +1 -0
  263. package/out/web/assets/{eventmodeling-NTZA5JFV-BKUH_DOp.js → eventmodeling-NTZA5JFV-xcfsA0XD.js} +1 -1
  264. package/out/web/assets/fira-code-latin-600-normal-BmSSHshu.woff +0 -0
  265. package/out/web/assets/fira-code-latin-600-normal-CvYZpPHg.woff2 +0 -0
  266. package/out/web/assets/fira-code-symbols2-600-normal-Dvq6IQE5.woff +0 -0
  267. package/out/web/assets/flowDiagram-A5DVABFB-JdtH1673.js +1 -0
  268. package/out/web/assets/{ganttDiagram-EL5Y4UJY-D7fZKJNM.js → ganttDiagram-EL5Y4UJY-B3ClFGmL.js} +1 -1
  269. package/out/web/assets/{gitGraph-4MIJSDKK-BypWaaBU.js → gitGraph-4MIJSDKK-BZ_y_cDU.js} +1 -1
  270. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-DWiw_3bq.js → gitGraphDiagram-WWUBYQGX-drhRSYm3.js} +1 -1
  271. package/out/web/assets/index-BZ9xgmMP.css +1 -0
  272. package/out/web/assets/index-Ca8cCS4h.js +168 -0
  273. package/out/web/assets/{info-A6RAGUB7-KkAUxT4j.js → info-A6RAGUB7-B7I3K0F1.js} +1 -1
  274. package/out/web/assets/{infoDiagram-RXCK75RN-BNb6E48y.js → infoDiagram-RXCK75RN-deX_1MPh.js} +1 -1
  275. package/out/web/assets/{ishikawaDiagram-5VMMS53U-Caa-JoAK.js → ishikawaDiagram-5VMMS53U-s38yo28y.js} +1 -1
  276. package/out/web/assets/{journeyDiagram-EYS64GPL-DOrmFGz6.js → journeyDiagram-EYS64GPL-CZn2kB8b.js} +1 -1
  277. package/out/web/assets/jsx-runtime-DN4NhQUK.js +1 -0
  278. package/out/web/assets/{kanban-definition-3QL26DDD-BOWCYcK0.js → kanban-definition-3QL26DDD-DodjgySL.js} +1 -1
  279. package/out/web/assets/markdown-CMhYIcWB.js +39 -0
  280. package/out/web/assets/{mermaid-parser.core-BQqjCkS_.js → mermaid-parser.core-Whu2rlJC.js} +3 -3
  281. package/out/web/assets/mermaid.core-CKyPo8VH.js +11 -0
  282. package/out/web/assets/{mindmap-definition-FBJOCRG2-jTceWpr0.js → mindmap-definition-FBJOCRG2-260WBDxZ.js} +1 -1
  283. package/out/web/assets/modelManager-CUaGWMWu.js +2 -0
  284. package/out/web/assets/oauthManager-DlNhXJr6.js +1 -0
  285. package/out/web/assets/{packet-AYTQ26CC-CJ5RXgol.js → packet-AYTQ26CC-CEeIRKRG.js} +1 -1
  286. package/out/web/assets/{pegDiagram-XKGWAZYB-y8wMtd0y.js → pegDiagram-XKGWAZYB-BYadI-KO.js} +1 -1
  287. package/out/web/assets/{pie-WAS4IAKB-72fTBbl4.js → pie-WAS4IAKB-D8uf4dh5.js} +1 -1
  288. package/out/web/assets/{pieDiagram-E7YTZNPT-e0O02qWq.js → pieDiagram-E7YTZNPT-CjSDy4hO.js} +1 -1
  289. package/out/web/assets/{quadrantDiagram-AXDQQJYC-C9phYLom.js → quadrantDiagram-AXDQQJYC-B8D3gU5d.js} +1 -1
  290. package/out/web/assets/{radar-RG4KPBEZ-BAU2hzUT.js → radar-RG4KPBEZ-DZJxnokY.js} +1 -1
  291. package/out/web/assets/{railroad-74A4TZTK-B0bCAVHC.js → railroad-74A4TZTK-DBysUiUi.js} +1 -1
  292. package/out/web/assets/{railroad-abnf-HS5TGJTU-WhxO7ndE.js → railroad-abnf-HS5TGJTU-CHWxoJYg.js} +1 -1
  293. package/out/web/assets/{railroad-ebnf-LZEXJU2U-BkA0m7-F.js → railroad-ebnf-LZEXJU2U-DDv7X6bY.js} +1 -1
  294. package/out/web/assets/{railroad-peg-WCYAUIDC-BsVpHIaj.js → railroad-peg-WCYAUIDC-DpfcYYNJ.js} +1 -1
  295. package/out/web/assets/{railroadDiagram-O6MQD6OU-CyOLN65Q.js → railroadDiagram-O6MQD6OU-CYtyv0t3.js} +1 -1
  296. package/out/web/assets/{requirementDiagram-EFPCY7ZU-DQXJ7Dud.js → requirementDiagram-EFPCY7ZU-D2X-Ewik.js} +1 -1
  297. package/out/web/assets/{sankeyDiagram-P5KCCOFB-B2XLyIIc.js → sankeyDiagram-P5KCCOFB-Bn2x6C4Y.js} +1 -1
  298. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-DGQY4wwm.js → sequenceDiagram-WJ2MYXX4-BvFLKtPd.js} +1 -1
  299. package/out/web/assets/{stateDiagram-HBIQ2CUA-DHZpDwNy.js → stateDiagram-HBIQ2CUA-Bx0GWeaY.js} +1 -1
  300. package/out/web/assets/stateDiagram-v2-4QOOHH4V--hol1yg_.js +1 -0
  301. package/out/web/assets/studio-B1mk40wD.js +1 -0
  302. package/out/web/assets/{swimlanes-XN3QIQJK-Bn_jcZz-.js → swimlanes-XN3QIQJK-CCma0T7L.js} +1 -1
  303. package/out/web/assets/swimlanesDiagram-VK2B7HYN-BkohoVsU.js +8 -0
  304. package/out/web/assets/{timeline-definition-24CTP7MA-CdTERuJZ.js → timeline-definition-24CTP7MA-iCRhhJ7C.js} +1 -1
  305. package/out/web/assets/{treeView-Q6P3EWNA-TKBKQG6R.js → treeView-Q6P3EWNA-DZ-y08qp.js} +1 -1
  306. package/out/web/assets/{treemap-WGGIJYW6-BP7gQoiC.js → treemap-WGGIJYW6-De0CxzMx.js} +1 -1
  307. package/out/web/assets/{vennDiagram-4TSXK5OY-BHUmxDuK.js → vennDiagram-4TSXK5OY-w_7aZ6xe.js} +1 -1
  308. package/out/web/assets/{wardley-WFR3VGLG-9bct5AAN.js → wardley-WFR3VGLG-DT0y0xJ4.js} +1 -1
  309. package/out/web/assets/{wardleyDiagram-VM6X3IG4-BfJwDDso.js → wardleyDiagram-VM6X3IG4-DAdXELbQ.js} +1 -1
  310. package/out/web/assets/window-BJCVtyET.js +1 -0
  311. package/out/web/assets/{xychartDiagram-S5SC5T6Z-Dqd9Mvpo.js → xychartDiagram-S5SC5T6Z-IZIXf3Vo.js} +1 -1
  312. package/out/web/index-web.html +4 -4
  313. package/out/web/index.html +4 -4
  314. package/package.json +80 -20
  315. package/shared-modules/.github/workflows/ci.yml +120 -0
  316. package/shared-modules/.github/workflows/update.yaml +28 -0
  317. package/slash-commands.json +16 -2
  318. package/src/bin/auto-build.js +66 -0
  319. package/src/bin/desktop.js +113 -0
  320. package/src/bin/desktop.test.js +113 -0
  321. package/src/bin/index.js +18 -25
  322. package/src/bin/index.test.js +89 -0
  323. package/src/bin/measure-effects.mjs +53 -0
  324. package/src/bin/package.test.js +28 -0
  325. package/src/bin/release-notes.test.js +84 -0
  326. package/src/bin/styles.test.js +20 -0
  327. package/src/bin/web.js +5 -0
  328. package/src/bin/web.test.js +39 -0
  329. package/src/renderer/index-web.html +1 -0
  330. package/src/renderer/index.html +1 -2
  331. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  332. package/src/renderer/shared/agentProgress.ts +201 -3
  333. package/src/renderer/shared/components/AgentActivityDrawer.tsx +9 -8
  334. package/src/renderer/shared/components/AgentActivityTable.tsx +193 -133
  335. package/src/renderer/shared/components/ApprovalCard.test.tsx +24 -0
  336. package/src/renderer/shared/components/ApprovalCard.tsx +10 -3
  337. package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +91 -40
  338. package/src/renderer/shared/components/AuthGate.tsx +79 -48
  339. package/src/renderer/shared/components/BackgroundTerminals.tsx +181 -0
  340. package/src/renderer/shared/components/CalculationCard.tsx +41 -15
  341. package/src/renderer/shared/components/ChatCodeBlock.tsx +8 -4
  342. package/src/renderer/shared/components/ChatMessageItem.test.tsx +68 -0
  343. package/src/renderer/shared/components/ChatMessageItem.tsx +52 -33
  344. package/src/renderer/shared/components/ChatPanel.test.tsx +21 -0
  345. package/src/renderer/shared/components/ChatPanel.tsx +676 -415
  346. package/src/renderer/shared/components/ChunkErrorBoundary.tsx +115 -64
  347. package/src/renderer/shared/components/CliSessionsView.test.tsx +125 -0
  348. package/src/renderer/shared/components/CliSessionsView.tsx +543 -0
  349. package/src/renderer/shared/components/CodeReviewPage.tsx +668 -0
  350. package/src/renderer/shared/components/CollapsiblePasteInput.tsx +90 -0
  351. package/src/renderer/shared/components/CommandPalette.tsx +8 -8
  352. package/src/renderer/shared/components/ContextCompactionStatus.tsx +17 -0
  353. package/src/renderer/shared/components/DashboardSidebar.tsx +853 -229
  354. package/src/renderer/shared/components/DesktopTitlebar.tsx +371 -0
  355. package/src/renderer/shared/components/DiffReviewModal.tsx +3 -3
  356. package/src/renderer/shared/components/ErrorNotice.tsx +29 -0
  357. package/src/renderer/shared/components/FormattedMessage.tsx +11 -0
  358. package/src/renderer/shared/components/GeminiLiveOverlay.css +86 -0
  359. package/src/renderer/shared/components/GeminiLiveOverlay.test.tsx +24 -0
  360. package/src/renderer/shared/components/GeminiLiveOverlay.tsx +85 -101
  361. package/src/renderer/shared/components/GitBranchSelector.tsx +442 -0
  362. package/src/renderer/shared/components/GlideSelect.css +138 -0
  363. package/src/renderer/shared/components/GlideSelect.d.ts +31 -0
  364. package/src/renderer/shared/components/GlideSelect.jsx +266 -0
  365. package/src/renderer/shared/components/ImageGenCard.tsx +31 -12
  366. package/src/renderer/shared/components/ImageSearchCard.tsx +76 -65
  367. package/src/renderer/shared/components/ImageStudioPanel.tsx +49 -49
  368. package/src/renderer/shared/components/InlineThoughtNote.tsx +27 -0
  369. package/src/renderer/shared/components/LibraryItemCard.tsx +72 -0
  370. package/src/renderer/shared/components/LibraryPage.tsx +165 -0
  371. package/src/renderer/shared/components/LibraryPreview.tsx +59 -0
  372. package/src/renderer/shared/components/LinkedFoldersView.tsx +365 -46
  373. package/src/renderer/shared/components/McpRegistryPicker.tsx +1 -1
  374. package/src/renderer/shared/components/McpServersView.tsx +51 -30
  375. package/src/renderer/shared/components/McpToolAllowlist.tsx +2 -4
  376. package/src/renderer/shared/components/McpToolResults.tsx +99 -0
  377. package/src/renderer/shared/components/MermaidCard.tsx +14 -8
  378. package/src/renderer/shared/components/MintDashboard.tsx +1002 -831
  379. package/src/renderer/shared/components/ModSessionControls.tsx +8 -0
  380. package/src/renderer/shared/components/ModUi.test.tsx +65 -0
  381. package/src/renderer/shared/components/ModUi.tsx +26 -0
  382. package/src/renderer/shared/components/ModelSelectorPopover.tsx +547 -219
  383. package/src/renderer/shared/components/ModsPanel.tsx +156 -0
  384. package/src/renderer/shared/components/PlanChecklistWidget.tsx +1 -1
  385. package/src/renderer/shared/components/PluginsView.tsx +1 -1
  386. package/src/renderer/shared/components/ProgressActivity.tsx +17 -0
  387. package/src/renderer/shared/components/ReviewHistory.tsx +42 -0
  388. package/src/renderer/shared/components/RewindModal.tsx +11 -18
  389. package/src/renderer/shared/components/RunSummaryDashboard.tsx +8 -14
  390. package/src/renderer/shared/components/ScheduledTasksView.tsx +11 -10
  391. package/src/renderer/shared/components/ScreenCaptureDialog.css +221 -0
  392. package/src/renderer/shared/components/ScreenCaptureDialog.tsx +199 -0
  393. package/src/renderer/shared/components/SelectField.test.tsx +27 -0
  394. package/src/renderer/shared/components/SelectField.tsx +38 -0
  395. package/src/renderer/shared/components/SessionSources.tsx +48 -0
  396. package/src/renderer/shared/components/SessionSourcesPopover.tsx +61 -0
  397. package/src/renderer/shared/components/Settings/AgentsTab.tsx +41 -45
  398. package/src/renderer/shared/components/Settings/AudioTab.tsx +16 -15
  399. package/src/renderer/shared/components/Settings/AutomationTab.tsx +10 -60
  400. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +368 -0
  401. package/src/renderer/shared/components/Settings/GeneralTab.tsx +368 -113
  402. package/src/renderer/shared/components/Settings/MemoryTab.test.tsx +37 -0
  403. package/src/renderer/shared/components/Settings/MemoryTab.tsx +71 -31
  404. package/src/renderer/shared/components/Settings/NotificationsTab.test.tsx +15 -0
  405. package/src/renderer/shared/components/Settings/NotificationsTab.tsx +84 -0
  406. package/src/renderer/shared/components/Settings/PluginsTab.tsx +20 -12
  407. package/src/renderer/shared/components/Settings/ProfileTab.tsx +29 -5
  408. package/src/renderer/shared/components/Settings/SearchableModelCombobox.tsx +411 -0
  409. package/src/renderer/shared/components/Settings/ThemeTab.tsx +416 -165
  410. package/src/renderer/shared/components/SkillsView.tsx +41 -16
  411. package/src/renderer/shared/components/SlashChoicePanel.tsx +88 -0
  412. package/src/renderer/shared/components/SlashSuggestions.test.tsx +33 -0
  413. package/src/renderer/shared/components/SlashSuggestions.tsx +41 -12
  414. package/src/renderer/shared/components/SourcesBlock.tsx +55 -51
  415. package/src/renderer/shared/components/StockCard.tsx +58 -24
  416. package/src/renderer/shared/components/SubagentDagView.tsx +4 -4
  417. package/src/renderer/shared/components/TerminalDock.tsx +241 -0
  418. package/src/renderer/shared/components/ThinkingBlock.tsx +14 -8
  419. package/src/renderer/shared/components/ToolSurfacePage.test.tsx +32 -0
  420. package/src/renderer/shared/components/ToolSurfacePage.tsx +568 -0
  421. package/src/renderer/shared/components/ToolsPanelToggle.tsx +13 -0
  422. package/src/renderer/shared/components/UiFeatureCard.tsx +267 -0
  423. package/src/renderer/shared/components/UiGridCard.tsx +188 -0
  424. package/src/renderer/shared/components/UiMockupWidget.tsx +149 -0
  425. package/src/renderer/shared/components/VeoStudioPanel.tsx +22 -37
  426. package/src/renderer/shared/components/ViewedImages.tsx +36 -0
  427. package/src/renderer/shared/components/VirtualChatHistory.tsx +82 -0
  428. package/src/renderer/shared/components/VoicePill.css +85 -0
  429. package/src/renderer/shared/components/VoicePill.d.ts +12 -0
  430. package/src/renderer/shared/components/VoicePill.jsx +190 -0
  431. package/src/renderer/shared/components/VoicePill.test.tsx +22 -0
  432. package/src/renderer/shared/components/WeatherCard.tsx +3 -3
  433. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +138 -0
  434. package/src/renderer/shared/components/WorkspacePaneResizer.tsx +92 -0
  435. package/src/renderer/shared/components/WorkspacePreviewActions.test.tsx +44 -0
  436. package/src/renderer/shared/components/WorkspacePreviewActions.tsx +19 -0
  437. package/src/renderer/shared/components/WorkspaceSelector.tsx +154 -0
  438. package/src/renderer/shared/components/background-terminals.css +292 -0
  439. package/src/renderer/shared/components/ui/README.md +52 -0
  440. package/src/renderer/shared/components/ui/button.tsx +37 -0
  441. package/src/renderer/shared/components/ui/demo.tsx +26 -0
  442. package/src/renderer/shared/components/ui/error-recovery.css +35 -0
  443. package/src/renderer/shared/components/ui/error-recovery.tsx +35 -0
  444. package/src/renderer/shared/components/ui/voice-powered-orb.css +6 -0
  445. package/src/renderer/shared/components/ui/voice-powered-orb.tsx +374 -0
  446. package/src/renderer/shared/components/workspace-selector.css +248 -0
  447. package/src/renderer/shared/constants/config.ts +57 -3
  448. package/src/renderer/shared/constants/models.ts +189 -2
  449. package/src/renderer/shared/constants/slashCommands.ts +1 -1
  450. package/src/renderer/shared/conversation/coordinator.test.ts +27 -0
  451. package/src/renderer/shared/conversation/coordinator.ts +82 -0
  452. package/src/renderer/shared/conversation/lifecycle.test.ts +362 -0
  453. package/src/renderer/shared/conversation/lifecycle.ts +418 -0
  454. package/src/renderer/shared/conversation/projectNavigation.test.ts +105 -0
  455. package/src/renderer/shared/conversation/projectNavigation.ts +39 -0
  456. package/src/renderer/shared/conversation/sourcesTransport.ts +27 -0
  457. package/src/renderer/shared/conversation/syncView.test.ts +13 -0
  458. package/src/renderer/shared/conversation/syncView.ts +6 -0
  459. package/src/renderer/shared/conversation/transport.test.ts +163 -0
  460. package/src/renderer/shared/conversation/transport.ts +159 -0
  461. package/src/renderer/shared/conversation/useConversationCoordinator.ts +64 -0
  462. package/src/renderer/shared/css/agent-observability.css +46 -15
  463. package/src/renderer/shared/css/auth-gate.css +114 -15
  464. package/src/renderer/shared/css/chat-ui-cards.css +495 -0
  465. package/src/renderer/shared/css/code-review.css +856 -0
  466. package/src/renderer/shared/css/code-sessions.css +479 -0
  467. package/src/renderer/shared/css/collapsed-paste.css +35 -0
  468. package/src/renderer/shared/css/effects.css +15 -0
  469. package/src/renderer/shared/css/error-notice.css +60 -0
  470. package/src/renderer/shared/css/file-changes.css +132 -0
  471. package/src/renderer/shared/css/image-studio.css +16 -16
  472. package/src/renderer/shared/css/library.css +476 -0
  473. package/src/renderer/shared/css/management-views.css +415 -27
  474. package/src/renderer/shared/css/mcp-results.css +11 -0
  475. package/src/renderer/shared/css/model-selector.css +543 -85
  476. package/src/renderer/shared/css/mods.css +77 -0
  477. package/src/renderer/shared/css/notification-notice.css +28 -0
  478. package/src/renderer/shared/css/session-sources-popover.css +9 -0
  479. package/src/renderer/shared/css/session-sources.css +23 -0
  480. package/src/renderer/shared/css/settings/agents.css +1 -1
  481. package/src/renderer/shared/css/settings/automation.css +13 -13
  482. package/src/renderer/shared/css/settings/general.css +526 -5
  483. package/src/renderer/shared/css/settings/memory.css +154 -0
  484. package/src/renderer/shared/css/settings/notifications.css +11 -0
  485. package/src/renderer/shared/css/settings/plugins.css +5 -5
  486. package/src/renderer/shared/css/settings/profile.css +331 -1
  487. package/src/renderer/shared/css/settings/theme.css +656 -3
  488. package/src/renderer/shared/css/slash-choice.css +95 -0
  489. package/src/renderer/shared/css/slash-suggestions.css +68 -0
  490. package/src/renderer/shared/css/sources-block.css +91 -0
  491. package/src/renderer/shared/css/thinking-status.css +46 -0
  492. package/src/renderer/shared/css/tools-panel.css +7 -0
  493. package/src/renderer/shared/css/ui-tailwind.css +19 -0
  494. package/src/renderer/shared/css/veo-studio.css +16 -15
  495. package/src/renderer/shared/css/viewed-images.css +15 -0
  496. package/src/renderer/shared/fonts.css +46 -0
  497. package/src/renderer/shared/fonts.ts +10 -37
  498. package/src/renderer/shared/lib/utils.ts +6 -0
  499. package/src/renderer/shared/library/controller.test.ts +61 -0
  500. package/src/renderer/shared/library/controller.ts +57 -0
  501. package/src/renderer/shared/library/transport.test.ts +26 -0
  502. package/src/renderer/shared/library/transport.ts +60 -0
  503. package/src/renderer/shared/library/useLibraryFile.ts +38 -0
  504. package/src/renderer/shared/mods.ts +58 -0
  505. package/src/renderer/shared/platform.test.ts +90 -0
  506. package/src/renderer/shared/platform.ts +219 -7
  507. package/src/renderer/shared/theme/themeManager.test.ts +43 -0
  508. package/src/renderer/shared/theme/themeManager.ts +157 -0
  509. package/src/renderer/shared/theme/themes.ts +116 -0
  510. package/src/renderer/shared/theme/tokens.css +318 -0
  511. package/src/renderer/shared/types.ts +137 -2
  512. package/src/renderer/shared/utils/agentActivity.test.ts +75 -0
  513. package/src/renderer/shared/utils/agentActivity.ts +168 -8
  514. package/src/renderer/shared/utils/approval.tsx +7 -0
  515. package/src/renderer/shared/utils/artifactBrowser.test.ts +80 -0
  516. package/src/renderer/shared/utils/artifactBrowser.ts +41 -0
  517. package/src/renderer/shared/utils/cardJson.test.ts +35 -0
  518. package/src/renderer/shared/utils/cardJson.ts +30 -0
  519. package/src/renderer/shared/utils/contextCompaction.test.tsx +48 -0
  520. package/src/renderer/shared/utils/contextCompaction.ts +21 -0
  521. package/src/renderer/shared/utils/effectVisibility.ts +9 -0
  522. package/src/renderer/shared/utils/fileIcons.ts +1 -1
  523. package/src/renderer/shared/utils/markdown.tsx +330 -81
  524. package/src/renderer/shared/utils/markdown.webImages.test.tsx +40 -0
  525. package/src/renderer/shared/utils/mcpResults.test.tsx +91 -0
  526. package/src/renderer/shared/utils/modViewport.ts +7 -0
  527. package/src/renderer/shared/utils/nativeRecordingSession.test.ts +61 -0
  528. package/src/renderer/shared/utils/nativeRecordingSession.ts +46 -0
  529. package/src/renderer/shared/utils/notificationSettings.test.ts +14 -0
  530. package/src/renderer/shared/utils/notificationSettings.ts +23 -0
  531. package/src/renderer/shared/utils/notifications.test.ts +51 -0
  532. package/src/renderer/shared/utils/notifications.ts +54 -0
  533. package/src/renderer/shared/utils/pastedText.test.ts +20 -0
  534. package/src/renderer/shared/utils/pastedText.ts +13 -0
  535. package/src/renderer/shared/utils/progressCache.test.ts +32 -0
  536. package/src/renderer/shared/utils/providers.ts +8 -3
  537. package/src/renderer/shared/utils/screenCapture.ts +43 -0
  538. package/src/renderer/shared/utils/sessionSources.test.ts +31 -0
  539. package/src/renderer/shared/utils/sessionSources.ts +34 -0
  540. package/src/renderer/shared/utils/slashCommandProcessor.ts +5 -2
  541. package/src/renderer/shared/utils/sourceAttachments.ts +28 -0
  542. package/src/renderer/shared/utils/speech.ts +51 -9
  543. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +22 -0
  544. package/src/renderer/shared/utils/syntaxHighlight.tsx +26 -0
  545. package/src/renderer/shared/utils/ui.ts +4 -64
  546. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +49 -5
  547. package/src/renderer/shared/utils/useLatestCallback.ts +9 -0
  548. package/src/renderer/shared/utils/useNativeVoiceInput.ts +80 -72
  549. package/src/renderer/shared/utils/useSessionSources.ts +33 -0
  550. package/src/renderer/shared/utils/viewedImages.ts +26 -0
  551. package/src/renderer/shared/utils/workspacePane.test.ts +33 -0
  552. package/src/renderer/shared/utils/workspacePane.ts +20 -0
  553. package/src/renderer/shared/utils/workspacePreview.test.ts +24 -0
  554. package/src/renderer/shared/utils/workspacePreview.ts +16 -0
  555. package/src/renderer/shared/utils/workspaceSnapshot.test.ts +49 -0
  556. package/src/renderer/shared/utils/workspaceSnapshot.ts +26 -0
  557. package/src/renderer/shared/utils/workspaces.ts +10 -0
  558. package/src/renderer/shared/workspaceUndo.ts +88 -0
  559. package/src/renderer/src/App.tsx +16 -1
  560. package/src/renderer/src/components/BrowserShell.tsx +108 -0
  561. package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +91 -0
  562. package/src/renderer/src/components/LiveTranslateControls.tsx +65 -0
  563. package/src/renderer/src/components/MintDashboard.tsx +4 -2
  564. package/src/renderer/src/components/PicturesLibrary.tsx +2 -287
  565. package/src/renderer/src/components/ScreenPicker.css +227 -0
  566. package/src/renderer/src/components/ScreenPicker.tsx +312 -526
  567. package/src/renderer/src/components/SettingsWindow.tsx +48 -5
  568. package/src/renderer/src/components/SpotlightWindow.tsx +1 -0
  569. package/src/renderer/src/components/WidgetWindow.tsx +27 -3
  570. package/src/renderer/src/components/WorkspacePanel.tsx +269 -61
  571. package/src/renderer/src/components/browser-shell.css +185 -0
  572. package/src/renderer/src/components/browser-suggestions-popup.css +55 -0
  573. package/src/renderer/src/components/liveTranslateFrames.test.ts +44 -0
  574. package/src/renderer/src/components/liveTranslateFrames.ts +45 -0
  575. package/src/renderer/src/components/liveTranslateLanguage.ts +6 -0
  576. package/src/renderer/src/css/app-shell.css +400 -204
  577. package/src/renderer/src/css/chat.css +1666 -594
  578. package/src/renderer/src/css/dashboard.css +11 -0
  579. package/src/renderer/src/css/desktop-tools.css +928 -0
  580. package/src/renderer/src/css/misc.css +5 -60
  581. package/src/renderer/src/css/pictures.css +2 -407
  582. package/src/renderer/src/css/settings/base.css +394 -157
  583. package/src/renderer/src/css/sidebar-workspace.css +1072 -376
  584. package/src/renderer/src/css/spotlight.css +15 -26
  585. package/src/renderer/src/css/tool-surfaces.css +784 -0
  586. package/src/renderer/src/css/widget.css +36 -8
  587. package/src/renderer/src/env.d.ts +4 -9
  588. package/src/renderer/src/hooks/useProviderModels.ts +2 -9
  589. package/src/renderer/src/index.css +53 -46
  590. package/src/renderer/src/main.tsx +12 -0
  591. package/src/renderer/src/proactiveSuggestions.ts +29 -0
  592. package/src/renderer/src/tauri.ts +302 -339
  593. package/src/renderer/src/voiceInput.ts +2 -1
  594. package/src/renderer/src-web/App.tsx +40 -0
  595. package/src/renderer/src-web/components/MintDashboard.tsx +4 -2
  596. package/src/renderer/src-web/components/PicturesLibrary.tsx +2 -284
  597. package/src/renderer/src-web/components/PullToRefresh.tsx +107 -0
  598. package/src/renderer/src-web/components/SettingsWindow.tsx +36 -5
  599. package/src/renderer/src-web/css/app-shell.css +188 -207
  600. package/src/renderer/src-web/css/chat.css +1236 -507
  601. package/src/renderer/src-web/css/dashboard.css +11 -0
  602. package/src/renderer/src-web/css/misc.css +3 -55
  603. package/src/renderer/src-web/css/mobile.css +679 -0
  604. package/src/renderer/src-web/css/pictures.css +2 -415
  605. package/src/renderer/src-web/css/pull-to-refresh.css +54 -0
  606. package/src/renderer/src-web/css/settings/base.css +353 -134
  607. package/src/renderer/src-web/css/settings/mobile.css +320 -0
  608. package/src/renderer/src-web/css/sidebar-workspace.css +546 -187
  609. package/src/renderer/src-web/css/spotlight.css +9 -26
  610. package/src/renderer/src-web/css/widget.css +1 -6
  611. package/src/renderer/src-web/env.d.ts +4 -12
  612. package/src/renderer/src-web/hooks/useProviderModels.ts +2 -9
  613. package/src/renderer/src-web/index.css +53 -96
  614. package/src/renderer/src-web/main.tsx +12 -0
  615. package/src/renderer/src-web/proactiveSuggestions.ts +3 -0
  616. package/src/renderer/src-web/tauri.ts +309 -364
  617. package/src/renderer/tests/backgroundTerminals.browser.html +81 -0
  618. package/src/renderer/tests/chatPanelFixture.ts +23 -0
  619. package/src/renderer/tests/chatPerformance.browser.html +71 -0
  620. package/src/renderer/tests/cliSessions.browser.html +82 -0
  621. package/src/renderer/tests/collapsedPaste.browser.html +70 -0
  622. package/src/renderer/tests/composerMenus.browser.html +116 -0
  623. package/src/renderer/tests/contextCompaction.browser.html +17 -0
  624. package/src/renderer/tests/conversationLifecycle.browser.html +70 -0
  625. package/src/renderer/tests/effectsPerformance.browser.html +13 -0
  626. package/src/renderer/tests/errorRecovery.browser.html +21 -0
  627. package/src/renderer/tests/glideSelect.browser.html +71 -0
  628. package/src/renderer/tests/glideSelect.preview.html +27 -0
  629. package/src/renderer/tests/library.browser.html +1 -0
  630. package/src/renderer/tests/library.browser.tsx +78 -0
  631. package/src/renderer/tests/liveOrb.browser.html +49 -0
  632. package/src/renderer/tests/liveVoice.browser.html +29 -0
  633. package/src/renderer/tests/modInput.browser.html +22 -0
  634. package/src/renderer/tests/sessionSources.browser.html +1 -0
  635. package/src/renderer/tests/sessionSources.browser.tsx +107 -0
  636. package/src/renderer/tests/skills.browser.html +24 -0
  637. package/src/renderer/tests/toolsPanel.browser.html +1 -0
  638. package/src/renderer/tests/toolsPanel.browser.tsx +113 -0
  639. package/src/renderer/tests/viewedImages.browser.html +1 -0
  640. package/src/renderer/tests/viewedImages.browser.tsx +93 -0
  641. package/src/renderer/tests/voicePill.browser.html +80 -0
  642. package/src/renderer/tests/voicePill.preview.html +34 -0
  643. package/src/renderer/tests/webSources.browser.html +1 -0
  644. package/src/renderer/tests/webSources.browser.tsx +98 -0
  645. package/src/renderer/tests/workspaceUndo.browser.html +92 -0
  646. package/src-tauri/Cargo.toml +40 -0
  647. package/src-tauri/build.rs +3 -0
  648. package/src-tauri/src/app/lifecycle.rs +277 -0
  649. package/src-tauri/src/app/mod.rs +4 -0
  650. package/src-tauri/src/app/shortcuts.rs +40 -0
  651. package/src-tauri/src/app/tray.rs +52 -0
  652. package/src-tauri/src/background/events.rs +70 -0
  653. package/src-tauri/src/background/headless.rs +230 -0
  654. package/src-tauri/src/background/mod.rs +4 -0
  655. package/src-tauri/src/background/proactive.rs +262 -0
  656. package/src-tauri/src/commands/approvals.rs +20 -0
  657. package/src-tauri/src/commands/auth.rs +127 -0
  658. package/src-tauri/src/commands/conversation.rs +642 -0
  659. package/src-tauri/src/commands/integrations.rs +157 -0
  660. package/src-tauri/src/commands/media.rs +179 -0
  661. package/src-tauri/src/commands/mod.rs +11 -0
  662. package/src-tauri/src/commands/mods.rs +6 -0
  663. package/src-tauri/src/commands/settings.rs +141 -0
  664. package/src-tauri/src/commands/voice.rs +153 -0
  665. package/src-tauri/src/commands/workspace.rs +501 -0
  666. package/src-tauri/src/lib.rs +10 -0
  667. package/src-tauri/src/main.rs +3 -0
  668. package/src-tauri/src/native/actions.rs +206 -0
  669. package/src-tauri/src/native/browser.rs +252 -0
  670. package/src-tauri/src/native/capture.rs +695 -0
  671. package/src-tauri/src/native/discord_rpc.rs +99 -0
  672. package/src-tauri/src/native/integrations.rs +126 -0
  673. package/src-tauri/src/native/mod.rs +12 -0
  674. package/src-tauri/src/native/notifications.rs +51 -0
  675. package/src-tauri/src/native/plugins.rs +14 -0
  676. package/src-tauri/src/native/system.rs +250 -0
  677. package/src-tauri/src/native/terminal.rs +201 -0
  678. package/src-tauri/src/native/updater.rs +148 -0
  679. package/src-tauri/src/native/windows.rs +143 -0
  680. package/tsconfig.json +2 -0
  681. package/vite.config.ts +4 -1
  682. package/vite.config.web.ts +5 -0
  683. package/.cargo/config.toml +0 -13
  684. package/.claude/settings.local.json +0 -182
  685. package/.codex +0 -0
  686. package/CLAUDE.md +0 -102
  687. package/out/web/Live2DCubismCore.js +0 -9
  688. package/out/web/assets/MintDashboard-DWSqucgb.js +0 -57
  689. package/out/web/assets/SettingsWindow-DFM2aj5B.js +0 -2
  690. package/out/web/assets/chunk-CKBBP62Z-nr1uKPj9.js +0 -2
  691. package/out/web/assets/chunk-DSNCTWBM-Dtj8Rqsd.js +0 -2
  692. package/out/web/assets/chunk-DU6HZSFF-2tv5K1wT.js +0 -128
  693. package/out/web/assets/classDiagram-DTDB5LWJ-BdSFArgw.js +0 -1
  694. package/out/web/assets/classDiagram-v2-JRS7N3AN-BdSFArgw.js +0 -1
  695. package/out/web/assets/core-DImejIUG.js +0 -1
  696. package/out/web/assets/dist-js-BxuKEVWM.js +0 -1
  697. package/out/web/assets/event-BNl8Zahw.js +0 -1
  698. package/out/web/assets/fira-code-cyrillic-400-normal-UC0NFL4U.woff2 +0 -0
  699. package/out/web/assets/fira-code-cyrillic-400-normal-dc1q3VD7.woff +0 -0
  700. package/out/web/assets/fira-code-cyrillic-500-normal-Bu7VCL72.woff +0 -0
  701. package/out/web/assets/fira-code-cyrillic-500-normal-CX35h3Mg.woff2 +0 -0
  702. package/out/web/assets/fira-code-cyrillic-ext-400-normal-Buh61xzB.woff +0 -0
  703. package/out/web/assets/fira-code-cyrillic-ext-400-normal-txZ9Fk_1.woff2 +0 -0
  704. package/out/web/assets/fira-code-cyrillic-ext-500-normal-CYRspHgm.woff2 +0 -0
  705. package/out/web/assets/fira-code-cyrillic-ext-500-normal-CwtLmSFD.woff +0 -0
  706. package/out/web/assets/fira-code-greek-400-normal-B2Gh_Y8s.woff2 +0 -0
  707. package/out/web/assets/fira-code-greek-400-normal-DobhaxvF.woff +0 -0
  708. package/out/web/assets/fira-code-greek-500-normal-CVO0IRNt.woff2 +0 -0
  709. package/out/web/assets/fira-code-greek-500-normal-DEXIWiey.woff +0 -0
  710. package/out/web/assets/fira-code-greek-ext-400-normal-DR7mBgIM.woff2 +0 -0
  711. package/out/web/assets/fira-code-greek-ext-400-normal-DSPUheWg.woff +0 -0
  712. package/out/web/assets/fira-code-greek-ext-500-normal-BO3iq16n.woff2 +0 -0
  713. package/out/web/assets/fira-code-greek-ext-500-normal-CMiNLXER.woff +0 -0
  714. package/out/web/assets/fira-code-latin-ext-400-normal-DG73JjOf.woff +0 -0
  715. package/out/web/assets/fira-code-latin-ext-400-normal-KSMg0QLl.woff2 +0 -0
  716. package/out/web/assets/fira-code-latin-ext-500-normal-CaANqXjI.woff +0 -0
  717. package/out/web/assets/fira-code-latin-ext-500-normal-Crd9e_oI.woff2 +0 -0
  718. package/out/web/assets/flowDiagram-A5DVABFB-BpnHUSU7.js +0 -1
  719. package/out/web/assets/index-CVRg270z.css +0 -1
  720. package/out/web/assets/index-DxZQr9yD.js +0 -10
  721. package/out/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  722. package/out/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  723. package/out/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  724. package/out/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  725. package/out/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  726. package/out/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  727. package/out/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  728. package/out/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  729. package/out/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  730. package/out/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  731. package/out/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  732. package/out/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  733. package/out/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  734. package/out/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  735. package/out/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  736. package/out/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  737. package/out/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  738. package/out/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  739. package/out/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  740. package/out/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  741. package/out/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  742. package/out/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  743. package/out/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  744. package/out/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  745. package/out/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  746. package/out/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  747. package/out/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  748. package/out/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  749. package/out/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  750. package/out/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  751. package/out/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  752. package/out/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  753. package/out/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  754. package/out/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  755. package/out/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  756. package/out/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  757. package/out/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  758. package/out/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  759. package/out/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  760. package/out/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  761. package/out/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  762. package/out/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  763. package/out/web/assets/kanit-latin-400-normal-BUlsQXH3.woff2 +0 -0
  764. package/out/web/assets/kanit-latin-400-normal-HVZWrfev.woff +0 -0
  765. package/out/web/assets/kanit-latin-500-normal-CX_4XcKi.woff2 +0 -0
  766. package/out/web/assets/kanit-latin-500-normal-qBha86O4.woff +0 -0
  767. package/out/web/assets/kanit-latin-600-normal-0lS78yAd.woff2 +0 -0
  768. package/out/web/assets/kanit-latin-600-normal-DlacpeTg.woff +0 -0
  769. package/out/web/assets/kanit-latin-ext-400-normal-B2xPf58u.woff2 +0 -0
  770. package/out/web/assets/kanit-latin-ext-400-normal-CHvNKHHD.woff +0 -0
  771. package/out/web/assets/kanit-latin-ext-500-normal-BFdWrG22.woff +0 -0
  772. package/out/web/assets/kanit-latin-ext-500-normal-EHs3suD6.woff2 +0 -0
  773. package/out/web/assets/kanit-latin-ext-600-normal-BBDzoLaj.woff +0 -0
  774. package/out/web/assets/kanit-latin-ext-600-normal-DAX3wtuq.woff2 +0 -0
  775. package/out/web/assets/kanit-thai-400-normal-D2nliXKa.woff +0 -0
  776. package/out/web/assets/kanit-thai-400-normal-D80Pglro.woff2 +0 -0
  777. package/out/web/assets/kanit-thai-500-normal-BB8gri04.woff2 +0 -0
  778. package/out/web/assets/kanit-thai-500-normal-Iy_w0a80.woff +0 -0
  779. package/out/web/assets/kanit-thai-600-normal-Bj6oiaDc.woff2 +0 -0
  780. package/out/web/assets/kanit-thai-600-normal-wbTlPO7I.woff +0 -0
  781. package/out/web/assets/kanit-vietnamese-400-normal-DRI8YWyb.woff2 +0 -0
  782. package/out/web/assets/kanit-vietnamese-400-normal-dow6Bnto.woff +0 -0
  783. package/out/web/assets/kanit-vietnamese-500-normal-BzTrpCM3.woff2 +0 -0
  784. package/out/web/assets/kanit-vietnamese-500-normal-T1ndT7MF.woff +0 -0
  785. package/out/web/assets/kanit-vietnamese-600-normal-Bi7fw3I4.woff2 +0 -0
  786. package/out/web/assets/kanit-vietnamese-600-normal-DXCRAa_8.woff +0 -0
  787. package/out/web/assets/mali-latin-400-normal-ByPcZ8Gx.woff +0 -0
  788. package/out/web/assets/mali-latin-400-normal-Cuc6Ykl8.woff2 +0 -0
  789. package/out/web/assets/mali-latin-500-normal-B01M-LP6.woff2 +0 -0
  790. package/out/web/assets/mali-latin-500-normal-Thgqw_9f.woff +0 -0
  791. package/out/web/assets/mali-latin-600-normal-B9TiUNpT.woff2 +0 -0
  792. package/out/web/assets/mali-latin-600-normal-CycpQCwU.woff +0 -0
  793. package/out/web/assets/mali-latin-ext-400-normal-DUVdJQO4.woff +0 -0
  794. package/out/web/assets/mali-latin-ext-400-normal-iCG1ALuJ.woff2 +0 -0
  795. package/out/web/assets/mali-latin-ext-500-normal-BfJE4UMZ.woff2 +0 -0
  796. package/out/web/assets/mali-latin-ext-500-normal-FoLEyOKS.woff +0 -0
  797. package/out/web/assets/mali-latin-ext-600-normal-BTvIfiCp.woff2 +0 -0
  798. package/out/web/assets/mali-latin-ext-600-normal-DAQcLVmD.woff +0 -0
  799. package/out/web/assets/mali-thai-400-normal-BO7A3bLZ.woff2 +0 -0
  800. package/out/web/assets/mali-thai-400-normal-CX9VrjTn.woff +0 -0
  801. package/out/web/assets/mali-thai-500-normal-Cph2qha9.woff +0 -0
  802. package/out/web/assets/mali-thai-500-normal-D89rMT77.woff2 +0 -0
  803. package/out/web/assets/mali-thai-600-normal-DPgXT6rZ.woff +0 -0
  804. package/out/web/assets/mali-thai-600-normal-DlTx3OsB.woff2 +0 -0
  805. package/out/web/assets/mali-vietnamese-400-normal-DUKxzXaz.woff2 +0 -0
  806. package/out/web/assets/mali-vietnamese-400-normal-gFDLvsOK.woff +0 -0
  807. package/out/web/assets/mali-vietnamese-500-normal--OY07q2c.woff +0 -0
  808. package/out/web/assets/mali-vietnamese-500-normal-KDrNRwfW.woff2 +0 -0
  809. package/out/web/assets/mali-vietnamese-600-normal-CtTMUU7T.woff +0 -0
  810. package/out/web/assets/mali-vietnamese-600-normal-DFlJr66u.woff2 +0 -0
  811. package/out/web/assets/mermaid.core-DYGGlqOf.js +0 -11
  812. package/out/web/assets/mitr-latin-400-normal-Cb18vPmZ.woff +0 -0
  813. package/out/web/assets/mitr-latin-400-normal-CwbkLQ4a.woff2 +0 -0
  814. package/out/web/assets/mitr-latin-500-normal-B4JOhzAi.woff +0 -0
  815. package/out/web/assets/mitr-latin-500-normal-BmClOow2.woff2 +0 -0
  816. package/out/web/assets/mitr-latin-ext-400-normal-B3CPG7Yo.woff2 +0 -0
  817. package/out/web/assets/mitr-latin-ext-400-normal-DTx65hiC.woff +0 -0
  818. package/out/web/assets/mitr-latin-ext-500-normal-B_Diajz1.woff2 +0 -0
  819. package/out/web/assets/mitr-latin-ext-500-normal-DYOsVaVT.woff +0 -0
  820. package/out/web/assets/mitr-thai-400-normal-CS4Do6aM.woff +0 -0
  821. package/out/web/assets/mitr-thai-400-normal-CXWaAdVj.woff2 +0 -0
  822. package/out/web/assets/mitr-thai-500-normal-CVdBrEVH.woff2 +0 -0
  823. package/out/web/assets/mitr-thai-500-normal-CW_B5D-i.woff +0 -0
  824. package/out/web/assets/mitr-vietnamese-400-normal-CPMGWyoC.woff2 +0 -0
  825. package/out/web/assets/mitr-vietnamese-400-normal-Z6XhIpdY.woff +0 -0
  826. package/out/web/assets/mitr-vietnamese-500-normal-CDrv0585.woff2 +0 -0
  827. package/out/web/assets/mitr-vietnamese-500-normal-CMQWyq-Z.woff +0 -0
  828. package/out/web/assets/noto-sans-thai-latin-400-normal-BaWBrqFP.woff2 +0 -0
  829. package/out/web/assets/noto-sans-thai-latin-400-normal-Dh_FJjOB.woff +0 -0
  830. package/out/web/assets/noto-sans-thai-latin-500-normal-BC04jsmm.woff +0 -0
  831. package/out/web/assets/noto-sans-thai-latin-500-normal-fyBKClF1.woff2 +0 -0
  832. package/out/web/assets/noto-sans-thai-latin-600-normal-CemcjnVR.woff2 +0 -0
  833. package/out/web/assets/noto-sans-thai-latin-600-normal-Dbc-dNO6.woff +0 -0
  834. package/out/web/assets/noto-sans-thai-latin-ext-400-normal-B-1H0YkH.woff +0 -0
  835. package/out/web/assets/noto-sans-thai-latin-ext-400-normal-nBP59pVw.woff2 +0 -0
  836. package/out/web/assets/noto-sans-thai-latin-ext-500-normal-B-ZWb8SZ.woff2 +0 -0
  837. package/out/web/assets/noto-sans-thai-latin-ext-500-normal-CrEHO7C8.woff +0 -0
  838. package/out/web/assets/noto-sans-thai-latin-ext-600-normal-PkMZo0xa.woff +0 -0
  839. package/out/web/assets/noto-sans-thai-latin-ext-600-normal-maHHBsez.woff2 +0 -0
  840. package/out/web/assets/noto-sans-thai-thai-400-normal-BEOmKAI1.woff2 +0 -0
  841. package/out/web/assets/noto-sans-thai-thai-400-normal-Dxb_unuU.woff +0 -0
  842. package/out/web/assets/noto-sans-thai-thai-500-normal-BOGXO2tw.woff +0 -0
  843. package/out/web/assets/noto-sans-thai-thai-500-normal-CU_X86GQ.woff2 +0 -0
  844. package/out/web/assets/noto-sans-thai-thai-600-normal-CmgB8_JK.woff +0 -0
  845. package/out/web/assets/noto-sans-thai-thai-600-normal-Cs73sVjl.woff2 +0 -0
  846. package/out/web/assets/oauthManager-DNiwM8Rn.js +0 -2
  847. package/out/web/assets/oauthManager-DuamS1Av.css +0 -1
  848. package/out/web/assets/outfit-latin-300-normal-AWLfuCom.woff +0 -0
  849. package/out/web/assets/outfit-latin-300-normal-CRYTwY6m.woff2 +0 -0
  850. package/out/web/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
  851. package/out/web/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
  852. package/out/web/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
  853. package/out/web/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
  854. package/out/web/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
  855. package/out/web/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
  856. package/out/web/assets/outfit-latin-ext-300-normal-BYS6Dpvq.woff2 +0 -0
  857. package/out/web/assets/outfit-latin-ext-300-normal-CMIMshEd.woff +0 -0
  858. package/out/web/assets/outfit-latin-ext-400-normal-5tcqmc2S.woff2 +0 -0
  859. package/out/web/assets/outfit-latin-ext-400-normal-DHm7mdGe.woff +0 -0
  860. package/out/web/assets/outfit-latin-ext-500-normal-DrCvqoFD.woff +0 -0
  861. package/out/web/assets/outfit-latin-ext-500-normal-zeox_O30.woff2 +0 -0
  862. package/out/web/assets/outfit-latin-ext-600-normal-B85nYjL1.woff2 +0 -0
  863. package/out/web/assets/outfit-latin-ext-600-normal-CWJcPgd7.woff +0 -0
  864. package/out/web/assets/preload-helper-D0uHx7FM.js +0 -1
  865. package/out/web/assets/prompt-latin-400-normal-BQ9zjSN8.woff2 +0 -0
  866. package/out/web/assets/prompt-latin-400-normal-CxU4ec_r.woff +0 -0
  867. package/out/web/assets/prompt-latin-500-normal-B1edBDQ9.woff +0 -0
  868. package/out/web/assets/prompt-latin-500-normal-CxzxEHZc.woff2 +0 -0
  869. package/out/web/assets/prompt-latin-600-normal-CSDDLorv.woff +0 -0
  870. package/out/web/assets/prompt-latin-600-normal-hKZWXsc1.woff2 +0 -0
  871. package/out/web/assets/prompt-latin-ext-400-normal-1pHbN9uy.woff +0 -0
  872. package/out/web/assets/prompt-latin-ext-400-normal-DdSafGZ9.woff2 +0 -0
  873. package/out/web/assets/prompt-latin-ext-500-normal--EZ1um7s.woff2 +0 -0
  874. package/out/web/assets/prompt-latin-ext-500-normal-D-lI1EVT.woff +0 -0
  875. package/out/web/assets/prompt-latin-ext-600-normal-C8b50x67.woff +0 -0
  876. package/out/web/assets/prompt-latin-ext-600-normal-Cg9L7iJU.woff2 +0 -0
  877. package/out/web/assets/prompt-thai-400-normal-BrkKv8cO.woff2 +0 -0
  878. package/out/web/assets/prompt-thai-400-normal-DJypnfr3.woff +0 -0
  879. package/out/web/assets/prompt-thai-500-normal-C18pDUoL.woff2 +0 -0
  880. package/out/web/assets/prompt-thai-500-normal-D7YNqUJD.woff +0 -0
  881. package/out/web/assets/prompt-thai-600-normal-CYPViXds.woff +0 -0
  882. package/out/web/assets/prompt-thai-600-normal-MrdfU7zR.woff2 +0 -0
  883. package/out/web/assets/prompt-vietnamese-400-normal-BCPzsgPT.woff2 +0 -0
  884. package/out/web/assets/prompt-vietnamese-400-normal-D4pLyeNK.woff +0 -0
  885. package/out/web/assets/prompt-vietnamese-500-normal-DmzxmPwa.woff2 +0 -0
  886. package/out/web/assets/prompt-vietnamese-500-normal-w_sS_j2F.woff +0 -0
  887. package/out/web/assets/prompt-vietnamese-600-normal-1dyohtNh.woff +0 -0
  888. package/out/web/assets/prompt-vietnamese-600-normal-7QWjJBsF.woff2 +0 -0
  889. package/out/web/assets/sarabun-latin-400-normal-DmguIS-7.woff +0 -0
  890. package/out/web/assets/sarabun-latin-400-normal-URPBxl-K.woff2 +0 -0
  891. package/out/web/assets/sarabun-latin-500-normal-BjUTcdxu.woff2 +0 -0
  892. package/out/web/assets/sarabun-latin-500-normal-CJexTAnl.woff +0 -0
  893. package/out/web/assets/sarabun-latin-600-normal-9FonlkKx.woff +0 -0
  894. package/out/web/assets/sarabun-latin-600-normal-DMD3TROr.woff2 +0 -0
  895. package/out/web/assets/sarabun-latin-ext-400-normal-BbP7GpLL.woff2 +0 -0
  896. package/out/web/assets/sarabun-latin-ext-400-normal-BsmTn0SS.woff +0 -0
  897. package/out/web/assets/sarabun-latin-ext-500-normal-Cot16Bd2.woff +0 -0
  898. package/out/web/assets/sarabun-latin-ext-500-normal-DE1LysqI.woff2 +0 -0
  899. package/out/web/assets/sarabun-latin-ext-600-normal-0tXu9x_i.woff +0 -0
  900. package/out/web/assets/sarabun-latin-ext-600-normal-BRxjKUQ6.woff2 +0 -0
  901. package/out/web/assets/sarabun-thai-400-normal-C2DaJlKK.woff2 +0 -0
  902. package/out/web/assets/sarabun-thai-400-normal-CH-UbWgg.woff +0 -0
  903. package/out/web/assets/sarabun-thai-500-normal-BVssjame.woff2 +0 -0
  904. package/out/web/assets/sarabun-thai-500-normal-C3iDLEzi.woff +0 -0
  905. package/out/web/assets/sarabun-thai-600-normal-3dIAQ-_s.woff2 +0 -0
  906. package/out/web/assets/sarabun-thai-600-normal-DWKeTe6a.woff +0 -0
  907. package/out/web/assets/sarabun-vietnamese-400-normal-DPfkSCMy.woff +0 -0
  908. package/out/web/assets/sarabun-vietnamese-400-normal-igdrzAGY.woff2 +0 -0
  909. package/out/web/assets/sarabun-vietnamese-500-normal-CpRVvGRU.woff +0 -0
  910. package/out/web/assets/sarabun-vietnamese-500-normal-Dpe7Br9m.woff2 +0 -0
  911. package/out/web/assets/sarabun-vietnamese-600-normal-BMbOgAW-.woff +0 -0
  912. package/out/web/assets/sarabun-vietnamese-600-normal-DkxrOlLB.woff2 +0 -0
  913. package/out/web/assets/stateDiagram-v2-4QOOHH4V-s6P9g2_b.js +0 -1
  914. package/out/web/assets/swimlanesDiagram-VK2B7HYN-CV9fOLrV.js +0 -8
  915. package/out/web/assets/tauri-CNrHQ2Gb.js +0 -3
  916. package/out/web/assets/window-DkIhgI7B.js +0 -1
  917. package/skills-lock.json +0 -41
  918. package/src/renderer/public/Live2DCubismCore.js +0 -9
  919. package/src/renderer/src/companionWidget.ts +0 -95
  920. package/src/renderer/src/components/Live2DStage.tsx +0 -389
  921. package/src/renderer/src/components/ModelPanel.tsx +0 -154
  922. package/src/renderer/src/css/model.css +0 -627
  923. package/src/renderer/src-web/companionWidget.ts +0 -37
  924. package/src/renderer/src-web/components/ModelPanel.tsx +0 -12
  925. package/src/renderer/src-web/css/model.css +0 -628
@@ -1,41 +1,35 @@
1
- import { type ChangeEvent, type CSSProperties, type FormEvent, useEffect, useRef, useState } from 'react'
2
- import { mergeActivitySnapshots, trimAgentProgress } from '../agentProgress'
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'
3
6
  import {
4
- clearChatHistory,
5
- deleteChatSession,
6
- renameChatSession,
7
- getRecentInteractions,
8
- saveSystemInteraction,
9
- getRuntimeStatus,
10
- setActiveModel,
11
- listChatSessions,
12
- listSavedPictures,
13
- selectWorkspaceDirectory,
14
- selectLinkedFolderPath,
15
- saveInteractionAgentActivity,
16
- streamChatMessage,
17
- cancelChatMessage,
18
- submitToolApproval,
19
- listen,
20
- readClipboardImage,
21
- isTauriRuntime,
22
- type AgentProgress,
23
- type ChatResponse,
24
- type ChatSession,
25
- type DocumentAttachment,
26
- type PictureEntry,
27
- type RuntimeStatus,
28
- } from '@/tauri'
29
-
30
- import ChatPanel from './ChatPanel'
7
+ mergeFileChanges,
8
+ parseFileChangesFromProgress,
9
+ } from '../agentProgress'
10
+ import { catalogPlatform, conversationPlatform, mediaPlatform, runtimePlatform, workspacePlatform, type SlashResponse } from '../platform'
11
+ import type { ChatSession, DocumentAttachment, RuntimeStatus } from '../types'
12
+
13
+ const {
14
+ clearChatHistory, deleteChatSession, renameChatSession,
15
+ saveSystemInteraction, updateChatSessionWorkspace,
16
+ listen, readClipboardImage,
17
+ } = conversationPlatform
18
+ const { getRuntimeStatus, setActiveModel, selectWorkspaceDirectory, selectLinkedFolderPath, isTauriRuntime } = runtimePlatform
19
+ const { convertFileSrc } = mediaPlatform
20
+ const {
21
+ listLearnedSkills, addLearnedSkill, deleteLearnedSkill, detectSystemTools, reauthMcpServer,
22
+ listMcpServerTools, setProfileValue, listCronJobs, addCronJob, removeCronJob,
23
+ setCronJobEnabled, listLinkedFolders, addLinkedFolder, removeLinkedFolder, runSlashCommand,
24
+ linkedFolderStatus, refreshLinkedFolder, listLinkedFolderNotes, readLinkedFolderNote, openLinkedFolderNote,
25
+ } = catalogPlatform
26
+
27
+ import ChatPanel, { type ConversationActions, type ConversationViewModel } from './ChatPanel'
28
+ import ScreenCaptureDialog from './ScreenCaptureDialog'
31
29
  import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
32
- import ImageStudioPanel from './ImageStudioPanel'
33
- import VeoStudioPanel from './VeoStudioPanel'
34
- import ModelPanel from '@/components/ModelPanel'
35
- import type { ModelInteraction } from '@/components/ModelPanel'
36
- import PicturesLibrary from '@/components/PicturesLibrary'
37
- import WorkspacePanel from '@/components/WorkspacePanel'
38
- import { CommandPalette } from './CommandPalette'
30
+ import DesktopTitlebar from './DesktopTitlebar'
31
+ import ErrorNotice from './ErrorNotice'
32
+ import type { ToolSurface } from './ToolSurfacePage'
39
33
  import {
40
34
  errorMessage,
41
35
  readImage,
@@ -45,32 +39,37 @@ import {
45
39
  applyThemeStyles,
46
40
  parseUtcDate,
47
41
  } from '../utils/ui'
42
+ import { workspacePaths } from '../utils/workspaces'
48
43
  import { executeSlashCommand } from '../utils/slashCommandProcessor'
44
+ import { captureScreenForChat } from '../utils/screenCapture'
45
+ import { useConversationCoordinator } from '../conversation/useConversationCoordinator'
46
+ import { isPersistedConversation } from '../conversation/projectNavigation'
47
+ import type { PromptOptions } from '../conversation/lifecycle'
49
48
 
50
49
 
51
- const EXPRESSIONS = [
52
- "Default",
53
- "Dumb Cat",
54
- "Dumb Cat Eye Roll",
55
- "Take Photo",
56
- "Poke",
57
- "Cat Filter",
58
- ]
59
50
 
60
- const ACCESSORIES = [
61
- "None",
62
- "Apron",
63
- "Glasses",
64
- "Hold Pen",
65
- ]
66
-
67
- import { DEFAULT_CONFIG } from '../constants/config'
51
+ import { DEFAULT_CONFIG, migrateFontFamily, migrateTypographyScale } from '../constants/config'
52
+ import { BackgroundTerminals } from './BackgroundTerminals'
68
53
 
69
54
  const LAST_WORKSPACE_PATH_KEY = 'mint:last-workspace-path'
55
+ const RECENT_WORKSPACE_PATHS_KEY = 'mint:recent-workspace-paths'
56
+ const MAX_RECENT_WORKSPACES = 8
70
57
  const ACTIVE_CONVERSATION_ID_KEY = 'mint:active-conversation-id'
71
58
  const LAST_ACTIVE_TIME_KEY = 'mint:last-active-timestamp'
72
59
  const DESKTOP_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 // 30 minutes
73
60
 
61
+ function readRecentWorkspacePaths(): string[] {
62
+ if (typeof window === 'undefined') return []
63
+ try {
64
+ const parsed: unknown = JSON.parse(window.localStorage.getItem(RECENT_WORKSPACE_PATHS_KEY) || '[]')
65
+ return Array.isArray(parsed)
66
+ ? [...new Set(parsed.filter((path): path is string => typeof path === 'string' && Boolean(path.trim())))].slice(0, MAX_RECENT_WORKSPACES)
67
+ : []
68
+ } catch {
69
+ return []
70
+ }
71
+ }
72
+
74
73
  function touchActiveTimestamp() {
75
74
  if (typeof window === 'undefined') return
76
75
  try {
@@ -103,25 +102,14 @@ function isDesktopRecentlyActive(): boolean {
103
102
  // Unsent composer text, stashed per conversation so switching chats or
104
103
  // reloading the page doesn't lose an in-progress message.
105
104
  const DRAFT_KEY_PREFIX = 'mint:draft:'
106
- const draftStorageKey = (id: string) => `${DRAFT_KEY_PREFIX}${id}`
107
105
  function readDraft(id: string | null | undefined): string {
108
106
  if (!id) return ''
109
107
  try {
110
- return window.localStorage.getItem(draftStorageKey(id)) || ''
108
+ return window.localStorage.getItem(`${DRAFT_KEY_PREFIX}${id}`) || ''
111
109
  } catch {
112
110
  return ''
113
111
  }
114
112
  }
115
- function writeDraft(id: string | null | undefined, value: string) {
116
- if (!id) return
117
- try {
118
- if (value) window.localStorage.setItem(draftStorageKey(id), value)
119
- else window.localStorage.removeItem(draftStorageKey(id))
120
- } catch {
121
- /* private mode / quota — draft persistence is best-effort */
122
- }
123
- }
124
-
125
113
  const SIDEBAR_DEFAULT_WIDTH = 264
126
114
  const SIDEBAR_MIN_WIDTH = 200
127
115
  const SIDEBAR_MAX_WIDTH = 420
@@ -172,6 +160,10 @@ function isRootOrNewChatRoute(): boolean {
172
160
  target.includes('imagine') ||
173
161
  target.includes('veo-studio') ||
174
162
  target.includes('veo') ||
163
+ target.includes('code') ||
164
+ target === '/cli' ||
165
+ target === 'cli' ||
166
+ target.includes('cli-sessions') ||
175
167
  target.includes('settings')
176
168
  ) {
177
169
  return false
@@ -200,7 +192,7 @@ function activeConversationId() {
200
192
  }
201
193
  }
202
194
 
203
- // When visiting Root URL ('/' or '/chat' without specific ID), always start a fresh New Chat
195
+ // When visiting Root URL ('/' or '/chat' without specific ID), always start a fresh New chat
204
196
  // (matching ChatGPT / Claude / Gemini industry-standard UX).
205
197
  // Applies to Web UI always, and Desktop App upon cold launch / inactivity / new day.
206
198
  if (isRootOrNewChatRoute()) {
@@ -234,31 +226,25 @@ const MOCK_WELCOME_INTERACTION = {
234
226
  }
235
227
 
236
228
 
237
- import SkillsView from './SkillsView'
238
- import ScheduledTasksView from './ScheduledTasksView'
239
- import LinkedFoldersView from './LinkedFoldersView'
240
- import McpServersView from './McpServersView'
241
- import PluginsView from './PluginsView'
242
229
  import { isSupportedDocument } from '../utils/documentTypes'
243
- import { useCompanionWidget } from '@/companionWidget'
244
- import {
245
- listLearnedSkills,
246
- addLearnedSkill,
247
- deleteLearnedSkill,
248
- detectSystemTools,
249
- reauthMcpServer,
250
- listMcpServerTools,
251
- setProfileValue,
252
- listCronJobs,
253
- addCronJob,
254
- removeCronJob,
255
- setCronJobEnabled,
256
- listLinkedFolders,
257
- addLinkedFolder,
258
- removeLinkedFolder,
259
- runSlashCommand,
260
- } from '@/tauri'
261
- import type { SlashResponse } from '../platform'
230
+ import { useProactiveSuggestions } from '@/proactiveSuggestions'
231
+
232
+ const WorkspacePanel = lazy(() => import('@/components/WorkspacePanel'))
233
+ const ToolSurfacePage = lazy(() => import('./ToolSurfacePage'))
234
+ const SkillsView = lazy(() => import('./SkillsView'))
235
+ const ScheduledTasksView = lazy(() => import('./ScheduledTasksView'))
236
+ const LinkedFoldersView = lazy(() => import('./LinkedFoldersView'))
237
+ const McpServersView = lazy(() => import('./McpServersView'))
238
+ const PluginsView = lazy(() => import('./PluginsView'))
239
+ const CliSessionsView = lazy(() => import('./CliSessionsView'))
240
+ const LibraryPage = lazy(() => import('./LibraryPage'))
241
+ const ImageStudioPanel = lazy(() => import('./ImageStudioPanel'))
242
+ const VeoStudioPanel = lazy(() => import('./VeoStudioPanel'))
243
+ const CommandPalette = lazy(() => import('./CommandPalette').then(({ CommandPalette: Component }) => ({ default: Component })))
244
+
245
+ function LazyPanelFallback() {
246
+ return <div className="auth-gate-loading" style={{ minHeight: 160 }}>Loading panel…</div>
247
+ }
262
248
 
263
249
  function getInitialViewFromUrl(): DashboardView {
264
250
  if (typeof window === 'undefined') return 'chat'
@@ -269,9 +255,10 @@ function getInitialViewFromUrl(): DashboardView {
269
255
  if (target.includes('skills')) return 'skills'
270
256
  if (target.includes('mcp')) return 'mcp'
271
257
  if (target.includes('plugins')) return 'plugins'
272
- if (target.includes('picture')) return 'pictures'
258
+ if (target.includes('picture') || target.includes('library')) return 'pictures'
273
259
  if (target.includes('image-studio') || target.includes('imagine')) return 'imagine'
274
260
  if (target.includes('veo-studio') || target.includes('veo')) return 'veo'
261
+ if (target.includes('code') || target === '/cli' || target === 'cli' || target.includes('cli-sessions')) return 'code'
275
262
  return 'chat'
276
263
  }
277
264
 
@@ -279,9 +266,10 @@ function getCleanPathForView(v: string, activeId?: string): string {
279
266
  if (v === 'skills') return '/skills'
280
267
  if (v === 'mcp') return '/mcp'
281
268
  if (v === 'plugins') return '/plugins'
282
- if (v === 'pictures') return '/pictures'
269
+ if (v === 'pictures') return '/library'
283
270
  if (v === 'imagine') return '/image-studio'
284
271
  if (v === 'veo' || v === 'veo_studio') return '/veo-studio'
272
+ if (v === 'code') return '/code'
285
273
  if (v === 'settings') return '/settings'
286
274
  if (activeId) return `/chat/${encodeURIComponent(activeId)}`
287
275
  return '/chat'
@@ -317,27 +305,93 @@ 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
- // state) so the URL-change effect below can read it — that effect closes
335
- // over `workspacePath` to scope its own `getRecentInteractions` call.
336
- const [workspacePath, setWorkspacePath] = useState(() => window.localStorage.getItem(LAST_WORKSPACE_PATH_KEY) || '')
313
+ // state) so the URL-change effect below can read it.
314
+ const { conversation, actions: conversationActions } = useConversationCoordinator(
315
+ {
316
+ workspacePath: window.localStorage.getItem(LAST_WORKSPACE_PATH_KEY) || '',
317
+ message: readDraft(initialConversationId),
318
+ },
319
+ {
320
+ conversationId: initialConversationId,
321
+ draftKeyPrefix: DRAFT_KEY_PREFIX,
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
+ },
347
+ },
348
+ )
349
+ const {
350
+ conversationId, workspacePath, message, imageAttachments, videoAttachments, documentAttachment,
351
+ sending, sendingMessage, sendingImageCount, sendingVideoCount, streamedReply,
352
+ streamedResponse, agentProgress, interactions, hasOlderInteractions,
353
+ streamingConversationId, activeTurnId, agentActivitySnapshots, pendingApprovals,
354
+ sessionAutoApproved, chatSessions,
355
+ } = conversation
356
+ const activateConversation = (id: string) => {
357
+ conversationActions.activate(id)
358
+ }
337
359
  // Web only in practice (desktop's window has no mobile-width breakpoint,
338
360
  // so nothing ever sets this true there) — declared unconditionally so
339
361
  // `changeView` can close it on every navigation without branching.
340
362
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
363
+ const [screenCaptureOpen, setScreenCaptureOpen] = useState(false)
364
+
365
+ useEffect(() => {
366
+ if (!mobileSidebarOpen) return
367
+ const closeOnEscape = (event: KeyboardEvent) => {
368
+ if (event.key === 'Escape') setMobileSidebarOpen(false)
369
+ }
370
+ window.addEventListener('keydown', closeOnEscape)
371
+ return () => window.removeEventListener('keydown', closeOnEscape)
372
+ }, [mobileSidebarOpen])
373
+ const [workspaceRefreshRevision, setWorkspaceRefreshRevision] = useState(0)
374
+ const [recentWorkspacePaths, setRecentWorkspacePaths] = useState<string[]>(readRecentWorkspacePaths)
375
+
376
+ function rememberWorkspace(path: string) {
377
+ const recent = workspacePaths([path, ...readRecentWorkspacePaths()]).slice(0, MAX_RECENT_WORKSPACES)
378
+ try {
379
+ window.localStorage.setItem(RECENT_WORKSPACE_PATHS_KEY, JSON.stringify(recent))
380
+ } catch {
381
+ /* Keep the in-memory recent list usable when storage is unavailable. */
382
+ }
383
+ setRecentWorkspacePaths(recent)
384
+ }
385
+
386
+ useEffect(() => {
387
+ if (workspacePath.trim()) rememberWorkspace(workspacePath)
388
+ }, [workspacePath])
389
+
390
+ async function selectCodeHubFolder() {
391
+ const selected = await selectLinkedFolderPath()
392
+ if (selected) rememberWorkspace(selected)
393
+ return selected
394
+ }
341
395
 
342
396
  const changeView = (newView: any, targetConversationId?: string) => {
343
397
  setMobileSidebarOpen(false)
@@ -353,7 +407,10 @@ export default function MintDashboard() {
353
407
  }
354
408
  return prev
355
409
  }
356
- 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
357
414
  const targetPath = getCleanPathForView(mappedView, targetConversationId || conversationId)
358
415
  if (typeof window !== 'undefined' && window.location.pathname !== targetPath) {
359
416
  window.history.pushState({}, '', targetPath)
@@ -376,23 +433,13 @@ export default function MintDashboard() {
376
433
  const urlSessionId = getConversationIdFromUrl()
377
434
  if (urlSessionId && urlSessionId !== conversationId) {
378
435
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, urlSessionId)
379
- setConversationId(urlSessionId)
380
- getRecentInteractions(50, urlSessionId, workspacePath || null).then((history) => {
381
- const reversed = history.reverse()
382
- setInteractions(reversed)
383
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
436
+ activateConversation(urlSessionId)
437
+ conversationActions.refreshHistory().catch((error) => {
438
+ console.error('Failed to load conversation history:', error)
384
439
  })
385
440
  } else if (!urlSessionId && isRootOrNewChatRoute()) {
386
441
  const next = createConversationId()
387
- setConversationId(next)
388
- setInteractions([])
389
- setAgentActivitySnapshots({})
390
- setStreamedReply('')
391
- setStreamedResponse(null)
392
- setMessage('')
393
- setImageAttachments([])
394
- setDocumentAttachment(null)
395
- setAgentProgress([])
442
+ activateConversation(next)
396
443
  }
397
444
  }
398
445
  window.addEventListener('popstate', handleUrlChange)
@@ -417,69 +464,99 @@ export default function MintDashboard() {
417
464
  }, [])
418
465
  const [status, setStatus] = useState<RuntimeStatus | null>(null)
419
466
  const [error, setError] = useState('')
420
- const [message, setMessage] = useState(() => readDraft(conversationId))
421
- // When `conversationId` changes we swap `message` to that chat's saved draft;
422
- // this ref tells the persist effect to skip the render right after that swap,
423
- // so the outgoing chat's text is never written under the incoming chat's key.
424
- const skipDraftPersistRef = useRef(false)
425
- const [interactions, setInteractions] = useState<any[]>([])
426
- const [pictures, setPictures] = useState<PictureEntry[]>([])
427
- const [sending, setSending] = useState(false)
428
- const [sendingMessage, setSendingMessage] = useState('')
429
- const [sendingImageCount, setSendingImageCount] = useState(0)
430
- const [sendingVideoCount, setSendingVideoCount] = useState(0)
431
- const [streamedReply, setStreamedReply] = useState('')
432
- const [streamedResponse, setStreamedResponse] = useState<ChatResponse | null>(null)
433
- const [streamingConversationId, setStreamingConversationId] = useState<string | null>(null)
434
- const [agentProgress, setAgentProgress] = useState<AgentProgress[]>([])
435
- 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
+
436
513
  const [thinkingExpanded, setThinkingExpanded] = useState<Record<string, boolean>>({})
437
514
  const liveThinkingOpenRef = useRef(true)
438
- const [imageAttachments, setImageAttachments] = useState<Array<{ dataUri: string; name: string; previewDataUri?: string }>>([])
439
- const [videoAttachments, setVideoAttachments] = useState<Array<{ dataUri: string; name: string }>>([])
440
- const [documentAttachment, setDocumentAttachment] = useState<DocumentAttachment | null>(null)
441
- const [pendingApproval, setPendingApproval] = useState<any | null>(null)
442
- const [sessionAutoApproved, setSessionAutoApproved] = useState(false)
443
- const sessionAutoApprovedRef = useRef(false)
515
+ const liveExtendedThinkingOpenRef = useRef(true)
444
516
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => window.localStorage.getItem('mint:sidebar-collapsed') === 'true')
517
+ const [toolSurfaces, setToolSurfaces] = useState<ToolSurface[]>([])
518
+ const [activeSurfaceId, setActiveSurfaceId] = useState<string | null>(null)
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)
523
+ const [terminalRightWidth, setTerminalRightWidth] = useState(() => {
524
+ const saved = Number(window.localStorage.getItem('mint:terminal-right-width'))
525
+ return saved >= 280 && saved <= 900 ? saved : 440
526
+ })
527
+ const terminalSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
445
528
  const [sidebarWidth, setSidebarWidth] = useState(() => {
446
529
  const saved = Number(window.localStorage.getItem('mint:sidebar-width'))
447
530
  return saved >= SIDEBAR_MIN_WIDTH && saved <= SIDEBAR_MAX_WIDTH ? saved : SIDEBAR_DEFAULT_WIDTH
448
531
  })
449
- const [smartContext, setSmartContext] = useState(() => window.localStorage.getItem('mint:smart-context') !== 'false')
532
+ const recentAgentChanges = parseFileChangesFromProgress(agentProgress)
533
+ const conversationReviewChanges = mergeFileChanges([
534
+ ...interactions.map((interaction) => {
535
+ const interactionId = String(interaction.id)
536
+ return parseFileChangesFromProgress(agentActivitySnapshots[interactionId] ?? interaction.agentActivity ?? [])
537
+ }),
538
+ parseFileChangesFromProgress(agentProgress),
539
+ ])
540
+
450
541
  const [agentMode, setAgentMode] = useState(() => window.localStorage.getItem('mint:agent-mode') === 'true')
451
542
  const [planMode, setPlanMode] = useState(() => window.localStorage.getItem('mint:plan-mode') === 'true')
452
543
  const [toastMessage, setToastMessage] = useState('')
453
544
  const [dashboardDataReady, setDashboardDataReady] = useState(false)
454
545
  const [startupTimedOut, setStartupTimedOut] = useState(false)
455
546
  const [settingsConfig, setSettingsConfig] = useState<any>(null)
456
- 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])
457
556
  const chatEnd = useRef<HTMLDivElement | null>(null)
458
557
  const lastNativePasteTimeRef = useRef(0)
459
- const {
460
- modelVisible,
461
- scale,
462
- interactionEnabled,
463
- showInteractionGuide,
464
- isLocked,
465
- layoutPreset,
466
- expressionIndex,
467
- accessoryIndex,
468
- modelReady,
469
- proactiveSuggestion,
470
- setScale,
471
- setIsLocked,
472
- setExpressionIndex,
473
- setAccessoryIndex,
474
- setModelReady,
475
- toggleModel,
476
- changeLayoutPreset,
477
- updateInteractionEnabled,
478
- updateInteractionGuide,
479
- dismissProactiveSuggestion,
480
- handleProactiveAction,
481
- } = useCompanionWidget((message) => setError(message))
482
- const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
558
+ const { proactiveSuggestion, dismissProactiveSuggestion, handleProactiveAction } = useProactiveSuggestions((message) => setError(message))
559
+ const startupReady = dashboardDataReady || startupTimedOut
483
560
 
484
561
  const [mcpName, setMcpName] = useState('')
485
562
  const [mcpCmd, setMcpCmd] = useState('')
@@ -590,64 +667,6 @@ export default function MintDashboard() {
590
667
  }
591
668
  }, [isSearchOpen])
592
669
 
593
- // Another process — the CLI's interactive chat, a scheduled task, a
594
- // messaging bridge — can add messages to this same conversation while
595
- // this page is open, and nothing here would otherwise notice: Mint's
596
- // surfaces only share a SQLite file, not a live event bus, so this polls
597
- // for changes instead of requiring a manual refresh. Kept in a ref rather
598
- // than the effect's own dependency array so a poll landing doesn't tear
599
- // down and recreate the interval on every tick.
600
- const interactionsRef = useRef(interactions)
601
- useEffect(() => {
602
- interactionsRef.current = interactions
603
- }, [interactions])
604
-
605
- // Load the saved draft whenever the active conversation changes. Declared
606
- // before the persist effect so it runs first within the same commit. The
607
- // first run is a no-op: `message`'s initial value already came from the
608
- // lazy `useState` initializer above.
609
- const draftLoadedRef = useRef(false)
610
- useEffect(() => {
611
- if (!draftLoadedRef.current) {
612
- draftLoadedRef.current = true
613
- return
614
- }
615
- skipDraftPersistRef.current = true
616
- setMessage(readDraft(conversationId))
617
- }, [conversationId])
618
-
619
- // Persist the composer text for the current conversation (debounced).
620
- useEffect(() => {
621
- if (skipDraftPersistRef.current) {
622
- skipDraftPersistRef.current = false
623
- return
624
- }
625
- const timer = window.setTimeout(() => writeDraft(conversationId, message), 300)
626
- return () => window.clearTimeout(timer)
627
- }, [message, conversationId])
628
-
629
- useEffect(() => {
630
- if (view !== 'chat' || !conversationId) return
631
- const interval = window.setInterval(async () => {
632
- // Skip while backgrounded (nothing to show anyone) or mid-send (a
633
- // fetch landing here would clobber the in-flight optimistic/streamed
634
- // reply with stale history).
635
- if (document.visibilityState !== 'visible' || sending) return
636
- try {
637
- const history = (await getRecentInteractions(50, conversationId, workspacePath || null)).reverse()
638
- const current = interactionsRef.current
639
- const currentLast = current[current.length - 1]
640
- const nextLast = history[history.length - 1]
641
- if (current.length === history.length && currentLast?.id === nextLast?.id) return
642
- setInteractions(history)
643
- setAgentActivitySnapshots((snapshots) => mergeActivitySnapshots(snapshots, history))
644
- } catch {
645
- // Best-effort — a transient fetch failure just waits for the next tick.
646
- }
647
- }, 3000)
648
- return () => window.clearInterval(interval)
649
- }, [view, conversationId, sending, workspacePath])
650
-
651
670
  const filteredSessions = chatSessions.filter((session) => {
652
671
  if (session.kind === 'cli' || session.id === 'conversation-default') return false
653
672
  return session.title.toLowerCase().includes(searchQuery.toLowerCase())
@@ -699,28 +718,42 @@ export default function MintDashboard() {
699
718
  const groupedSearchSessions = groupSessionsByDate(filteredSessions)
700
719
 
701
720
 
702
- async function refreshHistory() {
703
- const history = await getRecentInteractions(50, conversationId, workspacePath || null)
704
- const reversed = history.reverse()
705
- setInteractions(reversed)
706
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
707
- }
708
-
709
- async function refreshChatSessions() {
710
- const sessions = await listChatSessions()
711
- setChatSessions(sessions)
712
- }
721
+ const refreshHistory = conversationActions.refreshHistory
722
+ const refreshChatSessions = useCallback(async () => {
723
+ try { await conversationActions.refreshSessions() }
724
+ catch (error) { console.error('Failed to refresh chat sessions:', error) }
725
+ }, [conversationActions])
726
+ const loadOlderInteractions = conversationActions.loadOlder
713
727
 
714
- const [picturesRefreshing, setPicturesRefreshing] = useState(false)
715
-
716
- async function refreshPictures() {
717
- setPicturesRefreshing(true)
728
+ const workspaceUpdateQueues = useRef(new Map<string, Promise<void>>())
729
+ const handleUpdateSessionWorkspace = useCallback(async (sessionId: string, targetPath: string | null) => {
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 () => {
733
+ await updateChatSessionWorkspace(sessionId, targetPath)
734
+ await refreshChatSessions()
735
+ })
736
+ workspaceUpdateQueues.current.set(sessionId, update)
718
737
  try {
719
- setPictures(await listSavedPictures())
738
+ await update
739
+ } catch (e) {
740
+ console.error('Failed to update session workspace:', e)
741
+ setError(errorMessage(e))
720
742
  } finally {
721
- setPicturesRefreshing(false)
743
+ if (workspaceUpdateQueues.current.get(sessionId) === update) {
744
+ workspaceUpdateQueues.current.delete(sessionId)
745
+ }
722
746
  }
723
- }
747
+ }, [refreshChatSessions])
748
+
749
+ const handleBrowseFolderForMove = useCallback(async () => {
750
+ try {
751
+ const selected = await selectWorkspaceDirectory()
752
+ return selected || null
753
+ } catch {
754
+ return null
755
+ }
756
+ }, [])
724
757
 
725
758
  useEffect(() => {
726
759
  Promise.allSettled([
@@ -729,8 +762,9 @@ export default function MintDashboard() {
729
762
  refreshChatSessions(),
730
763
  window.settingsApi?.getSettings()
731
764
  .then((loaded: any) => {
732
- setSettingsConfig(loaded)
733
- applyThemeStyles({ ...DEFAULT_CONFIG, ...loaded })
765
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
766
+ setSettingsConfig(migrated)
767
+ applyThemeStyles({ ...DEFAULT_CONFIG, ...migrated })
734
768
  }),
735
769
  ]).then((results) => {
736
770
  const failure = results.find((result) => result.status === 'rejected')
@@ -739,21 +773,15 @@ export default function MintDashboard() {
739
773
  })
740
774
  const unlistenSpotlight = window.api.onSpotlightToChat((query) => {
741
775
  changeView('chat')
742
- setMessage(query)
743
- })
744
- const unlistenVision = window.api.onVisionReady((image) => {
745
- createTrimmedImagePreview(image)
746
- .catch(() => image)
747
- .then((previewDataUri) => {
748
- setImageAttachments((current) => [...current, { dataUri: image, previewDataUri, name: 'Screen capture' }])
749
- })
776
+ conversationActions.compose(query)
750
777
  })
751
778
  const handleWindowFocus = () => {
752
779
  getRuntimeStatus().then(setStatus).catch(() => {})
753
780
  window.settingsApi?.getSettings?.().then((loaded: any) => {
754
781
  if (loaded) {
755
- setSettingsConfig(loaded)
756
- applyThemeStyles(loaded)
782
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
783
+ setSettingsConfig(migrated)
784
+ applyThemeStyles(migrated)
757
785
  }
758
786
  }).catch(() => {})
759
787
  window.api?.clearAiNotifications?.()
@@ -761,26 +789,20 @@ export default function MintDashboard() {
761
789
  window.addEventListener('focus', handleWindowFocus)
762
790
 
763
791
  window.api?.onSettingsChanged?.((loaded: any) => {
764
- setSettingsConfig(loaded)
765
- applyThemeStyles(loaded)
792
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
793
+ setSettingsConfig(migrated)
794
+ applyThemeStyles(migrated)
766
795
  getRuntimeStatus().then(setStatus).catch(() => {})
767
796
  })
768
797
 
769
- const unlistenPromise = listen<any>('tool-approval-requested', (event) => {
770
- if (sessionAutoApprovedRef.current) {
771
- submitToolApproval(event.payload.token, true).catch((err) => {
772
- console.error("Auto approval failed:", err)
773
- })
774
- } else {
775
- setPendingApproval(event.payload)
776
- notifyPendingApproval(event.payload)
777
- }
798
+ const unlistenPromise = listen('tool-approval-requested', (event: { payload: any }) => {
799
+ const chatId = event.payload.chatId as string | undefined
800
+ if (chatId) conversationActions.receiveApproval(chatId, event.payload)
778
801
  })
779
802
  return () => {
780
803
  window.removeEventListener('focus', handleWindowFocus)
781
804
  unlistenPromise?.then?.((unlisten) => unlisten?.())
782
805
  unlistenSpotlight?.then?.((unlisten) => unlisten?.())
783
- unlistenVision?.then?.((unlisten) => unlisten?.())
784
806
  }
785
807
  }, [])
786
808
 
@@ -789,23 +811,195 @@ export default function MintDashboard() {
789
811
  return () => window.clearTimeout(timer)
790
812
  }, [])
791
813
 
792
- useEffect(() => {
793
- if (view === 'pictures' || view === 'imagine') refreshPictures().catch((reason: unknown) => setError(errorMessage(reason)))
794
- }, [view])
795
-
796
814
  useEffect(() => {
797
815
  if (view === 'workspace' && !agentMode) updateAgentMode(true)
798
816
  }, [view, agentMode])
799
817
 
800
- useEffect(() => {
801
- chatEnd.current?.scrollIntoView({ behavior: 'smooth' })
802
- }, [interactions, sending, streamedReply, pendingApproval, agentProgress])
803
-
804
818
  const showToast = (nextMessage: string) => {
805
819
  setToastMessage(nextMessage)
806
820
  setTimeout(() => setToastMessage((current) => current === nextMessage ? '' : current), 3000)
807
821
  }
808
822
 
823
+ const openMintBrowser = async (url: string, throwOnError = false) => {
824
+ if (!isDesktopApp) {
825
+ window.open(url, '_blank', 'noopener,noreferrer')
826
+ return
827
+ }
828
+ try {
829
+ showToast('Opening Co-browsing browser (Mint Auto)...')
830
+ const { invoke } = await import('@tauri-apps/api/core')
831
+ await invoke('open_mint_browser', { url })
832
+ } catch (error) {
833
+ if (throwOnError) throw error
834
+ showToast(`Could not open browser: ${String(error)}`)
835
+ }
836
+ }
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
+
862
+ const openTerminalSurface = () => {
863
+ setToolsPanelOpen(true)
864
+ const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
865
+ setToolSurfaces((current) => {
866
+ const terminalCount = current.filter((surface) => surface.kind === 'terminal').length
867
+ let baseTitle = 'terminal'
868
+ if (workspacePath && workspacePath.trim()) {
869
+ const cleanPath = workspacePath.trim().replace(/[\\/]+$/, '')
870
+ const folderName = cleanPath.split(/[\\/]/).pop()
871
+ if (folderName) baseTitle = folderName
872
+ }
873
+ const title = terminalCount === 0 ? baseTitle : `${baseTitle} (${terminalCount + 1})`
874
+ const next = { id, kind: 'terminal' as const, title }
875
+ setActiveSurfaceId(id)
876
+ return [...current, next]
877
+ })
878
+ }
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
+
891
+ const openNativeBrowser = (url = 'https://www.google.com') => {
892
+ void openMintBrowser(url)
893
+ }
894
+
895
+ const duplicateToolSurface = (id: string) => {
896
+ setToolSurfaces((current) => {
897
+ const source = current.find((surface) => surface.id === id)
898
+ if (!source) return current
899
+ if (source.kind === 'mods' || source.kind === 'background') return current
900
+ const duplicateId = `${source.kind}:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
901
+ const duplicate = { ...source, id: duplicateId, title: `${source.title} copy` } as ToolSurface
902
+ setActiveSurfaceId(duplicateId)
903
+ return [...current, duplicate]
904
+ })
905
+ }
906
+
907
+ const renameToolSurface = (id: string) => {
908
+ const source = toolSurfaces.find((surface) => surface.id === id)
909
+ if (!source) return
910
+ const nextTitle = window.prompt('Rename tab', source.title)?.trim()
911
+ if (!nextTitle) return
912
+ setToolSurfaces((current) => current.map((surface) => surface.id === id ? { ...surface, title: nextTitle } : surface))
913
+ }
914
+
915
+ const closeOtherToolSurfaces = (id: string) => {
916
+ setToolSurfaces((current) => current.filter((surface) => surface.id === id))
917
+ setActiveSurfaceId(id)
918
+ }
919
+
920
+ const toggleTerminalSurface = () => {
921
+ setToolSurfaces((current) => {
922
+ const terminal = current.find((surface) => surface.kind === 'terminal')
923
+ if (!terminal) {
924
+ const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
925
+ setActiveSurfaceId(id)
926
+ return [...current, { id, kind: 'terminal' as const, title: 'Terminal' }]
927
+ }
928
+ setActiveSurfaceId((active) => active === terminal.id ? null : terminal.id)
929
+ return current
930
+ })
931
+ }
932
+
933
+ const checkForUpdatesFromMenu = async () => {
934
+ try {
935
+ const update = await window.settingsApi.checkForUpdates()
936
+ showToast(update?.available ? `Mint ${update.version} is available.` : 'Mint is up to date.')
937
+ } catch (error) {
938
+ showToast(`Could not check for updates: ${String(error)}`)
939
+ }
940
+ }
941
+
942
+ const openArtifactSurface = (artifact: import('./ArtifactPreviewPanel').ArtifactFile) => {
943
+ setToolsPanelOpen(true)
944
+ const fileName = artifact.path.replace(/\\/g, '/').split('/').pop() || artifact.path
945
+ artifact = { ...artifact, workspacePath: artifact.workspacePath ?? workspacePath }
946
+ const id = `preview:${JSON.stringify([artifact.workspacePath, artifact.path])}`
947
+ setToolSurfaces((current) => {
948
+ const next = current.filter((surface) => surface.id !== id)
949
+ return [...next, { id, kind: 'preview', title: fileName, artifact }]
950
+ })
951
+ setActiveSurfaceId(id)
952
+ }
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
+
962
+ const openReviewSurface = (review: Extract<ToolSurface, { kind: 'review' }>) => {
963
+ setToolsPanelOpen(true)
964
+ setToolSurfaces((current) => [...current.filter((surface) => surface.id !== review.id), review])
965
+ setActiveSurfaceId(review.id)
966
+ }
967
+
968
+ const openConversationReview = () => {
969
+ setToolsPanelOpen(true)
970
+ setToolSurfaces((current) => {
971
+ const reviewCount = current.filter((surface) => surface.kind === 'review').length
972
+ const id = `review:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
973
+ const title = reviewCount === 0 ? 'Review' : `Review (${reviewCount + 1})`
974
+ const next = {
975
+ id,
976
+ kind: 'review' as const,
977
+ title,
978
+ reviewTitle: 'All conversation changes',
979
+ changes: conversationReviewChanges,
980
+ }
981
+ setActiveSurfaceId(id)
982
+ return [...current, next]
983
+ })
984
+ }
985
+
986
+ const closeToolSurface = (id: string) => {
987
+ setToolSurfaces((current) => {
988
+ const index = current.findIndex((surface) => surface.id === id)
989
+ const next = current.filter((surface) => surface.id !== id)
990
+ setActiveSurfaceId((active) => {
991
+ if (active !== id) return active
992
+ return next[Math.max(0, index - 1)]?.id ?? null
993
+ })
994
+ return next
995
+ })
996
+ }
997
+
998
+ const closeAllToolSurfaces = () => {
999
+ setToolSurfaces([])
1000
+ setActiveSurfaceId(null)
1001
+ }
1002
+
809
1003
  const toggleSidebar = () => {
810
1004
  if (window.innerWidth <= 760) {
811
1005
  setMobileSidebarOpen(false)
@@ -835,11 +1029,6 @@ export default function MintDashboard() {
835
1029
  window.localStorage.setItem('mint:sidebar-width', String(clamped))
836
1030
  }
837
1031
 
838
- const updateSmartContext = (enabled: boolean) => {
839
- window.localStorage.setItem('mint:smart-context', String(enabled))
840
- setSmartContext(enabled)
841
- }
842
-
843
1032
  const updateAgentMode = (enabled: boolean) => {
844
1033
  window.localStorage.setItem('mint:agent-mode', String(enabled))
845
1034
  setAgentMode(enabled)
@@ -850,176 +1039,25 @@ export default function MintDashboard() {
850
1039
  setPlanMode(enabled)
851
1040
  }
852
1041
 
853
- const updateWorkspacePath = (path: string) => {
1042
+ const updateWorkspacePath = (path: string, persist = true) => {
854
1043
  const next = path.trim()
855
- if (next) {
856
- window.localStorage.setItem(LAST_WORKSPACE_PATH_KEY, next)
857
- } else {
858
- window.localStorage.removeItem(LAST_WORKSPACE_PATH_KEY)
1044
+ conversationActions.selectWorkspace(next)
1045
+ const activeId = conversationActions.getSnapshot().conversationId
1046
+ if (persist && isPersistedConversation(activeId, chatSessions)) {
1047
+ void handleUpdateSessionWorkspace(activeId, next || null)
859
1048
  }
860
- setWorkspacePath(next)
861
1049
  }
862
1050
 
863
- useEffect(() => {
864
- if (!sending) {
865
- sessionAutoApprovedRef.current = false
866
- setSessionAutoApproved(false)
867
- }
868
- }, [sending])
869
-
870
- async function handleApproval(approved: boolean, autoApproveSession = false, answer?: string) {
871
- if (!pendingApproval) return
872
- try {
873
- if (autoApproveSession) {
874
- sessionAutoApprovedRef.current = true
875
- setSessionAutoApproved(true)
876
- }
877
- await submitToolApproval(pendingApproval.token, approved, answer)
878
- } catch (reason) {
879
- setError(errorMessage(reason))
880
- } finally {
881
- setPendingApproval(null)
882
- }
883
- }
1051
+ const handleApproval = conversationActions.resolveApproval
1052
+ const handleCancelMessage = conversationActions.cancel
884
1053
 
885
- async function handleCancelMessage() {
886
- if (!sending || !streamingConversationId) return
887
- try {
888
- await cancelChatMessage(streamingConversationId)
889
- } catch (e) {
890
- console.error("Failed to cancel message stream:", e)
891
- } finally {
892
- setSending(false)
893
- setStreamingConversationId(null)
894
- setSendingMessage('')
895
- setSendingImageCount(0)
896
- setSendingVideoCount(0)
897
- }
898
- }
899
-
900
- async function sendPrompt(
901
- promptText: string,
902
- options: {
903
- imageAttachments?: Array<{ dataUri: string; name: string; previewDataUri?: string }>
904
- videoAttachments?: Array<{ dataUri: string; name: string }>
905
- audioDataUri?: string | null
906
- documentAttachment?: DocumentAttachment | null
907
- systemInstruction?: string
908
- clearComposer?: boolean
909
- pinnedMcpServer?: string
910
- forceAgentMode?: boolean
911
- } = {},
912
- ) {
913
- if (sending) return
914
- const outgoingImages = options.imageAttachments ?? []
915
- const outgoingVideos = options.videoAttachments ?? []
916
- const outgoingDocument = options.documentAttachment ?? null
917
- const shouldUseAgentMode = agentMode || options.forceAgentMode || promptText.toLowerCase().startsWith('search web:')
918
- const outgoingImage = outgoingImages.map((img) => img.dataUri).join(' ')
919
- const outgoingVideo = outgoingVideos.map((vid) => vid.dataUri).join(' ')
920
- const outgoingImageCount = outgoingImages.length
921
- setSending(true)
922
- setStreamingConversationId(conversationId)
923
- setSendingMessage(promptText)
924
- setSendingImageCount(outgoingImageCount)
925
- setSendingVideoCount(outgoingVideos.length)
1054
+ async function sendPrompt(promptText: string, options: PromptOptions = {}) {
1055
+ if (conversationActions.getSnapshot().sending) return
926
1056
  setError('')
927
- setStreamedReply('')
928
- setStreamedResponse(null)
929
- setAgentProgress([])
930
1057
  liveThinkingOpenRef.current = true
931
- setThinkingExpanded((current) => ({ ...current, live: true }))
932
- const progressSnapshot: AgentProgress[] = []
933
- if (options.clearComposer) {
934
- setMessage('')
935
- setImageAttachments([])
936
- setVideoAttachments([])
937
- setDocumentAttachment(null)
938
- }
939
-
940
- try {
941
- const response = await streamChatMessage(
942
- shouldUseAgentMode ? promptText : `/chat ${promptText}`,
943
- (chunk) => setStreamedReply((current) => `${current}${chunk}`),
944
- outgoingImage,
945
- options.audioDataUri ?? null,
946
- outgoingVideo,
947
- options.systemInstruction ?? '',
948
- (progress) => {
949
- progressSnapshot.push(progress)
950
- setAgentProgress((current) => trimAgentProgress([...current, progress]))
951
- },
952
- outgoingDocument,
953
- workspacePath || null,
954
- conversationId,
955
- undefined,
956
- shouldUseAgentMode ? planMode : false,
957
- options.pinnedMcpServer ?? null,
958
- (payload) => {
959
- if (sessionAutoApprovedRef.current) {
960
- submitToolApproval(payload.token, true).catch((err) => {
961
- console.error("Auto approval failed:", err)
962
- })
963
- } else {
964
- setPendingApproval(payload)
965
- notifyPendingApproval(payload)
966
- }
967
- },
968
- )
969
- setStreamedResponse(response)
970
- if (document.hidden || !document.hasFocus()) {
971
- window.api?.notifyAiResponse?.(truncateForNotification(response.text))
972
- }
973
- const history = (await getRecentInteractions(50, conversationId, workspacePath || null)).reverse()
974
- let enrichedHistory = history
975
- if (progressSnapshot.length > 0) {
976
- const newestInteraction = [...history]
977
- .reverse()
978
- .find((interaction) => interaction.aiText === response.text || interaction.userText === promptText) ?? history[history.length - 1]
979
- if (newestInteraction?.id != null) {
980
- const interactionKey = String(newestInteraction.id)
981
- enrichedHistory = history.map((interaction) =>
982
- interaction.id === newestInteraction.id
983
- ? { ...interaction, agentActivity: progressSnapshot }
984
- : interaction,
985
- )
986
- setAgentActivitySnapshots((current) => ({
987
- ...current,
988
- [interactionKey]: progressSnapshot.slice(),
989
- }))
990
- if (liveThinkingOpenRef.current) {
991
- setThinkingExpanded((current) => ({
992
- ...current,
993
- [interactionKey]: true,
994
- }))
995
- }
996
- await saveInteractionAgentActivity(newestInteraction.id, progressSnapshot)
997
- }
998
- }
999
- setInteractions(enrichedHistory)
1000
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, enrichedHistory))
1001
- await refreshChatSessions()
1002
- await refreshPictures()
1003
- if (typeof window !== 'undefined') {
1004
- const pathname = (window.location.pathname || '').replace(/\/+$/, '')
1005
- const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
1006
- const target = (pathname || hash).toLowerCase()
1007
- if (!target || target === '' || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html') {
1008
- window.history.replaceState({}, '', `/chat/${encodeURIComponent(conversationId)}`)
1009
- }
1010
- }
1011
- getRuntimeStatus().then(setStatus).catch(() => {})
1012
- setStreamedReply('')
1013
- setStreamedResponse(null)
1014
- } catch (reason) {
1015
- setError(errorMessage(reason))
1016
- } finally {
1017
- setSending(false)
1018
- setStreamingConversationId(null)
1019
- setSendingMessage('')
1020
- setSendingImageCount(0)
1021
- setSendingVideoCount(0)
1022
- }
1058
+ liveExtendedThinkingOpenRef.current = true
1059
+ setThinkingExpanded(current => ({ ...current, live: true, 'live-extended': true }))
1060
+ await conversationActions.sendPrompt(promptText, { ...options, agentMode, planMode })
1023
1061
  }
1024
1062
 
1025
1063
  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
@@ -1036,15 +1074,12 @@ export default function MintDashboard() {
1036
1074
  // comes back as `not_handled` and falls through to the TS processor below.
1037
1075
  let engineResp: SlashResponse | null = null
1038
1076
  try {
1039
- engineResp = await runSlashCommand(trimmed, workspacePath || null)
1077
+ engineResp = await runSlashCommand(trimmed, workspacePath || null, conversationId)
1040
1078
  } catch {
1041
1079
  engineResp = null
1042
1080
  }
1043
1081
  if (engineResp && engineResp.kind !== 'not_handled') {
1044
- setMessage('')
1045
- setImageAttachments([])
1046
- setVideoAttachments([])
1047
- setDocumentAttachment(null)
1082
+ conversationActions.clearComposer()
1048
1083
  await handleEngineSlashResponse(trimmed, engineResp)
1049
1084
  return
1050
1085
  }
@@ -1052,22 +1087,49 @@ export default function MintDashboard() {
1052
1087
  const slashResult = executeSlashCommand(trimmed)
1053
1088
 
1054
1089
  if (slashResult.handled) {
1055
- setMessage('')
1056
- setImageAttachments([])
1057
- setVideoAttachments([])
1058
- setDocumentAttachment(null)
1090
+ conversationActions.clearComposer()
1059
1091
 
1060
1092
  if (slashResult.action === 'open_image_picker') {
1061
1093
  document.getElementById('vision-file-input')?.click()
1062
1094
  return
1063
1095
  } else if (slashResult.action === 'paste_image') {
1064
1096
  readClipboardImage().then((uri) => {
1065
- if (uri) setImageAttachments((curr) => [...curr, { dataUri: uri, name: 'Clipboard Image' }])
1097
+ if (uri) conversationActions.attachImage({ dataUri: uri, name: 'Clipboard Image' })
1066
1098
  }).catch(() => {})
1067
1099
  return
1068
1100
  } else if (slashResult.action === 'generate_veo') {
1069
1101
  changeView('veo')
1070
1102
  return
1103
+ } else if (slashResult.action === 'resume_session') {
1104
+ const query = (slashResult.payload?.query || '').trim().toLowerCase()
1105
+ const target = query
1106
+ ? chatSessions.find((s) => s.id.toLowerCase() === query || s.id.toLowerCase().includes(query) || s.title.toLowerCase().includes(query))
1107
+ : chatSessions.find((s) => s.id !== conversationId)
1108
+ if (target) {
1109
+ activateConversation(target.id)
1110
+ const targetPath = getCleanPathForView('chat', target.id)
1111
+ window.history.replaceState({}, '', targetPath)
1112
+ const systemMsg = {
1113
+ id: Date.now(),
1114
+ userText: trimmed,
1115
+ aiText: `🔄 Resumed session: **${target.title}** (\`${target.id}\`)`,
1116
+ createdAt: new Date().toISOString(),
1117
+ provider: 'system',
1118
+ model: 'mint-cli',
1119
+ }
1120
+ conversationActions.appendInteraction(systemMsg)
1121
+ } else {
1122
+ const systemMsg = {
1123
+ id: Date.now(),
1124
+ userText: trimmed,
1125
+ aiText: query ? `⚠️ No matching session found for "${query}".` : 'ℹ️ No other sessions found to resume.',
1126
+ createdAt: new Date().toISOString(),
1127
+ provider: 'system',
1128
+ model: 'mint-cli',
1129
+ }
1130
+ conversationActions.appendInteraction(systemMsg)
1131
+ }
1132
+ return
1071
1133
  }
1072
1134
 
1073
1135
  if (slashResult.systemText) {
@@ -1079,7 +1141,7 @@ export default function MintDashboard() {
1079
1141
  provider: 'system',
1080
1142
  model: 'mint-cli',
1081
1143
  }
1082
- setInteractions((prev) => [...prev, systemMsg])
1144
+ conversationActions.appendInteraction(systemMsg)
1083
1145
  saveSystemInteraction(conversationId, trimmed, slashResult.systemText || '', 'system', 'mint-cli').catch(() => {})
1084
1146
  }
1085
1147
  return
@@ -1125,12 +1187,7 @@ export default function MintDashboard() {
1125
1187
  const objectUrl = createObjectUrlPreview(file).objectUrl
1126
1188
  const dataUri = await readImage(file)
1127
1189
  const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
1128
- setImageAttachments((current) => {
1129
- if (current.some((item) => item.name === file.name && item.dataUri === dataUri)) {
1130
- return current
1131
- }
1132
- return [...current, { dataUri, previewDataUri, objectUrl, name: file.name }]
1133
- })
1190
+ conversationActions.attachImage({ dataUri, previewDataUri, objectUrl, name: file.name })
1134
1191
  } catch (reason) {
1135
1192
  setError(errorMessage(reason))
1136
1193
  } finally {
@@ -1152,12 +1209,7 @@ export default function MintDashboard() {
1152
1209
  reader.onerror = reject
1153
1210
  reader.readAsDataURL(file)
1154
1211
  })
1155
- setVideoAttachments((current) => {
1156
- if (current.some((item) => item.name === file.name && item.dataUri === dataUri)) {
1157
- return current
1158
- }
1159
- return [...current, { dataUri, name: file.name }]
1160
- })
1212
+ conversationActions.attachVideo({ dataUri, name: file.name })
1161
1213
  } catch (reason) {
1162
1214
  setError(errorMessage(reason))
1163
1215
  } finally {
@@ -1203,7 +1255,7 @@ export default function MintDashboard() {
1203
1255
  createTrimmedImagePreview(dataUri)
1204
1256
  .catch(() => dataUri)
1205
1257
  .then((previewDataUri) => {
1206
- setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
1258
+ conversationActions.attachImage({ dataUri, previewDataUri, name })
1207
1259
  })
1208
1260
  }
1209
1261
  }).catch((err) => {
@@ -1226,7 +1278,7 @@ export default function MintDashboard() {
1226
1278
  createTrimmedImagePreview(dataUri)
1227
1279
  .catch(() => dataUri)
1228
1280
  .then((previewDataUri) => {
1229
- setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
1281
+ conversationActions.attachImage({ dataUri, previewDataUri, name })
1230
1282
  })
1231
1283
  })
1232
1284
  .catch((reason) => setError(errorMessage(reason)))
@@ -1242,7 +1294,7 @@ export default function MintDashboard() {
1242
1294
  if (!isSupportedDocument(file.name)) {
1243
1295
  throw new Error('Unsupported document type')
1244
1296
  }
1245
- setDocumentAttachment({
1297
+ conversationActions.attachDocument({
1246
1298
  filename: file.name,
1247
1299
  dataUri: await readDocument(file),
1248
1300
  })
@@ -1255,22 +1307,28 @@ export default function MintDashboard() {
1255
1307
 
1256
1308
  function startWebSearch() {
1257
1309
  updateAgentMode(true)
1258
- setMessage((current) => current.trim() ? `Search web: ${current.trim()}` : 'Search web: ')
1310
+ conversationActions.compose(message.trim() ? `Search web: ${message.trim()}` : 'Search web: ')
1259
1311
  }
1260
1312
 
1261
- async function selectWorkspace() {
1262
- try {
1263
- const selected = await selectWorkspaceDirectory()
1264
- if (selected) {
1265
- updateWorkspacePath(selected)
1266
- changeView('workspace')
1267
- }
1268
- } catch (reason) {
1269
- setError(errorMessage(reason))
1270
- }
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
+ )
1323
+ }
1324
+
1325
+ function captureScreen() {
1326
+ setScreenCaptureOpen(true)
1271
1327
  }
1272
1328
 
1273
- async function captureScreen() {
1329
+ async function startLiveTranslation() {
1330
+ setScreenCaptureOpen(false)
1331
+ await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))
1274
1332
  try {
1275
1333
  await window.api.startVision()
1276
1334
  } catch (reason) {
@@ -1278,20 +1336,28 @@ export default function MintDashboard() {
1278
1336
  }
1279
1337
  }
1280
1338
 
1281
- async function clearHistory(action: 'New chat' | 'Clear history') {
1339
+ function attachScreenCapture(image: string) {
1340
+ void createTrimmedImagePreview(image)
1341
+ .catch(() => image)
1342
+ .then((previewDataUri) => {
1343
+ conversationActions.attachImage({ dataUri: image, previewDataUri, name: 'Screen capture' })
1344
+ setScreenCaptureOpen(false)
1345
+ })
1346
+ }
1347
+
1348
+ async function clearHistory(action: 'New chat' | 'Clear history', targetWorkspacePath?: string | null, targetView: 'chat' | 'workspace' = 'chat') {
1282
1349
  try {
1283
1350
  if (action === 'New chat') {
1284
1351
  const next = createConversationId()
1285
- setConversationId(next)
1286
- setInteractions([])
1287
- setAgentActivitySnapshots({})
1288
- setStreamedReply('')
1289
- setStreamedResponse(null)
1290
- setMessage('')
1291
- setImageAttachments([])
1292
- setDocumentAttachment(null)
1293
- setAgentProgress([])
1294
- setViewState('chat')
1352
+ activateConversation(next)
1353
+ setViewState(targetView)
1354
+
1355
+ if (targetWorkspacePath !== undefined) {
1356
+ updateWorkspacePath(targetWorkspacePath || '')
1357
+ } else {
1358
+ updateWorkspacePath(workspacePath)
1359
+ }
1360
+
1295
1361
  if (typeof window !== 'undefined') {
1296
1362
  const currentPath = (window.location.pathname || '').replace(/\/+$/, '')
1297
1363
  if (currentPath !== '' && currentPath !== '/chat') {
@@ -1301,14 +1367,8 @@ export default function MintDashboard() {
1301
1367
  return
1302
1368
  } else {
1303
1369
  if (!window.confirm(`${action} will clear the current conversation history. Continue?`)) return
1304
- await clearChatHistory(conversationId)
1370
+ await conversationActions.clearHistory()
1305
1371
  }
1306
- setInteractions([])
1307
- setAgentActivitySnapshots({})
1308
- setStreamedReply('')
1309
- setStreamedResponse(null)
1310
- setMessage('')
1311
- setImageAttachments([])
1312
1372
  } catch (reason) {
1313
1373
  setError(errorMessage(reason))
1314
1374
  }
@@ -1326,7 +1386,7 @@ export default function MintDashboard() {
1326
1386
  provider: 'system',
1327
1387
  model: 'mint-cli',
1328
1388
  }
1329
- setInteractions((prev) => [...prev, systemMsg])
1389
+ conversationActions.appendInteraction(systemMsg)
1330
1390
  saveSystemInteraction(conversationId, userText, aiText, 'system', 'mint-cli').catch(() => {})
1331
1391
  }
1332
1392
 
@@ -1354,13 +1414,19 @@ export default function MintDashboard() {
1354
1414
  async function handleEngineSlashResponse(userText: string, resp: SlashResponse) {
1355
1415
  switch (resp.kind) {
1356
1416
  case 'message':
1357
- 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
+ }
1358
1421
  break
1359
1422
  case 'applied': {
1360
1423
  const wsChange = resp.effects.find((e) => e.kind === 'workspace_changed') as
1361
1424
  | { kind: 'workspace_changed'; path: string }
1362
1425
  | undefined
1363
- if (wsChange) setWorkspacePath(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
+ }
1364
1430
  const planModeChange = resp.effects.find((e) => e.kind === 'plan_mode_changed') as
1365
1431
  | { kind: 'plan_mode_changed'; enabled: boolean }
1366
1432
  | undefined
@@ -1372,21 +1438,17 @@ export default function MintDashboard() {
1372
1438
  // Render the same "✨ Gemini • model" chip the GUI's own /models shows.
1373
1439
  // The engine already logged the `provider_change` row into the shared
1374
1440
  // "cli" conversation, so this is just the immediate local echo.
1375
- setInteractions((prev) => [
1376
- ...prev,
1377
- {
1378
- id: Date.now(),
1379
- userText: providerChange.display,
1380
- aiText: '',
1381
- createdAt: new Date().toISOString(),
1382
- provider: 'system',
1383
- model: 'provider_change',
1384
- },
1385
- ])
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
+ })
1386
1449
  }
1387
1450
  if (resp.effects.some((e) => e.kind === 'history_cleared')) {
1388
- await clearChatHistory(conversationId).catch(() => {})
1389
- setInteractions([])
1451
+ await conversationActions.clearHistory().catch(() => {})
1390
1452
  }
1391
1453
  if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1392
1454
  if (resp.effects.some((e) => e.kind !== 'history_cleared' && e.kind !== 'workspace_changed')) {
@@ -1415,40 +1477,64 @@ export default function MintDashboard() {
1415
1477
 
1416
1478
  function handleThinkingExpandedChange(key: string, open: boolean) {
1417
1479
  if (key === 'live') liveThinkingOpenRef.current = open
1480
+ if (key === 'live-extended') liveExtendedThinkingOpenRef.current = open
1418
1481
  setThinkingExpanded((current) => ({ ...current, [key]: open }))
1419
1482
  }
1420
1483
 
1421
- async function selectConversation(id: string) {
1484
+ async function selectConversation(id: string, selectedSession?: ChatSession, targetView: 'chat' | 'workspace' = 'chat') {
1422
1485
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, id)
1423
1486
  touchActiveTimestamp()
1424
- setConversationId(id)
1425
- changeView('chat', id)
1426
- setStreamedReply('')
1427
- setStreamedResponse(null)
1428
- setMessage('')
1429
- setImageAttachments([])
1430
- setDocumentAttachment(null)
1431
- setAgentProgress([])
1432
- const history = await getRecentInteractions(50, id, workspacePath || null)
1433
- const reversed = history.reverse()
1434
- setInteractions(reversed)
1435
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
1487
+ activateConversation(id)
1488
+ changeView(targetView, id)
1489
+ const session = selectedSession ?? chatSessions.find((item) => item.id === id)
1490
+ if (session) {
1491
+ if (session.workspacePath) {
1492
+ if (session.workspacePath !== workspacePath) {
1493
+ updateWorkspacePath(session.workspacePath, false)
1494
+ }
1495
+ } else {
1496
+ if (workspacePath) {
1497
+ updateWorkspacePath('', false)
1498
+ }
1499
+ }
1500
+ }
1501
+ try {
1502
+ await conversationActions.refreshHistory()
1503
+ } catch (reason) {
1504
+ setError(errorMessage(reason))
1505
+ }
1506
+ }
1507
+
1508
+ const handleNewChatInProject = (targetPath: string) => {
1509
+ void clearHistory('New chat', targetPath)
1436
1510
  }
1437
1511
 
1438
1512
  async function deleteConversation(id: string) {
1439
- if (id === 'cli') return
1513
+ if (id === 'cli') {
1514
+ if (!window.confirm('Clear history for default CLI session? This cannot be undone.')) return
1515
+ try {
1516
+ await clearChatHistory('cli')
1517
+ await refreshHistory()
1518
+ await refreshChatSessions()
1519
+ showToast('CLI session history cleared')
1520
+ } catch (reason) {
1521
+ setError(errorMessage(reason))
1522
+ }
1523
+ return
1524
+ }
1440
1525
  const session = chatSessions.find((item) => item.id === id)
1441
1526
  const title = session?.title || 'this chat'
1442
1527
  if (!window.confirm(`Delete "${title}"? This will remove the conversation and its messages.`)) return
1443
1528
 
1444
1529
  try {
1445
1530
  await deleteChatSession(id)
1446
- const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && item.id !== 'conversation-default')
1447
- const nextActive = id === conversationId
1531
+ const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && !item.id.startsWith('cli') && item.id !== 'conversation-default')
1532
+ const currentChatId = conversationActions.getSnapshot().conversationId
1533
+ const nextActive = id === currentChatId
1448
1534
  ? (remaining[0]?.id ?? createConversationId())
1449
- : conversationId
1535
+ : currentChatId
1450
1536
 
1451
- if (nextActive !== conversationId) {
1537
+ if (nextActive !== currentChatId) {
1452
1538
  if (remaining.length > 0 && remaining[0]?.id) {
1453
1539
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, nextActive)
1454
1540
  if (typeof window !== 'undefined') {
@@ -1460,21 +1546,11 @@ export default function MintDashboard() {
1460
1546
  window.history.replaceState({}, '', '/chat')
1461
1547
  }
1462
1548
  }
1463
- setConversationId(nextActive)
1464
- setAgentProgress([])
1465
- const history = await getRecentInteractions(50, nextActive, workspacePath || null)
1466
- const reversed = history.reverse()
1467
- setInteractions(reversed)
1468
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
1549
+ activateConversation(nextActive)
1550
+ await conversationActions.refreshHistory()
1469
1551
  }
1470
1552
 
1471
1553
  await refreshChatSessions()
1472
- if (id !== conversationId) return
1473
- setStreamedReply('')
1474
- setStreamedResponse(null)
1475
- setMessage('')
1476
- setImageAttachments([])
1477
- setDocumentAttachment(null)
1478
1554
  } catch (reason) {
1479
1555
  setError(errorMessage(reason))
1480
1556
  }
@@ -1652,62 +1728,157 @@ export default function MintDashboard() {
1652
1728
  }
1653
1729
  }
1654
1730
 
1655
- async function handleModelInteraction(area: ModelInteraction) {
1656
- if (sending) return
1657
-
1658
- const labels: Record<ModelInteraction, string> = {
1659
- head: 'Pats Mint on the head',
1660
- cheek: 'Pokes Mint on the cheek',
1661
- 'left hand': "Touches Mint's left hand",
1662
- 'right hand': "Touches Mint's right hand",
1663
- body: 'Touches Mint',
1664
- 'lower body': "Touches Mint's lower body",
1665
- }
1666
- const interactionMessage = `*${labels[area]}*`
1667
- 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.`
1668
-
1669
- setSending(true)
1670
- setSendingMessage(interactionMessage)
1671
- setSendingImageCount(0)
1672
- setError('')
1673
- setStreamedReply('')
1674
- setStreamedResponse(null)
1675
- setAgentProgress([])
1676
-
1677
- try {
1678
- const response = await streamChatMessage(
1679
- `/chat ${interactionMessage}`,
1680
- (chunk) => setStreamedReply((current) => `${current}${chunk}`),
1681
- null,
1682
- null,
1683
- null,
1684
- instruction,
1685
- undefined,
1686
- null,
1687
- workspacePath || null,
1688
- conversationId,
1689
- )
1690
- setStreamedResponse(response)
1691
- await refreshHistory()
1692
- setStreamedReply('')
1693
- setStreamedResponse(null)
1694
- } catch (reason) {
1695
- setError(errorMessage(reason))
1696
- } finally {
1697
- setSending(false)
1698
- setSendingMessage('')
1699
- setSendingImageCount(0)
1700
- }
1731
+ const chatConversation: ConversationViewModel = {
1732
+ interactions,
1733
+ hasOlder: hasOlderInteractions,
1734
+ sendingInteractionId: streamingConversationId === conversationId ? activeTurnId : null,
1735
+ sending: sending && streamingConversationId === conversationId,
1736
+ sendingMessage: streamingConversationId === conversationId ? sendingMessage : '',
1737
+ sendingImageCount: streamingConversationId === conversationId ? sendingImageCount : 0,
1738
+ sendingVideoCount: streamingConversationId === conversationId ? sendingVideoCount : 0,
1739
+ streamedReply: streamingConversationId === conversationId ? streamedReply : '',
1740
+ streamedResponse: streamingConversationId === conversationId ? streamedResponse : null,
1741
+ agentProgress: streamingConversationId === conversationId ? agentProgress : [],
1742
+ agentActivitySnapshots,
1743
+ thinkingExpanded,
1744
+ message,
1745
+ imageAttachments,
1746
+ videoAttachments,
1747
+ documentName: documentAttachment?.filename ?? '',
1748
+ pendingApproval: pendingApprovals[conversationId] ?? null,
1749
+ agentMode,
1750
+ planMode,
1751
+ status,
1752
+ workspacePath,
1753
+ chatId: conversationId,
1754
+ recentWorkspacePaths: availableWorkspacePaths,
1755
+ chatEnd,
1756
+ welcomeInteraction: MOCK_WELCOME_INTERACTION,
1757
+ settingsConfig,
1758
+ slashChoice: pendingSlashChoice,
1759
+ isCliSession: conversationId.startsWith('cli') || conversationId === 'cli',
1760
+ cliSessionId: conversationId.startsWith('cli') ? conversationId : undefined,
1761
+ conversationTitle: chatSessions.find((session) => session.id === conversationId)?.title,
1762
+ }
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),
1774
+ onLoadOlder: loadOlderInteractions,
1775
+ onThinkingExpandedChange: handleThinkingExpandedChange,
1776
+ onSubmit: handleSubmit,
1777
+ onSelectImage: selectImage,
1778
+ onSelectVideo: selectVideo,
1779
+ onSelectDocument: selectDocument,
1780
+ onPasteImage: pasteImage,
1781
+ onSetMessage: conversationActions.compose,
1782
+ onSendVoiceMessage: sendVoiceMessage,
1783
+ onRemoveImage: conversationActions.removeImage,
1784
+ onRemoveVideo: conversationActions.removeVideo,
1785
+ onRemoveDocument: () => conversationActions.attachDocument(null),
1786
+ onStartWebSearch: startWebSearch,
1787
+ onCaptureScreen: captureScreen,
1788
+ onSetAgentMode: updateAgentMode,
1789
+ onSetPlanMode: isDesktopApp ? updatePlanMode : undefined,
1790
+ onSetProvider: changeProvider,
1791
+ onSelectWorkspace: selectWorkspace,
1792
+ onWorkspaceChanged: () => setWorkspaceRefreshRevision((revision) => revision + 1),
1793
+ onApproval: handleApproval,
1794
+ onUpdateSettings: (updated) => setSettingsConfig(updated),
1795
+ onSetModel: changeModel,
1796
+ onSelectModelAndProvider: changeProviderAndModel,
1797
+ onCancelMessage: handleCancelMessage,
1798
+ onClearMessages: () => clearHistory('Clear history'),
1799
+ onSetGeminiLiveVoice: changeGeminiLiveVoice,
1800
+ onToggleMobileSidebar: () => setMobileSidebarOpen(!mobileSidebarOpen),
1801
+ onBackToCode: () => changeView('code'),
1802
+ onOpenArtifact: isDesktopApp ? openArtifactSurface : undefined,
1803
+ onOpenReview: isDesktopApp ? openReviewSurface : undefined,
1701
1804
  }
1702
1805
 
1703
1806
  return (
1704
1807
  <div className={`app-container ${startupReady ? '' : 'is-loading'}`}>
1808
+ {screenCaptureOpen && (
1809
+ <ScreenCaptureDialog
1810
+ onClose={() => setScreenCaptureOpen(false)}
1811
+ onAttach={attachScreenCapture}
1812
+ capture={() => captureScreenForChat(isDesktopApp)}
1813
+ onLiveTranslate={isDesktopApp ? () => { void startLiveTranslation() } : undefined}
1814
+ />
1815
+ )}
1816
+ {isDesktopApp && (
1817
+ <DesktopTitlebar
1818
+ sidebarCollapsed={sidebarCollapsed}
1819
+ onToggleSidebar={toggleSidebar}
1820
+ onNewChat={() => clearHistory('New chat')}
1821
+ onOpenWorkspace={() => selectWorkspace(undefined, true)}
1822
+ onOpenTerminal={openTerminalSurface}
1823
+ onToggleTerminal={toggleTerminalSurface}
1824
+ onOpenBrowser={() => openNativeBrowser()}
1825
+ toolsPanelOpen={toolsPanelOpen}
1826
+ backgroundTaskCount={backgroundTaskCount}
1827
+ onToggleToolsPanel={() => setToolsPanelOpen((open) => !open)}
1828
+ onOpenSettings={() => changeView('settings')}
1829
+ onCheckForUpdates={checkForUpdatesFromMenu}
1830
+ onShowAbout={() => showToast('Mint Agent — AI workspace')}
1831
+ workspacePath={workspacePath}
1832
+ workspaceActive={view === 'workspace' && !toolsPanelOpen}
1833
+ workspaceRevision={workspaceRefreshRevision}
1834
+ sending={sending}
1835
+ onWorkspaceUndoError={showToast}
1836
+ terminalCount={toolSurfaces.filter((surface) => surface.kind === 'terminal').length}
1837
+ chatId={conversationId}
1838
+ liveProgress={streamingConversationId === conversationId ? agentProgress : []}
1839
+ recentChanges={recentAgentChanges}
1840
+ onOpenReview={(changes) => openReviewSurface({ id: `review:workspace:${Date.now()}`, kind: 'review', title: 'Review', reviewTitle: 'Recent agent changes', changes })}
1841
+ onRefreshWorkspace={() => setWorkspaceRefreshRevision((revision) => revision + 1)}
1842
+ />
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('')} />}
1705
1852
  <div
1706
- className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}
1707
- style={{ '--sidebar-expanded-width': `${sidebarWidth}px` } as CSSProperties}
1853
+ className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''} ${toolsPanelOpen ? 'tool-surface-active' : ''}`}
1854
+ style={{
1855
+ '--sidebar-expanded-width': `${sidebarWidth}px`,
1856
+ '--terminal-dock-size': `${terminalRightWidth}px`,
1857
+ // A terminal shown in the right tool surface resizes the surface
1858
+ // itself (and therefore the remaining chat area), not just its
1859
+ // inner xterm viewport.
1860
+ '--tool-surface-width': `${terminalRightWidth}px`,
1861
+ } as CSSProperties}
1708
1862
  >
1863
+ {!isDesktopApp && view !== 'chat' && (
1864
+ <button
1865
+ type="button"
1866
+ className="web-mobile-nav-btn"
1867
+ aria-label="Open navigation menu"
1868
+ aria-expanded={mobileSidebarOpen}
1869
+ onClick={() => setMobileSidebarOpen(true)}
1870
+ >
1871
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
1872
+ <line x1="3" y1="6" x2="21" y2="6" />
1873
+ <line x1="3" y1="12" x2="21" y2="12" />
1874
+ <line x1="3" y1="18" x2="21" y2="18" />
1875
+ </svg>
1876
+ </button>
1877
+ )}
1709
1878
  {mobileSidebarOpen && (
1710
- <div
1879
+ <button
1880
+ type="button"
1881
+ aria-label="Close navigation menu"
1711
1882
  className="sidebar-backdrop"
1712
1883
  onClick={() => setMobileSidebarOpen(false)}
1713
1884
  style={{
@@ -1722,17 +1893,11 @@ export default function MintDashboard() {
1722
1893
  }}
1723
1894
  />
1724
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>}
1725
1897
  <DashboardSidebar
1726
1898
  view={view}
1727
1899
  sidebarCollapsed={sidebarCollapsed}
1728
- modelVisible={modelVisible}
1729
1900
  sending={sending}
1730
- expressionIndex={expressionIndex}
1731
- accessoryIndex={accessoryIndex}
1732
- expressions={EXPRESSIONS}
1733
- accessories={ACCESSORIES}
1734
- interactionEnabled={interactionEnabled}
1735
- showInteractionGuide={showInteractionGuide}
1736
1901
  onToggleSidebar={toggleSidebar}
1737
1902
  onSidebarResize={handleSidebarResize}
1738
1903
  onSidebarResizeEnd={handleSidebarResizeEnd}
@@ -1743,18 +1908,17 @@ export default function MintDashboard() {
1743
1908
  onDeleteConversation={deleteConversation}
1744
1909
  onRenameConversation={renameConversation}
1745
1910
  onSetView={changeView}
1746
- onToggleModel={toggleModel}
1747
- onSetExpressionIndex={setExpressionIndex}
1748
- onSetAccessoryIndex={setAccessoryIndex}
1749
- onSetInteractionEnabled={updateInteractionEnabled}
1750
- onSetShowInteractionGuide={updateInteractionGuide}
1751
- onShowToast={showToast}
1752
1911
  isSearchOpen={isSearchOpen}
1753
1912
  onSetSearchOpen={setIsSearchOpen}
1754
1913
  showWorkspaceTab={isDesktopApp}
1755
1914
  promoteMediaStudios={!isDesktopApp}
1915
+ activeWorkspacePath={workspacePath}
1916
+ recentWorkspacePaths={recentWorkspacePaths}
1917
+ onBrowseFolder={isDesktopApp ? handleBrowseFolderForMove : undefined}
1918
+ onUpdateSessionWorkspace={handleUpdateSessionWorkspace}
1919
+ onNewChatInProject={handleNewChatInProject}
1756
1920
  />
1757
- <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') ? { 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}>
1758
1922
  {proactiveSuggestion && (
1759
1923
  <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
1760
1924
  <div className="proactive-header">
@@ -1777,201 +1941,215 @@ export default function MintDashboard() {
1777
1941
  </div>
1778
1942
  </div>
1779
1943
  )}
1780
- {pendingSlashChoice && (
1781
- <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
1782
- <div className="proactive-header">
1783
- <span className="proactive-icon">⚡</span>
1784
- <div className="proactive-message">{pendingSlashChoice.title}</div>
1785
- <button className="proactive-dismiss-btn" onClick={() => setPendingSlashChoice(null)}>
1786
- Dismiss
1787
- </button>
1788
- </div>
1789
- <div className="proactive-chips">
1790
- {pendingSlashChoice.options.map((opt) => (
1791
- <button
1792
- key={opt.value}
1793
- className="suggestion-chip"
1794
- onClick={() => {
1795
- const next = `${pendingSlashChoice.command} ${opt.value}`
1796
- setPendingSlashChoice(null)
1797
- runSlashCommand(next, workspacePath || null)
1798
- .then((r) => handleEngineSlashResponse(next, r))
1799
- .catch(() => {})
1800
- }}
1801
- >
1802
- {opt.label}
1803
- </button>
1804
- ))}
1805
- </div>
1806
- </div>
1807
- )}
1808
1944
  {view === 'workspace' && (
1809
- <WorkspacePanel
1810
- agentMode={agentMode}
1811
- sending={sending}
1812
- workspacePath={workspacePath}
1813
- onEnableAgentMode={() => updateAgentMode(true)}
1814
- onSetMessage={setMessage}
1815
- onWorkspaceReady={updateWorkspacePath}
1816
- />
1945
+ <Suspense fallback={<LazyPanelFallback />}>
1946
+ <WorkspacePanel
1947
+ key={workspacePath}
1948
+ agentMode={agentMode}
1949
+ sending={sending}
1950
+ workspacePath={workspacePath}
1951
+ onEnableAgentMode={() => updateAgentMode(true)}
1952
+ onSetMessage={conversationActions.compose}
1953
+ onWorkspaceReady={updateWorkspacePath}
1954
+ refreshRevision={workspaceRefreshRevision}
1955
+ onOpenHtml={openHtmlFile}
1956
+ onOpenPreview={openWorkspacePreview}
1957
+ />
1958
+ </Suspense>
1817
1959
  )}
1818
- <ModelPanel
1819
- scale={scale}
1820
- expressionIndex={expressionIndex}
1821
- accessoryIndex={accessoryIndex}
1822
- isLocked={isLocked}
1823
- isActive={modelVisible && view !== 'pictures' && view !== 'workspace' && view !== 'imagine' && view !== 'veo' && view !== 'skills' && view !== 'mcp' && view !== 'plugins'}
1824
- layoutPreset={layoutPreset}
1825
- sending={sending}
1826
- interactionEnabled={interactionEnabled}
1827
- showInteractionGuide={showInteractionGuide}
1828
- toastMessage={toastMessage}
1829
- onSetScale={setScale}
1830
- onSetLocked={setIsLocked}
1831
- onSetView={changeView}
1832
- onChangeLayoutPreset={changeLayoutPreset}
1833
- onDismissToast={() => setToastMessage('')}
1834
- onInteract={handleModelInteraction}
1835
- onModelLoadComplete={() => setModelReady(true)}
1836
- />
1837
- <ChatPanel
1838
- interactions={interactions}
1839
- sending={sending && streamingConversationId === conversationId}
1840
- sendingMessage={streamingConversationId === conversationId ? sendingMessage : ''}
1841
- sendingImageCount={streamingConversationId === conversationId ? sendingImageCount : 0}
1842
- streamedReply={streamingConversationId === conversationId ? streamedReply : ''}
1843
- streamedResponse={streamingConversationId === conversationId ? streamedResponse : null}
1844
- agentProgress={streamingConversationId === conversationId ? agentProgress : []}
1845
- sendingVideoCount={streamingConversationId === conversationId ? sendingVideoCount : 0}
1846
- agentActivitySnapshots={agentActivitySnapshots}
1847
- thinkingExpanded={thinkingExpanded}
1848
- onThinkingExpandedChange={handleThinkingExpandedChange}
1849
- message={message}
1850
- imageAttachments={imageAttachments}
1851
- videoAttachments={videoAttachments}
1852
- documentName={documentAttachment?.filename ?? ''}
1853
- pendingApproval={streamingConversationId === conversationId ? pendingApproval : null}
1854
- smartContext={smartContext}
1855
- agentMode={agentMode}
1856
- planMode={planMode}
1857
- status={status}
1858
- workspacePath={workspacePath}
1859
- chatEnd={chatEnd}
1860
- welcomeInteraction={MOCK_WELCOME_INTERACTION}
1861
- onSubmit={handleSubmit}
1862
- onSelectImage={selectImage}
1863
- onSelectVideo={selectVideo}
1864
- onSelectDocument={selectDocument}
1865
- onPasteImage={pasteImage}
1866
- onSetMessage={setMessage}
1867
- onSendVoiceMessage={sendVoiceMessage}
1868
- onRemoveImage={(idx: number) => {
1869
- setImageAttachments((current) => current.filter((_, i) => i !== idx))
1870
- }}
1871
- onRemoveVideo={(idx: number) => {
1872
- setVideoAttachments((current) => current.filter((_, i) => i !== idx))
1873
- }}
1874
- onRemoveDocument={() => setDocumentAttachment(null)}
1875
- onStartWebSearch={startWebSearch}
1876
- onCaptureScreen={captureScreen}
1877
- onSetSmartContext={updateSmartContext}
1878
- onSetAgentMode={updateAgentMode}
1879
- onSetPlanMode={isDesktopApp ? updatePlanMode : undefined}
1880
- onSetProvider={changeProvider}
1881
- onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
1882
- settingsConfig={settingsConfig}
1883
- onSetModel={changeModel}
1884
- onSelectModelAndProvider={changeProviderAndModel}
1885
- onSetGeminiLiveVoice={changeGeminiLiveVoice}
1886
- onApproval={handleApproval}
1887
- onCancelMessage={handleCancelMessage}
1888
- onClearMessages={() => clearHistory('Clear history')}
1889
- onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
1890
- />
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
+ }} />
1891
1965
  </main>
1892
- {view === 'skills' && (
1893
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1894
- <SkillsView
1895
- listSkills={listLearnedSkills}
1896
- addSkill={addLearnedSkill}
1897
- deleteSkill={deleteLearnedSkill}
1966
+ {toolsPanelOpen && (
1967
+ <Suspense fallback={<LazyPanelFallback />}>
1968
+ <ToolSurfacePage
1969
+ surfaces={toolSurfaces}
1970
+ activeSurfaceId={activeSurfaceId}
1898
1971
  workspacePath={workspacePath}
1972
+ desktopTools={isDesktopApp}
1973
+ backgroundTaskCount={backgroundTaskCount}
1974
+ onOpenMods={() => openUtilitySurface('mods')}
1975
+ onOpenBackgroundTasks={() => openUtilitySurface('background')}
1976
+ onModsPanelTarget={setModsPanelTarget}
1977
+ onBackgroundPanelTarget={setBackgroundPanelTarget}
1978
+ terminalSize={terminalRightWidth}
1979
+ onSelect={setActiveSurfaceId}
1980
+ onClose={closeToolSurface}
1981
+ onCloseAll={closeAllToolSurfaces}
1982
+ onDuplicate={duplicateToolSurface}
1983
+ onRename={renameToolSurface}
1984
+ onCloseOthers={closeOtherToolSurfaces}
1985
+ onOpenTerminal={openTerminalSurface}
1986
+ onOpenBrowser={() => openNativeBrowser()}
1987
+ onOpenReview={openConversationReview}
1988
+ onOpenFiles={() => {
1989
+ setActiveSurfaceId(null)
1990
+ setToolsPanelOpen(false)
1991
+ changeView('workspace')
1992
+ }}
1993
+ onOpenSideChat={() => { setActiveSurfaceId(null); setToolsPanelOpen(false) }}
1994
+ onClosePanel={closeToolsPanel}
1995
+ onResizeTerminal={(size) => {
1996
+ setTerminalRightWidth(size)
1997
+ if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
1998
+ terminalSaveTimeoutRef.current = setTimeout(() => {
1999
+ window.localStorage.setItem('mint:terminal-right-width', String(size))
2000
+ }, 250)
2001
+ }}
2002
+ onResizePanel={(size) => {
2003
+ setTerminalRightWidth(size)
2004
+ if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
2005
+ terminalSaveTimeoutRef.current = setTimeout(() => {
2006
+ window.localStorage.setItem('mint:terminal-right-width', String(size))
2007
+ }, 250)
2008
+ }}
1899
2009
  />
2010
+ </Suspense>
2011
+ )}
2012
+ <BackgroundTerminals workspacePath={workspacePath} panelTarget={backgroundPanelTarget} onCountChange={setBackgroundTaskCount} />
2013
+ {view === 'skills' && (
2014
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2015
+ <Suspense fallback={<LazyPanelFallback />}>
2016
+ <SkillsView
2017
+ listSkills={listLearnedSkills}
2018
+ addSkill={addLearnedSkill}
2019
+ deleteSkill={deleteLearnedSkill}
2020
+ workspacePath={workspacePath}
2021
+ />
2022
+ </Suspense>
1900
2023
  </div>
1901
2024
  )}
1902
2025
  {view === 'mcp' && (
1903
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1904
- <McpServersView
1905
- config={settingsConfig || DEFAULT_CONFIG}
1906
- updateField={handleUpdateSettingsField}
1907
- mcpName={mcpName}
1908
- setMcpName={setMcpName}
1909
- mcpCmd={mcpCmd}
1910
- setMcpCmd={setMcpCmd}
1911
- mcpArgs={mcpArgs}
1912
- setMcpArgs={setMcpArgs}
1913
- mcpEnv={mcpEnv}
1914
- setMcpEnv={setMcpEnv}
1915
- mcpIcon={mcpIcon}
1916
- setMcpIcon={setMcpIcon}
1917
- handleAddMcpServer={handleAddMcpServer}
1918
- handleRemoveMcpServer={handleRemoveMcpServer}
1919
- detectTools={detectSystemTools}
1920
- onReauth={reauthMcpServer}
1921
- listServerTools={listMcpServerTools}
1922
- />
2026
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2027
+ <Suspense fallback={<LazyPanelFallback />}>
2028
+ <McpServersView
2029
+ config={settingsConfig || DEFAULT_CONFIG}
2030
+ updateField={handleUpdateSettingsField}
2031
+ mcpName={mcpName}
2032
+ setMcpName={setMcpName}
2033
+ mcpCmd={mcpCmd}
2034
+ setMcpCmd={setMcpCmd}
2035
+ mcpArgs={mcpArgs}
2036
+ setMcpArgs={setMcpArgs}
2037
+ mcpEnv={mcpEnv}
2038
+ setMcpEnv={setMcpEnv}
2039
+ mcpIcon={mcpIcon}
2040
+ setMcpIcon={setMcpIcon}
2041
+ handleAddMcpServer={handleAddMcpServer}
2042
+ handleRemoveMcpServer={handleRemoveMcpServer}
2043
+ detectTools={detectSystemTools}
2044
+ onReauth={reauthMcpServer}
2045
+ listServerTools={listMcpServerTools}
2046
+ />
2047
+ </Suspense>
1923
2048
  </div>
1924
2049
  )}
1925
2050
  {view === 'plugins' && (
1926
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1927
- <PluginsView
1928
- config={settingsConfig || DEFAULT_CONFIG}
1929
- updateField={handleUpdateSettingsField}
1930
- handleConnectPlugin={handleConnectPlugin}
1931
- />
2051
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2052
+ <Suspense fallback={<LazyPanelFallback />}>
2053
+ <PluginsView
2054
+ config={settingsConfig || DEFAULT_CONFIG}
2055
+ updateField={handleUpdateSettingsField}
2056
+ handleConnectPlugin={handleConnectPlugin}
2057
+ />
2058
+ </Suspense>
1932
2059
  </div>
1933
2060
  )}
1934
2061
  {view === 'cron' && (
1935
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1936
- <ScheduledTasksView
1937
- listCronJobs={listCronJobs}
1938
- addCronJob={addCronJob}
1939
- removeCronJob={removeCronJob}
1940
- setCronJobEnabled={setCronJobEnabled}
1941
- workspacePath={workspacePath}
1942
- />
2062
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2063
+ <Suspense fallback={<LazyPanelFallback />}>
2064
+ <ScheduledTasksView
2065
+ listCronJobs={listCronJobs}
2066
+ addCronJob={addCronJob}
2067
+ removeCronJob={removeCronJob}
2068
+ setCronJobEnabled={setCronJobEnabled}
2069
+ workspacePath={workspacePath}
2070
+ />
2071
+ </Suspense>
1943
2072
  </div>
1944
2073
  )}
1945
2074
  {view === 'link' && (
1946
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1947
- <LinkedFoldersView
1948
- listLinkedFolders={listLinkedFolders}
1949
- addLinkedFolder={addLinkedFolder}
1950
- removeLinkedFolder={removeLinkedFolder}
1951
- // Desktop: native Tauri picker. Web: asks `mint web` (same
1952
- // machine) to open its own dialog via a loopback-gated route.
1953
- selectFolder={selectLinkedFolderPath}
1954
- />
2075
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2076
+ <Suspense fallback={<LazyPanelFallback />}>
2077
+ <LinkedFoldersView
2078
+ listLinkedFolders={listLinkedFolders}
2079
+ addLinkedFolder={addLinkedFolder}
2080
+ removeLinkedFolder={removeLinkedFolder}
2081
+ linkedFolderStatus={linkedFolderStatus}
2082
+ refreshLinkedFolder={refreshLinkedFolder}
2083
+ listLinkedFolderNotes={listLinkedFolderNotes}
2084
+ readLinkedFolderNote={readLinkedFolderNote}
2085
+ openLinkedFolderNote={openLinkedFolderNote}
2086
+ canOpenNote={isTauriRuntime()}
2087
+ // Desktop: native Tauri picker. Web: asks `mint web` (same
2088
+ // machine) to open its own dialog via a loopback-gated route.
2089
+ selectFolder={selectLinkedFolderPath}
2090
+ />
2091
+ </Suspense>
1955
2092
  </div>
1956
2093
  )}
1957
- <PicturesLibrary view={view} pictures={pictures} onSetView={changeView} onRefreshPictures={refreshPictures} />
1958
- <ImageStudioPanel
1959
- view={view}
1960
- onRefreshPictures={refreshPictures}
1961
- onSendToChat={(_url, imgPrompt) => {
1962
- changeView('chat')
1963
- setMessage(imgPrompt)
1964
- }}
1965
- onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
1966
- />
1967
- <VeoStudioPanel
1968
- view={view}
1969
- onSendToChat={(vidPrompt) => {
1970
- changeView('chat')
1971
- setMessage(vidPrompt)
1972
- }}
1973
- onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
1974
- />
2094
+ {view === 'code' && (
2095
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2096
+ <Suspense fallback={<LazyPanelFallback />}>
2097
+ <CliSessionsView
2098
+ chatSessions={chatSessions}
2099
+ activeConversationId={conversationId}
2100
+ workspacePath={workspacePath}
2101
+ recentWorkspacePaths={availableWorkspacePaths}
2102
+ onSelectFolder={selectCodeHubFolder}
2103
+ onSelectSession={(id) => {
2104
+ selectConversation(id)
2105
+ }}
2106
+ onDeleteSession={deleteConversation}
2107
+ onRenameSession={renameConversation}
2108
+ onRefreshSessions={refreshChatSessions}
2109
+ onShowToast={showToast}
2110
+ />
2111
+ </Suspense>
2112
+ </div>
2113
+ )}
2114
+ {view === 'pictures' && (
2115
+ <Suspense fallback={<LazyPanelFallback />}>
2116
+ <LibraryPage chatId={conversationId} onClose={() => changeView('chat')} onOpenConversation={id => { void selectConversation(id) }} />
2117
+ </Suspense>
2118
+ )}
2119
+ {view === 'imagine' && (
2120
+ <Suspense fallback={<LazyPanelFallback />}>
2121
+ <ImageStudioPanel
2122
+ view={view}
2123
+ onSendToChat={async (imagePath, imgPrompt) => {
2124
+ try {
2125
+ const response = await fetch(convertFileSrc(imagePath))
2126
+ if (!response.ok) throw new Error(`Unable to load image (${response.status})`)
2127
+ const blob = await response.blob()
2128
+ const imageName = imagePath.split(/[\\/]/).pop() || 'generated-image.png'
2129
+ const dataUri = await readImage(new File([blob], imageName, { type: blob.type || 'image/png' }))
2130
+ const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
2131
+
2132
+ changeView('chat')
2133
+ conversationActions.compose(imgPrompt)
2134
+ conversationActions.attachImage({ dataUri, previewDataUri, name: imageName })
2135
+ } catch (error) {
2136
+ showToast(`Could not attach image: ${errorMessage(error)}`)
2137
+ }
2138
+ }}
2139
+ />
2140
+ </Suspense>
2141
+ )}
2142
+ {view === 'veo' && (
2143
+ <Suspense fallback={<LazyPanelFallback />}>
2144
+ <VeoStudioPanel
2145
+ view={view}
2146
+ onSendToChat={(vidPrompt) => {
2147
+ changeView('chat')
2148
+ conversationActions.compose(vidPrompt)
2149
+ }}
2150
+ />
2151
+ </Suspense>
2152
+ )}
1975
2153
  </div>
1976
2154
  <div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
1977
2155
  <div className="startup-loading-content">
@@ -1979,54 +2157,47 @@ export default function MintDashboard() {
1979
2157
  <div className="startup-loading-text">Loading Mint Agent</div>
1980
2158
  </div>
1981
2159
  </div>
1982
- {error && (
1983
- <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' }}>
1984
- <span>{error}</span>
1985
- <button onClick={() => setError('')} style={{ background: 'transparent', border: 0, color: 'white', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', padding: 0 }}>
1986
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
1987
- <line x1="18" y1="6" x2="6" y2="18"></line>
1988
- <line x1="6" y1="6" x2="18" y2="18"></line>
1989
- </svg>
1990
- </button>
1991
- </div>
1992
- )}
1993
2160
 
1994
- <CommandPalette
1995
- isOpen={isSearchOpen}
1996
- onClose={() => setIsSearchOpen(false)}
1997
- onSelectChat={(chatId) => {
1998
- selectConversation(chatId)
1999
- setIsSearchOpen(false)
2000
- }}
2001
- onNewChat={() => {
2002
- clearHistory('New chat')
2003
- setIsSearchOpen(false)
2004
- }}
2005
- onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
2006
- onOpenSettings={() => {
2007
- changeView('settings')
2008
- setIsSearchOpen(false)
2009
- }}
2010
- onChangeView={(targetView) => {
2011
- changeView(targetView)
2012
- setIsSearchOpen(false)
2013
- }}
2014
- onChangeModel={(model) => {
2015
- handleUpdateSettingsField('model', model)
2016
- setToastMessage(`Switched model to ${model}`)
2017
- }}
2018
- onChangeProvider={(provider) => {
2019
- handleUpdateSettingsField('provider', provider)
2020
- setToastMessage(`Switched provider to ${provider}`)
2021
- }}
2022
- onExecuteSlash={(cmd) => {
2023
- setMessage(cmd + ' ')
2024
- setIsSearchOpen(false)
2025
- }}
2026
- chatSessions={chatSessions}
2027
- currentChatId={conversationId}
2028
- workspacePath={workspacePath}
2029
- />
2161
+ {isSearchOpen && (
2162
+ <Suspense fallback={null}>
2163
+ <CommandPalette
2164
+ isOpen
2165
+ onClose={() => setIsSearchOpen(false)}
2166
+ onSelectChat={(chatId) => {
2167
+ selectConversation(chatId)
2168
+ setIsSearchOpen(false)
2169
+ }}
2170
+ onNewChat={() => {
2171
+ clearHistory('New chat')
2172
+ setIsSearchOpen(false)
2173
+ }}
2174
+ onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
2175
+ onOpenSettings={() => {
2176
+ changeView('settings')
2177
+ setIsSearchOpen(false)
2178
+ }}
2179
+ onChangeView={(targetView) => {
2180
+ changeView(targetView)
2181
+ setIsSearchOpen(false)
2182
+ }}
2183
+ onChangeModel={(model) => {
2184
+ handleUpdateSettingsField('model', model)
2185
+ setToastMessage(`Switched model to ${model}`)
2186
+ }}
2187
+ onChangeProvider={(provider) => {
2188
+ handleUpdateSettingsField('provider', provider)
2189
+ setToastMessage(`Switched provider to ${provider}`)
2190
+ }}
2191
+ onExecuteSlash={(cmd) => {
2192
+ conversationActions.compose(cmd + ' ')
2193
+ setIsSearchOpen(false)
2194
+ }}
2195
+ chatSessions={chatSessions}
2196
+ currentChatId={conversationId}
2197
+ workspacePath={workspacePath}
2198
+ />
2199
+ </Suspense>
2200
+ )}
2030
2201
  </div>
2031
2202
  )
2032
2203
  }