@pheem49/mint 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +1 -1
- package/Cargo.lock +34 -3
- package/Cargo.toml +2 -2
- package/README.md +20 -6
- package/Release_Note.md +129 -0
- package/assets/Mint_CLI.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/agent.rs +242 -54
- package/crates/mint-cli/src/main.rs +397 -74
- package/crates/mint-core/src/agent_loop.rs +1 -0
- package/crates/mint-core/src/api_server.rs +64 -2
- package/crates/mint-core/src/browser.rs +456 -0
- package/crates/mint-core/src/channels.rs +1 -0
- package/crates/mint-core/src/chat.rs +3 -0
- package/crates/mint-core/src/config.rs +130 -1
- package/crates/mint-core/src/lib.rs +19 -0
- package/crates/mint-core/src/orchestration.rs +317 -12
- package/crates/mint-core/src/skills.rs +113 -4
- package/crates/mint-core/src/tts.rs +3 -1
- package/crates/mint-core/src/workflows.rs +1 -22
- package/flatpak/com.pheem49.mint.desktop +9 -0
- package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
- package/flatpak/com.pheem49.mint.yaml +52 -0
- package/package.json +1 -1
- package/shared-modules/.github/workflows/ci.yml +120 -0
- package/shared-modules/.github/workflows/update.yaml +28 -0
- package/shared-modules/CODEOWNERS +23 -0
- package/shared-modules/README.md +79 -0
- package/shared-modules/SDL/README.md +3 -0
- package/shared-modules/SDL/SDL-1.2.15.json +51 -0
- package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
- package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
- package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
- package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
- package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
- package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
- package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
- package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
- package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
- package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
- package/shared-modules/SDL/sdl12-compat.json +22 -0
- package/shared-modules/SDL2/SDL2_image.json +27 -0
- package/shared-modules/SDL2/SDL2_ttf.json +29 -0
- package/shared-modules/cld2/CMakeLists.txt +157 -0
- package/shared-modules/cld2/cld2.json +24 -0
- package/shared-modules/clutter/clutter.json +72 -0
- package/shared-modules/dbus-glib/dbus-glib.json +29 -0
- package/shared-modules/glew/glew.json +36 -0
- package/shared-modules/glu/glu-9.json +12 -0
- package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
- package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
- package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
- package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
- package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
- package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
- package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
- package/shared-modules/gtk2/gtk2.json +120 -0
- package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
- package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
- package/shared-modules/gtk2/readme.md +10 -0
- package/shared-modules/intltool/intltool-0.51.json +20 -0
- package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
- package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
- package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
- package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
- package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
- package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
- package/shared-modules/lame/lame-3.99.5.json +32 -0
- package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
- package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
- package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
- package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
- package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
- package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
- package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
- package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
- package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
- package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
- package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
- package/shared-modules/libcanberra/libcanberra.json +44 -0
- package/shared-modules/libmad/libmad.json +19 -0
- package/shared-modules/libsecret/libsecret.json +23 -0
- package/shared-modules/libsoup/libsoup-2.4.json +14 -0
- package/shared-modules/libusb/libusb.json +27 -0
- package/shared-modules/linux-audio/dssi.json +31 -0
- package/shared-modules/linux-audio/fftw3f-static.json +44 -0
- package/shared-modules/linux-audio/fftw3f.json +42 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +30 -0
- package/shared-modules/linux-audio/fluidsynth2.json +19 -0
- package/shared-modules/linux-audio/ladspa.json +26 -0
- package/shared-modules/linux-audio/lash.json +35 -0
- package/shared-modules/linux-audio/libinstpatch.json +16 -0
- package/shared-modules/linux-audio/liblo-static.json +22 -0
- package/shared-modules/linux-audio/liblo.json +17 -0
- package/shared-modules/linux-audio/lilv-static.json +94 -0
- package/shared-modules/linux-audio/lilv.json +82 -0
- package/shared-modules/linux-audio/lrdf.json +57 -0
- package/shared-modules/linux-audio/lv2.json +21 -0
- package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
- package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
- package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
- package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
- package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
- package/shared-modules/linux-audio/stk.json +28 -0
- package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
- package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
- package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
- package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
- package/shared-modules/lua5.3/lua.pc.in +8 -0
- package/shared-modules/lua5.3/luaconf.patch +47 -0
- package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
- package/shared-modules/lua5.4/lua-5.4.json +41 -0
- package/shared-modules/lua5.4/lua.pc.in +8 -0
- package/shared-modules/lua5.4/luaconf.patch +34 -0
- package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
- package/shared-modules/luajit/luajit.json +30 -0
- package/shared-modules/lzo/lzo.json +27 -0
- package/shared-modules/mac/mac.json +18 -0
- package/shared-modules/physfs/physfs.json +26 -0
- package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
- package/shared-modules/pygame/cython-fixes.patch +34 -0
- package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
- package/shared-modules/pygame/glib.patch +25 -0
- package/shared-modules/pygame/pygame1-python2.json +157 -0
- package/shared-modules/pygtk/pygtk-c99.patch +15 -0
- package/shared-modules/pygtk/pygtk.json +117 -0
- package/shared-modules/pygtk/pygtk.patch +39 -0
- package/shared-modules/python2.7/python-2.7.json +48 -0
- package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
- package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
- package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
- package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
- package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
- package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
- package/shared-modules/update-checker.sh +50 -0
- package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
- package/shared-modules/vorbisgain/0002-errno.patch +33 -0
- package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
- package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
- package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
- package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
- package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
- package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
- package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
- package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
- package/shared-modules/vorbisgain/README.md +23 -0
- package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
- package/src/renderer/shared/agentProgress.ts +145 -0
- package/src/renderer/shared/calculator.ts +52 -0
- package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
- package/src/renderer/shared/components/AgentActivityTable.tsx +66 -0
- package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
- package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
- package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
- package/src/renderer/shared/constants/models.ts +91 -0
- package/src/renderer/shared/platform.ts +82 -0
- package/src/renderer/shared/types.ts +140 -0
- package/src/renderer/shared/utils/agentActivity.ts +187 -0
- package/src/renderer/shared/utils/approval.ts +1 -0
- package/src/renderer/shared/utils/approval.tsx +64 -0
- package/src/renderer/shared/utils/markdown.tsx +347 -0
- package/src/renderer/shared/utils/providers.ts +36 -0
- package/src/renderer/shared/utils/speech.ts +216 -0
- package/src/renderer/shared/utils/ui.ts +147 -0
- package/src/renderer/src/agentProgress.ts +11 -39
- package/src/renderer/src/calculator.ts +3 -46
- package/src/renderer/src/components/ChatPanel.tsx +230 -1202
- package/src/renderer/src/components/ImageStudioPanel.tsx +11 -1
- package/src/renderer/src/components/MintDashboard.tsx +63 -171
- package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
- package/src/renderer/src/components/Settings/AgentsTab.tsx +362 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
- package/src/renderer/src/components/Settings/PluginsTab.tsx +221 -41
- package/src/renderer/src/components/SettingsWindow.tsx +54 -12
- package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
- package/src/renderer/src/css/image-studio.css +6 -0
- package/src/renderer/src/css/styles.css +148 -11
- package/src/renderer/src/index.css +12 -1
- package/src/renderer/src/tauri.ts +169 -143
- package/src/renderer/src-web/agentProgress.ts +11 -39
- package/src/renderer/src-web/calculator.ts +3 -46
- package/src/renderer/src-web/components/ChatPanel.tsx +223 -1199
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +11 -1
- package/src/renderer/src-web/components/MintDashboard.tsx +31 -172
- package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +362 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +221 -41
- package/src/renderer/src-web/components/SettingsWindow.tsx +54 -12
- package/src/renderer/src-web/css/image-studio.css +6 -0
- package/src/renderer/src-web/css/styles.css +184 -12
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +204 -104
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +254 -59
- package/src-tauri/src/webhooks.rs +1 -0
- package/src-tauri/src/workflows.rs +57 -4
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -93,7 +93,7 @@ pub async fn orchestrate_chat(
|
|
|
93
93
|
let mut resolved_request = request.clone();
|
|
94
94
|
resolved_request.message = resolve_github_links(&request.message, config).await;
|
|
95
95
|
let memory = MemoryStore::open_default()?;
|
|
96
|
-
let enriched = enrich_request(&memory, &resolved_request)?;
|
|
96
|
+
let enriched = enrich_request(config, &memory, &resolved_request)?;
|
|
97
97
|
let response = send_chat(config, &enriched).await?;
|
|
98
98
|
memory.add_interaction_for_chat_with_fallback(
|
|
99
99
|
request_chat_id(request),
|
|
@@ -122,7 +122,7 @@ where
|
|
|
122
122
|
let mut resolved_request = request.clone();
|
|
123
123
|
resolved_request.message = resolve_github_links(&request.message, config).await;
|
|
124
124
|
let memory = MemoryStore::open_default()?;
|
|
125
|
-
let enriched = enrich_request(&memory, &resolved_request)?;
|
|
125
|
+
let enriched = enrich_request(config, &memory, &resolved_request)?;
|
|
126
126
|
let response = stream_chat(config, &enriched, on_chunk).await?;
|
|
127
127
|
memory.add_interaction_for_chat_with_fallback(
|
|
128
128
|
request_chat_id(request),
|
|
@@ -147,7 +147,7 @@ pub async fn orchestrate_chat_with_fallback(
|
|
|
147
147
|
let mut resolved_request = request.clone();
|
|
148
148
|
resolved_request.message = resolve_github_links(&request.message, config).await;
|
|
149
149
|
let memory = MemoryStore::open_default()?;
|
|
150
|
-
let enriched = enrich_request(&memory, &resolved_request)?;
|
|
150
|
+
let enriched = enrich_request(config, &memory, &resolved_request)?;
|
|
151
151
|
let (response, fallback) = send_chat_with_fallback(config, &enriched).await?;
|
|
152
152
|
memory.add_interaction_for_chat_with_fallback(
|
|
153
153
|
request_chat_id(request),
|
|
@@ -176,7 +176,7 @@ where
|
|
|
176
176
|
let mut resolved_request = request.clone();
|
|
177
177
|
resolved_request.message = resolve_github_links(&request.message, config).await;
|
|
178
178
|
let memory = MemoryStore::open_default()?;
|
|
179
|
-
let enriched = enrich_request(&memory, &resolved_request)?;
|
|
179
|
+
let enriched = enrich_request(config, &memory, &resolved_request)?;
|
|
180
180
|
let (response, fallback) = stream_chat_with_fallback(config, &enriched, on_chunk).await?;
|
|
181
181
|
memory.add_interaction_for_chat_with_fallback(
|
|
182
182
|
request_chat_id(request),
|
|
@@ -194,7 +194,11 @@ where
|
|
|
194
194
|
Ok((response, fallback))
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
fn enrich_request(
|
|
197
|
+
fn enrich_request(
|
|
198
|
+
config: &MintConfig,
|
|
199
|
+
memory: &MemoryStore,
|
|
200
|
+
request: &ChatRequest,
|
|
201
|
+
) -> Result<ChatRequest, MemoryError> {
|
|
198
202
|
let mut interactions =
|
|
199
203
|
memory.recent_interactions_for_chat(request_chat_id(request), CONTEXT_LIMIT)?;
|
|
200
204
|
interactions.reverse();
|
|
@@ -230,6 +234,16 @@ fn enrich_request(memory: &MemoryStore, request: &ChatRequest) -> Result<ChatReq
|
|
|
230
234
|
.to_owned();
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
// Inject active AI model/provider context to system instructions
|
|
238
|
+
let active_model_info = format!(
|
|
239
|
+
"\n\n[Active Environment Context]\n\
|
|
240
|
+
You are running on: {}\n\
|
|
241
|
+
Using AI Model: {}\n",
|
|
242
|
+
config.ai_provider,
|
|
243
|
+
config.active_model()
|
|
244
|
+
);
|
|
245
|
+
enriched.system_instruction.push_str(&active_model_info);
|
|
246
|
+
|
|
233
247
|
if !transcript.is_empty() {
|
|
234
248
|
enriched.system_instruction = format!(
|
|
235
249
|
"{}\n\nRecent conversation context:\n{}",
|
|
@@ -253,6 +267,16 @@ fn request_chat_id(request: &ChatRequest) -> &str {
|
|
|
253
267
|
|
|
254
268
|
const MAX_STEPS: usize = 32;
|
|
255
269
|
const MAX_OBSERVATION_BYTES: usize = 16_000;
|
|
270
|
+
fn is_port_9222_open() -> bool {
|
|
271
|
+
use std::net::TcpStream;
|
|
272
|
+
use std::time::Duration;
|
|
273
|
+
if let Ok(addr) = "127.0.0.1:9222".parse() {
|
|
274
|
+
TcpStream::connect_timeout(&addr, Duration::from_millis(50)).is_ok()
|
|
275
|
+
} else {
|
|
276
|
+
false
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
256
280
|
pub fn build_system_prompt(config: &MintConfig) -> String {
|
|
257
281
|
let mut allowed_actions = vec![
|
|
258
282
|
"list_files",
|
|
@@ -284,6 +308,14 @@ pub fn build_system_prompt(config: &MintConfig) -> String {
|
|
|
284
308
|
"apply_patch",
|
|
285
309
|
"write_file",
|
|
286
310
|
];
|
|
311
|
+
|
|
312
|
+
if is_port_9222_open() {
|
|
313
|
+
allowed_actions.push("browser_open");
|
|
314
|
+
allowed_actions.push("browser_click");
|
|
315
|
+
allowed_actions.push("browser_type");
|
|
316
|
+
allowed_actions.push("browser_read");
|
|
317
|
+
}
|
|
318
|
+
|
|
287
319
|
allowed_actions.retain(|action| !config.disabled_tools.contains(&action.to_string()));
|
|
288
320
|
allowed_actions.push("finish");
|
|
289
321
|
|
|
@@ -319,6 +351,19 @@ pub fn build_system_prompt(config: &MintConfig) -> String {
|
|
|
319
351
|
if allowed_actions.contains(&"web_search") {
|
|
320
352
|
input_formats.push("- web_search: {\"query\":\"search terms\",\"limit\":5}");
|
|
321
353
|
}
|
|
354
|
+
if allowed_actions.contains(&"browser_open") {
|
|
355
|
+
input_formats.push("- browser_open: {\"url\":\"https://example.com\"}");
|
|
356
|
+
}
|
|
357
|
+
if allowed_actions.contains(&"browser_click") {
|
|
358
|
+
input_formats.push("- browser_click: {\"selector\":\"button.submit-btn\"}");
|
|
359
|
+
}
|
|
360
|
+
if allowed_actions.contains(&"browser_type") {
|
|
361
|
+
input_formats
|
|
362
|
+
.push("- browser_type: {\"selector\":\"input.search-bar\", \"text\":\"search query\"}");
|
|
363
|
+
}
|
|
364
|
+
if allowed_actions.contains(&"browser_read") {
|
|
365
|
+
input_formats.push("- browser_read: {}");
|
|
366
|
+
}
|
|
322
367
|
if allowed_actions.contains(&"memory_recall") {
|
|
323
368
|
input_formats.push("- memory_recall: {\"query\":\"what did user say about X\"}");
|
|
324
369
|
}
|
|
@@ -420,6 +465,20 @@ pub fn build_system_prompt(config: &MintConfig) -> String {
|
|
|
420
465
|
if allowed_actions.contains(&"web_search") {
|
|
421
466
|
rules.push("7. Use web_search when the user asks to look something up online or needs current information.");
|
|
422
467
|
}
|
|
468
|
+
if allowed_actions.contains(&"browser_open") {
|
|
469
|
+
rules.push("7a. Use browser_open to navigate the virtual browser to a URL.");
|
|
470
|
+
}
|
|
471
|
+
if allowed_actions.contains(&"browser_click") {
|
|
472
|
+
rules.push("7b. Use browser_click to click elements on the page.");
|
|
473
|
+
}
|
|
474
|
+
if allowed_actions.contains(&"browser_type") {
|
|
475
|
+
rules.push("7c. Use browser_type to enter text/input into form fields or search boxes.");
|
|
476
|
+
}
|
|
477
|
+
if allowed_actions.contains(&"browser_read") {
|
|
478
|
+
rules.push(
|
|
479
|
+
"7d. Use browser_read to read the text content of the active page to analyze it.",
|
|
480
|
+
);
|
|
481
|
+
}
|
|
423
482
|
if allowed_actions.contains(&"memory_recall") {
|
|
424
483
|
rules.push("8. Use memory_recall to search past interactions before asking the user to repeat context.");
|
|
425
484
|
}
|
|
@@ -504,6 +563,10 @@ pub enum ApprovalOutcome {
|
|
|
504
563
|
pub enum AgentProgress {
|
|
505
564
|
Thinking {
|
|
506
565
|
elapsed_secs: u64,
|
|
566
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
567
|
+
agent_name: Option<String>,
|
|
568
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
569
|
+
model_name: Option<String>,
|
|
507
570
|
},
|
|
508
571
|
Thought {
|
|
509
572
|
thought: String,
|
|
@@ -590,6 +653,12 @@ struct AgentInput {
|
|
|
590
653
|
title: String,
|
|
591
654
|
#[serde(default)]
|
|
592
655
|
status: String,
|
|
656
|
+
#[serde(default)]
|
|
657
|
+
url: String,
|
|
658
|
+
#[serde(default)]
|
|
659
|
+
selector: String,
|
|
660
|
+
#[serde(default)]
|
|
661
|
+
text: String,
|
|
593
662
|
}
|
|
594
663
|
|
|
595
664
|
#[derive(Debug, Deserialize, Serialize)]
|
|
@@ -607,12 +676,95 @@ where
|
|
|
607
676
|
Ok(Option::<AgentInput>::deserialize(deserializer)?.unwrap_or_default())
|
|
608
677
|
}
|
|
609
678
|
|
|
679
|
+
fn resolve_agent_config(
|
|
680
|
+
config: &MintConfig,
|
|
681
|
+
agent_id: Option<&str>,
|
|
682
|
+
trajectory: &[String],
|
|
683
|
+
) -> (MintConfig, String, Option<String>, Option<String>) {
|
|
684
|
+
if !config.enable_agent_collaboration {
|
|
685
|
+
return (config.clone(), "".to_string(), None, None);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
let enabled_agents: Vec<&crate::config::AgentConfig> = 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.iter().any(|step| step.contains("- Action: create_plan"));
|
|
698
|
+
if !plan_created {
|
|
699
|
+
if let Some(planner) = enabled_agents.iter().find(|a| a.id == "planner") {
|
|
700
|
+
Some(*planner)
|
|
701
|
+
} else {
|
|
702
|
+
enabled_agents.iter().find(|a| a.id == "coder").copied()
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
// Check if edits have been made to verify
|
|
706
|
+
let edits_made = trajectory.iter().any(|step| {
|
|
707
|
+
step.contains("- Action: write_file") || step.contains("- Action: apply_patch")
|
|
708
|
+
});
|
|
709
|
+
if edits_made {
|
|
710
|
+
if let Some(last_step) = trajectory.last() {
|
|
711
|
+
if last_step.contains("- Action: write_file") || last_step.contains("- Action: apply_patch") {
|
|
712
|
+
if let Some(reviewer) = enabled_agents.iter().find(|a| a.id == "reviewer") {
|
|
713
|
+
Some(*reviewer)
|
|
714
|
+
} else {
|
|
715
|
+
enabled_agents.iter().find(|a| a.id == "coder").copied()
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
enabled_agents.iter().find(|a| a.id == "coder").copied()
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
enabled_agents.iter().find(|a| a.id == "coder").copied()
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
enabled_agents.iter().find(|a| a.id == "coder").copied()
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
let Some(agent) = active_agent else {
|
|
730
|
+
return (config.clone(), "".to_string(), None, None);
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
let mut cfg_clone = config.clone();
|
|
734
|
+
cfg_clone.ai_provider = agent.provider.clone();
|
|
735
|
+
cfg_clone.gemini_model = agent.model.clone();
|
|
736
|
+
cfg_clone.openai_model = agent.model.clone();
|
|
737
|
+
cfg_clone.anthropic_model = agent.model.clone();
|
|
738
|
+
cfg_clone.openrouter_model = agent.model.clone();
|
|
739
|
+
cfg_clone.deepseek_model = agent.model.clone();
|
|
740
|
+
cfg_clone.hf_model = agent.model.clone();
|
|
741
|
+
cfg_clone.local_model_name = agent.model.clone();
|
|
742
|
+
cfg_clone.ollama_model = agent.model.clone();
|
|
743
|
+
|
|
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
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
(cfg_clone, agent.system_instruction.clone(), Some(agent.name.clone()), Some(agent.model.clone()))
|
|
759
|
+
}
|
|
760
|
+
|
|
610
761
|
pub async fn orchestrate_agent_loop<Approve, Progress, Chunk>(
|
|
611
762
|
config: &MintConfig,
|
|
612
763
|
task: &str,
|
|
613
764
|
root: &Path,
|
|
614
765
|
image_data_uri: Option<String>,
|
|
615
766
|
chat_id: Option<&str>,
|
|
767
|
+
agent_id: Option<&str>,
|
|
616
768
|
fast_mode: bool,
|
|
617
769
|
mut approve: Approve,
|
|
618
770
|
mut progress: Progress,
|
|
@@ -632,7 +784,7 @@ where
|
|
|
632
784
|
))
|
|
633
785
|
})?;
|
|
634
786
|
let resolved_task = resolve_github_links(task, config).await;
|
|
635
|
-
let skills = crate::skills::learned_skills_context().unwrap_or_default();
|
|
787
|
+
let skills = crate::skills::learned_skills_context(Some(&root)).unwrap_or_default();
|
|
636
788
|
let mut observation = initial_observation(&resolved_task, &root, &skills);
|
|
637
789
|
let mut pending_image = image_data_uri;
|
|
638
790
|
|
|
@@ -692,20 +844,39 @@ where
|
|
|
692
844
|
let mut trajectory: Vec<String> = Vec::new();
|
|
693
845
|
|
|
694
846
|
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);
|
|
848
|
+
|
|
695
849
|
progress(AgentProgress::Thinking {
|
|
696
850
|
elapsed_secs: started_at.elapsed().as_secs(),
|
|
851
|
+
agent_name: active_agent_name,
|
|
852
|
+
model_name: active_model_name.clone(),
|
|
697
853
|
});
|
|
698
854
|
|
|
855
|
+
let mut active_system_prompt = system_prompt.clone();
|
|
856
|
+
if let Some(ref model_name) = active_model_name {
|
|
857
|
+
active_system_prompt.push_str(&format!(
|
|
858
|
+
"\n\n[Active Environment Context]\n\
|
|
859
|
+
You are running on: {}\n\
|
|
860
|
+
Using AI Model: {}\n",
|
|
861
|
+
active_config.ai_provider,
|
|
862
|
+
model_name
|
|
863
|
+
));
|
|
864
|
+
}
|
|
865
|
+
if !agent_instruction.is_empty() {
|
|
866
|
+
active_system_prompt.push_str(&format!("\n\nYour Current Role & System Instructions:\n{}", agent_instruction));
|
|
867
|
+
}
|
|
868
|
+
|
|
699
869
|
let (response, fallback) = send_chat_with_fallback(
|
|
700
|
-
|
|
870
|
+
&active_config,
|
|
701
871
|
&ChatRequest {
|
|
702
872
|
message: observation.clone(),
|
|
703
|
-
system_instruction:
|
|
873
|
+
system_instruction: active_system_prompt.clone(),
|
|
704
874
|
chat_id: Some(chat_id.to_owned()),
|
|
705
875
|
image_data_uri: pending_image.take(),
|
|
706
876
|
audio_data_uri: None,
|
|
707
877
|
document_attachment: None,
|
|
708
878
|
workspace_path: None,
|
|
879
|
+
agent_id: None,
|
|
709
880
|
},
|
|
710
881
|
)
|
|
711
882
|
.await?;
|
|
@@ -720,7 +891,7 @@ where
|
|
|
720
891
|
Ok(decision) => decision,
|
|
721
892
|
Err(_) => {
|
|
722
893
|
let (repaired, _) = send_chat_with_fallback(
|
|
723
|
-
|
|
894
|
+
&active_config,
|
|
724
895
|
&ChatRequest {
|
|
725
896
|
message: format!(
|
|
726
897
|
"Your previous response was not valid Mint agent JSON.\n\
|
|
@@ -728,12 +899,13 @@ where
|
|
|
728
899
|
Do not use markdown.\n\nPrevious response:\n{}",
|
|
729
900
|
truncate(&response.text)
|
|
730
901
|
),
|
|
731
|
-
system_instruction:
|
|
902
|
+
system_instruction: active_system_prompt.clone(),
|
|
732
903
|
chat_id: Some(chat_id.to_owned()),
|
|
733
904
|
image_data_uri: None,
|
|
734
905
|
audio_data_uri: None,
|
|
735
906
|
document_attachment: None,
|
|
736
907
|
workspace_path: None,
|
|
908
|
+
agent_id: None,
|
|
737
909
|
},
|
|
738
910
|
)
|
|
739
911
|
.await?;
|
|
@@ -1064,7 +1236,7 @@ where
|
|
|
1064
1236
|
.collect::<Vec<_>>()
|
|
1065
1237
|
.join("\n");
|
|
1066
1238
|
Ok(format!(
|
|
1067
|
-
"{formatted}\n\nNote: Web search succeeded using {provider} Search. In your finish summary, you MUST
|
|
1239
|
+
"{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."
|
|
1068
1240
|
))
|
|
1069
1241
|
}
|
|
1070
1242
|
}
|
|
@@ -1076,6 +1248,91 @@ where
|
|
|
1076
1248
|
)),
|
|
1077
1249
|
}
|
|
1078
1250
|
}
|
|
1251
|
+
"browser_open" => {
|
|
1252
|
+
let url = if !input.url.is_empty() {
|
|
1253
|
+
&input.url
|
|
1254
|
+
} else if !input.path.is_empty() {
|
|
1255
|
+
&input.path
|
|
1256
|
+
} else {
|
|
1257
|
+
return Err(OrchestrationError::Agent(
|
|
1258
|
+
"browser_open requires 'url'".into(),
|
|
1259
|
+
));
|
|
1260
|
+
};
|
|
1261
|
+
if crate::is_browser_running(config).await {
|
|
1262
|
+
let result = crate::browser::navigate(config, url)
|
|
1263
|
+
.await
|
|
1264
|
+
.map_err(|e| OrchestrationError::Agent(e))?;
|
|
1265
|
+
Ok(result)
|
|
1266
|
+
} else {
|
|
1267
|
+
let opened = if cfg!(target_os = "macos") {
|
|
1268
|
+
std::process::Command::new("open").arg(url).spawn().is_ok()
|
|
1269
|
+
} else if cfg!(target_os = "windows") {
|
|
1270
|
+
std::process::Command::new("cmd")
|
|
1271
|
+
.args(&["/C", "start", url])
|
|
1272
|
+
.spawn()
|
|
1273
|
+
.is_ok()
|
|
1274
|
+
} else {
|
|
1275
|
+
std::process::Command::new("xdg-open")
|
|
1276
|
+
.arg(url)
|
|
1277
|
+
.spawn()
|
|
1278
|
+
.is_ok()
|
|
1279
|
+
};
|
|
1280
|
+
if opened {
|
|
1281
|
+
Ok(format!(
|
|
1282
|
+
"Mint Auto is not active. Opened {url} in your default browser instead."
|
|
1283
|
+
))
|
|
1284
|
+
} else {
|
|
1285
|
+
Err(OrchestrationError::Agent(
|
|
1286
|
+
"Failed to open URL in default browser.".into(),
|
|
1287
|
+
))
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
"browser_click" => {
|
|
1292
|
+
let selector = if !input.selector.is_empty() {
|
|
1293
|
+
&input.selector
|
|
1294
|
+
} else if !input.path.is_empty() {
|
|
1295
|
+
&input.path
|
|
1296
|
+
} else {
|
|
1297
|
+
return Err(OrchestrationError::Agent(
|
|
1298
|
+
"browser_click requires 'selector'".into(),
|
|
1299
|
+
));
|
|
1300
|
+
};
|
|
1301
|
+
let result = crate::browser::click(config, selector)
|
|
1302
|
+
.await
|
|
1303
|
+
.map_err(|e| OrchestrationError::Agent(e))?;
|
|
1304
|
+
Ok(result)
|
|
1305
|
+
}
|
|
1306
|
+
"browser_type" => {
|
|
1307
|
+
let selector = if !input.selector.is_empty() {
|
|
1308
|
+
&input.selector
|
|
1309
|
+
} else if !input.path.is_empty() {
|
|
1310
|
+
&input.path
|
|
1311
|
+
} else {
|
|
1312
|
+
return Err(OrchestrationError::Agent(
|
|
1313
|
+
"browser_type requires 'selector'".into(),
|
|
1314
|
+
));
|
|
1315
|
+
};
|
|
1316
|
+
let text = if !input.text.is_empty() {
|
|
1317
|
+
&input.text
|
|
1318
|
+
} else if !input.query.is_empty() {
|
|
1319
|
+
&input.query
|
|
1320
|
+
} else {
|
|
1321
|
+
return Err(OrchestrationError::Agent(
|
|
1322
|
+
"browser_type requires 'text'".into(),
|
|
1323
|
+
));
|
|
1324
|
+
};
|
|
1325
|
+
let result = crate::browser::type_text(config, selector, text)
|
|
1326
|
+
.await
|
|
1327
|
+
.map_err(|e| OrchestrationError::Agent(e))?;
|
|
1328
|
+
Ok(result)
|
|
1329
|
+
}
|
|
1330
|
+
"browser_read" => {
|
|
1331
|
+
let result = crate::browser::read_page_text(config)
|
|
1332
|
+
.await
|
|
1333
|
+
.map_err(|e| OrchestrationError::Agent(e))?;
|
|
1334
|
+
Ok(result)
|
|
1335
|
+
}
|
|
1079
1336
|
"memory_recall" => {
|
|
1080
1337
|
let query = required(&input.query, "query")?;
|
|
1081
1338
|
let query_lower = query.to_ascii_lowercase();
|
|
@@ -1640,8 +1897,9 @@ fn action_fingerprint(decision: &AgentDecision) -> String {
|
|
|
1640
1897
|
}
|
|
1641
1898
|
|
|
1642
1899
|
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();
|
|
1643
1901
|
let mut observation = format!(
|
|
1644
|
-
"
|
|
1902
|
+
"Current Time: {now}\nTask: {task}\nWorkspace: {}\nLearned skills:\n{}\n",
|
|
1645
1903
|
root.display(),
|
|
1646
1904
|
if skills.trim().is_empty() {
|
|
1647
1905
|
"(none)"
|
|
@@ -1991,6 +2249,7 @@ Example response:
|
|
|
1991
2249
|
audio_data_uri: None,
|
|
1992
2250
|
document_attachment: None,
|
|
1993
2251
|
workspace_path: None,
|
|
2252
|
+
agent_id: None,
|
|
1994
2253
|
};
|
|
1995
2254
|
|
|
1996
2255
|
// Send the chat request to LLM
|
|
@@ -2049,6 +2308,7 @@ mod tests {
|
|
|
2049
2308
|
audio_data_uri: None,
|
|
2050
2309
|
document_attachment: None,
|
|
2051
2310
|
workspace_path: None,
|
|
2311
|
+
agent_id: None,
|
|
2052
2312
|
};
|
|
2053
2313
|
assert_eq!(
|
|
2054
2314
|
enrich_request(&store, &request).unwrap().system_instruction,
|
|
@@ -2154,4 +2414,49 @@ mod tests {
|
|
|
2154
2414
|
|
|
2155
2415
|
assert_eq!(classification.mode.as_str(), "readOnly");
|
|
2156
2416
|
}
|
|
2417
|
+
|
|
2418
|
+
#[test]
|
|
2419
|
+
fn test_resolve_agent_config() {
|
|
2420
|
+
let mut config = MintConfig::default();
|
|
2421
|
+
config.agents = vec![
|
|
2422
|
+
AgentConfig {
|
|
2423
|
+
id: "planner".into(),
|
|
2424
|
+
name: "Planner".into(),
|
|
2425
|
+
provider: "openai".into(),
|
|
2426
|
+
model: "gpt-4o".into(),
|
|
2427
|
+
api_key: Some("test-key".into()),
|
|
2428
|
+
system_instruction: "Planner instruction".into(),
|
|
2429
|
+
enabled: true,
|
|
2430
|
+
},
|
|
2431
|
+
AgentConfig {
|
|
2432
|
+
id: "coder".into(),
|
|
2433
|
+
name: "Coder".into(),
|
|
2434
|
+
provider: "gemini".into(),
|
|
2435
|
+
model: "gemini-2.5-flash".into(),
|
|
2436
|
+
api_key: None,
|
|
2437
|
+
system_instruction: "Coder instruction".into(),
|
|
2438
|
+
enabled: true,
|
|
2439
|
+
},
|
|
2440
|
+
];
|
|
2441
|
+
|
|
2442
|
+
let (cfg, instr, name, model) = resolve_agent_config(&config, Some("planner"), &[]);
|
|
2443
|
+
assert_eq!(cfg.ai_provider, "openai");
|
|
2444
|
+
assert_eq!(cfg.openai_model, "gpt-4o");
|
|
2445
|
+
assert_eq!(cfg.openai_api_key, "test-key");
|
|
2446
|
+
assert_eq!(instr, "Planner instruction");
|
|
2447
|
+
assert_eq!(name, Some("Planner".to_string()));
|
|
2448
|
+
assert_eq!(model, Some("gpt-4o".to_string()));
|
|
2449
|
+
|
|
2450
|
+
let (cfg, instr, name, model) = resolve_agent_config(&config, None, &[]);
|
|
2451
|
+
assert_eq!(cfg.ai_provider, "openai");
|
|
2452
|
+
assert_eq!(instr, "Planner instruction");
|
|
2453
|
+
assert_eq!(name, Some("Planner".to_string()));
|
|
2454
|
+
|
|
2455
|
+
let trajectory = vec!["Step 1:\n- Action: create_plan\n- Observation: all planned".to_string()];
|
|
2456
|
+
let (cfg, instr, name, model) = resolve_agent_config(&config, None, &trajectory);
|
|
2457
|
+
assert_eq!(cfg.ai_provider, "gemini");
|
|
2458
|
+
assert_eq!(instr, "Coder instruction");
|
|
2459
|
+
assert_eq!(name, Some("Coder".to_string()));
|
|
2460
|
+
assert_eq!(model, Some("gemini-2.5-flash".to_string()));
|
|
2461
|
+
}
|
|
2157
2462
|
}
|
|
@@ -53,10 +53,32 @@ pub fn learn_skill(path: &Path) -> Result<LearnedSkill, SkillError> {
|
|
|
53
53
|
Ok(MemoryStore::open_default()?.add_learned_skill(name, &path.to_string_lossy(), &content)?)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
pub fn learned_skills_context() -> Result<String, SkillError> {
|
|
57
|
-
let skills = MemoryStore::open_default()?.learned_skills(20)?;
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
pub fn learned_skills_context(workspace_root: Option<&Path>) -> Result<String, SkillError> {
|
|
57
|
+
let mut skills = MemoryStore::open_default()?.learned_skills(20)?;
|
|
58
|
+
|
|
59
|
+
if let Some(home) = dirs::home_dir() {
|
|
60
|
+
let global_skills_path = home.join(".config").join("mint").join("mint-skills");
|
|
61
|
+
if !global_skills_path.exists() {
|
|
62
|
+
let _ = std::fs::create_dir_all(&global_skills_path);
|
|
63
|
+
}
|
|
64
|
+
load_skills_from_dir(&global_skills_path, &mut skills);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if let Some(root) = workspace_root {
|
|
68
|
+
let workspace_skills_path1 = root.join(".agents").join("skills");
|
|
69
|
+
load_skills_from_dir(&workspace_skills_path1, &mut skills);
|
|
70
|
+
|
|
71
|
+
let workspace_skills_path2 = root.join("skills");
|
|
72
|
+
load_skills_from_dir(&workspace_skills_path2, &mut skills);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let mut unique_skills = std::collections::BTreeMap::new();
|
|
76
|
+
for skill in skills {
|
|
77
|
+
unique_skills.insert(skill.name.clone(), skill);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let value = unique_skills
|
|
81
|
+
.into_values()
|
|
60
82
|
.map(|skill| format!("Skill: {}\n{}", skill.name, skill.content))
|
|
61
83
|
.collect::<Vec<_>>()
|
|
62
84
|
.join("\n\n");
|
|
@@ -69,3 +91,90 @@ pub fn learned_skills_context() -> Result<String, SkillError> {
|
|
|
69
91
|
}
|
|
70
92
|
Ok(format!("{}\n...<learned skills truncated>", &value[..end]))
|
|
71
93
|
}
|
|
94
|
+
|
|
95
|
+
pub fn load_skills_from_dir(dir: &Path, list: &mut Vec<LearnedSkill>) {
|
|
96
|
+
if !dir.is_dir() {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if let Ok(entries) = fs::read_dir(dir) {
|
|
100
|
+
for entry in entries.flatten() {
|
|
101
|
+
let path = entry.path();
|
|
102
|
+
if path.is_dir() {
|
|
103
|
+
for filename in &["SKILL.md", "SKILL.txt", "skill.md", "skill.txt"] {
|
|
104
|
+
let skill_file = path.join(filename);
|
|
105
|
+
if skill_file.is_file() {
|
|
106
|
+
if let Ok(content) = fs::read_to_string(&skill_file) {
|
|
107
|
+
let name = path
|
|
108
|
+
.file_name()
|
|
109
|
+
.and_then(|n| n.to_str())
|
|
110
|
+
.unwrap_or("skill")
|
|
111
|
+
.to_string();
|
|
112
|
+
list.push(LearnedSkill {
|
|
113
|
+
id: 0,
|
|
114
|
+
name,
|
|
115
|
+
source_path: skill_file.to_string_lossy().to_string(),
|
|
116
|
+
content,
|
|
117
|
+
created_at: String::new(),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
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
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[cfg(test)]
|
|
148
|
+
mod tests {
|
|
149
|
+
use super::*;
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn test_fs_skills_loading() {
|
|
153
|
+
let test_dir = std::env::temp_dir().join("mint_skills_test_unique_dir");
|
|
154
|
+
let _ = std::fs::remove_dir_all(&test_dir);
|
|
155
|
+
let workspace_path = test_dir.join(".agents").join("skills");
|
|
156
|
+
std::fs::create_dir_all(&workspace_path).unwrap();
|
|
157
|
+
|
|
158
|
+
// Write a file skill
|
|
159
|
+
let file_skill_path = workspace_path.join("rust-style.md");
|
|
160
|
+
std::fs::write(&file_skill_path, "Use 4 spaces for Rust indent.").unwrap();
|
|
161
|
+
|
|
162
|
+
// Write a directory skill
|
|
163
|
+
let dir_skill_path = workspace_path.join("js-style");
|
|
164
|
+
std::fs::create_dir_all(&dir_skill_path).unwrap();
|
|
165
|
+
std::fs::write(dir_skill_path.join("SKILL.md"), "Use 2 spaces for JS.").unwrap();
|
|
166
|
+
|
|
167
|
+
let mut list = Vec::new();
|
|
168
|
+
load_skills_from_dir(&workspace_path, &mut list);
|
|
169
|
+
|
|
170
|
+
let _ = std::fs::remove_dir_all(&test_dir);
|
|
171
|
+
|
|
172
|
+
assert_eq!(list.len(), 2);
|
|
173
|
+
|
|
174
|
+
let rust_skill = list.iter().find(|s| s.name == "rust-style").unwrap();
|
|
175
|
+
assert_eq!(rust_skill.content, "Use 4 spaces for Rust indent.");
|
|
176
|
+
|
|
177
|
+
let js_skill = list.iter().find(|s| s.name == "js-style").unwrap();
|
|
178
|
+
assert_eq!(js_skill.content, "Use 2 spaces for JS.");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -10,6 +10,8 @@ pub struct TtsUrl {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
pub fn google_tts_urls(text: &str, language: &str) -> Vec<TtsUrl> {
|
|
13
|
+
let has_thai = text.chars().any(|c| ('\u{0e00}'..='\u{0e7f}').contains(&c));
|
|
14
|
+
let tl = if has_thai { "th" } else { language };
|
|
13
15
|
let chunks = split_text(text, MAX_GOOGLE_TTS_CHARS);
|
|
14
16
|
let total = chunks.len();
|
|
15
17
|
chunks
|
|
@@ -19,7 +21,7 @@ pub fn google_tts_urls(text: &str, language: &str) -> Vec<TtsUrl> {
|
|
|
19
21
|
url: format!(
|
|
20
22
|
"https://translate.google.com/translate_tts?ie=UTF-8&q={}&tl={}&client=tw-ob&idx={index}&total={total}&textlen={}",
|
|
21
23
|
encode_component(&chunk),
|
|
22
|
-
encode_component(
|
|
24
|
+
encode_component(tl),
|
|
23
25
|
chunk.chars().count()
|
|
24
26
|
),
|
|
25
27
|
short_text: chunk,
|
|
@@ -51,28 +51,7 @@ pub fn load_workflows() -> Result<Vec<Value>, WorkflowError> {
|
|
|
51
51
|
fn save_default_workflows(path: &PathBuf) -> Result<(), WorkflowError> {
|
|
52
52
|
let directory = path.parent().ok_or(WorkflowError::MissingDirectory)?;
|
|
53
53
|
fs::create_dir_all(directory).map_err(WorkflowError::CreateDirectory)?;
|
|
54
|
-
let workflows = json!([
|
|
55
|
-
{
|
|
56
|
-
"id": "wf-1",
|
|
57
|
-
"name": "Check Mic on Zoom",
|
|
58
|
-
"trigger": { "type": "process_running", "processName": "zoom" },
|
|
59
|
-
"action": {
|
|
60
|
-
"type": "system_info",
|
|
61
|
-
"message": "Looks like you opened Zoom. Should I check your system resources?",
|
|
62
|
-
"target": ""
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "wf-2",
|
|
67
|
-
"name": "Coding Time",
|
|
68
|
-
"trigger": { "type": "process_running", "processName": "code" },
|
|
69
|
-
"action": {
|
|
70
|
-
"type": "open_app",
|
|
71
|
-
"message": "Coding time. Want me to open Spotify?",
|
|
72
|
-
"target": "spotify"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
]);
|
|
54
|
+
let workflows = json!([]);
|
|
76
55
|
let raw = serde_json::to_string_pretty(&workflows).map_err(WorkflowError::Serialize)?;
|
|
77
56
|
fs::write(path, format!("{raw}\n")).map_err(|source| WorkflowError::Write {
|
|
78
57
|
path: path.clone(),
|