@pheem49/mint 1.8.0 → 1.8.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/.github/workflows/release.yml +1 -1
- package/Cargo.lock +34 -3
- package/Cargo.toml +2 -2
- package/README.md +31 -0
- package/Release_Note.md +95 -0
- package/assets/Mint_CLI.png +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/agent.rs +142 -48
- package/crates/mint-cli/src/main.rs +231 -22
- package/crates/mint-core/src/api_server.rs +9 -0
- package/crates/mint-core/src/browser.rs +401 -0
- package/crates/mint-core/src/config.rs +69 -1
- package/crates/mint-core/src/lib.rs +5 -0
- package/crates/mint-core/src/orchestration.rs +137 -1
- package/crates/mint-core/src/skills.rs +113 -4
- package/crates/mint-core/src/workflows.rs +1 -22
- package/package.json +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +170 -33
- package/src/renderer/src/components/ImageStudioPanel.tsx +11 -1
- package/src/renderer/src/components/MintDashboard.tsx +40 -2
- package/src/renderer/src/components/Settings/PluginsTab.tsx +221 -41
- package/src/renderer/src/components/SettingsWindow.tsx +1 -9
- 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 +64 -11
- package/src/renderer/src/index.css +12 -1
- package/src/renderer/src/tauri.ts +45 -4
- package/src/renderer/src-web/components/ChatPanel.tsx +163 -32
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +11 -1
- package/src/renderer/src-web/components/MintDashboard.tsx +8 -3
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +221 -41
- package/src/renderer/src-web/components/SettingsWindow.tsx +1 -9
- package/src/renderer/src-web/css/image-studio.css +6 -0
- package/src/renderer/src-web/css/styles.css +100 -12
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +24 -2
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +117 -26
- package/src-tauri/src/workflows.rs +56 -4
- package/src-tauri/tauri.conf.json +1 -1
package/Cargo.lock
CHANGED
|
@@ -2095,7 +2095,7 @@ dependencies = [
|
|
|
2095
2095
|
|
|
2096
2096
|
[[package]]
|
|
2097
2097
|
name = "mint-cli"
|
|
2098
|
-
version = "1.8.
|
|
2098
|
+
version = "1.8.1"
|
|
2099
2099
|
dependencies = [
|
|
2100
2100
|
"anyhow",
|
|
2101
2101
|
"base64 0.22.1",
|
|
@@ -2108,12 +2108,13 @@ dependencies = [
|
|
|
2108
2108
|
"serde",
|
|
2109
2109
|
"serde_json",
|
|
2110
2110
|
"sha2",
|
|
2111
|
+
"textwrap",
|
|
2111
2112
|
"tokio",
|
|
2112
2113
|
]
|
|
2113
2114
|
|
|
2114
2115
|
[[package]]
|
|
2115
2116
|
name = "mint-core"
|
|
2116
|
-
version = "1.8.
|
|
2117
|
+
version = "1.8.1"
|
|
2117
2118
|
dependencies = [
|
|
2118
2119
|
"base64 0.22.1",
|
|
2119
2120
|
"chrono",
|
|
@@ -2134,7 +2135,7 @@ dependencies = [
|
|
|
2134
2135
|
|
|
2135
2136
|
[[package]]
|
|
2136
2137
|
name = "mint-desktop"
|
|
2137
|
-
version = "1.8.
|
|
2138
|
+
version = "1.8.1"
|
|
2138
2139
|
dependencies = [
|
|
2139
2140
|
"base64 0.22.1",
|
|
2140
2141
|
"dirs",
|
|
@@ -3596,6 +3597,12 @@ version = "1.15.1"
|
|
|
3596
3597
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3597
3598
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
3598
3599
|
|
|
3600
|
+
[[package]]
|
|
3601
|
+
name = "smawk"
|
|
3602
|
+
version = "0.3.3"
|
|
3603
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3604
|
+
checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100"
|
|
3605
|
+
|
|
3599
3606
|
[[package]]
|
|
3600
3607
|
name = "socket2"
|
|
3601
3608
|
version = "0.6.4"
|
|
@@ -4130,6 +4137,17 @@ dependencies = [
|
|
|
4130
4137
|
"utf-8",
|
|
4131
4138
|
]
|
|
4132
4139
|
|
|
4140
|
+
[[package]]
|
|
4141
|
+
name = "textwrap"
|
|
4142
|
+
version = "0.16.2"
|
|
4143
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4144
|
+
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
|
4145
|
+
dependencies = [
|
|
4146
|
+
"smawk",
|
|
4147
|
+
"unicode-linebreak",
|
|
4148
|
+
"unicode-width",
|
|
4149
|
+
]
|
|
4150
|
+
|
|
4133
4151
|
[[package]]
|
|
4134
4152
|
name = "thiserror"
|
|
4135
4153
|
version = "1.0.69"
|
|
@@ -4236,6 +4254,7 @@ dependencies = [
|
|
|
4236
4254
|
"libc",
|
|
4237
4255
|
"mio",
|
|
4238
4256
|
"pin-project-lite",
|
|
4257
|
+
"signal-hook-registry",
|
|
4239
4258
|
"socket2",
|
|
4240
4259
|
"tokio-macros",
|
|
4241
4260
|
"windows-sys 0.61.2",
|
|
@@ -4581,12 +4600,24 @@ version = "1.0.24"
|
|
|
4581
4600
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4582
4601
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
4583
4602
|
|
|
4603
|
+
[[package]]
|
|
4604
|
+
name = "unicode-linebreak"
|
|
4605
|
+
version = "0.1.5"
|
|
4606
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4607
|
+
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
|
4608
|
+
|
|
4584
4609
|
[[package]]
|
|
4585
4610
|
name = "unicode-segmentation"
|
|
4586
4611
|
version = "1.13.2"
|
|
4587
4612
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4588
4613
|
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
|
4589
4614
|
|
|
4615
|
+
[[package]]
|
|
4616
|
+
name = "unicode-width"
|
|
4617
|
+
version = "0.2.2"
|
|
4618
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4619
|
+
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
|
4620
|
+
|
|
4590
4621
|
[[package]]
|
|
4591
4622
|
name = "unicode-xid"
|
|
4592
4623
|
version = "0.2.6"
|
package/Cargo.toml
CHANGED
|
@@ -7,7 +7,7 @@ members = [
|
|
|
7
7
|
resolver = "2"
|
|
8
8
|
|
|
9
9
|
[workspace.package]
|
|
10
|
-
version = "1.8.
|
|
10
|
+
version = "1.8.1"
|
|
11
11
|
edition = "2024"
|
|
12
12
|
license = "AGPL-3.0-only"
|
|
13
13
|
|
|
@@ -28,5 +28,5 @@ serde = { version = "1", features = ["derive"] }
|
|
|
28
28
|
serde_json = "1"
|
|
29
29
|
sha2 = "0.10"
|
|
30
30
|
thiserror = "2"
|
|
31
|
-
tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] }
|
|
31
|
+
tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "time", "signal"] }
|
|
32
32
|
tokio-tungstenite = { version = "0.28", features = ["rustls-tls-webpki-roots"] }
|
package/README.md
CHANGED
|
@@ -78,6 +78,14 @@ Mint is a local-first AI assistant running on your machine, capable of handling
|
|
|
78
78
|
|
|
79
79
|
---
|
|
80
80
|
|
|
81
|
+
### 9. <img src="assets/tools.svg" width="18" height="18" valign="middle" /> Browser Automation (`mint auto`)
|
|
82
|
+
- Control and automate web pages directly from either the terminal or the GUI desktop chat.
|
|
83
|
+
- Runs a dedicated, isolated Chromium instance on port `9222` with state separation using the command `mint auto`.
|
|
84
|
+
- Supports opening URLs (`browser_open`), clicking buttons/elements (`browser_click`), typing text (`browser_type`), and extracting content (`browser_read`).
|
|
85
|
+
- **Dynamic Tool Injection:** The agent automatically registers these browser capability tools only when it detects that the automation browser is active on port `9222`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
81
89
|
## Highlights
|
|
82
90
|
|
|
83
91
|
- Multi-provider chat with Gemini, OpenAI, Anthropic, Ollama, Hugging Face, and
|
|
@@ -97,6 +105,24 @@ Mint is a local-first AI assistant running on your machine, capable of handling
|
|
|
97
105
|
- Signed Tauri update checks with an explicit approval step before installation.
|
|
98
106
|
- Dynamic local Ollama model fetching in the Settings Window to query and display the actual models installed on your machine.
|
|
99
107
|
- Pill-styled clean horizontal system event dividers for provider and model change notifications in the chat panel.
|
|
108
|
+
- Global unrestricted text selection and copying enabled across all application components.
|
|
109
|
+
- Spacious 1100px widescreen layout for the Chat Panel when the interactive model is hidden.
|
|
110
|
+
- Advanced Workspace File Tree featuring:
|
|
111
|
+
- Automatic directory refreshing upon window focus and 15-second polling.
|
|
112
|
+
- Quick action buttons to create new files and folders.
|
|
113
|
+
- Right-click context menu to delete files/folders with confirmation modals.
|
|
114
|
+
- Drag-and-drop file mentions in the chat input with automatic spacing and dynamic accent-colored history bubble highlighting.
|
|
115
|
+
|
|
116
|
+
## What's New in v1.8.1
|
|
117
|
+
|
|
118
|
+
- **File-System Based AI Skills & UI Badges**: Scan and load AI skills from the Global folder (`~/.config/mint/mint-skills/`) and Workspace folders (`.agents/skills/` & `skills/`). Shows source badges (`Workspace` 🟢, `Global` 🔵, `Taught` 🟣) in settings UI and CLI listings (`mint learn --list` and `/learn`) with a clean hidden-content list view.
|
|
119
|
+
- **Unified MCP Servers UI & Toggle Controls**: Merged system-discovered servers into a unified list with toggles to enable/disable or install, and red trash can buttons to remove custom servers.
|
|
120
|
+
- **Advanced Workspace File Tree & Operations**: Manage files directly from the sidebar UI with file/folder creation buttons, right-click deletion modals, and automatic polling + focus-based syncing.
|
|
121
|
+
- **Drag-and-Drop File Mentions**: Drag files from the sidebar to chat input to insert `@filename` with auto-spacing, rendered as outline pills styled with your selected accent color.
|
|
122
|
+
- **CLI Agent Interface Polish & Terminal Line Wrapping Fix**: Indented status blocks and confirmation prompts by 2 spaces to align text. Implemented the *Dynamic Breathing Circle System* (`●` ⇄ `○` pulsing during thinking) and fixed terminal duplicate line bugs by counting physical terminal wrapping width and filtering Thai tone marks.
|
|
123
|
+
- **AskUser Text Input Modal (Desktop & Web)**: When the agent asks questions, a text input field is rendered inside the approval card so users can write and submit typed answers back to the agent backend.
|
|
124
|
+
- **Dynamic Language AI Suggestions**: Auto-translates process suggestions dynamically based on your configured `config.language`.
|
|
125
|
+
- **Mobile & Navigation Optimizations**: Mobile-fit Image Studio layout, hamburger menu access, and headers glass-morphism overlay fixes.
|
|
100
126
|
|
|
101
127
|
## What's New in v1.8.0
|
|
102
128
|
|
|
@@ -339,6 +365,7 @@ mint setup
|
|
|
339
365
|
mint status
|
|
340
366
|
mint web
|
|
341
367
|
mint api
|
|
368
|
+
mint auto
|
|
342
369
|
mint chat "<message>"
|
|
343
370
|
```
|
|
344
371
|
|
|
@@ -351,6 +378,7 @@ mint chat "<message>"
|
|
|
351
378
|
| `mint setup` | Interactively manage enabled agent tools |
|
|
352
379
|
| `mint web` | Launch the web UI and local API server |
|
|
353
380
|
| `mint api` | Start only the local API server |
|
|
381
|
+
| `mint auto` | Launch the GUI browser automation isolated port |
|
|
354
382
|
| `mint status` | Show runtime status |
|
|
355
383
|
| `mint config init` | Create the local configuration file |
|
|
356
384
|
| `mint config path` | Print the configuration file path |
|
|
@@ -428,15 +456,18 @@ mint mcp call filesystem list_directory \
|
|
|
428
456
|
| `/help` | Show interactive help |
|
|
429
457
|
| `/fast [on\|off]` | Toggle fast response mode |
|
|
430
458
|
| `/models [name]` | List or select a model |
|
|
459
|
+
| `/image-provider [name]` | List or select default image generation provider |
|
|
431
460
|
| `/clear` or `/reset` | Clear the active conversation |
|
|
432
461
|
| `/cd <path>` | Change workspace directory |
|
|
433
462
|
| `/image <path> [prompt]` | Send an image with an optional prompt |
|
|
434
463
|
| `/paste [prompt]` | Use an image from the clipboard |
|
|
435
464
|
| `/learn <path>` | Import a local skill |
|
|
465
|
+
| `/plugins [name]` | List or interact with available plugins/skills |
|
|
436
466
|
| `/memory list` | List stored memories |
|
|
437
467
|
| `/memory clear` | Clear stored memories |
|
|
438
468
|
| `/memory get <key>` | Read one memory value |
|
|
439
469
|
| `/memory set <key> <value>` | Store one memory value |
|
|
470
|
+
| `/mcp [subcmd]` | Manage configured MCP servers (list, allow, disallow) |
|
|
440
471
|
| `/stats` | Show session statistics |
|
|
441
472
|
| `/code <task>` | Start a code-agent task |
|
|
442
473
|
| `/exit` or `/quit` | Leave interactive mode |
|
package/Release_Note.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Release Notes - Mint Agent v1.8.1
|
|
2
|
+
|
|
3
|
+
We are excited to release **Mint Agent v1.8.1**! This release introduces powerful local workspace file management features, real-time AI-generated workflow suggestions in the user's active language, mobile layout optimization, and general UI/UX polish.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Key Features & Enhancements
|
|
8
|
+
|
|
9
|
+
### 📂 1. Native Workspace File Explorer Operations
|
|
10
|
+
You can now manage your workspace directory directly from the sidebar without switching to an external editor or using the terminal:
|
|
11
|
+
- **New File & New Folder Buttons:** Replaced the legacy "Use Agent" action button with dedicated creation triggers. Inputting a name will immediately create the empty file/folder on disk.
|
|
12
|
+
- **Right-Click Deletion:** Right-clicking on any file or directory in the tree view prompts a safety confirmation modal. Upon approval, the item is securely removed from your filesystem.
|
|
13
|
+
- **Workspace Auto-Sync:** The file list stays up-to-date automatically using two smart mechanisms:
|
|
14
|
+
- **Window Focus Reloading:** Whenever you switch back to the Mint Desktop window (e.g., after editing code in VS Code), it refreshes the file list immediately.
|
|
15
|
+
- **Periodic Polling:** Auto-polls the directory structure every 15 seconds.
|
|
16
|
+
|
|
17
|
+
### 💬 2. Drag-and-Drop Mentions with Accent Color Highlight
|
|
18
|
+
Referencing files in your conversations is now smoother and visually richer:
|
|
19
|
+
- **Trailing Spacing:** Dragging files from the sidebar into the chat input automatically appends spacing (e.g. `@index.html `), allowing you to keep typing immediately.
|
|
20
|
+
- **Accent Theme Highlight:** In your chat history, any `@filename` mention is parsed and rendered inside an outline pill container styled dynamically with your selected **Accent Color** theme.
|
|
21
|
+
|
|
22
|
+
### 🌐 3. Dynamic Language AI Suggestions
|
|
23
|
+
- Process monitor triggers (such as opening terminal processes) now format real-time queries to your active AI provider.
|
|
24
|
+
- Suggestions are dynamically translated and rewritten to match your configured user language (`config.language`), replacing the Thai-only hardcoded templates.
|
|
25
|
+
- Features a safe fallback to static trigger defaults if the AI request times out or encounters network errors.
|
|
26
|
+
|
|
27
|
+
### 📱 4. Mobile Layout & Navigation Optimizations (Web View)
|
|
28
|
+
- **Image Studio Mobile Fit:** Fixed position clashing media queries resetting Image Studio `.img-studio` offsets to fit smaller screen sizes correctly.
|
|
29
|
+
- **Hamburger Menu Visibility:** Rendered the sidebar drawer button (☰) inside the Image Studio header on mobile viewports so users can navigate back.
|
|
30
|
+
- **Header Overlaps Resolved:** Added a solid opaque background overlay, glass blur, and borders to the mobile chat header, preventing scrolled messages from clashing with header typography.
|
|
31
|
+
- **Mobile Sidebar Drawer Fix:** Prevented the sidebar from collapsing into desktop icon-only configurations on screen widths under 760px, resolving layout drawer compression bugs.
|
|
32
|
+
|
|
33
|
+
### 💅 5. General UX Polish
|
|
34
|
+
- **Widescreen Chat Layout:** Expanded the chat conversation box max-width to `1100px` when the assistant avatar model is hidden, optimizing readability on widescreen displays.
|
|
35
|
+
- **Global Text Selection:** Unlocked full text selection and copying across the entire application interface (sidebars, panels, buttons, headers) by overriding restricted user-select rules.
|
|
36
|
+
|
|
37
|
+
### 🖥️ 6. CLI Agent Interface Polish & Terminal Line Wrapping Fix
|
|
38
|
+
Improved CLI status rendering aesthetics, interactive pacing, and terminal output reliability:
|
|
39
|
+
- **Consistent Terminal Padding:** Indented status blocks (Plan, Tasks, Activity, Explored) and confirmation prompt headers by 2 spaces to align with the standard prompt margin and prevent text from sticking to the left edge of the terminal window.
|
|
40
|
+
- **Dynamic Breathing Circle System:** Consolidated status symbols into a unified, colored circle layout (`● plan`, `● tasks`, `● activity`, `● explored`) which pulses dynamically between solid and hollow (`●` ⇄ `○`) while the AI is thinking to signify active processing.
|
|
41
|
+
- **Wrapping & Thai Combining Characters Fix:** Solved the duplicate/orphaned terminal lines issue caused by text wrapping on narrow screens. It now strips ANSI sequences, filters out Thai combining characters/tone marks that stack vertically, and counts true physical display lines to correctly clear stdout between updates.
|
|
42
|
+
|
|
43
|
+
### 💬 7. AskUser Text Input Modal in Desktop & Web App
|
|
44
|
+
Brought parity to the interactive questioning feature between the CLI and GUI versions:
|
|
45
|
+
- **Dynamic Input Field:** When the agent runs the `ask_user` tool (asking questions like requesting answers or inputs), a sleek text input textarea is displayed inside the Desktop and Web application's approval card.
|
|
46
|
+
- **Answer Submission Backend:** Updated the Tauri backend to handle `ApprovalOutcome::Intercepted(answer)` channels, sending user-typed answers back to the orchestration layer.
|
|
47
|
+
- **Activity Log Integration:** Once submitted, the user's typed answer is appended directly to the tool's execution target within the expandable "Working through task" drawer (e.g. `คุณภีมชอบสีอะไรคะ? (Answered: "สีเขียวมิ้นต์")`), maintaining a clear visual history of interactions.
|
|
48
|
+
|
|
49
|
+
### 🐙 8. GitKraken MCP Auto-Detection
|
|
50
|
+
Enabled a seamless developer onboarding experience by auto-discovering GitKraken on the host machine:
|
|
51
|
+
- **Automatic Discovery:** Scans the user's local PATH during configuration load to check if `gk` (GitKraken CLI) is available on the system.
|
|
52
|
+
- **Auto-Config Injection:** If detected, automatically configures and adds the GitKraken MCP server (`gk mcp`) to the user's `mint-config.json` file.
|
|
53
|
+
- **Github Release Integration:** Updated the CI/CD `.github/workflows/release.yml` file to parse and use `Release_Note.md` directly for the body description of GitHub Releases.
|
|
54
|
+
|
|
55
|
+
### 🏷️ 9. File-System Based AI Skills & Source Badges
|
|
56
|
+
Upgraded the AI skills loading pipeline and settings interface:
|
|
57
|
+
- **Dual Workspace Scanning:** Automatically discovers and loads `.md` / `.txt` skills from both `.agents/skills/` and `skills/` folders in the active workspace.
|
|
58
|
+
- **Location Color Badges:** Displays tags indicating whether skills come from Workspace (🟢 green), Global (🔵 blue), or Taught database (🟣 purple) in settings UI and CLI list output (`mint learn --list` and `/learn`).
|
|
59
|
+
- **Clean List UI:** Hides the lengthy file content preview block under each skill, rendering a much cleaner and concise layout.
|
|
60
|
+
|
|
61
|
+
### 🛠️ 10. Unified MCP Servers & Toggle Management
|
|
62
|
+
- **Consolidated List:** Merged all system-discovered tools (Docker, Git, GitHub, Node) into the unified MCP Servers list.
|
|
63
|
+
- **Individual Toggle Switches:** Enables/disables or installs MCP servers dynamically with instant toggle state styling.
|
|
64
|
+
- **Red Trash Can Buttons:** Added a red trash icon button to easily remove custom/manually configured MCP servers.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🛠️ Codebase Changes
|
|
69
|
+
|
|
70
|
+
### Tauri Backend (`src-tauri` & `crates/mint-core`)
|
|
71
|
+
- Register Tauri commands: `create_workspace_file`, `create_workspace_folder`, and `delete_workspace_item` in `src-tauri/src/lib.rs`.
|
|
72
|
+
- Add `location` field to `LearnedSkillDto` and populate it based on workspace, global config, or database sources in `src-tauri/src/lib.rs`.
|
|
73
|
+
- Update `workspace_root` path resolver in `src-tauri/src/lib.rs` to strip `src-tauri` from the active directory during dev mode.
|
|
74
|
+
- Standardize process suggestion monitor logic to run AI prompt translations in the background thread inside `src-tauri/src/workflows.rs`.
|
|
75
|
+
- Change `submit_tool_approval` signature and `ApprovalsState` pending channel type to support `ApprovalOutcome` in `src-tauri/src/lib.rs`.
|
|
76
|
+
- Add executable search helper `which` and GitKraken auto-detection hooks to `load_config_from` in `crates/mint-core/src/config.rs`.
|
|
77
|
+
- Support multiple source directory scanning and deduplication of learned skills in `crates/mint-core/src/skills.rs`.
|
|
78
|
+
|
|
79
|
+
### Desktop/Web Frontend (`src/renderer`)
|
|
80
|
+
- Integrate creation/deletion actions and focus/polling lifecycle listeners inside `WorkspacePanel.tsx`.
|
|
81
|
+
- Redesign actions bar grid layouts inside `styles.css`.
|
|
82
|
+
- Update inline markdown parser inside `ChatPanel.tsx` to wrap `@` mentions, and style `.chat-mention` in `index.css`.
|
|
83
|
+
- Update `submitToolApproval` API and component state handlers (`MintDashboard.tsx`, `ChatPanel.tsx`) in both desktop and web directories to render and submit custom answers.
|
|
84
|
+
- Update `activitiesFrom` and `AgentActivity` structure to parse and append the user's answer into the active `ask_user` tool target block upon `ToolEnd`.
|
|
85
|
+
- Update `LearnedSkill` TypeScript interface in `src/renderer/src/tauri.ts` to include optional `location`.
|
|
86
|
+
- Render colored location badges, strip lengthy content text boxes, and unify MCP toggles/delete controls in `src/renderer/src/components/Settings/PluginsTab.tsx` and `src/renderer/src-web/components/Settings/PluginsTab.tsx`.
|
|
87
|
+
|
|
88
|
+
### CLI Agent (`crates/mint-cli`)
|
|
89
|
+
- Redefine live status print lines (`plan_lines`, `tasks_lines`, `activities_lines`, `explored_lines`) to accept progress tick state and apply the `get_bullet` helper.
|
|
90
|
+
- Update `render_live_status` in `crates/mint-cli/src/agent.rs` to compute true physical lines of terminal wrapped text, using a new `is_thai_combining` filter.
|
|
91
|
+
- Modify `confirm` in `crates/mint-cli/src/main.rs` to indent confirmation prompts.
|
|
92
|
+
- Format `mint learn --list` and `/learn` CLI output into a bullet-point summary showing active skill locations and source paths.
|
|
93
|
+
|
|
94
|
+
### Github CI/CD Workflows
|
|
95
|
+
- Modify `Publish GitHub release` step in `.github/workflows/release.yml` to use `body_path: Release_Note.md`.
|
package/assets/Mint_CLI.png
CHANGED
|
Binary file
|
|
@@ -338,7 +338,7 @@ pub async fn run_code_agent_with_options(
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
let formatted_summary = format_markdown_bold(&summary);
|
|
341
|
-
print!("\n{MINT}Mint:{RESET} ");
|
|
341
|
+
print!("\n {MINT}Mint:{RESET} ");
|
|
342
342
|
render_live_summary(&formatted_summary);
|
|
343
343
|
println!();
|
|
344
344
|
};
|
|
@@ -377,16 +377,16 @@ pub async fn run_code_agent_with_options(
|
|
|
377
377
|
let res = res.map_err(|e| anyhow!("{}", e))?;
|
|
378
378
|
|
|
379
379
|
if should_show_verification(&res.verification) {
|
|
380
|
-
println!("Verification: {}", res.verification);
|
|
380
|
+
println!(" Verification: {}", res.verification);
|
|
381
381
|
}
|
|
382
382
|
println!(
|
|
383
|
-
"{DIM}─ Worked for {}{RESET}",
|
|
383
|
+
" {DIM}─ Worked for {}{RESET}",
|
|
384
384
|
format_elapsed(started_at.elapsed())
|
|
385
385
|
);
|
|
386
386
|
|
|
387
387
|
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
388
388
|
let width = tw as usize;
|
|
389
|
-
println!("{DIM}{}{RESET}", "─".repeat(width));
|
|
389
|
+
println!(" {DIM}{}{RESET}", "─".repeat(width.saturating_sub(2)));
|
|
390
390
|
|
|
391
391
|
Ok(res)
|
|
392
392
|
}
|
|
@@ -408,19 +408,26 @@ fn format_elapsed(duration: Duration) -> String {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
fn render_live_summary(summary: &str) {
|
|
411
|
-
let
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
411
|
+
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
412
|
+
let width = tw as usize;
|
|
413
|
+
|
|
414
|
+
let mut is_first = true;
|
|
415
|
+
for line in summary.split('\n') {
|
|
416
|
+
let indent = if is_first { "" } else { " " };
|
|
417
|
+
let options = textwrap::Options::new(width)
|
|
418
|
+
.initial_indent(indent)
|
|
419
|
+
.subsequent_indent(" ")
|
|
420
|
+
.break_words(true);
|
|
421
|
+
let wrapped = textwrap::fill(line, &options);
|
|
422
|
+
|
|
423
|
+
if is_first {
|
|
424
|
+
print!("{wrapped}");
|
|
425
|
+
is_first = false;
|
|
426
|
+
} else {
|
|
427
|
+
print!("\n{wrapped}");
|
|
418
428
|
}
|
|
419
429
|
}
|
|
420
|
-
|
|
421
|
-
print!("{chunk}");
|
|
422
|
-
let _ = io::stdout().flush();
|
|
423
|
-
}
|
|
430
|
+
let _ = io::stdout().flush();
|
|
424
431
|
}
|
|
425
432
|
|
|
426
433
|
fn print_colored_diff(diff: &str) {
|
|
@@ -497,13 +504,21 @@ fn explored_action_label(action: &str, input: &serde_json::Value) -> Option<Expl
|
|
|
497
504
|
kind: "[list_files] List",
|
|
498
505
|
target: display_tool_target(path),
|
|
499
506
|
}),
|
|
500
|
-
"read_file" =>
|
|
501
|
-
.get("path")
|
|
502
|
-
.and_then(|v| v.
|
|
503
|
-
.
|
|
507
|
+
"read_file" => {
|
|
508
|
+
let path = input.get("path").and_then(|v| v.as_str())?;
|
|
509
|
+
let start = input.get("startLine").and_then(|v| v.as_u64());
|
|
510
|
+
let end = input.get("endLine").and_then(|v| v.as_u64());
|
|
511
|
+
let file_name = display_tool_target(path);
|
|
512
|
+
let target = match (start, end) {
|
|
513
|
+
(Some(s), Some(e)) => format!("{} #L{}-{}", file_name, s, e),
|
|
514
|
+
(Some(s), None) => format!("{} #L{}", file_name, s),
|
|
515
|
+
_ => file_name,
|
|
516
|
+
};
|
|
517
|
+
Some(ExploredAction {
|
|
504
518
|
kind: "[read_file] Read",
|
|
505
|
-
target
|
|
506
|
-
})
|
|
519
|
+
target,
|
|
520
|
+
})
|
|
521
|
+
}
|
|
507
522
|
"search_code" => {
|
|
508
523
|
let query = input.get("query").and_then(|v| v.as_str())?;
|
|
509
524
|
let path = input.get("path").and_then(|v| v.as_str()).unwrap_or(".");
|
|
@@ -540,6 +555,30 @@ fn display_tool_target(path: &str) -> String {
|
|
|
540
555
|
}
|
|
541
556
|
}
|
|
542
557
|
|
|
558
|
+
fn strip_ansi_escapes(s: &str) -> String {
|
|
559
|
+
let mut result = String::new();
|
|
560
|
+
let mut in_escape = false;
|
|
561
|
+
for c in s.chars() {
|
|
562
|
+
if c == '\x1b' {
|
|
563
|
+
in_escape = true;
|
|
564
|
+
} else if in_escape {
|
|
565
|
+
if c.is_ascii_alphabetic() {
|
|
566
|
+
in_escape = false;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
result.push(c);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
result
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
fn is_thai_combining(c: char) -> bool {
|
|
576
|
+
matches!(c,
|
|
577
|
+
'\u{0e31}' | '\u{0e34}'..='\u{0e37}' | '\u{0e38}'..='\u{0e39}' |
|
|
578
|
+
'\u{0e47}'..='\u{0e4e}'
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
|
|
543
582
|
fn render_live_status(status: &mut LiveStatus) {
|
|
544
583
|
clear_live_status(status);
|
|
545
584
|
let mut lines = Vec::new();
|
|
@@ -547,23 +586,48 @@ fn render_live_status(status: &mut LiveStatus) {
|
|
|
547
586
|
let activities_start = status.committed_activities.min(status.activities.len());
|
|
548
587
|
let tasks_start = status.committed_tasks.min(status.tasks.len());
|
|
549
588
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
lines.extend(
|
|
589
|
+
let is_thinking = status.thinking.is_some();
|
|
590
|
+
let tick = status.spinner_tick;
|
|
591
|
+
|
|
592
|
+
lines.extend(plan_lines(&status.plan_steps, is_thinking, tick));
|
|
593
|
+
lines.extend(tasks_lines(&status.tasks[tasks_start..], is_thinking, tick));
|
|
594
|
+
lines.extend(activities_lines(
|
|
595
|
+
&status.activities[activities_start..],
|
|
596
|
+
is_thinking,
|
|
597
|
+
tick,
|
|
598
|
+
));
|
|
599
|
+
lines.extend(explored_lines(
|
|
600
|
+
&status.explored[explored_start..],
|
|
601
|
+
is_thinking,
|
|
602
|
+
tick,
|
|
603
|
+
));
|
|
554
604
|
if let Some(thinking) = &status.thinking {
|
|
555
|
-
let frames = &["
|
|
605
|
+
let frames = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
556
606
|
let frame = frames[status.spinner_tick % frames.len()];
|
|
557
607
|
status.spinner_tick += 1;
|
|
558
|
-
lines.push(format!("{MINT}{frame}{RESET} {BRIGHT}{thinking}{RESET}"));
|
|
608
|
+
lines.push(format!(" {MINT}{frame}{RESET} {BRIGHT}{thinking}{RESET}"));
|
|
559
609
|
}
|
|
560
610
|
if lines.is_empty() {
|
|
561
611
|
return;
|
|
562
612
|
}
|
|
613
|
+
|
|
614
|
+
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
615
|
+
let width = tw as usize;
|
|
616
|
+
|
|
617
|
+
let mut physical_lines_count = 0;
|
|
563
618
|
for line in &lines {
|
|
619
|
+
let stripped = strip_ansi_escapes(line);
|
|
620
|
+
let line_len = stripped.chars().filter(|&c| !is_thai_combining(c)).count();
|
|
621
|
+
let physical_lines = if width > 0 {
|
|
622
|
+
(line_len + width - 1) / width
|
|
623
|
+
} else {
|
|
624
|
+
1
|
|
625
|
+
}
|
|
626
|
+
.max(1);
|
|
627
|
+
physical_lines_count += physical_lines;
|
|
564
628
|
println!("{line}");
|
|
565
629
|
}
|
|
566
|
-
status.rendered_lines =
|
|
630
|
+
status.rendered_lines = physical_lines_count;
|
|
567
631
|
let _ = io::stdout().flush();
|
|
568
632
|
}
|
|
569
633
|
|
|
@@ -573,9 +637,13 @@ fn commit_activity_snapshot(status: &mut LiveStatus) {
|
|
|
573
637
|
let activities_start = status.committed_activities.min(status.activities.len());
|
|
574
638
|
let tasks_start = status.committed_tasks.min(status.tasks.len());
|
|
575
639
|
|
|
576
|
-
let mut lines = explored_lines(&status.explored[explored_start..]);
|
|
577
|
-
lines.extend(activities_lines(
|
|
578
|
-
|
|
640
|
+
let mut lines = explored_lines(&status.explored[explored_start..], false, 0);
|
|
641
|
+
lines.extend(activities_lines(
|
|
642
|
+
&status.activities[activities_start..],
|
|
643
|
+
false,
|
|
644
|
+
0,
|
|
645
|
+
));
|
|
646
|
+
lines.extend(tasks_lines(&status.tasks[tasks_start..], false, 0));
|
|
579
647
|
if lines.is_empty() {
|
|
580
648
|
return;
|
|
581
649
|
}
|
|
@@ -594,7 +662,14 @@ fn print_timeline_note(thought: &str) {
|
|
|
594
662
|
if thought.is_empty() {
|
|
595
663
|
return;
|
|
596
664
|
}
|
|
597
|
-
|
|
665
|
+
let (tw, _) = crossterm::terminal::size().unwrap_or((80, 24));
|
|
666
|
+
let width = tw as usize;
|
|
667
|
+
let options = textwrap::Options::new(width)
|
|
668
|
+
.initial_indent(" • ")
|
|
669
|
+
.subsequent_indent(" ")
|
|
670
|
+
.break_words(true);
|
|
671
|
+
let wrapped = textwrap::fill(thought, &options);
|
|
672
|
+
println!("{DIM}{wrapped}{RESET}");
|
|
598
673
|
}
|
|
599
674
|
|
|
600
675
|
fn print_timeline_separator() {
|
|
@@ -615,18 +690,34 @@ fn clear_live_status(status: &mut LiveStatus) {
|
|
|
615
690
|
let _ = io::stdout().flush();
|
|
616
691
|
}
|
|
617
692
|
|
|
618
|
-
fn
|
|
693
|
+
fn get_bullet(name: &str, is_thinking: bool, tick: usize) -> String {
|
|
694
|
+
let char_str = if is_thinking {
|
|
695
|
+
if (tick / 4) % 2 == 0 { "●" } else { "○" }
|
|
696
|
+
} else {
|
|
697
|
+
"●"
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
match name {
|
|
701
|
+
"plan" => format!("{BLUE}{char_str}{RESET} plan"),
|
|
702
|
+
"tasks" => format!("{MINT}{char_str}{RESET} tasks"),
|
|
703
|
+
"activity" => format!("{BLUE}{char_str}{RESET} activity"),
|
|
704
|
+
"explored" => format!("{DIM}{char_str}{RESET} explored"),
|
|
705
|
+
_ => char_str.to_string(),
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
fn explored_lines(actions: &[ExploredAction], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
619
710
|
if actions.is_empty() {
|
|
620
711
|
return Vec::new();
|
|
621
712
|
}
|
|
622
713
|
let grouped = grouped_explored_actions(actions);
|
|
623
|
-
let mut lines = vec![format!("{
|
|
714
|
+
let mut lines = vec![format!(" {}", get_bullet("explored", is_thinking, tick))];
|
|
624
715
|
lines.extend(grouped.iter().take(24).enumerate().map(|(index, action)| {
|
|
625
|
-
let prefix = if index == 0 { "
|
|
716
|
+
let prefix = if index == 0 { " └" } else { " " };
|
|
626
717
|
format!("{DIM}{prefix} {action}{RESET}")
|
|
627
718
|
}));
|
|
628
719
|
if grouped.len() > 24 {
|
|
629
|
-
lines.push(format!("{DIM}
|
|
720
|
+
lines.push(format!("{DIM} ... {} more{RESET}", grouped.len() - 24));
|
|
630
721
|
}
|
|
631
722
|
lines
|
|
632
723
|
}
|
|
@@ -658,17 +749,20 @@ fn command_was_run(result: &str) -> bool {
|
|
|
658
749
|
result.lines().any(|line| line.starts_with("exit: "))
|
|
659
750
|
}
|
|
660
751
|
|
|
661
|
-
fn activities_lines(activities: &[String]) -> Vec<String> {
|
|
752
|
+
fn activities_lines(activities: &[String], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
662
753
|
if activities.is_empty() {
|
|
663
754
|
return Vec::new();
|
|
664
755
|
}
|
|
665
|
-
let mut lines = vec![format!("{
|
|
756
|
+
let mut lines = vec![format!(" {}", get_bullet("activity", is_thinking, tick))];
|
|
666
757
|
lines.extend(activities.iter().take(24).enumerate().map(|(index, act)| {
|
|
667
|
-
let prefix = if index == 0 { "
|
|
758
|
+
let prefix = if index == 0 { " └" } else { " " };
|
|
668
759
|
format!("{DIM}{prefix} {act}{RESET}")
|
|
669
760
|
}));
|
|
670
761
|
if activities.len() > 24 {
|
|
671
|
-
lines.push(format!(
|
|
762
|
+
lines.push(format!(
|
|
763
|
+
"{DIM} ... {} more{RESET}",
|
|
764
|
+
activities.len() - 24
|
|
765
|
+
));
|
|
672
766
|
}
|
|
673
767
|
lines
|
|
674
768
|
}
|
|
@@ -685,16 +779,16 @@ fn extract_plan_steps(input: &serde_json::Value) -> Option<Vec<String>> {
|
|
|
685
779
|
Some(steps)
|
|
686
780
|
}
|
|
687
781
|
|
|
688
|
-
fn plan_lines(steps: &[String]) -> Vec<String> {
|
|
782
|
+
fn plan_lines(steps: &[String], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
689
783
|
if steps.is_empty() {
|
|
690
784
|
return Vec::new();
|
|
691
785
|
}
|
|
692
|
-
let mut lines = vec![format!("{
|
|
786
|
+
let mut lines = vec![format!(" {}", get_bullet("plan", is_thinking, tick))];
|
|
693
787
|
for (index, step) in steps.iter().enumerate() {
|
|
694
788
|
let prefix = if index == steps.len() - 1 {
|
|
695
|
-
"
|
|
789
|
+
" └"
|
|
696
790
|
} else {
|
|
697
|
-
"
|
|
791
|
+
" ├"
|
|
698
792
|
};
|
|
699
793
|
let (checked, text) = if step.to_lowercase().starts_with("done:") {
|
|
700
794
|
(format!("{MINT}[x]{RESET}"), step["done:".len()..].trim())
|
|
@@ -722,17 +816,17 @@ fn plan_lines(steps: &[String]) -> Vec<String> {
|
|
|
722
816
|
lines
|
|
723
817
|
}
|
|
724
818
|
|
|
725
|
-
fn tasks_lines(tasks: &[String]) -> Vec<String> {
|
|
819
|
+
fn tasks_lines(tasks: &[String], is_thinking: bool, tick: usize) -> Vec<String> {
|
|
726
820
|
if tasks.is_empty() {
|
|
727
821
|
return Vec::new();
|
|
728
822
|
}
|
|
729
|
-
let mut lines = vec![format!("{
|
|
823
|
+
let mut lines = vec![format!(" {}", get_bullet("tasks", is_thinking, tick))];
|
|
730
824
|
lines.extend(tasks.iter().take(24).enumerate().map(|(index, task)| {
|
|
731
|
-
let prefix = if index == 0 { "
|
|
825
|
+
let prefix = if index == 0 { " └" } else { " " };
|
|
732
826
|
format!("{DIM}{prefix} {task}{RESET}")
|
|
733
827
|
}));
|
|
734
828
|
if tasks.len() > 24 {
|
|
735
|
-
lines.push(format!("{DIM}
|
|
829
|
+
lines.push(format!("{DIM} ... {} more{RESET}", tasks.len() - 24));
|
|
736
830
|
}
|
|
737
831
|
lines
|
|
738
832
|
}
|