@pheem49/mint 1.8.1 → 1.9.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 (200) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +0 -17
  4. package/Release_Note.md +137 -58
  5. package/bin/mint +0 -0
  6. package/crates/mint-cli/src/agent.rs +150 -45
  7. package/crates/mint-cli/src/image.rs +8 -8
  8. package/crates/mint-cli/src/main.rs +929 -412
  9. package/crates/mint-cli/src/onboard.rs +108 -108
  10. package/crates/mint-cli/src/setup.rs +143 -143
  11. package/crates/mint-core/src/agent_loop.rs +1 -0
  12. package/crates/mint-core/src/api_server.rs +108 -52
  13. package/crates/mint-core/src/browser.rs +56 -1
  14. package/crates/mint-core/src/channels.rs +1 -0
  15. package/crates/mint-core/src/chat.rs +126 -8
  16. package/crates/mint-core/src/code_tools.rs +18 -20
  17. package/crates/mint-core/src/config.rs +245 -17
  18. package/crates/mint-core/src/image_gen.rs +5 -5
  19. package/crates/mint-core/src/lib.rs +16 -2
  20. package/crates/mint-core/src/memory.rs +6 -1
  21. package/crates/mint-core/src/orchestration.rs +260 -72
  22. package/crates/mint-core/src/pictures.rs +3 -4
  23. package/crates/mint-core/src/semantic.rs +1 -1
  24. package/crates/mint-core/src/shell.rs +1 -1
  25. package/crates/mint-core/src/skills.rs +46 -19
  26. package/crates/mint-core/src/tts.rs +3 -1
  27. package/flatpak/com.pheem49.mint.desktop +9 -0
  28. package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
  29. package/flatpak/com.pheem49.mint.yaml +52 -0
  30. package/package.json +1 -1
  31. package/shared-modules/.github/workflows/ci.yml +120 -0
  32. package/shared-modules/.github/workflows/update.yaml +28 -0
  33. package/shared-modules/CODEOWNERS +23 -0
  34. package/shared-modules/README.md +79 -0
  35. package/shared-modules/SDL/README.md +3 -0
  36. package/shared-modules/SDL/SDL-1.2.15.json +51 -0
  37. package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
  38. package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
  39. package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
  40. package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
  41. package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
  42. package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
  43. package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
  44. package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
  45. package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
  46. package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
  47. package/shared-modules/SDL/sdl12-compat.json +22 -0
  48. package/shared-modules/SDL2/SDL2_image.json +27 -0
  49. package/shared-modules/SDL2/SDL2_ttf.json +29 -0
  50. package/shared-modules/cld2/CMakeLists.txt +157 -0
  51. package/shared-modules/cld2/cld2.json +24 -0
  52. package/shared-modules/clutter/clutter.json +72 -0
  53. package/shared-modules/dbus-glib/dbus-glib.json +29 -0
  54. package/shared-modules/glew/glew.json +36 -0
  55. package/shared-modules/glu/glu-9.json +12 -0
  56. package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
  57. package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
  58. package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
  59. package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
  60. package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
  61. package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
  62. package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
  63. package/shared-modules/gtk2/gtk2.json +120 -0
  64. package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
  65. package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
  66. package/shared-modules/gtk2/readme.md +10 -0
  67. package/shared-modules/intltool/intltool-0.51.json +20 -0
  68. package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
  69. package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
  70. package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
  71. package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
  72. package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
  73. package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
  74. package/shared-modules/lame/lame-3.99.5.json +32 -0
  75. package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
  76. package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
  77. package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
  78. package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
  79. package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
  80. package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
  81. package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
  82. package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
  83. package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
  84. package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
  85. package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
  86. package/shared-modules/libcanberra/libcanberra.json +44 -0
  87. package/shared-modules/libmad/libmad.json +19 -0
  88. package/shared-modules/libsecret/libsecret.json +23 -0
  89. package/shared-modules/libsoup/libsoup-2.4.json +14 -0
  90. package/shared-modules/libusb/libusb.json +27 -0
  91. package/shared-modules/linux-audio/dssi.json +31 -0
  92. package/shared-modules/linux-audio/fftw3f-static.json +44 -0
  93. package/shared-modules/linux-audio/fftw3f.json +42 -0
  94. package/shared-modules/linux-audio/fluidsynth2-static.json +36 -0
  95. package/shared-modules/linux-audio/fluidsynth2.json +25 -0
  96. package/shared-modules/linux-audio/ladspa.json +26 -0
  97. package/shared-modules/linux-audio/lash.json +35 -0
  98. package/shared-modules/linux-audio/libinstpatch.json +16 -0
  99. package/shared-modules/linux-audio/liblo-static.json +22 -0
  100. package/shared-modules/linux-audio/liblo.json +17 -0
  101. package/shared-modules/linux-audio/lilv-static.json +94 -0
  102. package/shared-modules/linux-audio/lilv.json +89 -0
  103. package/shared-modules/linux-audio/lrdf.json +57 -0
  104. package/shared-modules/linux-audio/lv2.json +20 -0
  105. package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
  106. package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
  107. package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
  108. package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
  109. package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
  110. package/shared-modules/linux-audio/stk.json +28 -0
  111. package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
  112. package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
  113. package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
  114. package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
  115. package/shared-modules/lua5.3/lua.pc.in +8 -0
  116. package/shared-modules/lua5.3/luaconf.patch +47 -0
  117. package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
  118. package/shared-modules/lua5.4/lua-5.4.json +41 -0
  119. package/shared-modules/lua5.4/lua.pc.in +8 -0
  120. package/shared-modules/lua5.4/luaconf.patch +34 -0
  121. package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
  122. package/shared-modules/luajit/luajit.json +30 -0
  123. package/shared-modules/lzo/lzo.json +27 -0
  124. package/shared-modules/mac/mac.json +18 -0
  125. package/shared-modules/physfs/physfs.json +26 -0
  126. package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
  127. package/shared-modules/pygame/cython-fixes.patch +34 -0
  128. package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
  129. package/shared-modules/pygame/glib.patch +25 -0
  130. package/shared-modules/pygame/pygame1-python2.json +157 -0
  131. package/shared-modules/pygtk/pygtk-c99.patch +15 -0
  132. package/shared-modules/pygtk/pygtk.json +117 -0
  133. package/shared-modules/pygtk/pygtk.patch +39 -0
  134. package/shared-modules/python2.7/python-2.7.json +48 -0
  135. package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
  136. package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
  137. package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
  138. package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
  139. package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
  140. package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
  141. package/shared-modules/update-checker.sh +50 -0
  142. package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
  143. package/shared-modules/vorbisgain/0002-errno.patch +33 -0
  144. package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
  145. package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
  146. package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
  147. package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
  148. package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
  149. package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
  150. package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
  151. package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
  152. package/shared-modules/vorbisgain/README.md +23 -0
  153. package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
  154. package/src/renderer/shared/agentProgress.ts +145 -0
  155. package/src/renderer/shared/calculator.ts +52 -0
  156. package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
  157. package/src/renderer/shared/components/AgentActivityTable.tsx +85 -0
  158. package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
  159. package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
  160. package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
  161. package/src/renderer/shared/constants/models.ts +91 -0
  162. package/src/renderer/shared/platform.ts +82 -0
  163. package/src/renderer/shared/types.ts +140 -0
  164. package/src/renderer/shared/utils/agentActivity.ts +187 -0
  165. package/src/renderer/shared/utils/approval.ts +1 -0
  166. package/src/renderer/shared/utils/approval.tsx +64 -0
  167. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  168. package/src/renderer/shared/utils/markdown.tsx +347 -0
  169. package/src/renderer/shared/utils/providers.ts +36 -0
  170. package/src/renderer/shared/utils/speech.ts +216 -0
  171. package/src/renderer/shared/utils/ui.ts +147 -0
  172. package/src/renderer/src/agentProgress.ts +11 -39
  173. package/src/renderer/src/calculator.ts +3 -46
  174. package/src/renderer/src/components/ChatPanel.tsx +219 -1308
  175. package/src/renderer/src/components/MintDashboard.tsx +67 -170
  176. package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
  177. package/src/renderer/src/components/Settings/AgentsTab.tsx +381 -0
  178. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
  179. package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
  180. package/src/renderer/src/components/SettingsWindow.tsx +120 -3
  181. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  182. package/src/renderer/src/css/styles.css +100 -0
  183. package/src/renderer/src/tauri.ts +128 -143
  184. package/src/renderer/src-web/agentProgress.ts +11 -39
  185. package/src/renderer/src-web/calculator.ts +3 -46
  186. package/src/renderer/src-web/components/ChatPanel.tsx +219 -1306
  187. package/src/renderer/src-web/components/MintDashboard.tsx +42 -170
  188. package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
  189. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +381 -0
  190. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
  191. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
  192. package/src/renderer/src-web/components/SettingsWindow.tsx +127 -3
  193. package/src/renderer/src-web/css/styles.css +100 -0
  194. package/src/renderer/src-web/tauri.ts +180 -102
  195. package/src-tauri/src/desktop.rs +18 -16
  196. package/src-tauri/src/discord_rpc.rs +1 -1
  197. package/src-tauri/src/lib.rs +168 -40
  198. package/src-tauri/src/webhooks.rs +1 -0
  199. package/src-tauri/src/workflows.rs +1 -0
  200. package/src-tauri/tauri.conf.json +1 -1
@@ -71,15 +71,14 @@ pub async fn resolve_github_links(message: &str, config: &MintConfig) -> String
71
71
  .collect();
72
72
 
73
73
  let repo_key = format!("{owner}/{repo_clean}");
74
- if resolved_repos.insert(repo_key.clone()) {
75
- if let Ok(summary) =
74
+ if resolved_repos.insert(repo_key.clone())
75
+ && let Ok(summary) =
76
76
  crate::code_tools::fetch_github_repo_summary(owner, &repo_clean).await
77
- {
78
- resolved_msg.push_str(&format!(
77
+ {
78
+ resolved_msg.push_str(&format!(
79
79
  "\n\n--- Auto-Resolved GitHub Metadata for {} ---\n{}\n--------------------------------------------",
80
80
  repo_key, summary
81
81
  ));
82
- }
83
82
  }
84
83
  }
85
84
  }
@@ -93,7 +92,7 @@ pub async fn orchestrate_chat(
93
92
  let mut resolved_request = request.clone();
94
93
  resolved_request.message = resolve_github_links(&request.message, config).await;
95
94
  let memory = MemoryStore::open_default()?;
96
- let enriched = enrich_request(&memory, &resolved_request)?;
95
+ let enriched = enrich_request(config, &memory, &resolved_request)?;
97
96
  let response = send_chat(config, &enriched).await?;
98
97
  memory.add_interaction_for_chat_with_fallback(
99
98
  request_chat_id(request),
@@ -122,7 +121,7 @@ where
122
121
  let mut resolved_request = request.clone();
123
122
  resolved_request.message = resolve_github_links(&request.message, config).await;
124
123
  let memory = MemoryStore::open_default()?;
125
- let enriched = enrich_request(&memory, &resolved_request)?;
124
+ let enriched = enrich_request(config, &memory, &resolved_request)?;
126
125
  let response = stream_chat(config, &enriched, on_chunk).await?;
127
126
  memory.add_interaction_for_chat_with_fallback(
128
127
  request_chat_id(request),
@@ -147,7 +146,7 @@ pub async fn orchestrate_chat_with_fallback(
147
146
  let mut resolved_request = request.clone();
148
147
  resolved_request.message = resolve_github_links(&request.message, config).await;
149
148
  let memory = MemoryStore::open_default()?;
150
- let enriched = enrich_request(&memory, &resolved_request)?;
149
+ let enriched = enrich_request(config, &memory, &resolved_request)?;
151
150
  let (response, fallback) = send_chat_with_fallback(config, &enriched).await?;
152
151
  memory.add_interaction_for_chat_with_fallback(
153
152
  request_chat_id(request),
@@ -176,7 +175,7 @@ where
176
175
  let mut resolved_request = request.clone();
177
176
  resolved_request.message = resolve_github_links(&request.message, config).await;
178
177
  let memory = MemoryStore::open_default()?;
179
- let enriched = enrich_request(&memory, &resolved_request)?;
178
+ let enriched = enrich_request(config, &memory, &resolved_request)?;
180
179
  let (response, fallback) = stream_chat_with_fallback(config, &enriched, on_chunk).await?;
181
180
  memory.add_interaction_for_chat_with_fallback(
182
181
  request_chat_id(request),
@@ -194,7 +193,11 @@ where
194
193
  Ok((response, fallback))
195
194
  }
196
195
 
197
- fn enrich_request(memory: &MemoryStore, request: &ChatRequest) -> Result<ChatRequest, MemoryError> {
196
+ fn enrich_request(
197
+ config: &MintConfig,
198
+ memory: &MemoryStore,
199
+ request: &ChatRequest,
200
+ ) -> Result<ChatRequest, MemoryError> {
198
201
  let mut interactions =
199
202
  memory.recent_interactions_for_chat(request_chat_id(request), CONTEXT_LIMIT)?;
200
203
  interactions.reverse();
@@ -206,18 +209,18 @@ fn enrich_request(memory: &MemoryStore, request: &ChatRequest) -> Result<ChatReq
206
209
  let mut enriched = request.clone();
207
210
 
208
211
  let mut profile_instructions = String::new();
209
- if let Ok(Some(name)) = memory.get_profile("name") {
210
- if !name.trim().is_empty() {
211
- profile_instructions.push_str(&format!("User Name: {}\n", name.trim()));
212
- }
212
+ if let Ok(Some(name)) = memory.get_profile("name")
213
+ && !name.trim().is_empty()
214
+ {
215
+ profile_instructions.push_str(&format!("User Name: {}\n", name.trim()));
213
216
  }
214
- if let Ok(Some(preferences)) = memory.get_profile("preferences") {
215
- if !preferences.trim().is_empty() {
216
- profile_instructions.push_str(&format!(
217
- "User Preferences & Profile:\n{}\n",
218
- preferences.trim()
219
- ));
220
- }
217
+ if let Ok(Some(preferences)) = memory.get_profile("preferences")
218
+ && !preferences.trim().is_empty()
219
+ {
220
+ profile_instructions.push_str(&format!(
221
+ "User Preferences & Profile:\n{}\n",
222
+ preferences.trim()
223
+ ));
221
224
  }
222
225
 
223
226
  if !profile_instructions.is_empty() {
@@ -230,6 +233,16 @@ fn enrich_request(memory: &MemoryStore, request: &ChatRequest) -> Result<ChatReq
230
233
  .to_owned();
231
234
  }
232
235
 
236
+ // Inject active AI model/provider context to system instructions
237
+ let active_model_info = format!(
238
+ "\n\n[Active Environment Context]\n\
239
+ You are running on: {}\n\
240
+ Using AI Model: {}\n",
241
+ config.ai_provider,
242
+ config.active_model()
243
+ );
244
+ enriched.system_instruction.push_str(&active_model_info);
245
+
233
246
  if !transcript.is_empty() {
234
247
  enriched.system_instruction = format!(
235
248
  "{}\n\nRecent conversation context:\n{}",
@@ -549,6 +562,10 @@ pub enum ApprovalOutcome {
549
562
  pub enum AgentProgress {
550
563
  Thinking {
551
564
  elapsed_secs: u64,
565
+ #[serde(skip_serializing_if = "Option::is_none")]
566
+ agent_name: Option<String>,
567
+ #[serde(skip_serializing_if = "Option::is_none")]
568
+ model_name: Option<String>,
552
569
  },
553
570
  Thought {
554
571
  thought: String,
@@ -658,12 +675,105 @@ where
658
675
  Ok(Option::<AgentInput>::deserialize(deserializer)?.unwrap_or_default())
659
676
  }
660
677
 
678
+ fn resolve_agent_config(
679
+ config: &MintConfig,
680
+ agent_id: Option<&str>,
681
+ trajectory: &[String],
682
+ ) -> (MintConfig, String, Option<String>, Option<String>) {
683
+ if !config.enable_agent_collaboration {
684
+ return (config.clone(), "".to_string(), None, None);
685
+ }
686
+
687
+ let enabled_agents: Vec<&crate::config::AgentConfig> =
688
+ config.agents.iter().filter(|a| a.enabled).collect();
689
+ if enabled_agents.is_empty() {
690
+ return (config.clone(), "".to_string(), None, None);
691
+ }
692
+
693
+ let active_agent = if let Some(id) = agent_id {
694
+ enabled_agents.iter().find(|a| a.id == id).copied()
695
+ } else {
696
+ // Multi-Agent Pipeline Collaboration (Planner -> Coder -> Reviewer)
697
+ let plan_created = trajectory
698
+ .iter()
699
+ .any(|step| step.contains("- Action: create_plan"));
700
+ if !plan_created {
701
+ if let Some(planner) = enabled_agents.iter().find(|a| a.id == "planner") {
702
+ Some(*planner)
703
+ } else {
704
+ enabled_agents.iter().find(|a| a.id == "coder").copied()
705
+ }
706
+ } else {
707
+ // Check if edits have been made to verify
708
+ let edits_made = trajectory.iter().any(|step| {
709
+ step.contains("- Action: write_file") || step.contains("- Action: apply_patch")
710
+ });
711
+ if edits_made {
712
+ if let Some(last_step) = trajectory.last() {
713
+ if last_step.contains("- Action: write_file")
714
+ || last_step.contains("- Action: apply_patch")
715
+ {
716
+ if let Some(reviewer) = enabled_agents.iter().find(|a| a.id == "reviewer") {
717
+ Some(*reviewer)
718
+ } else {
719
+ enabled_agents.iter().find(|a| a.id == "coder").copied()
720
+ }
721
+ } else {
722
+ enabled_agents.iter().find(|a| a.id == "coder").copied()
723
+ }
724
+ } else {
725
+ enabled_agents.iter().find(|a| a.id == "coder").copied()
726
+ }
727
+ } else {
728
+ enabled_agents.iter().find(|a| a.id == "coder").copied()
729
+ }
730
+ }
731
+ };
732
+
733
+ let Some(agent) = active_agent else {
734
+ return (config.clone(), "".to_string(), None, None);
735
+ };
736
+
737
+ let mut cfg_clone = config.clone();
738
+ cfg_clone.ai_provider = agent.provider.clone();
739
+ cfg_clone.gemini_model = agent.model.clone();
740
+ cfg_clone.openai_model = agent.model.clone();
741
+ cfg_clone.anthropic_model = agent.model.clone();
742
+ cfg_clone.openrouter_model = agent.model.clone();
743
+ cfg_clone.deepseek_model = agent.model.clone();
744
+ cfg_clone.hf_model = agent.model.clone();
745
+ cfg_clone.local_model_name = agent.model.clone();
746
+ cfg_clone.ollama_model = agent.model.clone();
747
+
748
+ if let Some(key) = &agent.api_key
749
+ && !key.trim().is_empty()
750
+ {
751
+ match agent.provider.as_str() {
752
+ "gemini" => cfg_clone.api_key = key.clone(),
753
+ "openai" => cfg_clone.openai_api_key = key.clone(),
754
+ "anthropic" => cfg_clone.anthropic_api_key = key.clone(),
755
+ "openrouter" => cfg_clone.openrouter_api_key = key.clone(),
756
+ "deepseek" => cfg_clone.deepseek_api_key = key.clone(),
757
+ "huggingface" => cfg_clone.hf_api_key = key.clone(),
758
+ _ => {}
759
+ }
760
+ }
761
+
762
+ (
763
+ cfg_clone,
764
+ agent.system_instruction.clone(),
765
+ Some(agent.name.clone()),
766
+ Some(agent.model.clone()),
767
+ )
768
+ }
769
+
661
770
  pub async fn orchestrate_agent_loop<Approve, Progress, Chunk>(
662
771
  config: &MintConfig,
663
772
  task: &str,
664
773
  root: &Path,
665
774
  image_data_uri: Option<String>,
666
775
  chat_id: Option<&str>,
776
+ agent_id: Option<&str>,
667
777
  fast_mode: bool,
668
778
  mut approve: Approve,
669
779
  mut progress: Progress,
@@ -695,18 +805,18 @@ where
695
805
 
696
806
  if let Ok(memory) = MemoryStore::open_default() {
697
807
  let mut profile_instructions = String::new();
698
- if let Ok(Some(name)) = memory.get_profile("name") {
699
- if !name.trim().is_empty() {
700
- profile_instructions.push_str(&format!("User Name: {}\n", name.trim()));
701
- }
808
+ if let Ok(Some(name)) = memory.get_profile("name")
809
+ && !name.trim().is_empty()
810
+ {
811
+ profile_instructions.push_str(&format!("User Name: {}\n", name.trim()));
702
812
  }
703
- if let Ok(Some(preferences)) = memory.get_profile("preferences") {
704
- if !preferences.trim().is_empty() {
705
- profile_instructions.push_str(&format!(
706
- "User Preferences & Profile:\n{}\n",
707
- preferences.trim()
708
- ));
709
- }
813
+ if let Ok(Some(preferences)) = memory.get_profile("preferences")
814
+ && !preferences.trim().is_empty()
815
+ {
816
+ profile_instructions.push_str(&format!(
817
+ "User Preferences & Profile:\n{}\n",
818
+ preferences.trim()
819
+ ));
710
820
  }
711
821
 
712
822
  if !profile_instructions.is_empty() {
@@ -743,20 +853,42 @@ where
743
853
  let mut trajectory: Vec<String> = Vec::new();
744
854
 
745
855
  for step in 1..=MAX_STEPS {
856
+ let (active_config, agent_instruction, active_agent_name, active_model_name) =
857
+ resolve_agent_config(config, agent_id, &trajectory);
858
+
746
859
  progress(AgentProgress::Thinking {
747
860
  elapsed_secs: started_at.elapsed().as_secs(),
861
+ agent_name: active_agent_name,
862
+ model_name: active_model_name.clone(),
748
863
  });
749
864
 
865
+ let mut active_system_prompt = system_prompt.clone();
866
+ if let Some(ref model_name) = active_model_name {
867
+ active_system_prompt.push_str(&format!(
868
+ "\n\n[Active Environment Context]\n\
869
+ You are running on: {}\n\
870
+ Using AI Model: {}\n",
871
+ active_config.ai_provider, model_name
872
+ ));
873
+ }
874
+ if !agent_instruction.is_empty() {
875
+ active_system_prompt.push_str(&format!(
876
+ "\n\nYour Current Role & System Instructions:\n{}",
877
+ agent_instruction
878
+ ));
879
+ }
880
+
750
881
  let (response, fallback) = send_chat_with_fallback(
751
- config,
882
+ &active_config,
752
883
  &ChatRequest {
753
884
  message: observation.clone(),
754
- system_instruction: system_prompt.clone(),
885
+ system_instruction: active_system_prompt.clone(),
755
886
  chat_id: Some(chat_id.to_owned()),
756
887
  image_data_uri: pending_image.take(),
757
888
  audio_data_uri: None,
758
889
  document_attachment: None,
759
890
  workspace_path: None,
891
+ agent_id: None,
760
892
  },
761
893
  )
762
894
  .await?;
@@ -771,7 +903,7 @@ where
771
903
  Ok(decision) => decision,
772
904
  Err(_) => {
773
905
  let (repaired, _) = send_chat_with_fallback(
774
- config,
906
+ &active_config,
775
907
  &ChatRequest {
776
908
  message: format!(
777
909
  "Your previous response was not valid Mint agent JSON.\n\
@@ -779,12 +911,13 @@ where
779
911
  Do not use markdown.\n\nPrevious response:\n{}",
780
912
  truncate(&response.text)
781
913
  ),
782
- system_instruction: system_prompt.clone(),
914
+ system_instruction: active_system_prompt.clone(),
783
915
  chat_id: Some(chat_id.to_owned()),
784
916
  image_data_uri: None,
785
917
  audio_data_uri: None,
786
918
  document_attachment: None,
787
919
  workspace_path: None,
920
+ agent_id: None,
788
921
  },
789
922
  )
790
923
  .await?;
@@ -1115,7 +1248,7 @@ where
1115
1248
  .collect::<Vec<_>>()
1116
1249
  .join("\n");
1117
1250
  Ok(format!(
1118
- "{formatted}\n\nNote: Web search succeeded using {provider} Search. In your finish summary, you MUST state that you found this information using the {provider} Search API (e.g. \"มิ้นท์ค้นหาข้อมูลนี้มาจาก {provider} Search นะคะ\")."
1251
+ "{formatted}\n\nNote: Web search succeeded using {provider} Search. In your finish summary, you MUST:\n1. Answer the user's question using the information above.\n2. Mention that you found this information via {provider} Search (e.g. \"I found this information using {provider} Search.\").\nDo NOT list sources manually — the UI will display them automatically."
1119
1252
  ))
1120
1253
  }
1121
1254
  }
@@ -1140,14 +1273,14 @@ where
1140
1273
  if crate::is_browser_running(config).await {
1141
1274
  let result = crate::browser::navigate(config, url)
1142
1275
  .await
1143
- .map_err(|e| OrchestrationError::Agent(e))?;
1276
+ .map_err(OrchestrationError::Agent)?;
1144
1277
  Ok(result)
1145
1278
  } else {
1146
1279
  let opened = if cfg!(target_os = "macos") {
1147
1280
  std::process::Command::new("open").arg(url).spawn().is_ok()
1148
1281
  } else if cfg!(target_os = "windows") {
1149
1282
  std::process::Command::new("cmd")
1150
- .args(&["/C", "start", url])
1283
+ .args(["/C", "start", url])
1151
1284
  .spawn()
1152
1285
  .is_ok()
1153
1286
  } else {
@@ -1179,7 +1312,7 @@ where
1179
1312
  };
1180
1313
  let result = crate::browser::click(config, selector)
1181
1314
  .await
1182
- .map_err(|e| OrchestrationError::Agent(e))?;
1315
+ .map_err(OrchestrationError::Agent)?;
1183
1316
  Ok(result)
1184
1317
  }
1185
1318
  "browser_type" => {
@@ -1203,13 +1336,13 @@ where
1203
1336
  };
1204
1337
  let result = crate::browser::type_text(config, selector, text)
1205
1338
  .await
1206
- .map_err(|e| OrchestrationError::Agent(e))?;
1339
+ .map_err(OrchestrationError::Agent)?;
1207
1340
  Ok(result)
1208
1341
  }
1209
1342
  "browser_read" => {
1210
1343
  let result = crate::browser::read_page_text(config)
1211
1344
  .await
1212
- .map_err(|e| OrchestrationError::Agent(e))?;
1345
+ .map_err(OrchestrationError::Agent)?;
1213
1346
  Ok(result)
1214
1347
  }
1215
1348
  "memory_recall" => {
@@ -1365,7 +1498,7 @@ where
1365
1498
  path: file_name.to_owned(),
1366
1499
  content: input.file_content.clone(),
1367
1500
  })
1368
- .map_err(|e| OrchestrationError::Agent(e))?;
1501
+ .map_err(OrchestrationError::Agent)?;
1369
1502
 
1370
1503
  match approved {
1371
1504
  ApprovalOutcome::Approved => {
@@ -1388,7 +1521,7 @@ where
1388
1521
  name: name.to_owned(),
1389
1522
  instruction: instruction.to_owned(),
1390
1523
  })
1391
- .map_err(|e| OrchestrationError::Agent(e))?;
1524
+ .map_err(OrchestrationError::Agent)?;
1392
1525
 
1393
1526
  match approved {
1394
1527
  ApprovalOutcome::Approved => Ok(execute_native_plugin(config, name, instruction)
@@ -1406,7 +1539,7 @@ where
1406
1539
  tool: tool.to_owned(),
1407
1540
  arguments: input.arguments.clone(),
1408
1541
  })
1409
- .map_err(|e| OrchestrationError::Agent(e))?;
1542
+ .map_err(OrchestrationError::Agent)?;
1410
1543
 
1411
1544
  match approved {
1412
1545
  ApprovalOutcome::Approved => Ok(serde_json::to_string_pretty(
@@ -1427,7 +1560,7 @@ where
1427
1560
  command: command.to_owned(),
1428
1561
  mode,
1429
1562
  })
1430
- .map_err(|e| OrchestrationError::Agent(e))?;
1563
+ .map_err(OrchestrationError::Agent)?;
1431
1564
 
1432
1565
  match approved {
1433
1566
  ApprovalOutcome::Approved => run_shell(root, config, command),
@@ -1472,7 +1605,7 @@ where
1472
1605
  hunks: patch.hunks.clone(),
1473
1606
  diff,
1474
1607
  })
1475
- .map_err(|e| OrchestrationError::Agent(e))?;
1608
+ .map_err(OrchestrationError::Agent)?;
1476
1609
 
1477
1610
  match approved {
1478
1611
  ApprovalOutcome::Approved => {
@@ -1508,7 +1641,7 @@ where
1508
1641
  content: input.file_content.clone(),
1509
1642
  diff,
1510
1643
  })
1511
- .map_err(|e| OrchestrationError::Agent(e))?;
1644
+ .map_err(OrchestrationError::Agent)?;
1512
1645
 
1513
1646
  match approved {
1514
1647
  ApprovalOutcome::Approved => {
@@ -1702,13 +1835,13 @@ fn view_image(path: &Path, config: &MintConfig) -> Result<String, OrchestrationE
1702
1835
  }
1703
1836
  let bytes = std::fs::read(&path)
1704
1837
  .map_err(|e| OrchestrationError::Agent(format!("cannot read image: {e}")))?;
1705
- Ok(serde_json::to_string_pretty(&serde_json::json!({
1838
+ serde_json::to_string_pretty(&serde_json::json!({
1706
1839
  "path": path,
1707
1840
  "bytes": bytes.len(),
1708
1841
  "mime": mime,
1709
1842
  "dataUri": format!("data:{mime};base64,{}", BASE64_STANDARD.encode(bytes)),
1710
1843
  }))
1711
- .map_err(|e| OrchestrationError::Agent(e.to_string()))?)
1844
+ .map_err(|e| OrchestrationError::Agent(e.to_string()))
1712
1845
  }
1713
1846
 
1714
1847
  fn run_shell(
@@ -1724,14 +1857,13 @@ fn run_shell(
1724
1857
 
1725
1858
  let mut hint = "";
1726
1859
  let cmd_lower = command.to_lowercase();
1727
- if output.success {
1728
- if cmd_lower.contains("open")
1860
+ if output.success
1861
+ && (cmd_lower.contains("open")
1729
1862
  || cmd_lower.contains("launch")
1730
1863
  || cmd_lower.contains("chrome")
1731
- || cmd_lower.contains("firefox")
1732
- {
1733
- hint = "\nNote: Opening URLs, files, folders, or launching applications are background processes. Even if there are warnings or stdout/stderr outputs, since the command exited successfully with status 0, the operation has succeeded and you should now use the 'finish' action to inform the user.";
1734
- }
1864
+ || cmd_lower.contains("firefox"))
1865
+ {
1866
+ hint = "\nNote: Opening URLs, files, folders, or launching applications are background processes. Even if there are warnings or stdout/stderr outputs, since the command exited successfully with status 0, the operation has succeeded and you should now use the 'finish' action to inform the user.";
1735
1867
  }
1736
1868
 
1737
1869
  Ok(format!(
@@ -1776,8 +1908,11 @@ fn action_fingerprint(decision: &AgentDecision) -> String {
1776
1908
  }
1777
1909
 
1778
1910
  fn initial_observation(task: &str, root: &Path, skills: &str) -> String {
1911
+ let now = chrono::Local::now()
1912
+ .format("%Y-%m-%d %H:%M:%S %Z")
1913
+ .to_string();
1779
1914
  let mut observation = format!(
1780
- "Task: {task}\nWorkspace: {}\nLearned skills:\n{}\n",
1915
+ "Current Time: {now}\nTask: {task}\nWorkspace: {}\nLearned skills:\n{}\n",
1781
1916
  root.display(),
1782
1917
  if skills.trim().is_empty() {
1783
1918
  "(none)"
@@ -2127,6 +2262,7 @@ Example response:
2127
2262
  audio_data_uri: None,
2128
2263
  document_attachment: None,
2129
2264
  workspace_path: None,
2265
+ agent_id: None,
2130
2266
  };
2131
2267
 
2132
2268
  // Send the chat request to LLM
@@ -2148,19 +2284,19 @@ Example response:
2148
2284
  text_reply.to_string()
2149
2285
  };
2150
2286
 
2151
- if let Ok(value) = serde_json::from_str::<serde_json::Value>(&clean_json) {
2152
- if let Some(obj) = value.as_object() {
2153
- if let Some(new_name) = obj.get("name").and_then(|v| v.as_str()) {
2154
- let trimmed_name = new_name.trim();
2155
- if !trimmed_name.is_empty() && trimmed_name != current_name {
2156
- memory.set_profile("name", trimmed_name)?;
2157
- }
2287
+ if let Ok(value) = serde_json::from_str::<serde_json::Value>(&clean_json)
2288
+ && let Some(obj) = value.as_object()
2289
+ {
2290
+ if let Some(new_name) = obj.get("name").and_then(|v| v.as_str()) {
2291
+ let trimmed_name = new_name.trim();
2292
+ if !trimmed_name.is_empty() && trimmed_name != current_name {
2293
+ memory.set_profile("name", trimmed_name)?;
2158
2294
  }
2159
- if let Some(new_pref) = obj.get("preferences").and_then(|v| v.as_str()) {
2160
- let trimmed_pref = new_pref.trim();
2161
- if !trimmed_pref.is_empty() && trimmed_pref != current_pref {
2162
- memory.set_profile("preferences", trimmed_pref)?;
2163
- }
2295
+ }
2296
+ if let Some(new_pref) = obj.get("preferences").and_then(|v| v.as_str()) {
2297
+ let trimmed_pref = new_pref.trim();
2298
+ if !trimmed_pref.is_empty() && trimmed_pref != current_pref {
2299
+ memory.set_profile("preferences", trimmed_pref)?;
2164
2300
  }
2165
2301
  }
2166
2302
  }
@@ -2171,6 +2307,7 @@ Example response:
2171
2307
  #[cfg(test)]
2172
2308
  mod tests {
2173
2309
  use super::*;
2310
+ use crate::config::AgentConfig;
2174
2311
 
2175
2312
  #[test]
2176
2313
  fn preserves_request_without_history() {
@@ -2185,10 +2322,14 @@ mod tests {
2185
2322
  audio_data_uri: None,
2186
2323
  document_attachment: None,
2187
2324
  workspace_path: None,
2325
+ agent_id: None,
2188
2326
  };
2189
- assert_eq!(
2190
- enrich_request(&store, &request).unwrap().system_instruction,
2191
- "system"
2327
+ let config = MintConfig::default();
2328
+ assert!(
2329
+ enrich_request(&config, &store, &request)
2330
+ .unwrap()
2331
+ .system_instruction
2332
+ .starts_with("system")
2192
2333
  );
2193
2334
  }
2194
2335
 
@@ -2290,4 +2431,51 @@ mod tests {
2290
2431
 
2291
2432
  assert_eq!(classification.mode.as_str(), "readOnly");
2292
2433
  }
2434
+
2435
+ #[test]
2436
+ fn test_resolve_agent_config() {
2437
+ let mut config = MintConfig::default();
2438
+ config.enable_agent_collaboration = true;
2439
+ config.agents = vec![
2440
+ AgentConfig {
2441
+ id: "planner".into(),
2442
+ name: "Planner".into(),
2443
+ provider: "openai".into(),
2444
+ model: "gpt-4o".into(),
2445
+ api_key: Some("test-key".into()),
2446
+ system_instruction: "Planner instruction".into(),
2447
+ enabled: true,
2448
+ },
2449
+ AgentConfig {
2450
+ id: "coder".into(),
2451
+ name: "Coder".into(),
2452
+ provider: "gemini".into(),
2453
+ model: "gemini-2.5-flash".into(),
2454
+ api_key: None,
2455
+ system_instruction: "Coder instruction".into(),
2456
+ enabled: true,
2457
+ },
2458
+ ];
2459
+
2460
+ let (cfg, instr, name, model) = resolve_agent_config(&config, Some("planner"), &[]);
2461
+ assert_eq!(cfg.ai_provider, "openai");
2462
+ assert_eq!(cfg.openai_model, "gpt-4o");
2463
+ assert_eq!(cfg.openai_api_key, "test-key");
2464
+ assert_eq!(instr, "Planner instruction");
2465
+ assert_eq!(name, Some("Planner".to_string()));
2466
+ assert_eq!(model, Some("gpt-4o".to_string()));
2467
+
2468
+ let (cfg, instr, name, _model) = resolve_agent_config(&config, None, &[]);
2469
+ assert_eq!(cfg.ai_provider, "openai");
2470
+ assert_eq!(instr, "Planner instruction");
2471
+ assert_eq!(name, Some("Planner".to_string()));
2472
+
2473
+ let trajectory =
2474
+ vec!["Step 1:\n- Action: create_plan\n- Observation: all planned".to_string()];
2475
+ let (cfg, instr, name, model) = resolve_agent_config(&config, None, &trajectory);
2476
+ assert_eq!(cfg.ai_provider, "gemini");
2477
+ assert_eq!(instr, "Coder instruction");
2478
+ assert_eq!(name, Some("Coder".to_string()));
2479
+ assert_eq!(model, Some("gemini-2.5-flash".to_string()));
2480
+ }
2293
2481
  }
@@ -79,11 +79,10 @@ pub fn list_saved_pictures() -> Result<Vec<PictureEntry>, PictureError> {
79
79
  .thumbnail_path
80
80
  .as_ref()
81
81
  .is_none_or(|thumbnail_path| !thumbnail_path.exists())
82
+ && let Some(thumbnail_path) = create_thumbnail_from_file(&directory, &entry)
82
83
  {
83
- if let Some(thumbnail_path) = create_thumbnail_from_file(&directory, &entry) {
84
- entry.thumbnail_path = Some(thumbnail_path);
85
- changed = true;
86
- }
84
+ entry.thumbnail_path = Some(thumbnail_path);
85
+ changed = true;
87
86
  }
88
87
  entry.url = Some(file_url(&entry.path));
89
88
  entry.thumbnail_url = entry.thumbnail_path.as_deref().map(file_url);
@@ -169,7 +169,7 @@ fn chunk_text(content: &str) -> Vec<(usize, usize, String)> {
169
169
  while start < lines.len() {
170
170
  let mut end = start;
171
171
  let mut chars = 0;
172
- while end < lines.len() && (end == start || chars + lines[end].len() + 1 <= MAX_CHARS) {
172
+ while end < lines.len() && (end == start || chars + lines[end].len() < MAX_CHARS) {
173
173
  chars += lines[end].len() + 1;
174
174
  end += 1;
175
175
  }
@@ -121,7 +121,7 @@ fn run_in_sandbox(
121
121
  .arg(cwd)
122
122
  .args(["bash", "-lc", command])
123
123
  .output()?;
124
- return Ok(Some(output));
124
+ Ok(Some(output))
125
125
  }
126
126
 
127
127
  #[cfg(target_os = "macos")]