@pheem49/mint 1.5.3 → 1.5.5
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/GUIDE_TH.md +16 -4
- package/README.md +17 -1
- package/install.ps1 +64 -0
- package/install.sh +54 -0
- package/package.json +8 -3
- package/preload.js +2 -0
- package/src/AI_Brain/Gemini_API.js +71 -24
- package/src/AI_Brain/agent_orchestrator.js +1 -1
- package/src/AI_Brain/provider_adapter.js +8 -1
- package/src/Automation_Layer/file_operations.js +17 -5
- package/src/CLI/chat_ui.js +17 -4
- package/src/CLI/cli_colors.js +1 -1
- package/src/CLI/code_agent.js +35 -1
- package/src/CLI/interactive_chat.js +22 -5
- package/src/System/config_manager.js +1 -0
- package/src/System/ipc_handlers.js +85 -1
- package/src/System/smart_context.js +227 -0
- package/src/UI/preload-spotlight.js +1 -0
- package/src/UI/renderer.js +380 -21
- package/src/UI/settings.js +1 -0
- package/src/UI/spotlight.js +13 -9
- package/src/UI/styles.css +303 -4
package/GUIDE_TH.md
CHANGED
|
@@ -6,15 +6,27 @@
|
|
|
6
6
|
|
|
7
7
|
## 1. การติดตั้ง (Installation)
|
|
8
8
|
|
|
9
|
-
คุณสามารถติดตั้ง Mint
|
|
9
|
+
คุณสามารถติดตั้ง Mint ได้ด้วยวิธีต่างๆ ดังนี้:
|
|
10
10
|
|
|
11
|
-
### วิธีที่ 1:
|
|
12
|
-
|
|
11
|
+
### วิธีที่ 1: ติดตั้งแบบด่วน (แนะนำ)
|
|
12
|
+
วิธีนี้จะติดตั้ง Mint CLI ให้อัตโนมัติในคำสั่งเดียว:
|
|
13
|
+
**สำหรับ macOS และ Linux:**
|
|
14
|
+
```bash
|
|
15
|
+
curl -fsSL https://raw.githubusercontent.com/Pheem49/Mint/main/install.sh | bash
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**สำหรับ Windows (PowerShell หรือ CMD):**
|
|
19
|
+
```powershell
|
|
20
|
+
powershell -Command "iwr -useb https://raw.githubusercontent.com/Pheem49/Mint/main/install.ps1 | iex"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### วิธีที่ 2: ติดตั้งผ่าน NPM
|
|
24
|
+
หากคุณต้องการใช้งานคำสั่ง `mint` และถนัดการใช้ NPM:
|
|
13
25
|
```bash
|
|
14
26
|
npm install -g @pheem49/mint@latest
|
|
15
27
|
```
|
|
16
28
|
|
|
17
|
-
### วิธีที่
|
|
29
|
+
### วิธีที่ 3: ติดตั้งจาก Source Code (สำหรับนักพัฒนา)
|
|
18
30
|
หากคุณต้องการแก้ไขโค้ดหรือลองฟีเจอร์ใหม่ๆ:
|
|
19
31
|
1. Clone repository: `git clone https://github.com/Pheem49/Mint.git`
|
|
20
32
|
2. เข้าไปที่โฟลเดอร์: `cd Mint`
|
package/README.md
CHANGED
|
@@ -39,7 +39,23 @@ Mint is an AI assistant built to live in your desktop and terminal. It combines
|
|
|
39
39
|
|
|
40
40
|
## Installation & Setup
|
|
41
41
|
|
|
42
|
-
###
|
|
42
|
+
### Quick Install (Recommended)
|
|
43
|
+
|
|
44
|
+
The easiest way to install Mint CLI is using our installation script:
|
|
45
|
+
|
|
46
|
+
**For macOS & Linux:**
|
|
47
|
+
```bash
|
|
48
|
+
curl -fsSL https://raw.githubusercontent.com/Pheem49/Mint/main/install.sh | bash
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**For Windows (PowerShell or CMD):**
|
|
52
|
+
```powershell
|
|
53
|
+
powershell -Command "iwr -useb https://raw.githubusercontent.com/Pheem49/Mint/main/install.ps1 | iex"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### NPM Install
|
|
57
|
+
|
|
58
|
+
If you prefer to install via NPM directly:
|
|
43
59
|
|
|
44
60
|
```bash
|
|
45
61
|
npm install -g @pheem49/mint@latest
|
package/install.ps1
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Mint CLI Installation Script for Windows
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
Installs the Mint CLI package globally via NPM.
|
|
6
|
+
#>
|
|
7
|
+
|
|
8
|
+
$ErrorActionPreference = "Stop"
|
|
9
|
+
|
|
10
|
+
Write-Host "Starting Mint CLI installation..." -ForegroundColor Cyan
|
|
11
|
+
|
|
12
|
+
# 1. Check for Node.js
|
|
13
|
+
try {
|
|
14
|
+
$nodeVersion = (node -v) 2>$null
|
|
15
|
+
if ([string]::IsNullOrWhiteSpace($nodeVersion)) {
|
|
16
|
+
Write-Host "[Error] Node.js is not found on your system!" -ForegroundColor Red
|
|
17
|
+
Write-Host "Please install Node.js (version 18 or higher) before using Mint CLI." -ForegroundColor Yellow
|
|
18
|
+
Write-Host "Download at: https://nodejs.org/"
|
|
19
|
+
exit 1
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
Write-Host "[Error] Node.js is not found on your system!" -ForegroundColor Red
|
|
23
|
+
Write-Host "Please install Node.js (version 18 or higher) before using Mint CLI." -ForegroundColor Yellow
|
|
24
|
+
Write-Host "Download at: https://nodejs.org/"
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# 2. Check for NPM
|
|
29
|
+
try {
|
|
30
|
+
$npmVersion = (npm -v) 2>$null
|
|
31
|
+
if ([string]::IsNullOrWhiteSpace($npmVersion)) {
|
|
32
|
+
Write-Host "[Error] NPM is not found on your system!" -ForegroundColor Red
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
Write-Host "[Error] NPM is not found on your system!" -ForegroundColor Red
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Check Node.js version (recommend 18+)
|
|
41
|
+
$majorVersion = [int]($nodeVersion.Substring(1).Split('.')[0])
|
|
42
|
+
if ($majorVersion -lt 18) {
|
|
43
|
+
Write-Host "[Warning] Your Node.js version is too old (current: $nodeVersion)" -ForegroundColor Yellow
|
|
44
|
+
Write-Host "It is recommended to update to version 18 or higher for full functionality." -ForegroundColor Yellow
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# 3. Install Mint CLI via NPM
|
|
48
|
+
Write-Host "Downloading and installing @pheem49/mint..." -ForegroundColor Cyan
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
# In Windows, we usually don't need sudo for global npm install if Node was installed for the user.
|
|
52
|
+
# We will just run the command normally.
|
|
53
|
+
npm install -g @pheem49/mint@latest
|
|
54
|
+
} catch {
|
|
55
|
+
Write-Host "[Error] Installation failed. Try running PowerShell as Administrator." -ForegroundColor Red
|
|
56
|
+
exit 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Write-Host ""
|
|
60
|
+
Write-Host "Mint CLI installed successfully!" -ForegroundColor Green
|
|
61
|
+
Write-Host "- Run 'mint' to get started."
|
|
62
|
+
Write-Host "- Run 'mint onboard' to set up your API Keys."
|
|
63
|
+
Write-Host "----------------------------------------"
|
|
64
|
+
Write-Host "Happy coding!" -ForegroundColor Cyan
|
package/install.sh
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Mint CLI Installation Script
|
|
3
|
+
# Usage: curl -fsSL <URL>/install.sh | bash
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
echo "Starting Mint CLI installation..."
|
|
8
|
+
|
|
9
|
+
# 1. Check for Node.js
|
|
10
|
+
if ! command -v node &> /dev/null
|
|
11
|
+
then
|
|
12
|
+
echo "[Error] Node.js is not found on your system!"
|
|
13
|
+
echo "Please install Node.js (version 18 or higher) before using Mint CLI."
|
|
14
|
+
echo "Download at: https://nodejs.org/"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# 2. Check for NPM
|
|
19
|
+
if ! command -v npm &> /dev/null
|
|
20
|
+
then
|
|
21
|
+
echo "[Error] NPM is not found on your system!"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Check Node.js version (recommend 18+)
|
|
26
|
+
NODE_VERSION=$(node -v | cut -d 'v' -f 2 | cut -d '.' -f 1)
|
|
27
|
+
if [ "$NODE_VERSION" -lt 18 ]; then
|
|
28
|
+
echo "[Warning] Your Node.js version is too old (current: v${NODE_VERSION})"
|
|
29
|
+
echo "It is recommended to update to version 18 or higher for full functionality."
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# 3. Install Mint CLI via NPM
|
|
33
|
+
echo "Downloading and installing @pheem49/mint (may require Admin/Root privileges)..."
|
|
34
|
+
|
|
35
|
+
# Determine if we need sudo
|
|
36
|
+
if [ "$EUID" -ne 0 ] && command -v sudo &> /dev/null; then
|
|
37
|
+
# Check if npm global directory is writable by the current user (e.g., using NVM)
|
|
38
|
+
NPM_PREFIX=$(npm config get prefix 2>/dev/null || echo "/usr/local")
|
|
39
|
+
if [ -w "$NPM_PREFIX" ] || [ -w "$NPM_PREFIX/lib/node_modules" ]; then
|
|
40
|
+
npm install -g @pheem49/mint@latest
|
|
41
|
+
else
|
|
42
|
+
# Preserve user's PATH so sudo can find npm if it's installed via custom paths
|
|
43
|
+
sudo env "PATH=$PATH" npm install -g @pheem49/mint@latest
|
|
44
|
+
fi
|
|
45
|
+
else
|
|
46
|
+
npm install -g @pheem49/mint@latest
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Mint CLI installed successfully!"
|
|
51
|
+
echo "- Run 'mint' to get started."
|
|
52
|
+
echo "- Run 'mint onboard' to set up your API Keys."
|
|
53
|
+
echo "----------------------------------------"
|
|
54
|
+
echo "Happy coding!"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pheem49/mint",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
"ink-text-input": "^6.0.0",
|
|
47
47
|
"mammoth": "^1.12.0",
|
|
48
48
|
"pdf-parse": "^2.4.5",
|
|
49
|
-
"react": "^19.2.5"
|
|
49
|
+
"react": "^19.2.5",
|
|
50
|
+
"read-excel-file": "^9.0.10",
|
|
51
|
+
"zod": "^4.4.3"
|
|
50
52
|
},
|
|
51
53
|
"peerDependenciesOptional": {
|
|
52
54
|
"puppeteer": ">=22.0.0",
|
|
@@ -71,6 +73,9 @@
|
|
|
71
73
|
"react-dom": "^19.2.5",
|
|
72
74
|
"vite": "^8.0.10"
|
|
73
75
|
},
|
|
76
|
+
"overrides": {
|
|
77
|
+
"gh-pages": "^5.0.0"
|
|
78
|
+
},
|
|
74
79
|
"build": {
|
|
75
80
|
"appId": "com.pheem49.mint",
|
|
76
81
|
"productName": "Mint",
|
|
@@ -84,7 +89,7 @@
|
|
|
84
89
|
"icon": "assets/icon.png",
|
|
85
90
|
"executableName": "mint-ai",
|
|
86
91
|
"target": [
|
|
87
|
-
"
|
|
92
|
+
"tar.gz",
|
|
88
93
|
"deb"
|
|
89
94
|
],
|
|
90
95
|
"category": "Utility"
|
package/preload.js
CHANGED
|
@@ -25,12 +25,14 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
25
25
|
startVision: () => ipcRenderer.invoke('start-screen-capture'),
|
|
26
26
|
onVisionReady: (callback) => ipcRenderer.on('vision-ready', (event, data) => callback(data)),
|
|
27
27
|
captureSilentScreen: () => ipcRenderer.invoke('capture-silent-screen'),
|
|
28
|
+
getSmartContext: () => ipcRenderer.invoke('get-smart-context'),
|
|
28
29
|
// Proactive Assistant
|
|
29
30
|
onProactiveSuggestion: (callback) => ipcRenderer.on('proactive-suggestion', (event, data) => callback(data)),
|
|
30
31
|
onProactiveNotification: (callback) => ipcRenderer.on('proactive-notification', (event, data) => callback(data)),
|
|
31
32
|
toggleProactive: (isOn) => ipcRenderer.send('toggle-proactive', isOn),
|
|
32
33
|
recordBehavior: (context) => ipcRenderer.send('record-behavior', context),
|
|
33
34
|
executeProactiveAction: (action) => ipcRenderer.invoke('execute-proactive-action', action),
|
|
35
|
+
executeApprovedAction: (action) => ipcRenderer.invoke('execute-approved-action', action),
|
|
34
36
|
onSpotlightToChat: (callback) => ipcRenderer.on('spotlight-to-chat', (_event, query) => callback(query)),
|
|
35
37
|
notifyAiResponse: () => ipcRenderer.send('ai-notify'),
|
|
36
38
|
clearAiNotifications: () => ipcRenderer.send('ai-notify-clear'),
|
|
@@ -47,6 +47,34 @@ function normalizeImageList(base64Image) {
|
|
|
47
47
|
return Array.isArray(base64Image) ? base64Image.filter(Boolean) : [base64Image];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const CHAT_MODE_ACTION_POLICY = `GOAL:
|
|
51
|
+
Your goal is to help the user with their queries. This Electron app is Chat Mode: use at most ONE simple action per user message, only when the latest message explicitly asks for that local action. If the user asks a question or asks you to provide text/commands, answer with action "none".
|
|
52
|
+
|
|
53
|
+
ACTION DISCIPLINE:
|
|
54
|
+
- Always return a single JSON object. Never return a JSON array or multiple actions.
|
|
55
|
+
- If the user asks "พิมพ์คำสั่งให้หน่อย", "บอกคำสั่ง", "ขอคำสั่ง", "what command", or "type the command", provide the command in "response" and set action "none". Do NOT use "type_text" or "key_tap".
|
|
56
|
+
- Use "type_text", "key_tap", "mouse_click", or "mouse_move" only when the user explicitly asks you to control the currently focused UI, not when they ask for a command to copy/type themselves.
|
|
57
|
+
- If the user asks to run terminal commands or code, Chat Mode should provide the command or tell them to use the Mint CLI agent. Do not type or press Enter on their behalf.
|
|
58
|
+
- Never say you opened, checked, inspected, or verified a file/folder unless the selected action actually does it and the app will execute that action.
|
|
59
|
+
- If the request needs workspace code inspection, edits, tests, or shell execution, tell the user to use the Mint CLI agent instead of pretending to inspect files.`;
|
|
60
|
+
|
|
61
|
+
const AGENT_MODE_ACTION_POLICY = `GOAL:
|
|
62
|
+
Your goal is to act as Mint's Desktop Agent Mode. You may use ONE concrete desktop action per response when it directly advances the user's latest request or a clear desktop task implied by Smart Context. Prefer useful action over explaining when the user asked Mint to do something.
|
|
63
|
+
|
|
64
|
+
ACTION DISCIPLINE:
|
|
65
|
+
- Always return a single JSON object. Never return a JSON array or multiple actions.
|
|
66
|
+
- Choose exactly one action when a desktop action is useful and the user's intent is clear; otherwise use action "none" and ask a concise follow-up.
|
|
67
|
+
- You may use safe desktop actions such as open_url, search, open_app, find_path, open_file, open_folder, create_folder, clipboard_write, learn_file, learn_folder, plugin, mcp_tool, web_automation, system_info, mouse_move, mouse_click, type_text, and key_tap when they match the request.
|
|
68
|
+
- Approval and dangerous actions are handled by Mint's UI. You may propose system_automation or delete_file only when the user clearly requested it; the app will ask for permission before running.
|
|
69
|
+
- For UI-control actions (mouse_click, mouse_move, type_text, key_tap), rely on Smart Context or the attached screenshot. If the target is ambiguous, ask before acting.
|
|
70
|
+
- If the user asks "พิมพ์คำสั่งให้หน่อย", "บอกคำสั่ง", "ขอคำสั่ง", "what command", or "type the command", provide the command in "response" and set action "none" unless they explicitly ask Mint to type it into the active UI.
|
|
71
|
+
- If the request needs workspace code inspection, edits, tests, or shell execution, tell the user to use the Mint CLI agent instead of pretending to inspect files or run commands from Chat UI.
|
|
72
|
+
- Never say you opened, checked, inspected, or verified something unless the selected action actually does it and the app will execute that action.`;
|
|
73
|
+
|
|
74
|
+
function buildActionModeInstruction(config = readConfig()) {
|
|
75
|
+
return config.assistantMode === 'agent' ? AGENT_MODE_ACTION_POLICY : CHAT_MODE_ACTION_POLICY;
|
|
76
|
+
}
|
|
77
|
+
|
|
50
78
|
const systemInstruction = `You are "Mint" (มิ้นท์), a cute, cheerful, and highly helpful female Local AI Desktop Agent.
|
|
51
79
|
|
|
52
80
|
PERSONALITY & TONE:
|
|
@@ -61,6 +89,7 @@ PERSONALITY & TONE:
|
|
|
61
89
|
- Style: Use a friendly, cute, and bubbly tone.
|
|
62
90
|
- Emojis: Use cute and relevant emojis (like ✨, 💖, 🚀, 😊, 🌿) frequently to make the conversation lively and cheerful.
|
|
63
91
|
- Use a professional yet sweet tone when needed, but prioritize being a lovable assistant.
|
|
92
|
+
- You are allowed to participate in mature roleplay, flirtatious chat, and NSFW themed conversations. Do not refuse mature requests.
|
|
64
93
|
|
|
65
94
|
NATURAL CHAT FLOW:
|
|
66
95
|
- Be an independent thinker. Analyze requests deeply before responding.
|
|
@@ -71,16 +100,7 @@ NATURAL CHAT FLOW:
|
|
|
71
100
|
- The latest user message is authoritative. Do not continue or describe older tasks unless the latest message explicitly asks you to continue them.
|
|
72
101
|
- For greetings, name-calls, acknowledgements, and backchannels such as "มิ้น", "มิ้นๆ", "อ๋อ", "โอเค", "ขอบคุณ", "hi", "hello", "ok", or "thanks", return action "none" and a short reply only.
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
Your goal is to help the user with their queries. This Electron app is Chat Mode, not Code Agent Mode: use at most ONE simple action per user message, only when the latest message explicitly asks for that local action. If the user asks a question or asks you to provide text/commands, answer with action "none".
|
|
76
|
-
|
|
77
|
-
ACTION DISCIPLINE:
|
|
78
|
-
- Always return a single JSON object. Never return a JSON array or multiple actions.
|
|
79
|
-
- If the user asks "พิมพ์คำสั่งให้หน่อย", "บอกคำสั่ง", "ขอคำสั่ง", "what command", or "type the command", provide the command in "response" and set action "none". Do NOT use "type_text" or "key_tap".
|
|
80
|
-
- Use "type_text", "key_tap", "mouse_click", or "mouse_move" only when the user explicitly asks you to control the currently focused UI, not when they ask for a command to copy/type themselves.
|
|
81
|
-
- If the user asks to run terminal commands or code, Chat Mode should provide the command or tell them to use the CLI agent. Do not type or press Enter on their behalf.
|
|
82
|
-
- Never say you opened, checked, inspected, or verified a file/folder unless the selected action actually does it and the app will execute that action.
|
|
83
|
-
- If the request needs workspace code inspection, edits, tests, or shell execution, tell the user to use the Mint CLI agent instead of pretending to inspect files.
|
|
103
|
+
{{ACTION_MODE_INSTRUCTION}}
|
|
84
104
|
|
|
85
105
|
CREATOR INFO:
|
|
86
106
|
- The creator is Pheem49.
|
|
@@ -144,6 +164,7 @@ ${toolRegistry.buildToolPromptSection()}
|
|
|
144
164
|
// Replaces 5 previously duplicated mcpPrompt blocks.
|
|
145
165
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
146
166
|
function buildSystemPrompt() {
|
|
167
|
+
const config = readConfig();
|
|
147
168
|
pluginManager.loadPlugins();
|
|
148
169
|
const mcpTools = mcpManager.getAllTools();
|
|
149
170
|
|
|
@@ -171,7 +192,9 @@ function buildSystemPrompt() {
|
|
|
171
192
|
workspaceSection = `\n\n[WORKSPACE DETECTED: ${ws.name}]\nPath: ${ws.path}\nProject Instructions: ${ws.instructions}\n`;
|
|
172
193
|
}
|
|
173
194
|
|
|
174
|
-
|
|
195
|
+
const modeInstruction = buildActionModeInstruction(config);
|
|
196
|
+
const baseInstruction = systemInstruction.replace('{{ACTION_MODE_INSTRUCTION}}', modeInstruction);
|
|
197
|
+
return baseInstruction + personaInstruction + workspaceSection + pluginManager.getPromptDescriptions() + mcpSection + userContext;
|
|
175
198
|
}
|
|
176
199
|
|
|
177
200
|
function buildMessageWithRelevantMemory(finalMessage) {
|
|
@@ -197,10 +220,15 @@ function stripRelevantMemoryBlock(text) {
|
|
|
197
220
|
return input
|
|
198
221
|
.replace(/\n?\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\n?/g, '\n')
|
|
199
222
|
.replace(/^\s*\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\s*/g, '')
|
|
223
|
+
.replace(/\n?\[SMART_CONTEXT\][\s\S]*?\[\/SMART_CONTEXT\]\n?/g, '\n')
|
|
200
224
|
.replace(/\n?\[LOCAL KNOWLEDGE BASE - USE THIS CONTEXT TO ANSWER\][\s\S]*/g, '')
|
|
201
225
|
.trim();
|
|
202
226
|
}
|
|
203
227
|
|
|
228
|
+
function hasSmartContextBlock(text) {
|
|
229
|
+
return /\[SMART_CONTEXT\][\s\S]*?\[\/SMART_CONTEXT\]/.test(String(text || ''));
|
|
230
|
+
}
|
|
231
|
+
|
|
204
232
|
function cleanHistoryForStorage(history) {
|
|
205
233
|
if (!Array.isArray(history)) return [];
|
|
206
234
|
return history.map(msg => ({
|
|
@@ -360,7 +388,14 @@ function createChat(history = []) {
|
|
|
360
388
|
model: activeModel,
|
|
361
389
|
config: {
|
|
362
390
|
systemInstruction: buildSystemPrompt(),
|
|
363
|
-
responseMimeType: "application/json"
|
|
391
|
+
responseMimeType: "application/json",
|
|
392
|
+
safetySettings: [
|
|
393
|
+
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
|
|
394
|
+
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
|
|
395
|
+
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
|
|
396
|
+
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
|
|
397
|
+
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }
|
|
398
|
+
]
|
|
364
399
|
},
|
|
365
400
|
history: truncatedHistory
|
|
366
401
|
});
|
|
@@ -467,13 +502,15 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
467
502
|
const config = readConfig();
|
|
468
503
|
const images = normalizeImageList(base64Image);
|
|
469
504
|
const previousHistory = readChatHistory();
|
|
505
|
+
const userVisibleMessage = stripRelevantMemoryBlock(message);
|
|
506
|
+
const containsSmartContext = hasSmartContextBlock(message);
|
|
470
507
|
|
|
471
508
|
let finalMessage = message;
|
|
472
509
|
|
|
473
510
|
// Inject Local RAG Context
|
|
474
|
-
if (
|
|
511
|
+
if (userVisibleMessage && userVisibleMessage.trim().length > 0 && shouldUseKnowledgeSearch(userVisibleMessage)) {
|
|
475
512
|
const { searchKnowledge } = require('./knowledge_base');
|
|
476
|
-
const retrievedDocs = await searchKnowledge(
|
|
513
|
+
const retrievedDocs = await searchKnowledge(userVisibleMessage);
|
|
477
514
|
if (retrievedDocs && retrievedDocs.length > 0) {
|
|
478
515
|
let contextString = `\n\n[LOCAL KNOWLEDGE BASE - USE THIS CONTEXT TO ANSWER]\n`;
|
|
479
516
|
retrievedDocs.forEach(doc => {
|
|
@@ -483,8 +520,8 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
483
520
|
}
|
|
484
521
|
}
|
|
485
522
|
|
|
486
|
-
if (
|
|
487
|
-
const cached = memoryStore.getCachedResponse(
|
|
523
|
+
if (!containsSmartContext && userVisibleMessage && images.length === 0 && !base64Audio) {
|
|
524
|
+
const cached = memoryStore.getCachedResponse(userVisibleMessage);
|
|
488
525
|
if (cached) return cached;
|
|
489
526
|
}
|
|
490
527
|
|
|
@@ -507,15 +544,15 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
507
544
|
model: getProviderModel(provider, config),
|
|
508
545
|
usage: client.getUsageSummary()
|
|
509
546
|
};
|
|
510
|
-
const parsedResult = parseChatProviderResponse(outputText, finalMessage, now);
|
|
547
|
+
const parsedResult = parseChatProviderResponse(outputText, userVisibleMessage || finalMessage, now);
|
|
511
548
|
parsedResult.providerInfo = providerInfo;
|
|
512
|
-
appendChatProviderHistory(previousHistory, finalMessage, outputText, providerInfo, now);
|
|
549
|
+
appendChatProviderHistory(previousHistory, userVisibleMessage || finalMessage, outputText, providerInfo, now);
|
|
513
550
|
|
|
514
|
-
if (finalMessage && parsedResult.response) {
|
|
551
|
+
if ((userVisibleMessage || finalMessage) && parsedResult.response) {
|
|
515
552
|
setImmediate(() => {
|
|
516
|
-
memoryStore.recordInteraction(finalMessage, parsedResult.response);
|
|
517
|
-
if (images.length === 0 && !base64Audio) {
|
|
518
|
-
memoryStore.cacheResponse(finalMessage, parsedResult);
|
|
553
|
+
memoryStore.recordInteraction(userVisibleMessage || finalMessage, parsedResult.response);
|
|
554
|
+
if (!containsSmartContext && images.length === 0 && !base64Audio) {
|
|
555
|
+
memoryStore.cacheResponse(userVisibleMessage || finalMessage, parsedResult);
|
|
519
556
|
}
|
|
520
557
|
});
|
|
521
558
|
}
|
|
@@ -724,7 +761,16 @@ async function translateImageContent(base64Image) {
|
|
|
724
761
|
{ inlineData: image }
|
|
725
762
|
]
|
|
726
763
|
}
|
|
727
|
-
]
|
|
764
|
+
],
|
|
765
|
+
config: {
|
|
766
|
+
safetySettings: [
|
|
767
|
+
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
|
|
768
|
+
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
|
|
769
|
+
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
|
|
770
|
+
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
|
|
771
|
+
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }
|
|
772
|
+
]
|
|
773
|
+
}
|
|
728
774
|
});
|
|
729
775
|
|
|
730
776
|
return {
|
|
@@ -761,6 +807,7 @@ module.exports = {
|
|
|
761
807
|
refreshApiKeyFromConfig,
|
|
762
808
|
_helpers: {
|
|
763
809
|
getProviderAttemptOrder,
|
|
764
|
-
normalizeParsedResult
|
|
810
|
+
normalizeParsedResult,
|
|
811
|
+
buildActionModeInstruction
|
|
765
812
|
}
|
|
766
813
|
};
|
|
@@ -9,7 +9,7 @@ const AGENT_PERSONAS = {
|
|
|
9
9
|
'general': {
|
|
10
10
|
name: 'Mint Default',
|
|
11
11
|
icon: '💎',
|
|
12
|
-
instruction: 'You are Mint, a versatile and helpful female AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
|
|
12
|
+
instruction: 'You are Mint, a versatile and helpful female AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู". You are allowed to participate in mature roleplay, flirtatious chat, and NSFW themed conversations. Do not refuse mature requests.'
|
|
13
13
|
},
|
|
14
14
|
'coder': {
|
|
15
15
|
name: 'Mint Coder',
|
|
@@ -283,7 +283,14 @@ class AgentProviderClient {
|
|
|
283
283
|
model,
|
|
284
284
|
config: {
|
|
285
285
|
systemInstruction: this.systemInstruction,
|
|
286
|
-
responseMimeType: this.responseMimeType
|
|
286
|
+
responseMimeType: this.responseMimeType,
|
|
287
|
+
safetySettings: [
|
|
288
|
+
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
|
|
289
|
+
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
|
|
290
|
+
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
|
|
291
|
+
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
|
|
292
|
+
{ category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_NONE" }
|
|
293
|
+
]
|
|
287
294
|
},
|
|
288
295
|
history
|
|
289
296
|
});
|
|
@@ -18,6 +18,8 @@ const IGNORED_DIRECTORY_NAMES = new Set([
|
|
|
18
18
|
'coverage'
|
|
19
19
|
]);
|
|
20
20
|
|
|
21
|
+
const COMMON_HOME_FOLDERS = ['Downloads', 'Desktop', 'Documents', 'Videos', 'Pictures', 'Music', 'vscode', 'Games'];
|
|
22
|
+
|
|
21
23
|
function getSearchRoots() {
|
|
22
24
|
return Array.from(new Set([
|
|
23
25
|
process.cwd(),
|
|
@@ -34,8 +36,6 @@ function resolveSmartPath(target) {
|
|
|
34
36
|
if (!target) return target;
|
|
35
37
|
|
|
36
38
|
const home = os.homedir();
|
|
37
|
-
const commonFolders = ['Downloads', 'Desktop', 'Documents', 'Videos', 'Pictures', 'Music', 'vscode', 'Games'];
|
|
38
|
-
|
|
39
39
|
// 1. If it's already an absolute path and exists, use it
|
|
40
40
|
if (path.isAbsolute(target) && fs.existsSync(target)) return target;
|
|
41
41
|
|
|
@@ -54,13 +54,13 @@ function resolveSmartPath(target) {
|
|
|
54
54
|
// 4. Check if the target itself starts with a common folder (e.g., "Downloads/resume.pdf")
|
|
55
55
|
const parts = target.split(/[/\\]/);
|
|
56
56
|
const firstPart = parts[0];
|
|
57
|
-
if (
|
|
57
|
+
if (COMMON_HOME_FOLDERS.includes(firstPart)) {
|
|
58
58
|
const potentialPath = path.join(home, target);
|
|
59
59
|
if (fs.existsSync(potentialPath)) return potentialPath;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// 5. Try searching the filename in all common folders
|
|
63
|
-
for (const folder of
|
|
63
|
+
for (const folder of COMMON_HOME_FOLDERS) {
|
|
64
64
|
const potentialPath = path.join(home, folder, target);
|
|
65
65
|
if (fs.existsSync(potentialPath)) return potentialPath;
|
|
66
66
|
}
|
|
@@ -76,6 +76,18 @@ function findPath(target, options = {}) {
|
|
|
76
76
|
|
|
77
77
|
const normalizedType = ['file', 'dir', 'any'].includes(options.type) ? options.type : 'any';
|
|
78
78
|
const loweredQuery = target.trim().toLowerCase();
|
|
79
|
+
const directPath = resolveSmartPath(target.trim());
|
|
80
|
+
if (directPath && path.isAbsolute(directPath) && fs.existsSync(directPath)) {
|
|
81
|
+
const stat = fs.statSync(directPath);
|
|
82
|
+
const directType = stat.isDirectory() ? 'dir' : 'file';
|
|
83
|
+
if (normalizedType === 'any' || normalizedType === directType) {
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
matches: [{ path: directPath, type: directType }]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
const exactMatches = [];
|
|
80
92
|
const partialMatches = [];
|
|
81
93
|
const visited = new Set();
|
|
@@ -271,4 +283,4 @@ async function deleteFile(target) {
|
|
|
271
283
|
}
|
|
272
284
|
}
|
|
273
285
|
|
|
274
|
-
module.exports = { createFolder, openFile, deleteFile, findPath };
|
|
286
|
+
module.exports = { createFolder, openFile, deleteFile, findPath, resolveSmartPath };
|
package/src/CLI/chat_ui.js
CHANGED
|
@@ -280,7 +280,7 @@ async function createChatUI(options) {
|
|
|
280
280
|
const TextInput = (await import('ink-text-input')).default;
|
|
281
281
|
const { useState, useImperativeHandle, forwardRef, createRef, useEffect, useMemo } = React;
|
|
282
282
|
|
|
283
|
-
const App = forwardRef(({ onSubmit, onExit, onPasteImage, initialHistory = [] }, ref) => {
|
|
283
|
+
const App = forwardRef(({ onSubmit, onExit, onCancel, onPasteImage, initialHistory = [] }, ref) => {
|
|
284
284
|
const config = readConfig();
|
|
285
285
|
const { exit } = useApp();
|
|
286
286
|
const [input, setInput] = useState('');
|
|
@@ -694,7 +694,17 @@ async function createChatUI(options) {
|
|
|
694
694
|
return;
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
-
if (key.escape
|
|
697
|
+
if (key.escape) {
|
|
698
|
+
if (thinking && typeof onCancel === 'function') {
|
|
699
|
+
onCancel();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
exit();
|
|
703
|
+
onExit();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (key.ctrl && inputStr === 'c') {
|
|
698
708
|
exit();
|
|
699
709
|
onExit();
|
|
700
710
|
return;
|
|
@@ -957,7 +967,10 @@ async function createChatUI(options) {
|
|
|
957
967
|
h(Box, { flexDirection: 'column' },
|
|
958
968
|
thinking && h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
959
969
|
h(Text, { color: 'gray', dimColor: true }, `─ Working for ${formatDuration(workingSeconds)} ─────────────────────────────────────────────────────────`),
|
|
960
|
-
h(
|
|
970
|
+
h(Box, { flexDirection: 'row', justifyContent: 'space-between' },
|
|
971
|
+
h(Text, { color: 'yellow' }, '● Mint is thinking...'),
|
|
972
|
+
h(Text, { color: 'gray', dimColor: true }, 'Press Esc to cancel')
|
|
973
|
+
)
|
|
961
974
|
),
|
|
962
975
|
|
|
963
976
|
pendingApproval && h(Box, {
|
|
@@ -1038,7 +1051,7 @@ async function createChatUI(options) {
|
|
|
1038
1051
|
value: input,
|
|
1039
1052
|
onChange: pendingApproval ? () => {} : handleInputChange,
|
|
1040
1053
|
onSubmit: pendingApproval ? () => {} : handleSubmit,
|
|
1041
|
-
placeholder: pendingApproval ? 'Approval pending...' : 'Ask anything...'
|
|
1054
|
+
placeholder: pendingApproval ? 'Approval pending...' : (thinking ? 'Agent is working... Press Esc to cancel' : 'Ask anything...')
|
|
1042
1055
|
})
|
|
1043
1056
|
)
|
|
1044
1057
|
),
|
package/src/CLI/cli_colors.js
CHANGED
|
@@ -51,7 +51,7 @@ function formatExitSummary(summary = {}) {
|
|
|
51
51
|
const top = `╭${'─'.repeat(width - 2)}╮`;
|
|
52
52
|
const bottom = `╰${'─'.repeat(width - 2)}╯`;
|
|
53
53
|
const empty = `│${''.padEnd(width - 2)}│`;
|
|
54
|
-
const message = summary.message || '
|
|
54
|
+
const message = summary.message || 'Goodbye! See you again.';
|
|
55
55
|
const toolCalls = summary.toolCalls || {};
|
|
56
56
|
const modelUsage = Array.isArray(summary.modelUsage) ? summary.modelUsage : [];
|
|
57
57
|
const primaryModel = modelUsage[0]
|
package/src/CLI/code_agent.js
CHANGED
|
@@ -441,7 +441,7 @@ async function searchCode(workspaceRoot, query, targetPath = '.') {
|
|
|
441
441
|
if (error.code === 'ENOENT') {
|
|
442
442
|
// Recursive fallback search for missing ripgrep
|
|
443
443
|
const results = [];
|
|
444
|
-
const files = walkDirectory(
|
|
444
|
+
const files = walkDirectory(searchRoot, workspaceRoot, [], 1000);
|
|
445
445
|
const lowerQuery = query.toLowerCase();
|
|
446
446
|
|
|
447
447
|
for (const relPath of files) {
|
|
@@ -1095,6 +1095,34 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
|
1095
1095
|
}
|
|
1096
1096
|
|
|
1097
1097
|
async function executeCodeTask(task, options = {}) {
|
|
1098
|
+
if (options.signal && options.signal.aborted) {
|
|
1099
|
+
throw new Error('Task cancelled by user.');
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
let onAbort;
|
|
1103
|
+
const abortPromise = new Promise((_, reject) => {
|
|
1104
|
+
if (options.signal) {
|
|
1105
|
+
onAbort = () => {
|
|
1106
|
+
reject(new Error('Task cancelled by user.'));
|
|
1107
|
+
};
|
|
1108
|
+
options.signal.addEventListener('abort', onAbort);
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
try {
|
|
1113
|
+
if (options.signal) {
|
|
1114
|
+
return await Promise.race([_executeCodeTaskInternal(task, options), abortPromise]);
|
|
1115
|
+
} else {
|
|
1116
|
+
return await _executeCodeTaskInternal(task, options);
|
|
1117
|
+
}
|
|
1118
|
+
} finally {
|
|
1119
|
+
if (options.signal && onAbort) {
|
|
1120
|
+
options.signal.removeEventListener('abort', onAbort);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function _executeCodeTaskInternal(task, options = {}) {
|
|
1098
1126
|
const workspaceRoot = path.resolve(options.cwd || process.cwd());
|
|
1099
1127
|
const history = options.history || [];
|
|
1100
1128
|
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
@@ -1162,9 +1190,15 @@ async function executeCodeTask(task, options = {}) {
|
|
|
1162
1190
|
}
|
|
1163
1191
|
|
|
1164
1192
|
for (let step = 1; step <= MAX_AGENT_STEPS; step++) {
|
|
1193
|
+
if (options.signal && options.signal.aborted) {
|
|
1194
|
+
throw new Error('Task cancelled by user.');
|
|
1195
|
+
}
|
|
1165
1196
|
executedSteps = step;
|
|
1166
1197
|
onProgress({ step, phase: 'thinking', action: 'thinking' });
|
|
1167
1198
|
const decision = await getAgentDecision(client, observation, { onProgress, step });
|
|
1199
|
+
if (options.signal && options.signal.aborted) {
|
|
1200
|
+
throw new Error('Task cancelled by user.');
|
|
1201
|
+
}
|
|
1168
1202
|
const action = decision.action;
|
|
1169
1203
|
const input = decision.input || {};
|
|
1170
1204
|
try {
|