@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
package/src-tauri/src/browser.rs
CHANGED
|
@@ -1,141 +1 @@
|
|
|
1
|
-
|
|
2
|
-
use mint_core::MintConfig;
|
|
3
|
-
use serde::Serialize;
|
|
4
|
-
use serde_json::{Value, json};
|
|
5
|
-
use tokio_tungstenite::{connect_async, tungstenite::Message};
|
|
6
|
-
|
|
7
|
-
#[derive(Debug, Clone, Serialize)]
|
|
8
|
-
#[serde(rename_all = "camelCase")]
|
|
9
|
-
pub struct BrowserTab {
|
|
10
|
-
pub id: String,
|
|
11
|
-
pub title: String,
|
|
12
|
-
pub url: String,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
pub async fn list_tabs(config: &MintConfig) -> Result<Vec<BrowserTab>, String> {
|
|
16
|
-
Ok(fetch_pages(config)
|
|
17
|
-
.await?
|
|
18
|
-
.into_iter()
|
|
19
|
-
.filter_map(|page| {
|
|
20
|
-
Some(BrowserTab {
|
|
21
|
-
id: page["id"].as_str()?.to_owned(),
|
|
22
|
-
title: page["title"].as_str().unwrap_or_default().to_owned(),
|
|
23
|
-
url: page["url"].as_str().unwrap_or_default().to_owned(),
|
|
24
|
-
})
|
|
25
|
-
})
|
|
26
|
-
.collect())
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
pub async fn navigate(config: &MintConfig, url: &str) -> Result<String, String> {
|
|
30
|
-
if !(url.starts_with("https://") || url.starts_with("http://")) {
|
|
31
|
-
return Err("browser navigation only supports http and https URLs".into());
|
|
32
|
-
}
|
|
33
|
-
let response = cdp_call(config, "Page.navigate", json!({ "url": url })).await?;
|
|
34
|
-
response["result"]["frameId"]
|
|
35
|
-
.as_str()
|
|
36
|
-
.map(|_| format!("navigating to {url}"))
|
|
37
|
-
.ok_or_else(|| response_error(&response))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
pub async fn read_page_text(config: &MintConfig) -> Result<String, String> {
|
|
41
|
-
let response = cdp_call(
|
|
42
|
-
config,
|
|
43
|
-
"Runtime.evaluate",
|
|
44
|
-
json!({
|
|
45
|
-
"expression": "document.body ? document.body.innerText.substring(0, 12000) : ''",
|
|
46
|
-
"returnByValue": true
|
|
47
|
-
}),
|
|
48
|
-
)
|
|
49
|
-
.await?;
|
|
50
|
-
response["result"]["result"]["value"]
|
|
51
|
-
.as_str()
|
|
52
|
-
.map(str::to_owned)
|
|
53
|
-
.ok_or_else(|| response_error(&response))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
pub async fn click(config: &MintConfig, selector: &str) -> Result<String, String> {
|
|
57
|
-
let selector = selector.trim();
|
|
58
|
-
if selector.is_empty() || selector.len() > 500 {
|
|
59
|
-
return Err("browser selector must contain between 1 and 500 characters".into());
|
|
60
|
-
}
|
|
61
|
-
let selector = serde_json::to_string(selector).map_err(|error| error.to_string())?;
|
|
62
|
-
let response = cdp_call(
|
|
63
|
-
config,
|
|
64
|
-
"Runtime.evaluate",
|
|
65
|
-
json!({
|
|
66
|
-
"expression": format!(
|
|
67
|
-
"(() => {{ const element = document.querySelector({selector}); if (!element) return 'not-found'; element.click(); return 'clicked'; }})()"
|
|
68
|
-
),
|
|
69
|
-
"returnByValue": true
|
|
70
|
-
}),
|
|
71
|
-
)
|
|
72
|
-
.await?;
|
|
73
|
-
match response["result"]["result"]["value"].as_str() {
|
|
74
|
-
Some("clicked") => Ok("clicked".into()),
|
|
75
|
-
Some("not-found") => Err(format!("browser selector not found: {selector}")),
|
|
76
|
-
_ => Err(response_error(&response)),
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async fn cdp_call(config: &MintConfig, method: &str, params: Value) -> Result<Value, String> {
|
|
81
|
-
let page = fetch_pages(config)
|
|
82
|
-
.await?
|
|
83
|
-
.into_iter()
|
|
84
|
-
.find(|page| page["type"] == "page")
|
|
85
|
-
.ok_or("Chrome DevTools did not report an open browser page")?;
|
|
86
|
-
let socket_url = page["webSocketDebuggerUrl"]
|
|
87
|
-
.as_str()
|
|
88
|
-
.ok_or("Chrome DevTools page does not expose a websocket URL")?;
|
|
89
|
-
let (mut socket, _) = connect_async(socket_url)
|
|
90
|
-
.await
|
|
91
|
-
.map_err(|error| format!("unable to connect to Chrome DevTools websocket: {error}"))?;
|
|
92
|
-
socket
|
|
93
|
-
.send(Message::Text(
|
|
94
|
-
json!({ "id": 1, "method": method, "params": params })
|
|
95
|
-
.to_string()
|
|
96
|
-
.into(),
|
|
97
|
-
))
|
|
98
|
-
.await
|
|
99
|
-
.map_err(|error| error.to_string())?;
|
|
100
|
-
while let Some(message) = socket.next().await {
|
|
101
|
-
let message = message.map_err(|error| error.to_string())?;
|
|
102
|
-
let Message::Text(raw) = message else {
|
|
103
|
-
continue;
|
|
104
|
-
};
|
|
105
|
-
let value: Value = serde_json::from_str(&raw).map_err(|error| error.to_string())?;
|
|
106
|
-
if value["id"] == 1 {
|
|
107
|
-
return Ok(value);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
Err("Chrome DevTools websocket closed before returning a response".into())
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async fn fetch_pages(config: &MintConfig) -> Result<Vec<Value>, String> {
|
|
114
|
-
let endpoint = config
|
|
115
|
-
.extra
|
|
116
|
-
.get("browserDebugUrl")
|
|
117
|
-
.and_then(Value::as_str)
|
|
118
|
-
.unwrap_or("http://127.0.0.1:9222/json/list");
|
|
119
|
-
let value: Value = mint_core::HTTP_CLIENT
|
|
120
|
-
.clone()
|
|
121
|
-
.get(endpoint)
|
|
122
|
-
.send()
|
|
123
|
-
.await
|
|
124
|
-
.map_err(|error| format!("unable to reach Chrome DevTools at {endpoint}: {error}"))?
|
|
125
|
-
.error_for_status()
|
|
126
|
-
.map_err(|error| error.to_string())?
|
|
127
|
-
.json()
|
|
128
|
-
.await
|
|
129
|
-
.map_err(|error| error.to_string())?;
|
|
130
|
-
value
|
|
131
|
-
.as_array()
|
|
132
|
-
.cloned()
|
|
133
|
-
.ok_or_else(|| "Chrome DevTools response was not a page list".into())
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
fn response_error(response: &Value) -> String {
|
|
137
|
-
response["error"]["message"]
|
|
138
|
-
.as_str()
|
|
139
|
-
.unwrap_or("Chrome DevTools returned an unexpected response")
|
|
140
|
-
.to_owned()
|
|
141
|
-
}
|
|
1
|
+
// Deleted - Moved browser automation functions to mint-core.
|
|
@@ -8,11 +8,11 @@ use serde::Deserialize;
|
|
|
8
8
|
use serde_json::{Value, json};
|
|
9
9
|
use tauri::{AppHandle, Emitter, Manager};
|
|
10
10
|
|
|
11
|
-
use
|
|
11
|
+
use mint_core::browser::{click, navigate, read_page_text, type_text};
|
|
12
12
|
|
|
13
13
|
const MAX_STEPS: usize = 20;
|
|
14
14
|
const SYSTEM_PROMPT: &str = r#"You are Mint's native background task agent. Return only JSON:
|
|
15
|
-
{"thought":"short progress note","action":"done|propose_folder|propose_write_file|open_url|browser_read|browser_click|knowledge_search|propose_bash","target":"path, URL, selector, query, command, or final result","data":"optional file content"}
|
|
15
|
+
{"thought":"short progress note","action":"done|propose_folder|propose_write_file|open_url|browser_read|browser_click|browser_type|knowledge_search|propose_bash","target":"path, URL, selector, query, command, or final result","data":"optional file content"}
|
|
16
16
|
Use only one action per response. Background tasks never mutate the filesystem or execute shell commands. Use propose_folder, propose_write_file, and propose_bash so Mint can record a proposal for explicit user approval."#;
|
|
17
17
|
|
|
18
18
|
#[derive(Debug, Deserialize)]
|
|
@@ -177,6 +177,7 @@ async fn execute_action(
|
|
|
177
177
|
"open_url" => navigate(config, &action.target).await,
|
|
178
178
|
"browser_read" => read_page_text(config).await,
|
|
179
179
|
"browser_click" => click(config, &action.target).await,
|
|
180
|
+
"browser_type" => type_text(config, &action.target, &action.data).await,
|
|
180
181
|
"knowledge_search" => {
|
|
181
182
|
let hits = KnowledgeStore::open_default()
|
|
182
183
|
.map_err(|error| error.to_string())?
|
package/src-tauri/src/lib.rs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
mod browser;
|
|
2
1
|
mod desktop;
|
|
3
2
|
mod discord_rpc;
|
|
4
3
|
mod events;
|
|
@@ -11,7 +10,7 @@ mod updater;
|
|
|
11
10
|
mod webhooks;
|
|
12
11
|
mod workflows;
|
|
13
12
|
|
|
14
|
-
use browser::{
|
|
13
|
+
use mint_core::browser::{
|
|
15
14
|
BrowserTab, click as browser_click, list_tabs as browser_list_tabs,
|
|
16
15
|
navigate as browser_navigate, read_page_text,
|
|
17
16
|
};
|
|
@@ -41,7 +40,7 @@ use mint_core::{
|
|
|
41
40
|
use plugins::execute_plugin;
|
|
42
41
|
|
|
43
42
|
pub struct ApprovalsState {
|
|
44
|
-
pub pending: Mutex<HashMap<String, oneshot::Sender<
|
|
43
|
+
pub pending: Mutex<HashMap<String, oneshot::Sender<ApprovalOutcome>>>,
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
static COUNTER: AtomicU64 = AtomicU64::new(1);
|
|
@@ -136,6 +135,26 @@ async fn get_workspace_tree(path: Option<String>) -> Result<WorkspaceTreeEntry,
|
|
|
136
135
|
.map_err(|error| format!("workspace tree task failed: {error}"))?
|
|
137
136
|
}
|
|
138
137
|
|
|
138
|
+
#[tauri::command]
|
|
139
|
+
async fn create_workspace_file(path: String) -> Result<(), String> {
|
|
140
|
+
std::fs::write(&path, "").map_err(|error| error.to_string())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[tauri::command]
|
|
144
|
+
async fn create_workspace_folder(path: String) -> Result<(), String> {
|
|
145
|
+
std::fs::create_dir_all(&path).map_err(|error| error.to_string())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[tauri::command]
|
|
149
|
+
async fn delete_workspace_item(path: String) -> Result<(), String> {
|
|
150
|
+
let path_buf = std::path::PathBuf::from(path);
|
|
151
|
+
if path_buf.is_dir() {
|
|
152
|
+
std::fs::remove_dir_all(path_buf).map_err(|error| error.to_string())
|
|
153
|
+
} else {
|
|
154
|
+
std::fs::remove_file(path_buf).map_err(|error| error.to_string())
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
139
158
|
fn build_workspace_tree(path: Option<String>) -> Result<WorkspaceTreeEntry, String> {
|
|
140
159
|
let root = workspace_root(path.as_deref())?;
|
|
141
160
|
let name = root
|
|
@@ -191,6 +210,11 @@ fn workspace_root(path: Option<&str>) -> Result<PathBuf, String> {
|
|
|
191
210
|
None => std::env::current_dir().map_err(|error| error.to_string())?,
|
|
192
211
|
};
|
|
193
212
|
let root = root.canonicalize().map_err(|error| error.to_string())?;
|
|
213
|
+
let root = if root.ends_with("src-tauri") {
|
|
214
|
+
root.parent().unwrap_or(&root).to_path_buf()
|
|
215
|
+
} else {
|
|
216
|
+
root
|
|
217
|
+
};
|
|
194
218
|
if !root.is_dir() {
|
|
195
219
|
return Err(format!("workspace is not a directory: {}", root.display()));
|
|
196
220
|
}
|
|
@@ -298,10 +322,30 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
|
|
|
298
322
|
if request.message.starts_with("/chat ") {
|
|
299
323
|
let mut clean_request = request.clone();
|
|
300
324
|
clean_request.message = request.message.strip_prefix("/chat ").unwrap().to_owned();
|
|
325
|
+
let config_clone = config.clone();
|
|
326
|
+
let chat_id_str = request.chat_id.clone().unwrap_or_default();
|
|
301
327
|
|
|
302
|
-
let
|
|
303
|
-
.await
|
|
304
|
-
|
|
328
|
+
let join_handle = tokio::spawn(async move {
|
|
329
|
+
orchestrate_chat_with_fallback(&config_clone, &clean_request).await
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
let abort_handle = join_handle.abort_handle();
|
|
333
|
+
if !chat_id_str.is_empty() {
|
|
334
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let res = join_handle.await;
|
|
338
|
+
|
|
339
|
+
if !chat_id_str.is_empty() {
|
|
340
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let (response, _) = match res {
|
|
344
|
+
Ok(Ok(val)) => val,
|
|
345
|
+
Ok(Err(e)) => return Err(e.to_string()),
|
|
346
|
+
Err(e) if e.is_cancelled() => return Err("Chat execution cancelled by user".to_string()),
|
|
347
|
+
Err(e) => return Err(format!("Task panicked: {}", e)),
|
|
348
|
+
};
|
|
305
349
|
return Ok(response);
|
|
306
350
|
}
|
|
307
351
|
|
|
@@ -330,33 +374,56 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
|
|
|
330
374
|
)
|
|
331
375
|
.map_err(|e| e.to_string())?;
|
|
332
376
|
|
|
333
|
-
let
|
|
377
|
+
let outcome =
|
|
334
378
|
tokio::task::block_in_place(move || tokio::runtime::Handle::current().block_on(rx))
|
|
335
|
-
.unwrap_or(
|
|
336
|
-
|
|
337
|
-
if approved {
|
|
338
|
-
Ok(ApprovalOutcome::Approved)
|
|
339
|
-
} else {
|
|
340
|
-
Ok(ApprovalOutcome::Denied)
|
|
341
|
-
}
|
|
379
|
+
.unwrap_or(ApprovalOutcome::Denied);
|
|
380
|
+
Ok(outcome)
|
|
342
381
|
};
|
|
343
382
|
|
|
344
383
|
let progress_cb = |_| {};
|
|
345
384
|
let on_chunk = |_| {};
|
|
346
385
|
|
|
347
|
-
let
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
386
|
+
let chat_id_str = request.chat_id.clone().unwrap_or_default();
|
|
387
|
+
let config_clone = config.clone();
|
|
388
|
+
let message_clone = request.message.clone();
|
|
389
|
+
let root_clone = root.clone();
|
|
390
|
+
let image_data_uri_clone = request.image_data_uri.clone();
|
|
391
|
+
let chat_id_clone = request.chat_id.clone();
|
|
392
|
+
let agent_id_clone = request.agent_id.clone();
|
|
393
|
+
|
|
394
|
+
let join_handle = tokio::spawn(async move {
|
|
395
|
+
orchestrate_agent_loop(
|
|
396
|
+
&config_clone,
|
|
397
|
+
&message_clone,
|
|
398
|
+
&root_clone,
|
|
399
|
+
image_data_uri_clone,
|
|
400
|
+
chat_id_clone.as_deref(),
|
|
401
|
+
agent_id_clone.as_deref(),
|
|
402
|
+
fast_mode,
|
|
403
|
+
approve_cb,
|
|
404
|
+
progress_cb,
|
|
405
|
+
on_chunk,
|
|
406
|
+
)
|
|
407
|
+
.await
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
let abort_handle = join_handle.abort_handle();
|
|
411
|
+
if !chat_id_str.is_empty() {
|
|
412
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
let res = join_handle.await;
|
|
416
|
+
|
|
417
|
+
if !chat_id_str.is_empty() {
|
|
418
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let res = match res {
|
|
422
|
+
Ok(Ok(val)) => val,
|
|
423
|
+
Ok(Err(e)) => return Err(e.to_string()),
|
|
424
|
+
Err(e) if e.is_cancelled() => return Err("Agent execution cancelled by user".to_string()),
|
|
425
|
+
Err(e) => return Err(format!("Task panicked: {}", e)),
|
|
426
|
+
};
|
|
360
427
|
|
|
361
428
|
Ok(ChatResponse {
|
|
362
429
|
provider: res.provider,
|
|
@@ -378,13 +445,34 @@ async fn stream_chat_message(
|
|
|
378
445
|
if request.message.starts_with("/chat ") {
|
|
379
446
|
let mut clean_request = request.clone();
|
|
380
447
|
clean_request.message = request.message.strip_prefix("/chat ").unwrap().to_owned();
|
|
448
|
+
let config_clone = config.clone();
|
|
449
|
+
let on_event_clone = on_event.clone();
|
|
450
|
+
let chat_id_str = request.chat_id.clone().unwrap_or_default();
|
|
381
451
|
|
|
382
|
-
let (
|
|
383
|
-
orchestrate_chat_stream_with_fallback(&
|
|
384
|
-
let _ =
|
|
452
|
+
let join_handle = tokio::spawn(async move {
|
|
453
|
+
orchestrate_chat_stream_with_fallback(&config_clone, &clean_request, move |chunk| {
|
|
454
|
+
let _ = on_event_clone.send(DesktopStreamEvent::Chunk { chunk });
|
|
385
455
|
})
|
|
386
456
|
.await
|
|
387
|
-
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
let abort_handle = join_handle.abort_handle();
|
|
460
|
+
if !chat_id_str.is_empty() {
|
|
461
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let res = join_handle.await;
|
|
465
|
+
|
|
466
|
+
if !chat_id_str.is_empty() {
|
|
467
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let (response, _) = match res {
|
|
471
|
+
Ok(Ok(val)) => val,
|
|
472
|
+
Ok(Err(e)) => return Err(e.to_string()),
|
|
473
|
+
Err(e) if e.is_cancelled() => return Err("Chat execution cancelled by user".to_string()),
|
|
474
|
+
Err(e) => return Err(format!("Task panicked: {}", e)),
|
|
475
|
+
};
|
|
388
476
|
return Ok(response);
|
|
389
477
|
}
|
|
390
478
|
|
|
@@ -413,15 +501,10 @@ async fn stream_chat_message(
|
|
|
413
501
|
)
|
|
414
502
|
.map_err(|e| e.to_string())?;
|
|
415
503
|
|
|
416
|
-
let
|
|
504
|
+
let outcome =
|
|
417
505
|
tokio::task::block_in_place(move || tokio::runtime::Handle::current().block_on(rx))
|
|
418
|
-
.unwrap_or(
|
|
419
|
-
|
|
420
|
-
if approved {
|
|
421
|
-
Ok(ApprovalOutcome::Approved)
|
|
422
|
-
} else {
|
|
423
|
-
Ok(ApprovalOutcome::Denied)
|
|
424
|
-
}
|
|
506
|
+
.unwrap_or(ApprovalOutcome::Denied);
|
|
507
|
+
Ok(outcome)
|
|
425
508
|
};
|
|
426
509
|
|
|
427
510
|
let on_progress_event = on_event.clone();
|
|
@@ -442,19 +525,47 @@ async fn stream_chat_message(
|
|
|
442
525
|
}
|
|
443
526
|
};
|
|
444
527
|
|
|
445
|
-
let
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
528
|
+
let chat_id_str = request.chat_id.clone().unwrap_or_default();
|
|
529
|
+
let config_clone = config.clone();
|
|
530
|
+
let message_clone = request.message.clone();
|
|
531
|
+
let root_clone = root.clone();
|
|
532
|
+
let image_data_uri_clone = request.image_data_uri.clone();
|
|
533
|
+
let chat_id_clone = request.chat_id.clone();
|
|
534
|
+
let agent_id_clone = request.agent_id.clone();
|
|
535
|
+
|
|
536
|
+
let join_handle = tokio::spawn(async move {
|
|
537
|
+
orchestrate_agent_loop(
|
|
538
|
+
&config_clone,
|
|
539
|
+
&message_clone,
|
|
540
|
+
&root_clone,
|
|
541
|
+
image_data_uri_clone,
|
|
542
|
+
chat_id_clone.as_deref(),
|
|
543
|
+
agent_id_clone.as_deref(),
|
|
544
|
+
fast_mode,
|
|
545
|
+
approve_cb,
|
|
546
|
+
progress_cb,
|
|
547
|
+
on_chunk,
|
|
548
|
+
)
|
|
549
|
+
.await
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
let abort_handle = join_handle.abort_handle();
|
|
553
|
+
if !chat_id_str.is_empty() {
|
|
554
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().insert(chat_id_str.clone(), abort_handle);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
let res = join_handle.await;
|
|
558
|
+
|
|
559
|
+
if !chat_id_str.is_empty() {
|
|
560
|
+
mint_core::ACTIVE_AGENTS.lock().unwrap().remove(&chat_id_str);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let res = match res {
|
|
564
|
+
Ok(Ok(val)) => val,
|
|
565
|
+
Ok(Err(e)) => return Err(e.to_string()),
|
|
566
|
+
Err(e) if e.is_cancelled() => return Err("Agent execution cancelled by user".to_string()),
|
|
567
|
+
Err(e) => return Err(format!("Task panicked: {}", e)),
|
|
568
|
+
};
|
|
458
569
|
|
|
459
570
|
Ok(ChatResponse {
|
|
460
571
|
provider: res.provider,
|
|
@@ -464,6 +575,12 @@ async fn stream_chat_message(
|
|
|
464
575
|
})
|
|
465
576
|
}
|
|
466
577
|
|
|
578
|
+
#[tauri::command]
|
|
579
|
+
async fn cancel_chat_message(chat_id: String) -> Result<(), String> {
|
|
580
|
+
mint_core::cancel_agent(&chat_id);
|
|
581
|
+
Ok(())
|
|
582
|
+
}
|
|
583
|
+
|
|
467
584
|
#[tauri::command]
|
|
468
585
|
fn save_interaction_agent_activity(
|
|
469
586
|
interaction_id: i64,
|
|
@@ -553,20 +670,52 @@ struct LearnedSkillDto {
|
|
|
553
670
|
source_path: String,
|
|
554
671
|
content: String,
|
|
555
672
|
updated_at: String,
|
|
673
|
+
location: String,
|
|
556
674
|
}
|
|
557
675
|
|
|
558
676
|
#[tauri::command]
|
|
559
|
-
fn list_learned_skills() -> Result<Vec<LearnedSkillDto>, String> {
|
|
677
|
+
fn list_learned_skills(workspace_path: Option<String>) -> Result<Vec<LearnedSkillDto>, String> {
|
|
560
678
|
let store = MemoryStore::open_default().map_err(|e| e.to_string())?;
|
|
561
|
-
let
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
679
|
+
let db_skills = store.learned_skills(100).map_err(|e| e.to_string())?;
|
|
680
|
+
|
|
681
|
+
let mut global_skills = Vec::new();
|
|
682
|
+
if let Some(home) = dirs::home_dir() {
|
|
683
|
+
let global_skills_path = home.join(".config").join("mint").join("mint-skills");
|
|
684
|
+
if !global_skills_path.exists() {
|
|
685
|
+
let _ = std::fs::create_dir_all(&global_skills_path);
|
|
686
|
+
}
|
|
687
|
+
mint_core::skills::load_skills_from_dir(&global_skills_path, &mut global_skills);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let mut workspace_skills = Vec::new();
|
|
691
|
+
if let Ok(root) = workspace_root(workspace_path.as_deref()) {
|
|
692
|
+
let workspace_skills_path1 = root.join(".agents").join("skills");
|
|
693
|
+
mint_core::skills::load_skills_from_dir(&workspace_skills_path1, &mut workspace_skills);
|
|
694
|
+
|
|
695
|
+
let workspace_skills_path2 = root.join("skills");
|
|
696
|
+
mint_core::skills::load_skills_from_dir(&workspace_skills_path2, &mut workspace_skills);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
let mut unique_skills = std::collections::BTreeMap::new();
|
|
700
|
+
for s in db_skills {
|
|
701
|
+
unique_skills.insert(s.name.clone(), (s, "database"));
|
|
702
|
+
}
|
|
703
|
+
for s in global_skills {
|
|
704
|
+
unique_skills.insert(s.name.clone(), (s, "global"));
|
|
705
|
+
}
|
|
706
|
+
for s in workspace_skills {
|
|
707
|
+
unique_skills.insert(s.name.clone(), (s, "workspace"));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
let dtos = unique_skills
|
|
711
|
+
.into_values()
|
|
712
|
+
.map(|(s, loc)| LearnedSkillDto {
|
|
565
713
|
id: s.id,
|
|
566
714
|
name: s.name,
|
|
567
715
|
source_path: s.source_path,
|
|
568
716
|
content: s.content,
|
|
569
717
|
updated_at: s.created_at,
|
|
718
|
+
location: loc.to_string(),
|
|
570
719
|
})
|
|
571
720
|
.collect();
|
|
572
721
|
Ok(dtos)
|
|
@@ -584,6 +733,7 @@ fn add_learned_skill(name: String, content: String) -> Result<LearnedSkillDto, S
|
|
|
584
733
|
source_path: skill.source_path,
|
|
585
734
|
content: skill.content,
|
|
586
735
|
updated_at: skill.created_at,
|
|
736
|
+
location: "database".to_string(),
|
|
587
737
|
})
|
|
588
738
|
}
|
|
589
739
|
|
|
@@ -611,16 +761,47 @@ async fn submit_tool_approval(
|
|
|
611
761
|
state: tauri::State<'_, ApprovalsState>,
|
|
612
762
|
token: String,
|
|
613
763
|
approved: bool,
|
|
764
|
+
answer: Option<String>,
|
|
614
765
|
) -> Result<(), String> {
|
|
615
766
|
let mut pending = state.pending.lock().map_err(|error| error.to_string())?;
|
|
616
767
|
if let Some(tx) = pending.remove(&token) {
|
|
617
|
-
let
|
|
768
|
+
let outcome = if let Some(ans) = answer {
|
|
769
|
+
if !ans.trim().is_empty() {
|
|
770
|
+
ApprovalOutcome::Intercepted(ans)
|
|
771
|
+
} else {
|
|
772
|
+
ApprovalOutcome::Denied
|
|
773
|
+
}
|
|
774
|
+
} else if approved {
|
|
775
|
+
ApprovalOutcome::Approved
|
|
776
|
+
} else {
|
|
777
|
+
ApprovalOutcome::Denied
|
|
778
|
+
};
|
|
779
|
+
let _ = tx.send(outcome);
|
|
618
780
|
Ok(())
|
|
619
781
|
} else {
|
|
620
782
|
Err("No pending approval found for this token".into())
|
|
621
783
|
}
|
|
622
784
|
}
|
|
623
785
|
|
|
786
|
+
#[derive(serde::Serialize)]
|
|
787
|
+
#[serde(rename_all = "camelCase")]
|
|
788
|
+
pub struct DetectedTools {
|
|
789
|
+
pub docker: bool,
|
|
790
|
+
pub git: bool,
|
|
791
|
+
pub gh: bool,
|
|
792
|
+
pub node: bool,
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#[tauri::command]
|
|
796
|
+
async fn detect_system_tools() -> Result<DetectedTools, String> {
|
|
797
|
+
Ok(DetectedTools {
|
|
798
|
+
docker: mint_core::config::which("docker"),
|
|
799
|
+
git: mint_core::config::which("git"),
|
|
800
|
+
gh: mint_core::config::which("gh"),
|
|
801
|
+
node: mint_core::config::which("node"),
|
|
802
|
+
})
|
|
803
|
+
}
|
|
804
|
+
|
|
624
805
|
#[derive(serde::Serialize)]
|
|
625
806
|
#[serde(rename_all = "camelCase")]
|
|
626
807
|
struct DesktopImageGenResponse {
|
|
@@ -839,7 +1020,15 @@ async fn click_browser_selector(selector: String) -> Result<String, String> {
|
|
|
839
1020
|
)
|
|
840
1021
|
.await
|
|
841
1022
|
}
|
|
842
|
-
|
|
1023
|
+
#[tauri::command]
|
|
1024
|
+
async fn type_in_browser(selector: String, text: String) -> Result<String, String> {
|
|
1025
|
+
mint_core::browser::type_text(
|
|
1026
|
+
&load_config().map_err(|error| error.to_string())?,
|
|
1027
|
+
&selector,
|
|
1028
|
+
&text,
|
|
1029
|
+
)
|
|
1030
|
+
.await
|
|
1031
|
+
}
|
|
843
1032
|
#[tauri::command]
|
|
844
1033
|
fn start_screen_capture(app: AppHandle) -> Result<(), String> {
|
|
845
1034
|
open_desktop_window(&app, "screen-picker")
|
|
@@ -1022,7 +1211,11 @@ pub fn run() {
|
|
|
1022
1211
|
})
|
|
1023
1212
|
.invoke_handler(tauri::generate_handler![
|
|
1024
1213
|
get_runtime_status,
|
|
1214
|
+
detect_system_tools,
|
|
1025
1215
|
get_workspace_tree,
|
|
1216
|
+
create_workspace_file,
|
|
1217
|
+
create_workspace_folder,
|
|
1218
|
+
delete_workspace_item,
|
|
1026
1219
|
generate_images,
|
|
1027
1220
|
select_workspace_directory,
|
|
1028
1221
|
get_config,
|
|
@@ -1033,6 +1226,7 @@ pub fn run() {
|
|
|
1033
1226
|
inspect_shell_command,
|
|
1034
1227
|
send_chat_message,
|
|
1035
1228
|
stream_chat_message,
|
|
1229
|
+
cancel_chat_message,
|
|
1036
1230
|
submit_tool_approval,
|
|
1037
1231
|
get_recent_interactions,
|
|
1038
1232
|
save_interaction_agent_activity,
|
|
@@ -1067,6 +1261,7 @@ pub fn run() {
|
|
|
1067
1261
|
navigate_browser,
|
|
1068
1262
|
read_browser_page,
|
|
1069
1263
|
click_browser_selector,
|
|
1264
|
+
type_in_browser,
|
|
1070
1265
|
start_screen_capture,
|
|
1071
1266
|
submit_screen_selection,
|
|
1072
1267
|
submit_spotlight,
|