@pheem49/mint 1.6.4 → 1.7.1
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/Cargo.toml +1 -1
- package/bin/mint +0 -0
- package/crates/mint-cli/src/agent.rs +42 -14
- package/crates/mint-cli/src/main.rs +412 -2
- package/crates/mint-cli/src/setup.rs +173 -32
- package/crates/mint-core/src/api_server.rs +35 -0
- package/crates/mint-core/src/config.rs +2 -2
- package/crates/mint-core/src/memory.rs +27 -1
- package/crates/mint-core/src/orchestration.rs +1 -1
- package/crates/mint-core/src/plugins.rs +35 -0
- package/crates/mint-core/tests/memory_persistence.rs +28 -0
- package/package.json +12 -2
- package/src/renderer/src/agentProgress.ts +39 -0
- package/src/renderer/src/components/ChatPanel.tsx +308 -34
- package/src/renderer/src/components/DashboardSidebar.tsx +3 -3
- package/src/renderer/src/components/MintDashboard.tsx +71 -27
- package/src/renderer/src/components/ModelPanel.tsx +2 -2
- package/src/renderer/src/components/PicturesLibrary.tsx +5 -1
- package/src/renderer/src/components/SpotlightWindow.tsx +8 -8
- package/src/renderer/src/components/WorkspacePanel.tsx +1 -1
- package/src/renderer/src/css/styles.css +195 -17
- package/src/renderer/src/index.css +46 -0
- package/src/renderer/src/tauri.ts +25 -0
- package/src/renderer/src-web/App.tsx +16 -1
- package/src/renderer/src-web/agentProgress.ts +39 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +309 -35
- package/src/renderer/src-web/components/DashboardSidebar.tsx +1 -1
- package/src/renderer/src-web/components/MintDashboard.tsx +53 -16
- package/src/renderer/src-web/components/PicturesLibrary.tsx +5 -1
- package/src/renderer/src-web/css/styles.css +242 -18
- package/src/renderer/src-web/index.css +46 -0
- package/src/renderer/src-web/tauri.ts +25 -0
- package/src-tauri/src/lib.rs +16 -0
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use anyhow::Result;
|
|
2
2
|
use crossterm::event::{self, Event, KeyCode};
|
|
3
3
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
|
4
|
-
use mint_core::{load_config, save_config};
|
|
4
|
+
use mint_core::{load_config, native_plugins, save_config};
|
|
5
5
|
use std::io::{self, Write};
|
|
6
6
|
|
|
7
7
|
struct ToolOption {
|
|
@@ -13,15 +13,6 @@ struct ToolOption {
|
|
|
13
13
|
pub async fn run() -> Result<Option<String>> {
|
|
14
14
|
let mut config = load_config()?;
|
|
15
15
|
|
|
16
|
-
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
17
|
-
println!("\x1b[32m Mint CLI Tool Manager Wizard\x1b[0m");
|
|
18
|
-
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
19
|
-
println!("Configure which agent tools are enabled or disabled:");
|
|
20
|
-
println!(
|
|
21
|
-
" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
|
|
22
|
-
);
|
|
23
|
-
println!();
|
|
24
|
-
|
|
25
16
|
let mut options = vec![
|
|
26
17
|
ToolOption {
|
|
27
18
|
name: "list_files (List Workspace Files)",
|
|
@@ -178,7 +169,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
178
169
|
];
|
|
179
170
|
|
|
180
171
|
let mut cursor = 0;
|
|
181
|
-
|
|
172
|
+
redraw_phase_1(&options, cursor);
|
|
182
173
|
enable_raw_mode()?;
|
|
183
174
|
|
|
184
175
|
loop {
|
|
@@ -204,8 +195,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
204
195
|
cursor = options.len() - 1;
|
|
205
196
|
}
|
|
206
197
|
disable_raw_mode()?;
|
|
207
|
-
|
|
208
|
-
print_options(&options, cursor);
|
|
198
|
+
redraw_phase_1(&options, cursor);
|
|
209
199
|
enable_raw_mode()?;
|
|
210
200
|
}
|
|
211
201
|
KeyCode::Down => {
|
|
@@ -215,15 +205,13 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
215
205
|
cursor = 0;
|
|
216
206
|
}
|
|
217
207
|
disable_raw_mode()?;
|
|
218
|
-
|
|
219
|
-
print_options(&options, cursor);
|
|
208
|
+
redraw_phase_1(&options, cursor);
|
|
220
209
|
enable_raw_mode()?;
|
|
221
210
|
}
|
|
222
211
|
KeyCode::Char(' ') => {
|
|
223
212
|
options[cursor].enabled = !options[cursor].enabled;
|
|
224
213
|
disable_raw_mode()?;
|
|
225
|
-
|
|
226
|
-
print_options(&options, cursor);
|
|
214
|
+
redraw_phase_1(&options, cursor);
|
|
227
215
|
enable_raw_mode()?;
|
|
228
216
|
}
|
|
229
217
|
KeyCode::Char('a') => {
|
|
@@ -231,8 +219,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
231
219
|
opt.enabled = true;
|
|
232
220
|
}
|
|
233
221
|
disable_raw_mode()?;
|
|
234
|
-
|
|
235
|
-
print_options(&options, cursor);
|
|
222
|
+
redraw_phase_1(&options, cursor);
|
|
236
223
|
enable_raw_mode()?;
|
|
237
224
|
}
|
|
238
225
|
KeyCode::Char('i') => {
|
|
@@ -240,8 +227,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
240
227
|
opt.enabled = !opt.enabled;
|
|
241
228
|
}
|
|
242
229
|
disable_raw_mode()?;
|
|
243
|
-
|
|
244
|
-
print_options(&options, cursor);
|
|
230
|
+
redraw_phase_1(&options, cursor);
|
|
245
231
|
enable_raw_mode()?;
|
|
246
232
|
}
|
|
247
233
|
KeyCode::Enter => {
|
|
@@ -270,15 +256,135 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
270
256
|
config.disabled_tools = disabled;
|
|
271
257
|
save_config(&config)?;
|
|
272
258
|
|
|
273
|
-
println!("\x1b[32mSuccessfully updated tool configurations!\x1b[0m\n");
|
|
259
|
+
println!("\x1b[32mSuccessfully updated agent tool configurations!\x1b[0m\n");
|
|
274
260
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
261
|
+
let all_native = native_plugins();
|
|
262
|
+
let allowed_plugins: std::collections::HashSet<String> = config
|
|
263
|
+
.extra
|
|
264
|
+
.get("allowedNativePlugins")
|
|
265
|
+
.and_then(|v| v.as_array())
|
|
266
|
+
.map(|arr| {
|
|
267
|
+
arr.iter()
|
|
268
|
+
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
|
269
|
+
.collect()
|
|
270
|
+
})
|
|
271
|
+
.unwrap_or_else(|| {
|
|
272
|
+
vec![
|
|
273
|
+
"dev_tools".to_string(),
|
|
274
|
+
"system_metrics".to_string(),
|
|
275
|
+
"github".to_string(),
|
|
276
|
+
]
|
|
277
|
+
.into_iter()
|
|
278
|
+
.collect()
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
let mut native_options: Vec<ToolOption> = all_native
|
|
282
|
+
.iter()
|
|
283
|
+
.map(|p| {
|
|
284
|
+
let has_all_permission = allowed_plugins.contains("*");
|
|
285
|
+
let is_enabled = has_all_permission || allowed_plugins.contains(p.name);
|
|
286
|
+
ToolOption {
|
|
287
|
+
name: p.name,
|
|
288
|
+
key: p.name,
|
|
289
|
+
enabled: is_enabled,
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
.collect();
|
|
293
|
+
|
|
294
|
+
let mut native_cursor = 0;
|
|
295
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
296
|
+
enable_raw_mode()?;
|
|
297
|
+
|
|
298
|
+
loop {
|
|
299
|
+
match event::poll(std::time::Duration::from_millis(100)) {
|
|
300
|
+
Ok(true) => {
|
|
301
|
+
if let Event::Key(key_event) = event::read()? {
|
|
302
|
+
if key_event.kind == event::KeyEventKind::Press {
|
|
303
|
+
let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
|
|
304
|
+
&& key_event
|
|
305
|
+
.modifiers
|
|
306
|
+
.contains(crossterm::event::KeyModifiers::CONTROL);
|
|
307
|
+
if is_ctrl_c {
|
|
308
|
+
disable_raw_mode()?;
|
|
309
|
+
println!("\n\x1b[31mSetup cancelled.\x1b[0m");
|
|
310
|
+
return Ok(None);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
match key_event.code {
|
|
314
|
+
KeyCode::Up => {
|
|
315
|
+
if native_cursor > 0 {
|
|
316
|
+
native_cursor -= 1;
|
|
317
|
+
} else {
|
|
318
|
+
native_cursor = native_options.len() - 1;
|
|
319
|
+
}
|
|
320
|
+
disable_raw_mode()?;
|
|
321
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
322
|
+
enable_raw_mode()?;
|
|
323
|
+
}
|
|
324
|
+
KeyCode::Down => {
|
|
325
|
+
if native_cursor < native_options.len() - 1 {
|
|
326
|
+
native_cursor += 1;
|
|
327
|
+
} else {
|
|
328
|
+
native_cursor = 0;
|
|
329
|
+
}
|
|
330
|
+
disable_raw_mode()?;
|
|
331
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
332
|
+
enable_raw_mode()?;
|
|
333
|
+
}
|
|
334
|
+
KeyCode::Char(' ') => {
|
|
335
|
+
native_options[native_cursor].enabled =
|
|
336
|
+
!native_options[native_cursor].enabled;
|
|
337
|
+
disable_raw_mode()?;
|
|
338
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
339
|
+
enable_raw_mode()?;
|
|
340
|
+
}
|
|
341
|
+
KeyCode::Char('a') => {
|
|
342
|
+
for opt in &mut native_options {
|
|
343
|
+
opt.enabled = true;
|
|
344
|
+
}
|
|
345
|
+
disable_raw_mode()?;
|
|
346
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
347
|
+
enable_raw_mode()?;
|
|
348
|
+
}
|
|
349
|
+
KeyCode::Char('i') => {
|
|
350
|
+
for opt in &mut native_options {
|
|
351
|
+
opt.enabled = !opt.enabled;
|
|
352
|
+
}
|
|
353
|
+
disable_raw_mode()?;
|
|
354
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
355
|
+
enable_raw_mode()?;
|
|
356
|
+
}
|
|
357
|
+
KeyCode::Enter => {
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
_ => {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
Ok(false) => {}
|
|
366
|
+
Err(_) => {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
disable_raw_mode()?;
|
|
280
372
|
println!();
|
|
281
373
|
|
|
374
|
+
let enabled_natives: Vec<serde_json::Value> = native_options
|
|
375
|
+
.iter()
|
|
376
|
+
.filter(|o| o.enabled)
|
|
377
|
+
.map(|o| serde_json::Value::String(o.key.to_string()))
|
|
378
|
+
.collect();
|
|
379
|
+
|
|
380
|
+
config.extra.insert(
|
|
381
|
+
"allowedNativePlugins".to_string(),
|
|
382
|
+
serde_json::Value::Array(enabled_natives),
|
|
383
|
+
);
|
|
384
|
+
save_config(&config)?;
|
|
385
|
+
|
|
386
|
+
println!("\x1b[32mSuccessfully updated native plugins configurations!\x1b[0m\n");
|
|
387
|
+
|
|
282
388
|
let run_options = vec![
|
|
283
389
|
ToolOption {
|
|
284
390
|
name: "1. CLI (Interactive Terminal Assistant)",
|
|
@@ -298,7 +404,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
298
404
|
];
|
|
299
405
|
|
|
300
406
|
let mut run_cursor = 0;
|
|
301
|
-
|
|
407
|
+
redraw_phase_3(&run_options, run_cursor);
|
|
302
408
|
enable_raw_mode()?;
|
|
303
409
|
|
|
304
410
|
loop {
|
|
@@ -324,8 +430,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
324
430
|
run_cursor = run_options.len() - 1;
|
|
325
431
|
}
|
|
326
432
|
disable_raw_mode()?;
|
|
327
|
-
|
|
328
|
-
print_run_options(&run_options, run_cursor);
|
|
433
|
+
redraw_phase_3(&run_options, run_cursor);
|
|
329
434
|
enable_raw_mode()?;
|
|
330
435
|
}
|
|
331
436
|
KeyCode::Down => {
|
|
@@ -335,8 +440,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
335
440
|
run_cursor = 0;
|
|
336
441
|
}
|
|
337
442
|
disable_raw_mode()?;
|
|
338
|
-
|
|
339
|
-
print_run_options(&run_options, run_cursor);
|
|
443
|
+
redraw_phase_3(&run_options, run_cursor);
|
|
340
444
|
enable_raw_mode()?;
|
|
341
445
|
}
|
|
342
446
|
KeyCode::Enter => {
|
|
@@ -388,3 +492,40 @@ fn print_run_options(options: &[ToolOption], cursor: usize) {
|
|
|
388
492
|
}
|
|
389
493
|
let _ = io::stdout().flush();
|
|
390
494
|
}
|
|
495
|
+
|
|
496
|
+
fn redraw_phase_1(options: &[ToolOption], cursor: usize) {
|
|
497
|
+
print!("\x1b[2J\x1b[1;1H");
|
|
498
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
499
|
+
println!("\x1b[32m Mint CLI Tool Manager Wizard\x1b[0m");
|
|
500
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
501
|
+
println!("Configure which agent tools are enabled or disabled:");
|
|
502
|
+
println!(
|
|
503
|
+
" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
|
|
504
|
+
);
|
|
505
|
+
println!();
|
|
506
|
+
print_options(options, cursor);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn redraw_phase_2(options: &[ToolOption], cursor: usize) {
|
|
510
|
+
print!("\x1b[2J\x1b[1;1H");
|
|
511
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
512
|
+
println!("\x1b[32m Configure Native Plugins Access\x1b[0m");
|
|
513
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
514
|
+
println!("Select which native plugins are allowed to run:");
|
|
515
|
+
println!(
|
|
516
|
+
" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
|
|
517
|
+
);
|
|
518
|
+
println!();
|
|
519
|
+
print_options(options, cursor);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
fn redraw_phase_3(options: &[ToolOption], cursor: usize) {
|
|
523
|
+
print!("\x1b[2J\x1b[1;1H");
|
|
524
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
525
|
+
println!("\x1b[32m Choose where to run Mint AI Agent\x1b[0m");
|
|
526
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
527
|
+
println!("Select the environment you want to launch:");
|
|
528
|
+
println!(" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Enter: Confirm]\x1b[0m");
|
|
529
|
+
println!();
|
|
530
|
+
print_run_options(options, cursor);
|
|
531
|
+
}
|
|
@@ -272,6 +272,41 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
272
272
|
.await;
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
|
+
("POST", "/api/interactions/agent-activity") => {
|
|
276
|
+
#[derive(Deserialize)]
|
|
277
|
+
#[serde(rename_all = "camelCase")]
|
|
278
|
+
struct SaveAgentActivityRequest {
|
|
279
|
+
interaction_id: i64,
|
|
280
|
+
activity: Vec<AgentProgress>,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if let Ok(payload) = serde_json::from_str::<SaveAgentActivityRequest>(body) {
|
|
284
|
+
if let Ok(memory) = MemoryStore::open_default() {
|
|
285
|
+
match serde_json::to_string(&payload.activity) {
|
|
286
|
+
Ok(activity_json) => {
|
|
287
|
+
if memory
|
|
288
|
+
.set_interaction_agent_activity_json(
|
|
289
|
+
payload.interaction_id,
|
|
290
|
+
&activity_json,
|
|
291
|
+
)
|
|
292
|
+
.is_ok()
|
|
293
|
+
{
|
|
294
|
+
send_json_response(socket, "200 OK", "{\"status\":\"ok\"}")
|
|
295
|
+
.await;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
Err(_) => {}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
send_json_response(
|
|
304
|
+
socket,
|
|
305
|
+
"400 Bad Request",
|
|
306
|
+
"{\"status\":\"invalid agent activity payload\"}",
|
|
307
|
+
)
|
|
308
|
+
.await;
|
|
309
|
+
}
|
|
275
310
|
("GET", "/api/pictures") => match list_saved_pictures() {
|
|
276
311
|
Ok(mut pictures) => {
|
|
277
312
|
for picture in &mut pictures {
|
|
@@ -310,7 +310,7 @@ fn runtime_extra_defaults() -> BTreeMap<String, Value> {
|
|
|
310
310
|
"notionPageId": "",
|
|
311
311
|
"notionTitleProperty": "Name",
|
|
312
312
|
"allowedShellModes": ["readOnly", "test", "mutating", "network"],
|
|
313
|
-
"allowedNativePlugins": ["dev_tools", "system_metrics"],
|
|
313
|
+
"allowedNativePlugins": ["dev_tools", "system_metrics", "github"],
|
|
314
314
|
"allowedMcpTools": {},
|
|
315
315
|
"mcpServers": {}
|
|
316
316
|
}))
|
|
@@ -380,7 +380,7 @@ mod tests {
|
|
|
380
380
|
);
|
|
381
381
|
assert_eq!(
|
|
382
382
|
config.extra["allowedNativePlugins"],
|
|
383
|
-
serde_json::json!(["dev_tools", "system_metrics"])
|
|
383
|
+
serde_json::json!(["dev_tools", "system_metrics", "github"])
|
|
384
384
|
);
|
|
385
385
|
assert_eq!(config.extra["allowedMcpTools"], serde_json::json!({}));
|
|
386
386
|
}
|
|
@@ -36,6 +36,8 @@ pub struct InteractionMemory {
|
|
|
36
36
|
pub model: String,
|
|
37
37
|
pub fallback_provider: Option<String>,
|
|
38
38
|
pub created_at: String,
|
|
39
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
40
|
+
pub agent_activity: Option<serde_json::Value>,
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
@@ -172,7 +174,7 @@ impl MemoryStore {
|
|
|
172
174
|
ensure_builtin_chat_sessions(&connection)?;
|
|
173
175
|
ensure_chat_session_row(&connection, &chat_id)?;
|
|
174
176
|
let mut statement = connection.prepare(
|
|
175
|
-
"SELECT id, chat_id, user_text, ai_text, provider, model, fallback_provider, created_at
|
|
177
|
+
"SELECT id, chat_id, user_text, ai_text, provider, model, fallback_provider, created_at, agent_activity_json
|
|
176
178
|
FROM interaction_memories
|
|
177
179
|
WHERE chat_id = ?1
|
|
178
180
|
ORDER BY id DESC
|
|
@@ -202,6 +204,20 @@ impl MemoryStore {
|
|
|
202
204
|
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
pub fn set_interaction_agent_activity_json(
|
|
208
|
+
&self,
|
|
209
|
+
interaction_id: i64,
|
|
210
|
+
activity_json: &str,
|
|
211
|
+
) -> Result<usize, MemoryError> {
|
|
212
|
+
let connection = self.connection()?;
|
|
213
|
+
Ok(connection.execute(
|
|
214
|
+
"UPDATE interaction_memories
|
|
215
|
+
SET agent_activity_json = ?2
|
|
216
|
+
WHERE id = ?1",
|
|
217
|
+
params![interaction_id, activity_json],
|
|
218
|
+
)?)
|
|
219
|
+
}
|
|
220
|
+
|
|
205
221
|
pub fn clear_interactions(&self) -> Result<usize, MemoryError> {
|
|
206
222
|
self.clear_interactions_for_chat(DEFAULT_CONVERSATION_ID)
|
|
207
223
|
}
|
|
@@ -551,6 +567,12 @@ fn initialize(
|
|
|
551
567
|
"fallback_provider",
|
|
552
568
|
"TEXT DEFAULT NULL",
|
|
553
569
|
)?;
|
|
570
|
+
ensure_column(
|
|
571
|
+
connection,
|
|
572
|
+
"interaction_memories",
|
|
573
|
+
"agent_activity_json",
|
|
574
|
+
"TEXT DEFAULT NULL",
|
|
575
|
+
)?;
|
|
554
576
|
ensure_column(
|
|
555
577
|
connection,
|
|
556
578
|
"chat_sessions",
|
|
@@ -612,6 +634,9 @@ fn learned_skill_row(row: &rusqlite::Row<'_>) -> Result<LearnedSkill, rusqlite::
|
|
|
612
634
|
}
|
|
613
635
|
|
|
614
636
|
fn interaction_row(row: &rusqlite::Row<'_>) -> Result<InteractionMemory, rusqlite::Error> {
|
|
637
|
+
let agent_activity = row
|
|
638
|
+
.get::<_, Option<String>>(8)?
|
|
639
|
+
.and_then(|raw| serde_json::from_str(&raw).ok());
|
|
615
640
|
Ok(InteractionMemory {
|
|
616
641
|
id: row.get(0)?,
|
|
617
642
|
chat_id: row.get(1)?,
|
|
@@ -621,6 +646,7 @@ fn interaction_row(row: &rusqlite::Row<'_>) -> Result<InteractionMemory, rusqlit
|
|
|
621
646
|
model: row.get(5)?,
|
|
622
647
|
fallback_provider: row.get(6)?,
|
|
623
648
|
created_at: row.get(7)?,
|
|
649
|
+
agent_activity,
|
|
624
650
|
})
|
|
625
651
|
}
|
|
626
652
|
|
|
@@ -385,7 +385,7 @@ pub fn build_system_prompt(config: &MintConfig) -> String {
|
|
|
385
385
|
"- write_file: {\"path\":\"new/relative/path\",\"fileContent\":\"full file content\"}",
|
|
386
386
|
);
|
|
387
387
|
}
|
|
388
|
-
input_formats.push("- finish: {\"summary\":\"concise final answer\",\"verification\":\"checks run or not run\"}");
|
|
388
|
+
input_formats.push("- finish: {\"summary\":\"concise final answer in Thai when the user writes Thai\",\"verification\":\"checks run or not run\"}");
|
|
389
389
|
|
|
390
390
|
let input_formats_str = input_formats.join("\n");
|
|
391
391
|
|
|
@@ -84,6 +84,10 @@ pub fn native_plugins() -> Vec<NativePlugin> {
|
|
|
84
84
|
name: "notion",
|
|
85
85
|
description: "Create notes, read databases, and append blocks through Notion API.",
|
|
86
86
|
},
|
|
87
|
+
NativePlugin {
|
|
88
|
+
name: "github",
|
|
89
|
+
description: "List issues, view issues, list pull requests, or check PR status via gh CLI.",
|
|
90
|
+
},
|
|
87
91
|
]
|
|
88
92
|
}
|
|
89
93
|
|
|
@@ -104,6 +108,7 @@ pub async fn execute_native_plugin(
|
|
|
104
108
|
"gmail" => gmail(config, instruction).await,
|
|
105
109
|
"google_calendar" => calendar(config, instruction).await,
|
|
106
110
|
"notion" => notion(config, instruction).await,
|
|
111
|
+
"github" => github(instruction),
|
|
107
112
|
_ => Err(PluginError::UnknownPlugin(name.into())),
|
|
108
113
|
}
|
|
109
114
|
}
|
|
@@ -229,6 +234,36 @@ fn spotify(instruction: &str) -> Result<String, PluginError> {
|
|
|
229
234
|
run("playerctl", &args)
|
|
230
235
|
}
|
|
231
236
|
|
|
237
|
+
fn github(instruction: &str) -> Result<String, PluginError> {
|
|
238
|
+
let parts = instruction.split_whitespace().collect::<Vec<_>>();
|
|
239
|
+
match parts.as_slice() {
|
|
240
|
+
["list_issues"] | ["issue", "list"] => run("gh", &["issue", "list", "--limit", "10"]),
|
|
241
|
+
["view_issue", id] | ["issue", "view", id] => {
|
|
242
|
+
if id.chars().all(|c| c.is_ascii_digit()) {
|
|
243
|
+
run("gh", &["issue", "view", id])
|
|
244
|
+
} else {
|
|
245
|
+
invalid(
|
|
246
|
+
"github",
|
|
247
|
+
"view_issue/issue view requires a numeric issue ID",
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
["list_prs"] | ["pr", "list"] => run("gh", &["pr", "list", "--limit", "10"]),
|
|
252
|
+
["pr_status"] | ["pr", "status"] => run("gh", &["pr", "status"]),
|
|
253
|
+
["view_pr", id] | ["pr", "view", id] => {
|
|
254
|
+
if id.chars().all(|c| c.is_ascii_digit()) {
|
|
255
|
+
run("gh", &["pr", "view", id])
|
|
256
|
+
} else {
|
|
257
|
+
invalid("github", "view_pr/pr view requires a numeric PR ID")
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
_ => invalid(
|
|
261
|
+
"github",
|
|
262
|
+
"expected list_issues, view_issue <id>, list_prs, pr_status, or view_pr <id>",
|
|
263
|
+
),
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
232
267
|
fn system_metrics(instruction: &str) -> Result<String, PluginError> {
|
|
233
268
|
let uptime = fs::read_to_string("/proc/uptime")
|
|
234
269
|
.ok()
|
|
@@ -57,6 +57,34 @@ fn stores_recent_interactions_with_provider_metadata() {
|
|
|
57
57
|
assert_eq!(interactions[1].fallback_provider, None);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
#[test]
|
|
61
|
+
fn stores_and_reads_interaction_agent_activity() {
|
|
62
|
+
let store = store("agent-activity");
|
|
63
|
+
let interaction_id = store
|
|
64
|
+
.add_interaction_with_metadata("hello", "hi", "gemini", "gemini-test")
|
|
65
|
+
.unwrap();
|
|
66
|
+
store
|
|
67
|
+
.set_interaction_agent_activity_json(
|
|
68
|
+
interaction_id,
|
|
69
|
+
r#"[{"type":"Thought","data":{"thought":"step one"}}]"#,
|
|
70
|
+
)
|
|
71
|
+
.unwrap();
|
|
72
|
+
|
|
73
|
+
let interactions = store.recent_interactions(1).unwrap();
|
|
74
|
+
assert_eq!(interactions.len(), 1);
|
|
75
|
+
let activity = interactions[0]
|
|
76
|
+
.agent_activity
|
|
77
|
+
.as_ref()
|
|
78
|
+
.and_then(|value| value.as_array())
|
|
79
|
+
.cloned()
|
|
80
|
+
.unwrap_or_default();
|
|
81
|
+
assert_eq!(activity.len(), 1);
|
|
82
|
+
assert_eq!(
|
|
83
|
+
activity[0].get("type").and_then(|value| value.as_str()),
|
|
84
|
+
Some("Thought")
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
60
88
|
#[test]
|
|
61
89
|
fn preserves_chat_history_when_store_is_reopened() {
|
|
62
90
|
let path = test_path("reopen-history", "sqlite");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pheem49/mint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
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",
|
|
@@ -27,7 +27,17 @@
|
|
|
27
27
|
"cli": "cargo run -p mint-cli --",
|
|
28
28
|
"postinstall": "cargo build -p mint-cli --release && mkdir -p bin && cp target/release/mint bin/mint"
|
|
29
29
|
},
|
|
30
|
-
"keywords": [
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mint",
|
|
32
|
+
"tauri",
|
|
33
|
+
"ai",
|
|
34
|
+
"assistant",
|
|
35
|
+
"desktop",
|
|
36
|
+
"cli",
|
|
37
|
+
"rust",
|
|
38
|
+
"react",
|
|
39
|
+
"agent"
|
|
40
|
+
],
|
|
31
41
|
"repository": {
|
|
32
42
|
"type": "git",
|
|
33
43
|
"url": "git+https://github.com/Pheem49/Mint.git"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentProgress, InteractionMemory } from './tauri'
|
|
2
|
+
|
|
3
|
+
export function thoughtsFrom(progress: AgentProgress[]): string[] {
|
|
4
|
+
return progress
|
|
5
|
+
.filter((event) => event.type === 'Thought')
|
|
6
|
+
.map((event) => (event as Extract<AgentProgress, { type: 'Thought' }>).data.thought)
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function trimAgentProgress(progress: AgentProgress[], maxNonThought = 20): AgentProgress[] {
|
|
11
|
+
if (progress.length === 0) return progress
|
|
12
|
+
const keptNonThought = new Set(
|
|
13
|
+
progress.filter((event) => event.type !== 'Thought').slice(-maxNonThought),
|
|
14
|
+
)
|
|
15
|
+
return progress.filter((event) => event.type === 'Thought' || keptNonThought.has(event))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function hasAgentToolActivity(progress: AgentProgress[]): boolean {
|
|
19
|
+
return progress.some((event) => event.type === 'ToolStart' || event.type === 'ToolEnd')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function mergeActivitySnapshots(
|
|
23
|
+
current: Record<string, AgentProgress[]>,
|
|
24
|
+
interactions: InteractionMemory[],
|
|
25
|
+
): Record<string, AgentProgress[]> {
|
|
26
|
+
const merged = { ...current }
|
|
27
|
+
for (const interaction of interactions) {
|
|
28
|
+
const key = String(interaction.id)
|
|
29
|
+
if (interaction.agentActivity?.length && !merged[key]) {
|
|
30
|
+
merged[key] = interaction.agentActivity
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return merged
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseAgentActivity(value: unknown): AgentProgress[] | undefined {
|
|
37
|
+
if (!Array.isArray(value)) return undefined
|
|
38
|
+
return value as AgentProgress[]
|
|
39
|
+
}
|