@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
|
@@ -184,13 +184,19 @@ pub async fn run_code_agent_with_options(
|
|
|
184
184
|
}
|
|
185
185
|
let progress_live_status = Arc::clone(&live_status);
|
|
186
186
|
let progress_cb = |progress: AgentProgress| match progress {
|
|
187
|
-
AgentProgress::Thinking {
|
|
187
|
+
AgentProgress::Thinking {
|
|
188
|
+
elapsed_secs,
|
|
189
|
+
agent_name,
|
|
190
|
+
model_name,
|
|
191
|
+
} => {
|
|
188
192
|
if !options.fast_mode {
|
|
189
193
|
if let Ok(mut status) = progress_live_status.lock() {
|
|
190
|
-
|
|
191
|
-
"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
let label = if let (Some(a), Some(m)) = (agent_name, model_name) {
|
|
195
|
+
format!("{} ({}) is thinking ({} • Esc to interrupt)", a, m, format_elapsed(Duration::from_secs(elapsed_secs)))
|
|
196
|
+
} else {
|
|
197
|
+
format!("Thinking ({} • Esc to interrupt)", format_elapsed(Duration::from_secs(elapsed_secs)))
|
|
198
|
+
};
|
|
199
|
+
status.thinking = Some(label);
|
|
194
200
|
render_live_status(&mut status);
|
|
195
201
|
}
|
|
196
202
|
}
|
|
@@ -325,6 +331,16 @@ pub async fn run_code_agent_with_options(
|
|
|
325
331
|
render_live_status(&mut status);
|
|
326
332
|
}
|
|
327
333
|
}
|
|
334
|
+
// Parse web search sources from the result and store them for display
|
|
335
|
+
if action == "web_search"
|
|
336
|
+
&& !result.starts_with("Web search error:")
|
|
337
|
+
&& result != "No web search results found."
|
|
338
|
+
{
|
|
339
|
+
if let Ok(mut status) = progress_live_status.lock() {
|
|
340
|
+
let sources = parse_web_search_sources(&result);
|
|
341
|
+
status.web_sources.extend(sources);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
328
344
|
}
|
|
329
345
|
};
|
|
330
346
|
|
|
@@ -337,9 +353,23 @@ pub async fn run_code_agent_with_options(
|
|
|
337
353
|
clear_live_status(&mut status);
|
|
338
354
|
}
|
|
339
355
|
}
|
|
340
|
-
let formatted_summary = format_markdown_bold(&summary);
|
|
341
|
-
print!("\n{MINT}Mint:{RESET} ");
|
|
356
|
+
let formatted_summary = format_markdown_bold(&sanitize_latex(&summary));
|
|
357
|
+
print!("\n {MINT}Mint:{RESET} ");
|
|
342
358
|
render_live_summary(&formatted_summary);
|
|
359
|
+
|
|
360
|
+
// Print web search sources if any were collected
|
|
361
|
+
if let Ok(mut status) = chunk_live_status.lock() {
|
|
362
|
+
if !status.web_sources.is_empty() {
|
|
363
|
+
println!();
|
|
364
|
+
println!(" {DIM}Sources:{RESET}");
|
|
365
|
+
for (i, (title, url)) in status.web_sources.iter().enumerate() {
|
|
366
|
+
println!(" {DIM}{}.{RESET} {BLUE}{}{RESET}", i + 1, title);
|
|
367
|
+
println!(" {DIM}{}{RESET}", url);
|
|
368
|
+
}
|
|
369
|
+
status.web_sources.clear();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
343
373
|
println!();
|
|
344
374
|
};
|
|
345
375
|
|
|
@@ -349,6 +379,7 @@ pub async fn run_code_agent_with_options(
|
|
|
349
379
|
root,
|
|
350
380
|
image_data_uri,
|
|
351
381
|
Some(CHAT_CLI_ID),
|
|
382
|
+
None,
|
|
352
383
|
options.fast_mode,
|
|
353
384
|
approve_cb,
|
|
354
385
|
progress_cb,
|
|
@@ -377,16 +408,16 @@ pub async fn run_code_agent_with_options(
|
|
|
377
408
|
let res = res.map_err(|e| anyhow!("{}", e))?;
|
|
378
409
|
|
|
379
410
|
if should_show_verification(&res.verification) {
|
|
380
|
-
println!("Verification: {}", res.verification);
|
|
411
|
+
println!(" Verification: {}", res.verification);
|
|
381
412
|
}
|
|
382
413
|
println!(
|
|
383
|
-
"{DIM}─ Worked for {}{RESET}",
|
|
414
|
+
" {DIM}─ Worked for {}{RESET}",
|
|
384
415
|
format_elapsed(started_at.elapsed())
|
|
385
416
|
);
|
|
386
417
|
|
|
387
418
|
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
388
419
|
let width = tw as usize;
|
|
389
|
-
println!("{DIM}{}{RESET}", "─".repeat(width));
|
|
420
|
+
println!(" {DIM}{}{RESET}", "─".repeat(width.saturating_sub(2)));
|
|
390
421
|
|
|
391
422
|
Ok(res)
|
|
392
423
|
}
|
|
@@ -396,6 +427,34 @@ fn clear_working_status() {
|
|
|
396
427
|
let _ = io::stdout().flush();
|
|
397
428
|
}
|
|
398
429
|
|
|
430
|
+
/// Parse (title, url) pairs from the formatted `web_search` ToolEnd result text.
|
|
431
|
+
/// The format produced by orchestration.rs is:
|
|
432
|
+
/// 1. Title\n URL: https://...\n Snippet\n
|
|
433
|
+
fn parse_web_search_sources(result: &str) -> Vec<(String, String)> {
|
|
434
|
+
let mut sources = Vec::new();
|
|
435
|
+
let mut current_title: Option<String> = None;
|
|
436
|
+
for line in result.lines() {
|
|
437
|
+
let trimmed = line.trim();
|
|
438
|
+
// Match numbered title lines: "1. Title text"
|
|
439
|
+
if let Some(rest) = trimmed.split_once(". ") {
|
|
440
|
+
if rest.0.parse::<usize>().is_ok() {
|
|
441
|
+
current_title = Some(rest.1.trim().to_owned());
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Match URL lines: "URL: https://..."
|
|
446
|
+
if let Some(url) = trimmed.strip_prefix("URL: ") {
|
|
447
|
+
if let Some(title) = current_title.take() {
|
|
448
|
+
let url = url.trim().to_owned();
|
|
449
|
+
if !url.is_empty() {
|
|
450
|
+
sources.push((title, url));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
sources
|
|
456
|
+
}
|
|
457
|
+
|
|
399
458
|
fn format_elapsed(duration: Duration) -> String {
|
|
400
459
|
let total_seconds = duration.as_secs();
|
|
401
460
|
let minutes = total_seconds / 60;
|
|
@@ -408,19 +467,26 @@ fn format_elapsed(duration: Duration) -> String {
|
|
|
408
467
|
}
|
|
409
468
|
|
|
410
469
|
fn render_live_summary(summary: &str) {
|
|
411
|
-
let
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
470
|
+
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
471
|
+
let width = tw as usize;
|
|
472
|
+
|
|
473
|
+
let mut is_first = true;
|
|
474
|
+
for line in summary.split('\n') {
|
|
475
|
+
let indent = if is_first { "" } else { " " };
|
|
476
|
+
let options = textwrap::Options::new(width)
|
|
477
|
+
.initial_indent(indent)
|
|
478
|
+
.subsequent_indent(" ")
|
|
479
|
+
.break_words(true);
|
|
480
|
+
let wrapped = textwrap::fill(line, &options);
|
|
481
|
+
|
|
482
|
+
if is_first {
|
|
483
|
+
print!("{wrapped}");
|
|
484
|
+
is_first = false;
|
|
485
|
+
} else {
|
|
486
|
+
print!("\n{wrapped}");
|
|
418
487
|
}
|
|
419
488
|
}
|
|
420
|
-
|
|
421
|
-
print!("{chunk}");
|
|
422
|
-
let _ = io::stdout().flush();
|
|
423
|
-
}
|
|
489
|
+
let _ = io::stdout().flush();
|
|
424
490
|
}
|
|
425
491
|
|
|
426
492
|
fn print_colored_diff(diff: &str) {
|
|
@@ -480,6 +546,8 @@ struct LiveStatus {
|
|
|
480
546
|
committed_tasks: usize,
|
|
481
547
|
rendered_lines: usize,
|
|
482
548
|
spinner_tick: usize,
|
|
549
|
+
/// Sources collected from web_search ToolEnd results (title, url)
|
|
550
|
+
web_sources: Vec<(String, String)>,
|
|
483
551
|
}
|
|
484
552
|
|
|
485
553
|
#[derive(Debug, Clone)]
|
|
@@ -497,13 +565,21 @@ fn explored_action_label(action: &str, input: &serde_json::Value) -> Option<Expl
|
|
|
497
565
|
kind: "[list_files] List",
|
|
498
566
|
target: display_tool_target(path),
|
|
499
567
|
}),
|
|
500
|
-
"read_file" =>
|
|
501
|
-
.get("path")
|
|
502
|
-
.and_then(|v| v.
|
|
503
|
-
.
|
|
568
|
+
"read_file" => {
|
|
569
|
+
let path = input.get("path").and_then(|v| v.as_str())?;
|
|
570
|
+
let start = input.get("startLine").and_then(|v| v.as_u64());
|
|
571
|
+
let end = input.get("endLine").and_then(|v| v.as_u64());
|
|
572
|
+
let file_name = display_tool_target(path);
|
|
573
|
+
let target = match (start, end) {
|
|
574
|
+
(Some(s), Some(e)) => format!("{} #L{}-{}", file_name, s, e),
|
|
575
|
+
(Some(s), None) => format!("{} #L{}", file_name, s),
|
|
576
|
+
_ => file_name,
|
|
577
|
+
};
|
|
578
|
+
Some(ExploredAction {
|
|
504
579
|
kind: "[read_file] Read",
|
|
505
|
-
target
|
|
506
|
-
})
|
|
580
|
+
target,
|
|
581
|
+
})
|
|
582
|
+
}
|
|
507
583
|
"search_code" => {
|
|
508
584
|
let query = input.get("query").and_then(|v| v.as_str())?;
|
|
509
585
|
let path = input.get("path").and_then(|v| v.as_str()).unwrap_or(".");
|
|
@@ -540,6 +616,30 @@ fn display_tool_target(path: &str) -> String {
|
|
|
540
616
|
}
|
|
541
617
|
}
|
|
542
618
|
|
|
619
|
+
fn strip_ansi_escapes(s: &str) -> String {
|
|
620
|
+
let mut result = String::new();
|
|
621
|
+
let mut in_escape = false;
|
|
622
|
+
for c in s.chars() {
|
|
623
|
+
if c == '\x1b' {
|
|
624
|
+
in_escape = true;
|
|
625
|
+
} else if in_escape {
|
|
626
|
+
if c.is_ascii_alphabetic() {
|
|
627
|
+
in_escape = false;
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
result.push(c);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
result
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
fn is_thai_combining(c: char) -> bool {
|
|
637
|
+
matches!(c,
|
|
638
|
+
'\u{0e31}' | '\u{0e34}'..='\u{0e37}' | '\u{0e38}'..='\u{0e39}' |
|
|
639
|
+
'\u{0e47}'..='\u{0e4e}'
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
|
|
543
643
|
fn render_live_status(status: &mut LiveStatus) {
|
|
544
644
|
clear_live_status(status);
|
|
545
645
|
let mut lines = Vec::new();
|
|
@@ -547,23 +647,48 @@ fn render_live_status(status: &mut LiveStatus) {
|
|
|
547
647
|
let activities_start = status.committed_activities.min(status.activities.len());
|
|
548
648
|
let tasks_start = status.committed_tasks.min(status.tasks.len());
|
|
549
649
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
lines.extend(
|
|
650
|
+
let is_thinking = status.thinking.is_some();
|
|
651
|
+
let tick = status.spinner_tick;
|
|
652
|
+
|
|
653
|
+
lines.extend(plan_lines(&status.plan_steps, is_thinking, tick));
|
|
654
|
+
lines.extend(tasks_lines(&status.tasks[tasks_start..], is_thinking, tick));
|
|
655
|
+
lines.extend(activities_lines(
|
|
656
|
+
&status.activities[activities_start..],
|
|
657
|
+
is_thinking,
|
|
658
|
+
tick,
|
|
659
|
+
));
|
|
660
|
+
lines.extend(explored_lines(
|
|
661
|
+
&status.explored[explored_start..],
|
|
662
|
+
is_thinking,
|
|
663
|
+
tick,
|
|
664
|
+
));
|
|
554
665
|
if let Some(thinking) = &status.thinking {
|
|
555
|
-
let frames = &["
|
|
666
|
+
let frames = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
556
667
|
let frame = frames[status.spinner_tick % frames.len()];
|
|
557
668
|
status.spinner_tick += 1;
|
|
558
|
-
lines.push(format!("{MINT}{frame}{RESET} {BRIGHT}{thinking}{RESET}"));
|
|
669
|
+
lines.push(format!(" {MINT}{frame}{RESET} {BRIGHT}{thinking}{RESET}"));
|
|
559
670
|
}
|
|
560
671
|
if lines.is_empty() {
|
|
561
672
|
return;
|
|
562
673
|
}
|
|
674
|
+
|
|
675
|
+
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
676
|
+
let width = tw as usize;
|
|
677
|
+
|
|
678
|
+
let mut physical_lines_count = 0;
|
|
563
679
|
for line in &lines {
|
|
680
|
+
let stripped = strip_ansi_escapes(line);
|
|
681
|
+
let line_len = stripped.chars().filter(|&c| !is_thai_combining(c)).count();
|
|
682
|
+
let physical_lines = if width > 0 {
|
|
683
|
+
(line_len + width - 1) / width
|
|
684
|
+
} else {
|
|
685
|
+
1
|
|
686
|
+
}
|
|
687
|
+
.max(1);
|
|
688
|
+
physical_lines_count += physical_lines;
|
|
564
689
|
println!("{line}");
|
|
565
690
|
}
|
|
566
|
-
status.rendered_lines =
|
|
691
|
+
status.rendered_lines = physical_lines_count;
|
|
567
692
|
let _ = io::stdout().flush();
|
|
568
693
|
}
|
|
569
694
|
|
|
@@ -573,9 +698,13 @@ fn commit_activity_snapshot(status: &mut LiveStatus) {
|
|
|
573
698
|
let activities_start = status.committed_activities.min(status.activities.len());
|
|
574
699
|
let tasks_start = status.committed_tasks.min(status.tasks.len());
|
|
575
700
|
|
|
576
|
-
let mut lines = explored_lines(&status.explored[explored_start..]);
|
|
577
|
-
lines.extend(activities_lines(
|
|
578
|
-
|
|
701
|
+
let mut lines = explored_lines(&status.explored[explored_start..], false, 0);
|
|
702
|
+
lines.extend(activities_lines(
|
|
703
|
+
&status.activities[activities_start..],
|
|
704
|
+
false,
|
|
705
|
+
0,
|
|
706
|
+
));
|
|
707
|
+
lines.extend(tasks_lines(&status.tasks[tasks_start..], false, 0));
|
|
579
708
|
if lines.is_empty() {
|
|
580
709
|
return;
|
|
581
710
|
}
|
|
@@ -594,7 +723,14 @@ fn print_timeline_note(thought: &str) {
|
|
|
594
723
|
if thought.is_empty() {
|
|
595
724
|
return;
|
|
596
725
|
}
|
|
597
|
-
|
|
726
|
+
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
727
|
+
let width = tw as usize;
|
|
728
|
+
let options = textwrap::Options::new(width)
|
|
729
|
+
.initial_indent(" • ")
|
|
730
|
+
.subsequent_indent(" ")
|
|
731
|
+
.break_words(true);
|
|
732
|
+
let wrapped = textwrap::fill(thought, &options);
|
|
733
|
+
println!("{DIM}{wrapped}{RESET}");
|
|
598
734
|
}
|
|
599
735
|
|
|
600
736
|
fn print_timeline_separator() {
|
|
@@ -615,18 +751,34 @@ fn clear_live_status(status: &mut LiveStatus) {
|
|
|
615
751
|
let _ = io::stdout().flush();
|
|
616
752
|
}
|
|
617
753
|
|
|
618
|
-
fn
|
|
754
|
+
fn get_bullet(name: &str, is_thinking: bool, tick: usize) -> String {
|
|
755
|
+
let char_str = if is_thinking {
|
|
756
|
+
if (tick / 4) % 2 == 0 { "●" } else { "○" }
|
|
757
|
+
} else {
|
|
758
|
+
"●"
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
match name {
|
|
762
|
+
"plan" => format!("{BLUE}{char_str}{RESET} plan"),
|
|
763
|
+
"tasks" => format!("{MINT}{char_str}{RESET} tasks"),
|
|
764
|
+
"activity" => format!("{BLUE}{char_str}{RESET} activity"),
|
|
765
|
+
"explored" => format!("{DIM}{char_str}{RESET} explored"),
|
|
766
|
+
_ => char_str.to_string(),
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
fn explored_lines(actions: &[ExploredAction], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
619
771
|
if actions.is_empty() {
|
|
620
772
|
return Vec::new();
|
|
621
773
|
}
|
|
622
774
|
let grouped = grouped_explored_actions(actions);
|
|
623
|
-
let mut lines = vec![format!("{
|
|
775
|
+
let mut lines = vec![format!(" {}", get_bullet("explored", is_thinking, tick))];
|
|
624
776
|
lines.extend(grouped.iter().take(24).enumerate().map(|(index, action)| {
|
|
625
|
-
let prefix = if index == 0 { "
|
|
777
|
+
let prefix = if index == 0 { " └" } else { " " };
|
|
626
778
|
format!("{DIM}{prefix} {action}{RESET}")
|
|
627
779
|
}));
|
|
628
780
|
if grouped.len() > 24 {
|
|
629
|
-
lines.push(format!("{DIM}
|
|
781
|
+
lines.push(format!("{DIM} ... {} more{RESET}", grouped.len() - 24));
|
|
630
782
|
}
|
|
631
783
|
lines
|
|
632
784
|
}
|
|
@@ -658,17 +810,20 @@ fn command_was_run(result: &str) -> bool {
|
|
|
658
810
|
result.lines().any(|line| line.starts_with("exit: "))
|
|
659
811
|
}
|
|
660
812
|
|
|
661
|
-
fn activities_lines(activities: &[String]) -> Vec<String> {
|
|
813
|
+
fn activities_lines(activities: &[String], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
662
814
|
if activities.is_empty() {
|
|
663
815
|
return Vec::new();
|
|
664
816
|
}
|
|
665
|
-
let mut lines = vec![format!("{
|
|
817
|
+
let mut lines = vec![format!(" {}", get_bullet("activity", is_thinking, tick))];
|
|
666
818
|
lines.extend(activities.iter().take(24).enumerate().map(|(index, act)| {
|
|
667
|
-
let prefix = if index == 0 { "
|
|
819
|
+
let prefix = if index == 0 { " └" } else { " " };
|
|
668
820
|
format!("{DIM}{prefix} {act}{RESET}")
|
|
669
821
|
}));
|
|
670
822
|
if activities.len() > 24 {
|
|
671
|
-
lines.push(format!(
|
|
823
|
+
lines.push(format!(
|
|
824
|
+
"{DIM} ... {} more{RESET}",
|
|
825
|
+
activities.len() - 24
|
|
826
|
+
));
|
|
672
827
|
}
|
|
673
828
|
lines
|
|
674
829
|
}
|
|
@@ -685,16 +840,16 @@ fn extract_plan_steps(input: &serde_json::Value) -> Option<Vec<String>> {
|
|
|
685
840
|
Some(steps)
|
|
686
841
|
}
|
|
687
842
|
|
|
688
|
-
fn plan_lines(steps: &[String]) -> Vec<String> {
|
|
843
|
+
fn plan_lines(steps: &[String], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
689
844
|
if steps.is_empty() {
|
|
690
845
|
return Vec::new();
|
|
691
846
|
}
|
|
692
|
-
let mut lines = vec![format!("{
|
|
847
|
+
let mut lines = vec![format!(" {}", get_bullet("plan", is_thinking, tick))];
|
|
693
848
|
for (index, step) in steps.iter().enumerate() {
|
|
694
849
|
let prefix = if index == steps.len() - 1 {
|
|
695
|
-
"
|
|
850
|
+
" └"
|
|
696
851
|
} else {
|
|
697
|
-
"
|
|
852
|
+
" ├"
|
|
698
853
|
};
|
|
699
854
|
let (checked, text) = if step.to_lowercase().starts_with("done:") {
|
|
700
855
|
(format!("{MINT}[x]{RESET}"), step["done:".len()..].trim())
|
|
@@ -722,17 +877,17 @@ fn plan_lines(steps: &[String]) -> Vec<String> {
|
|
|
722
877
|
lines
|
|
723
878
|
}
|
|
724
879
|
|
|
725
|
-
fn tasks_lines(tasks: &[String]) -> Vec<String> {
|
|
880
|
+
fn tasks_lines(tasks: &[String], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
726
881
|
if tasks.is_empty() {
|
|
727
882
|
return Vec::new();
|
|
728
883
|
}
|
|
729
|
-
let mut lines = vec![format!("{
|
|
884
|
+
let mut lines = vec![format!(" {}", get_bullet("tasks", is_thinking, tick))];
|
|
730
885
|
lines.extend(tasks.iter().take(24).enumerate().map(|(index, task)| {
|
|
731
|
-
let prefix = if index == 0 { "
|
|
886
|
+
let prefix = if index == 0 { " └" } else { " " };
|
|
732
887
|
format!("{DIM}{prefix} {task}{RESET}")
|
|
733
888
|
}));
|
|
734
889
|
if tasks.len() > 24 {
|
|
735
|
-
lines.push(format!("{DIM}
|
|
890
|
+
lines.push(format!("{DIM} ... {} more{RESET}", tasks.len() - 24));
|
|
736
891
|
}
|
|
737
892
|
lines
|
|
738
893
|
}
|
|
@@ -881,3 +1036,36 @@ fn process_inline_bold(text: &str) -> String {
|
|
|
881
1036
|
}
|
|
882
1037
|
result
|
|
883
1038
|
}
|
|
1039
|
+
|
|
1040
|
+
/// Replace common LaTeX math symbols with Unicode equivalents.
|
|
1041
|
+
/// Fixes garbled output like "ightarrow$" from models that emit LaTeX notation.
|
|
1042
|
+
fn sanitize_latex(text: &str) -> String {
|
|
1043
|
+
let mut s = text.to_owned();
|
|
1044
|
+
for (pat, uni) in [
|
|
1045
|
+
// arrows
|
|
1046
|
+
("$\\rightarrow$", "→"),
|
|
1047
|
+
("$\\leftarrow$", "←"),
|
|
1048
|
+
("$\\Rightarrow$", "⇒"),
|
|
1049
|
+
("$\\Leftarrow$", "⇐"),
|
|
1050
|
+
("$\\leftrightarrow$", "↔"),
|
|
1051
|
+
// comparison
|
|
1052
|
+
("$\\leq$", "≤"),
|
|
1053
|
+
("$\\geq$", "≥"),
|
|
1054
|
+
("$\\neq$", "≠"),
|
|
1055
|
+
("$\\approx$", "≈"),
|
|
1056
|
+
// math
|
|
1057
|
+
("$\\times$", "×"),
|
|
1058
|
+
("$\\div$", "÷"),
|
|
1059
|
+
("$\\pm$", "±"),
|
|
1060
|
+
("$\\infty$", "∞"),
|
|
1061
|
+
("$\\cdot$", "·"),
|
|
1062
|
+
// sets
|
|
1063
|
+
("$\\in$", "∈"),
|
|
1064
|
+
("$\\subset$", "⊂"),
|
|
1065
|
+
("$\\cup$", "∪"),
|
|
1066
|
+
("$\\cap$", "∩"),
|
|
1067
|
+
] {
|
|
1068
|
+
s = s.replace(pat, uni);
|
|
1069
|
+
}
|
|
1070
|
+
s
|
|
1071
|
+
}
|