@pheem49/mint 1.8.2 → 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 (44) hide show
  1. package/Cargo.toml +1 -1
  2. package/Release_Note.md +48 -3
  3. package/crates/mint-cli/src/agent.rs +95 -84
  4. package/crates/mint-cli/src/image.rs +8 -8
  5. package/crates/mint-cli/src/main.rs +806 -403
  6. package/crates/mint-cli/src/onboard.rs +108 -108
  7. package/crates/mint-cli/src/setup.rs +143 -143
  8. package/crates/mint-core/src/api_server.rs +57 -54
  9. package/crates/mint-core/src/browser.rs +1 -1
  10. package/crates/mint-core/src/chat.rs +123 -8
  11. package/crates/mint-core/src/code_tools.rs +18 -20
  12. package/crates/mint-core/src/config.rs +184 -17
  13. package/crates/mint-core/src/image_gen.rs +5 -5
  14. package/crates/mint-core/src/lib.rs +5 -5
  15. package/crates/mint-core/src/memory.rs +6 -1
  16. package/crates/mint-core/src/orchestration.rs +104 -85
  17. package/crates/mint-core/src/pictures.rs +3 -4
  18. package/crates/mint-core/src/semantic.rs +1 -1
  19. package/crates/mint-core/src/shell.rs +1 -1
  20. package/crates/mint-core/src/skills.rs +46 -19
  21. package/package.json +1 -1
  22. package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
  23. package/shared-modules/linux-audio/fluidsynth2.json +8 -2
  24. package/shared-modules/linux-audio/lilv.json +26 -19
  25. package/shared-modules/linux-audio/lv2.json +0 -1
  26. package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
  27. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  28. package/src/renderer/src/components/ChatPanel.tsx +25 -5
  29. package/src/renderer/src/components/MintDashboard.tsx +45 -2
  30. package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
  31. package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
  32. package/src/renderer/src/components/SettingsWindow.tsx +68 -1
  33. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  34. package/src/renderer/src/css/styles.css +16 -0
  35. package/src/renderer/src-web/components/ChatPanel.tsx +25 -5
  36. package/src/renderer/src-web/components/MintDashboard.tsx +19 -1
  37. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
  38. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
  39. package/src/renderer/src-web/components/SettingsWindow.tsx +75 -1
  40. package/src/renderer/src-web/css/styles.css +16 -0
  41. package/src-tauri/src/desktop.rs +18 -16
  42. package/src-tauri/src/discord_rpc.rs +1 -1
  43. package/src-tauri/src/lib.rs +41 -17
  44. 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
  }
@@ -210,18 +209,18 @@ fn enrich_request(
210
209
  let mut enriched = request.clone();
211
210
 
212
211
  let mut profile_instructions = String::new();
213
- if let Ok(Some(name)) = memory.get_profile("name") {
214
- if !name.trim().is_empty() {
215
- profile_instructions.push_str(&format!("User Name: {}\n", name.trim()));
216
- }
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()));
217
216
  }
218
- if let Ok(Some(preferences)) = memory.get_profile("preferences") {
219
- if !preferences.trim().is_empty() {
220
- profile_instructions.push_str(&format!(
221
- "User Preferences & Profile:\n{}\n",
222
- preferences.trim()
223
- ));
224
- }
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
+ ));
225
224
  }
226
225
 
227
226
  if !profile_instructions.is_empty() {
@@ -239,8 +238,8 @@ fn enrich_request(
239
238
  "\n\n[Active Environment Context]\n\
240
239
  You are running on: {}\n\
241
240
  Using AI Model: {}\n",
242
- config.ai_provider,
243
- config.active_model()
241
+ config.ai_provider,
242
+ config.active_model()
244
243
  );
245
244
  enriched.system_instruction.push_str(&active_model_info);
246
245
 
@@ -685,7 +684,8 @@ fn resolve_agent_config(
685
684
  return (config.clone(), "".to_string(), None, None);
686
685
  }
687
686
 
688
- let enabled_agents: Vec<&crate::config::AgentConfig> = config.agents.iter().filter(|a| a.enabled).collect();
687
+ let enabled_agents: Vec<&crate::config::AgentConfig> =
688
+ config.agents.iter().filter(|a| a.enabled).collect();
689
689
  if enabled_agents.is_empty() {
690
690
  return (config.clone(), "".to_string(), None, None);
691
691
  }
@@ -694,7 +694,9 @@ fn resolve_agent_config(
694
694
  enabled_agents.iter().find(|a| a.id == id).copied()
695
695
  } else {
696
696
  // Multi-Agent Pipeline Collaboration (Planner -> Coder -> Reviewer)
697
- let plan_created = trajectory.iter().any(|step| step.contains("- Action: create_plan"));
697
+ let plan_created = trajectory
698
+ .iter()
699
+ .any(|step| step.contains("- Action: create_plan"));
698
700
  if !plan_created {
699
701
  if let Some(planner) = enabled_agents.iter().find(|a| a.id == "planner") {
700
702
  Some(*planner)
@@ -708,7 +710,9 @@ fn resolve_agent_config(
708
710
  });
709
711
  if edits_made {
710
712
  if let Some(last_step) = trajectory.last() {
711
- if last_step.contains("- Action: write_file") || last_step.contains("- Action: apply_patch") {
713
+ if last_step.contains("- Action: write_file")
714
+ || last_step.contains("- Action: apply_patch")
715
+ {
712
716
  if let Some(reviewer) = enabled_agents.iter().find(|a| a.id == "reviewer") {
713
717
  Some(*reviewer)
714
718
  } else {
@@ -741,21 +745,26 @@ fn resolve_agent_config(
741
745
  cfg_clone.local_model_name = agent.model.clone();
742
746
  cfg_clone.ollama_model = agent.model.clone();
743
747
 
744
- if let Some(key) = &agent.api_key {
745
- if !key.trim().is_empty() {
746
- match agent.provider.as_str() {
747
- "gemini" => cfg_clone.api_key = key.clone(),
748
- "openai" => cfg_clone.openai_api_key = key.clone(),
749
- "anthropic" => cfg_clone.anthropic_api_key = key.clone(),
750
- "openrouter" => cfg_clone.openrouter_api_key = key.clone(),
751
- "deepseek" => cfg_clone.deepseek_api_key = key.clone(),
752
- "huggingface" => cfg_clone.hf_api_key = key.clone(),
753
- _ => {}
754
- }
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
+ _ => {}
755
759
  }
756
760
  }
757
761
 
758
- (cfg_clone, agent.system_instruction.clone(), Some(agent.name.clone()), Some(agent.model.clone()))
762
+ (
763
+ cfg_clone,
764
+ agent.system_instruction.clone(),
765
+ Some(agent.name.clone()),
766
+ Some(agent.model.clone()),
767
+ )
759
768
  }
760
769
 
761
770
  pub async fn orchestrate_agent_loop<Approve, Progress, Chunk>(
@@ -796,18 +805,18 @@ where
796
805
 
797
806
  if let Ok(memory) = MemoryStore::open_default() {
798
807
  let mut profile_instructions = String::new();
799
- if let Ok(Some(name)) = memory.get_profile("name") {
800
- if !name.trim().is_empty() {
801
- profile_instructions.push_str(&format!("User Name: {}\n", name.trim()));
802
- }
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()));
803
812
  }
804
- if let Ok(Some(preferences)) = memory.get_profile("preferences") {
805
- if !preferences.trim().is_empty() {
806
- profile_instructions.push_str(&format!(
807
- "User Preferences & Profile:\n{}\n",
808
- preferences.trim()
809
- ));
810
- }
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
+ ));
811
820
  }
812
821
 
813
822
  if !profile_instructions.is_empty() {
@@ -844,7 +853,8 @@ where
844
853
  let mut trajectory: Vec<String> = Vec::new();
845
854
 
846
855
  for step in 1..=MAX_STEPS {
847
- let (active_config, agent_instruction, active_agent_name, active_model_name) = resolve_agent_config(config, agent_id, &trajectory);
856
+ let (active_config, agent_instruction, active_agent_name, active_model_name) =
857
+ resolve_agent_config(config, agent_id, &trajectory);
848
858
 
849
859
  progress(AgentProgress::Thinking {
850
860
  elapsed_secs: started_at.elapsed().as_secs(),
@@ -858,12 +868,14 @@ where
858
868
  "\n\n[Active Environment Context]\n\
859
869
  You are running on: {}\n\
860
870
  Using AI Model: {}\n",
861
- active_config.ai_provider,
862
- model_name
871
+ active_config.ai_provider, model_name
863
872
  ));
864
873
  }
865
874
  if !agent_instruction.is_empty() {
866
- active_system_prompt.push_str(&format!("\n\nYour Current Role & System Instructions:\n{}", agent_instruction));
875
+ active_system_prompt.push_str(&format!(
876
+ "\n\nYour Current Role & System Instructions:\n{}",
877
+ agent_instruction
878
+ ));
867
879
  }
868
880
 
869
881
  let (response, fallback) = send_chat_with_fallback(
@@ -1261,14 +1273,14 @@ where
1261
1273
  if crate::is_browser_running(config).await {
1262
1274
  let result = crate::browser::navigate(config, url)
1263
1275
  .await
1264
- .map_err(|e| OrchestrationError::Agent(e))?;
1276
+ .map_err(OrchestrationError::Agent)?;
1265
1277
  Ok(result)
1266
1278
  } else {
1267
1279
  let opened = if cfg!(target_os = "macos") {
1268
1280
  std::process::Command::new("open").arg(url).spawn().is_ok()
1269
1281
  } else if cfg!(target_os = "windows") {
1270
1282
  std::process::Command::new("cmd")
1271
- .args(&["/C", "start", url])
1283
+ .args(["/C", "start", url])
1272
1284
  .spawn()
1273
1285
  .is_ok()
1274
1286
  } else {
@@ -1300,7 +1312,7 @@ where
1300
1312
  };
1301
1313
  let result = crate::browser::click(config, selector)
1302
1314
  .await
1303
- .map_err(|e| OrchestrationError::Agent(e))?;
1315
+ .map_err(OrchestrationError::Agent)?;
1304
1316
  Ok(result)
1305
1317
  }
1306
1318
  "browser_type" => {
@@ -1324,13 +1336,13 @@ where
1324
1336
  };
1325
1337
  let result = crate::browser::type_text(config, selector, text)
1326
1338
  .await
1327
- .map_err(|e| OrchestrationError::Agent(e))?;
1339
+ .map_err(OrchestrationError::Agent)?;
1328
1340
  Ok(result)
1329
1341
  }
1330
1342
  "browser_read" => {
1331
1343
  let result = crate::browser::read_page_text(config)
1332
1344
  .await
1333
- .map_err(|e| OrchestrationError::Agent(e))?;
1345
+ .map_err(OrchestrationError::Agent)?;
1334
1346
  Ok(result)
1335
1347
  }
1336
1348
  "memory_recall" => {
@@ -1486,7 +1498,7 @@ where
1486
1498
  path: file_name.to_owned(),
1487
1499
  content: input.file_content.clone(),
1488
1500
  })
1489
- .map_err(|e| OrchestrationError::Agent(e))?;
1501
+ .map_err(OrchestrationError::Agent)?;
1490
1502
 
1491
1503
  match approved {
1492
1504
  ApprovalOutcome::Approved => {
@@ -1509,7 +1521,7 @@ where
1509
1521
  name: name.to_owned(),
1510
1522
  instruction: instruction.to_owned(),
1511
1523
  })
1512
- .map_err(|e| OrchestrationError::Agent(e))?;
1524
+ .map_err(OrchestrationError::Agent)?;
1513
1525
 
1514
1526
  match approved {
1515
1527
  ApprovalOutcome::Approved => Ok(execute_native_plugin(config, name, instruction)
@@ -1527,7 +1539,7 @@ where
1527
1539
  tool: tool.to_owned(),
1528
1540
  arguments: input.arguments.clone(),
1529
1541
  })
1530
- .map_err(|e| OrchestrationError::Agent(e))?;
1542
+ .map_err(OrchestrationError::Agent)?;
1531
1543
 
1532
1544
  match approved {
1533
1545
  ApprovalOutcome::Approved => Ok(serde_json::to_string_pretty(
@@ -1548,7 +1560,7 @@ where
1548
1560
  command: command.to_owned(),
1549
1561
  mode,
1550
1562
  })
1551
- .map_err(|e| OrchestrationError::Agent(e))?;
1563
+ .map_err(OrchestrationError::Agent)?;
1552
1564
 
1553
1565
  match approved {
1554
1566
  ApprovalOutcome::Approved => run_shell(root, config, command),
@@ -1593,7 +1605,7 @@ where
1593
1605
  hunks: patch.hunks.clone(),
1594
1606
  diff,
1595
1607
  })
1596
- .map_err(|e| OrchestrationError::Agent(e))?;
1608
+ .map_err(OrchestrationError::Agent)?;
1597
1609
 
1598
1610
  match approved {
1599
1611
  ApprovalOutcome::Approved => {
@@ -1629,7 +1641,7 @@ where
1629
1641
  content: input.file_content.clone(),
1630
1642
  diff,
1631
1643
  })
1632
- .map_err(|e| OrchestrationError::Agent(e))?;
1644
+ .map_err(OrchestrationError::Agent)?;
1633
1645
 
1634
1646
  match approved {
1635
1647
  ApprovalOutcome::Approved => {
@@ -1823,13 +1835,13 @@ fn view_image(path: &Path, config: &MintConfig) -> Result<String, OrchestrationE
1823
1835
  }
1824
1836
  let bytes = std::fs::read(&path)
1825
1837
  .map_err(|e| OrchestrationError::Agent(format!("cannot read image: {e}")))?;
1826
- Ok(serde_json::to_string_pretty(&serde_json::json!({
1838
+ serde_json::to_string_pretty(&serde_json::json!({
1827
1839
  "path": path,
1828
1840
  "bytes": bytes.len(),
1829
1841
  "mime": mime,
1830
1842
  "dataUri": format!("data:{mime};base64,{}", BASE64_STANDARD.encode(bytes)),
1831
1843
  }))
1832
- .map_err(|e| OrchestrationError::Agent(e.to_string()))?)
1844
+ .map_err(|e| OrchestrationError::Agent(e.to_string()))
1833
1845
  }
1834
1846
 
1835
1847
  fn run_shell(
@@ -1845,14 +1857,13 @@ fn run_shell(
1845
1857
 
1846
1858
  let mut hint = "";
1847
1859
  let cmd_lower = command.to_lowercase();
1848
- if output.success {
1849
- if cmd_lower.contains("open")
1860
+ if output.success
1861
+ && (cmd_lower.contains("open")
1850
1862
  || cmd_lower.contains("launch")
1851
1863
  || cmd_lower.contains("chrome")
1852
- || cmd_lower.contains("firefox")
1853
- {
1854
- 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.";
1855
- }
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.";
1856
1867
  }
1857
1868
 
1858
1869
  Ok(format!(
@@ -1897,7 +1908,9 @@ fn action_fingerprint(decision: &AgentDecision) -> String {
1897
1908
  }
1898
1909
 
1899
1910
  fn initial_observation(task: &str, root: &Path, skills: &str) -> String {
1900
- let now = chrono::Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string();
1911
+ let now = chrono::Local::now()
1912
+ .format("%Y-%m-%d %H:%M:%S %Z")
1913
+ .to_string();
1901
1914
  let mut observation = format!(
1902
1915
  "Current Time: {now}\nTask: {task}\nWorkspace: {}\nLearned skills:\n{}\n",
1903
1916
  root.display(),
@@ -2271,19 +2284,19 @@ Example response:
2271
2284
  text_reply.to_string()
2272
2285
  };
2273
2286
 
2274
- if let Ok(value) = serde_json::from_str::<serde_json::Value>(&clean_json) {
2275
- if let Some(obj) = value.as_object() {
2276
- if let Some(new_name) = obj.get("name").and_then(|v| v.as_str()) {
2277
- let trimmed_name = new_name.trim();
2278
- if !trimmed_name.is_empty() && trimmed_name != current_name {
2279
- memory.set_profile("name", trimmed_name)?;
2280
- }
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)?;
2281
2294
  }
2282
- if let Some(new_pref) = obj.get("preferences").and_then(|v| v.as_str()) {
2283
- let trimmed_pref = new_pref.trim();
2284
- if !trimmed_pref.is_empty() && trimmed_pref != current_pref {
2285
- memory.set_profile("preferences", trimmed_pref)?;
2286
- }
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)?;
2287
2300
  }
2288
2301
  }
2289
2302
  }
@@ -2294,6 +2307,7 @@ Example response:
2294
2307
  #[cfg(test)]
2295
2308
  mod tests {
2296
2309
  use super::*;
2310
+ use crate::config::AgentConfig;
2297
2311
 
2298
2312
  #[test]
2299
2313
  fn preserves_request_without_history() {
@@ -2310,9 +2324,12 @@ mod tests {
2310
2324
  workspace_path: None,
2311
2325
  agent_id: None,
2312
2326
  };
2313
- assert_eq!(
2314
- enrich_request(&store, &request).unwrap().system_instruction,
2315
- "system"
2327
+ let config = MintConfig::default();
2328
+ assert!(
2329
+ enrich_request(&config, &store, &request)
2330
+ .unwrap()
2331
+ .system_instruction
2332
+ .starts_with("system")
2316
2333
  );
2317
2334
  }
2318
2335
 
@@ -2418,6 +2435,7 @@ mod tests {
2418
2435
  #[test]
2419
2436
  fn test_resolve_agent_config() {
2420
2437
  let mut config = MintConfig::default();
2438
+ config.enable_agent_collaboration = true;
2421
2439
  config.agents = vec![
2422
2440
  AgentConfig {
2423
2441
  id: "planner".into(),
@@ -2447,12 +2465,13 @@ mod tests {
2447
2465
  assert_eq!(name, Some("Planner".to_string()));
2448
2466
  assert_eq!(model, Some("gpt-4o".to_string()));
2449
2467
 
2450
- let (cfg, instr, name, model) = resolve_agent_config(&config, None, &[]);
2468
+ let (cfg, instr, name, _model) = resolve_agent_config(&config, None, &[]);
2451
2469
  assert_eq!(cfg.ai_provider, "openai");
2452
2470
  assert_eq!(instr, "Planner instruction");
2453
2471
  assert_eq!(name, Some("Planner".to_string()));
2454
2472
 
2455
- let trajectory = vec!["Step 1:\n- Action: create_plan\n- Observation: all planned".to_string()];
2473
+ let trajectory =
2474
+ vec!["Step 1:\n- Action: create_plan\n- Observation: all planned".to_string()];
2456
2475
  let (cfg, instr, name, model) = resolve_agent_config(&config, None, &trajectory);
2457
2476
  assert_eq!(cfg.ai_provider, "gemini");
2458
2477
  assert_eq!(instr, "Coder instruction");
@@ -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")]
@@ -92,6 +92,31 @@ pub fn learned_skills_context(workspace_root: Option<&Path>) -> Result<String, S
92
92
  Ok(format!("{}\n...<learned skills truncated>", &value[..end]))
93
93
  }
94
94
 
95
+ pub fn parse_skill_description(content: &str) -> Option<String> {
96
+ let content = content.trim_start();
97
+ if !content.starts_with("---") {
98
+ return None;
99
+ }
100
+ let rest = &content[3..];
101
+ let end_idx = rest.find("---")?;
102
+ let frontmatter = &rest[..end_idx];
103
+ for line in frontmatter.lines() {
104
+ let line = line.trim();
105
+ if let Some(desc) = line.strip_prefix("description:") {
106
+ let mut val = desc.trim();
107
+ if (val.starts_with('"') && val.ends_with('"'))
108
+ || (val.starts_with('\'') && val.ends_with('\''))
109
+ {
110
+ if val.len() >= 2 {
111
+ val = &val[1..val.len() - 1];
112
+ }
113
+ }
114
+ return Some(val.to_string());
115
+ }
116
+ }
117
+ None
118
+ }
119
+
95
120
  pub fn load_skills_from_dir(dir: &Path, list: &mut Vec<LearnedSkill>) {
96
121
  if !dir.is_dir() {
97
122
  return;
@@ -109,36 +134,38 @@ pub fn load_skills_from_dir(dir: &Path, list: &mut Vec<LearnedSkill>) {
109
134
  .and_then(|n| n.to_str())
110
135
  .unwrap_or("skill")
111
136
  .to_string();
137
+ let description = parse_skill_description(&content);
112
138
  list.push(LearnedSkill {
113
139
  id: 0,
114
140
  name,
115
141
  source_path: skill_file.to_string_lossy().to_string(),
116
142
  content,
117
143
  created_at: String::new(),
144
+ description,
118
145
  });
119
146
  }
120
147
  break;
121
148
  }
122
149
  }
123
- } else if path.is_file() {
124
- if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
125
- if matches!(ext.to_ascii_lowercase().as_str(), "md" | "txt") {
126
- if let Ok(content) = fs::read_to_string(&path) {
127
- let name = path
128
- .file_stem()
129
- .and_then(|n| n.to_str())
130
- .unwrap_or("skill")
131
- .to_string();
132
- list.push(LearnedSkill {
133
- id: 0,
134
- name,
135
- source_path: path.to_string_lossy().to_string(),
136
- content,
137
- created_at: String::new(),
138
- });
139
- }
140
- }
141
- }
150
+ } else if path.is_file()
151
+ && let Some(ext) = path.extension().and_then(|e| e.to_str())
152
+ && matches!(ext.to_ascii_lowercase().as_str(), "md" | "txt")
153
+ && let Ok(content) = fs::read_to_string(&path)
154
+ {
155
+ let name = path
156
+ .file_stem()
157
+ .and_then(|n| n.to_str())
158
+ .unwrap_or("skill")
159
+ .to_string();
160
+ let description = parse_skill_description(&content);
161
+ list.push(LearnedSkill {
162
+ id: 0,
163
+ name,
164
+ source_path: path.to_string_lossy().to_string(),
165
+ content,
166
+ created_at: String::new(),
167
+ description,
168
+ });
142
169
  }
143
170
  }
144
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pheem49/mint",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "description": "A native Tauri desktop AI assistant with a Rust backend and React UI.",
5
5
  "scripts": {
6
6
  "dev:web": "vite --config vite.config.web.ts",
@@ -22,9 +22,15 @@
22
22
  ],
23
23
  "sources": [
24
24
  {
25
- "type": "archive",
26
- "url": "https://github.com/FluidSynth/fluidsynth/archive/refs/tags/v2.4.6.tar.gz",
27
- "sha256": "a6be90fd4842b9e7246500597180af5cf213c11bfa3998a3236dd8ff47961ea8"
25
+ "type": "archive",
26
+ "url": "https://github.com/FluidSynth/fluidsynth/archive/v2.5.6.tar.gz",
27
+ "sha256": "0825f024c9cf7a18073739b83612d46542ecbfb349ae9147a1e9f08e2d524407"
28
+ },
29
+ {
30
+ "type": "git",
31
+ "url": "https://github.com/kthohr/gcem.git",
32
+ "commit": "012ae73c6d0a2cb09ffe86475f5c6fba3926e200",
33
+ "dest": "gcem"
28
34
  }
29
35
  ]
30
36
  }
@@ -12,8 +12,14 @@
12
12
  "sources": [
13
13
  {
14
14
  "type": "archive",
15
- "url": "https://github.com/FluidSynth/fluidsynth/archive/refs/tags/v2.4.6.tar.gz",
16
- "sha256": "a6be90fd4842b9e7246500597180af5cf213c11bfa3998a3236dd8ff47961ea8"
15
+ "url": "https://github.com/FluidSynth/fluidsynth/archive/v2.5.6.tar.gz",
16
+ "sha256": "0825f024c9cf7a18073739b83612d46542ecbfb349ae9147a1e9f08e2d524407"
17
+ },
18
+ {
19
+ "type": "git",
20
+ "url": "https://github.com/kthohr/gcem.git",
21
+ "commit": "012ae73c6d0a2cb09ffe86475f5c6fba3926e200",
22
+ "dest": "gcem"
17
23
  }
18
24
  ]
19
25
  }