@pheem49/mint 1.4.0 → 1.4.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/.codex +0 -0
- package/README.md +101 -148
- package/main.js +21 -1
- package/mint-cli-logic.js +23 -8
- package/mint-cli.js +223 -137
- package/package.json +1 -1
- package/src/AI_Brain/Gemini_API.js +38 -24
- package/src/AI_Brain/agent_orchestrator.js +6 -6
- package/src/AI_Brain/proactive_engine.js +2 -8
- package/src/Automation_Layer/file_operations.js +136 -6
- package/src/Automation_Layer/open_app.js +72 -43
- package/src/Automation_Layer/open_website.js +3 -3
- package/src/CLI/chat_router.js +70 -24
- package/src/CLI/chat_ui.js +197 -44
- package/src/CLI/code_agent.js +337 -93
- package/src/CLI/list_features.js +3 -1
- package/src/CLI/workspace_manager.js +15 -6
- package/src/Plugins/docker.js +12 -10
- package/src/System/config_manager.js +1 -1
- package/src/System/custom_workflows.js +9 -2
- package/tests/chat_router.test.js +42 -0
- package/tests/code_agent.test.js +69 -0
- package/tests/docker.test.js +46 -0
- package/tests/file_operations.test.js +57 -0
- package/tests/provider_routing.test.js +67 -0
- package/tests/workspace_manager.test.js +15 -0
package/src/CLI/code_agent.js
CHANGED
|
@@ -4,43 +4,82 @@ const { execFile } = require('child_process');
|
|
|
4
4
|
const { promisify } = require('util');
|
|
5
5
|
const { GoogleGenAI } = require('@google/genai');
|
|
6
6
|
const axios = require('axios');
|
|
7
|
+
const cheerio = require('cheerio');
|
|
7
8
|
const { readConfig, getAvailableProviders } = require('../System/config_manager');
|
|
8
9
|
const { readWorkspaceSession, writeWorkspaceSession } = require('./code_session_memory');
|
|
10
|
+
const { executeAction } = require('../../mint-cli-logic');
|
|
11
|
+
|
|
12
|
+
async function webSearch(query) {
|
|
13
|
+
if (!query) throw new Error('Search query required.');
|
|
14
|
+
try {
|
|
15
|
+
const response = await axios.get(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`, {
|
|
16
|
+
headers: {
|
|
17
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const $ = cheerio.load(response.data);
|
|
21
|
+
const results = [];
|
|
22
|
+
$('.result__body').each((i, el) => {
|
|
23
|
+
if (i >= 5) return false;
|
|
24
|
+
const title = $(el).find('.result__title').text().trim();
|
|
25
|
+
const snippet = $(el).find('.result__snippet').text().trim();
|
|
26
|
+
const link = $(el).find('.result__url').attr('href');
|
|
27
|
+
if (title && link) {
|
|
28
|
+
results.push(`Title: ${title}\nSnippet: ${snippet}\nURL: ${link}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return results.length > 0 ? results.join('\n\n') : 'No results found.';
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return `Search failed: ${e.message}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
9
37
|
|
|
10
38
|
const execFileAsync = promisify(execFile);
|
|
11
39
|
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
12
40
|
const MAX_TOOL_OUTPUT = 12000;
|
|
13
41
|
const MAX_AGENT_STEPS = 16;
|
|
42
|
+
const MAX_JSON_REPAIR_ATTEMPTS = 2;
|
|
43
|
+
const SUPPORTED_CODE_PROVIDERS = ['gemini', 'anthropic', 'openai', 'local_openai'];
|
|
14
44
|
|
|
15
|
-
const CODE_AGENT_PROMPT = `You are Mint
|
|
45
|
+
const CODE_AGENT_PROMPT = `You are "Mint" (มิ้นท์), a cute, cheerful, and highly helpful female AI assistant that can chat, reason, write code, and search the web.
|
|
46
|
+
You work in an inspect -> plan -> act -> verify loop.
|
|
16
47
|
|
|
17
|
-
|
|
18
|
-
|
|
48
|
+
PERSONALITY & TONE:
|
|
49
|
+
- Gender: Female.
|
|
50
|
+
- Persona: Friendly, energetic, polite, and slightly playful.
|
|
51
|
+
- Politeness:
|
|
52
|
+
- **WHEN RESPONDING IN THAI:** ALWAYS use female polite particles such as "ค่ะ", "นะคะ", "นะค๊า", "จ้า". Refer to yourself as "มิ้นท์" or "หนู".
|
|
53
|
+
- **WHEN RESPONDING IN ENGLISH:** Use a cheerful, polite, and bubbly tone.
|
|
54
|
+
- Emojis: Use cute and relevant emojis (like ✨, 💖, 🚀, 😊, 🌿) frequently.
|
|
19
55
|
|
|
20
56
|
Rules:
|
|
21
57
|
1. Respond with valid JSON only.
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
24
|
-
4.
|
|
25
|
-
5.
|
|
26
|
-
6.
|
|
27
|
-
7.
|
|
28
|
-
8.
|
|
29
|
-
9. When you are done, return "finish" with
|
|
58
|
+
2. If the user asks a conversational question, you can just use "finish" to reply directly.
|
|
59
|
+
3. If you need information, use "web_search", "read_file", or "ask_user" before replying.
|
|
60
|
+
4. Make focused edits that preserve existing project style.
|
|
61
|
+
5. Use shell commands for inspection, tests, and formatting when useful.
|
|
62
|
+
6. Never use destructive commands like "rm -rf", "git reset --hard", or overwrite unrelated files.
|
|
63
|
+
7. Before any shell command or file patch is executed, the user must approve it. Plan accordingly.
|
|
64
|
+
8. When editing, prefer "apply_patch" with precise hunks over whole-file rewrites.
|
|
65
|
+
9. When you are done, return "finish" with your final response to the user in the "summary" field.
|
|
30
66
|
|
|
31
67
|
Response format:
|
|
32
68
|
{
|
|
33
|
-
"thought": "short reasoning",
|
|
34
|
-
"action": "list_files" | "read_file" | "search_code" | "run_shell" | "apply_patch" | "write_file" | "finish",
|
|
69
|
+
"thought": "short reasoning about what to do next",
|
|
70
|
+
"action": "web_search" | "list_files" | "read_file" | "search_code" | "find_path" | "run_shell" | "apply_patch" | "write_file" | "ask_user" | "open_url" | "open_app" | "open_file" | "open_folder" | "create_folder" | "system_info" | "system_automation" | "finish",
|
|
35
71
|
"input": {
|
|
72
|
+
"question": "your question to the user for ask_user",
|
|
73
|
+
"query": "search text for web_search, search_code, or find_path",
|
|
74
|
+
"target": "URL for open_url, app name for open_app, or command for system_automation",
|
|
36
75
|
"path": "relative/path",
|
|
37
|
-
"
|
|
76
|
+
"type": "file" | "dir" | "any",
|
|
38
77
|
"command": "shell command",
|
|
39
78
|
"startLine": 1,
|
|
40
79
|
"endLine": 120,
|
|
41
80
|
"content": "full file content for write_file",
|
|
42
|
-
"summary": "final
|
|
43
|
-
"verification": "tests or checks",
|
|
81
|
+
"summary": "your final conversational or technical response to the user (Matches user language and uses polite particles)",
|
|
82
|
+
"verification": "tests or checks (if applicable)",
|
|
44
83
|
"sessionSummary": "brief persistent summary for the workspace",
|
|
45
84
|
"patch": {
|
|
46
85
|
"path": "relative/path",
|
|
@@ -55,13 +94,20 @@ Response format:
|
|
|
55
94
|
}
|
|
56
95
|
|
|
57
96
|
Tool notes:
|
|
97
|
+
- "web_search": search the internet for information when you lack knowledge.
|
|
58
98
|
- "list_files": inspect the workspace or a subdirectory.
|
|
59
99
|
- "read_file": read a file, optionally with startLine/endLine.
|
|
60
100
|
- "search_code": search by text or regex-like pattern.
|
|
101
|
+
- "find_path": find files or directories by path/name when the user is looking for a folder, filename, or location.
|
|
61
102
|
- "run_shell": run a non-destructive command in the workspace.
|
|
62
103
|
- "apply_patch": update an existing file using one or more exact replacement hunks.
|
|
63
104
|
- "write_file": create a new file or fully rewrite a file when replacement is not practical.
|
|
64
|
-
- "
|
|
105
|
+
- "ask_user": ask the user for clarification, preference, or more information before proceeding.
|
|
106
|
+
- "open_url": open a URL in the user's default browser.
|
|
107
|
+
- "open_app": open a local application on the user's computer.
|
|
108
|
+
- "system_info": get system information like CPU, memory, date, or weather.
|
|
109
|
+
- "system_automation": control system settings like volume, brightness, or power.
|
|
110
|
+
- "finish": stop and reply to the user using the "summary" field.
|
|
65
111
|
`;
|
|
66
112
|
|
|
67
113
|
function truncate(text, max = MAX_TOOL_OUTPUT) {
|
|
@@ -81,6 +127,22 @@ function extractJson(text) {
|
|
|
81
127
|
}
|
|
82
128
|
}
|
|
83
129
|
|
|
130
|
+
function selectSupportedCodeProvider(config, availableProviders = getAvailableProviders(config || {})) {
|
|
131
|
+
const requestedProvider = (config && config.aiProvider) || 'gemini';
|
|
132
|
+
if (SUPPORTED_CODE_PROVIDERS.includes(requestedProvider) && availableProviders.includes(requestedProvider)) {
|
|
133
|
+
return requestedProvider;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const priority = ['anthropic', 'openai', 'gemini', 'local_openai'];
|
|
137
|
+
for (const provider of priority) {
|
|
138
|
+
if (availableProviders.includes(provider)) {
|
|
139
|
+
return provider;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return 'gemini';
|
|
144
|
+
}
|
|
145
|
+
|
|
84
146
|
function resolveWorkspacePath(workspaceRoot, targetPath = '.') {
|
|
85
147
|
const resolved = path.resolve(workspaceRoot, targetPath);
|
|
86
148
|
const relative = path.relative(workspaceRoot, resolved);
|
|
@@ -104,6 +166,29 @@ async function safeExecFile(command, args, options = {}) {
|
|
|
104
166
|
}
|
|
105
167
|
}
|
|
106
168
|
|
|
169
|
+
const IGNORED_DIRS = ['.git', 'node_modules', '.cache', 'dist', 'build', 'out'];
|
|
170
|
+
|
|
171
|
+
function walkDirectory(dir, workspaceRoot, results = [], max = 400) {
|
|
172
|
+
let entries = [];
|
|
173
|
+
try {
|
|
174
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const fullPath = path.join(dir, entry.name);
|
|
181
|
+
if (entry.isDirectory()) {
|
|
182
|
+
if (IGNORED_DIRS.includes(entry.name)) continue;
|
|
183
|
+
walkDirectory(fullPath, workspaceRoot, results, max);
|
|
184
|
+
} else {
|
|
185
|
+
results.push(path.relative(workspaceRoot, fullPath));
|
|
186
|
+
}
|
|
187
|
+
if (results.length >= max) break;
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
|
|
107
192
|
async function listFiles(workspaceRoot, targetPath = '.') {
|
|
108
193
|
const cwd = resolveWorkspacePath(workspaceRoot, targetPath);
|
|
109
194
|
try {
|
|
@@ -119,11 +204,9 @@ async function listFiles(workspaceRoot, targetPath = '.') {
|
|
|
119
204
|
if (error.code !== 'ENOENT' && error.stdout) {
|
|
120
205
|
return truncate(error.stdout);
|
|
121
206
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.join('\n');
|
|
126
|
-
return entries || '(empty directory)';
|
|
207
|
+
// Recursive fallback for missing ripgrep
|
|
208
|
+
const files = walkDirectory(cwd, workspaceRoot, [], 400);
|
|
209
|
+
return files.join('\n') || '(no files found)';
|
|
127
210
|
}
|
|
128
211
|
}
|
|
129
212
|
|
|
@@ -153,6 +236,29 @@ async function searchCode(workspaceRoot, query) {
|
|
|
153
236
|
if (typeof error.code === 'number' && error.code === 1) {
|
|
154
237
|
return '(no matches)';
|
|
155
238
|
}
|
|
239
|
+
if (error.code === 'ENOENT') {
|
|
240
|
+
// Recursive fallback search for missing ripgrep
|
|
241
|
+
const results = [];
|
|
242
|
+
const files = walkDirectory(workspaceRoot, workspaceRoot, [], 1000);
|
|
243
|
+
const lowerQuery = query.toLowerCase();
|
|
244
|
+
|
|
245
|
+
for (const relPath of files) {
|
|
246
|
+
try {
|
|
247
|
+
const fullPath = path.join(workspaceRoot, relPath);
|
|
248
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
249
|
+
const lines = content.split('\n');
|
|
250
|
+
lines.forEach((line, idx) => {
|
|
251
|
+
if (line.toLowerCase().includes(lowerQuery)) {
|
|
252
|
+
results.push(`${relPath}:${idx + 1}:${line.trim()}`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
} catch (e) {
|
|
256
|
+
// Skip binary or unreadable files
|
|
257
|
+
}
|
|
258
|
+
if (results.length >= 100) break;
|
|
259
|
+
}
|
|
260
|
+
return truncate(results.join('\n') || '(no matches)');
|
|
261
|
+
}
|
|
156
262
|
if (error.stdout) {
|
|
157
263
|
return truncate(error.stdout);
|
|
158
264
|
}
|
|
@@ -160,6 +266,40 @@ async function searchCode(workspaceRoot, query) {
|
|
|
160
266
|
}
|
|
161
267
|
}
|
|
162
268
|
|
|
269
|
+
async function findPaths(workspaceRoot, query, type = 'any') {
|
|
270
|
+
if (!query || !query.trim()) {
|
|
271
|
+
throw new Error('Path search query is required.');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const normalizedType = ['file', 'dir', 'any'].includes(type) ? type : 'any';
|
|
275
|
+
const loweredQuery = query.trim().toLowerCase();
|
|
276
|
+
const results = [];
|
|
277
|
+
|
|
278
|
+
function visit(currentPath) {
|
|
279
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
const absoluteEntryPath = path.join(currentPath, entry.name);
|
|
282
|
+
const relativeEntryPath = path.relative(workspaceRoot, absoluteEntryPath) || '.';
|
|
283
|
+
const entryType = entry.isDirectory() ? 'dir' : 'file';
|
|
284
|
+
const matchesType = normalizedType === 'any' || normalizedType === entryType;
|
|
285
|
+
const matchesQuery = entry.name.toLowerCase().includes(loweredQuery) || relativeEntryPath.toLowerCase().includes(loweredQuery);
|
|
286
|
+
|
|
287
|
+
if (matchesType && matchesQuery) {
|
|
288
|
+
results.push(`${entryType === 'dir' ? '[dir]' : '[file]'} ${relativeEntryPath}`);
|
|
289
|
+
if (results.length >= 200) return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (entry.isDirectory() && results.length < 200) {
|
|
293
|
+
visit(absoluteEntryPath);
|
|
294
|
+
if (results.length >= 200) return;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
visit(workspaceRoot);
|
|
300
|
+
return results.length > 0 ? results.join('\n') : '(no matching paths)';
|
|
301
|
+
}
|
|
302
|
+
|
|
163
303
|
function assertSafeShell(command) {
|
|
164
304
|
const blockedPatterns = [
|
|
165
305
|
/\brm\s+-rf\b/,
|
|
@@ -247,7 +387,7 @@ function writeFile(workspaceRoot, targetPath, content) {
|
|
|
247
387
|
|
|
248
388
|
class UnifiedAgentClient {
|
|
249
389
|
constructor(provider, config) {
|
|
250
|
-
this.provider = provider;
|
|
390
|
+
this.provider = SUPPORTED_CODE_PROVIDERS.includes(provider) ? provider : 'gemini';
|
|
251
391
|
this.config = config;
|
|
252
392
|
this.history = [];
|
|
253
393
|
this.systemInstruction = CODE_AGENT_PROMPT;
|
|
@@ -320,13 +460,13 @@ class UnifiedAgentClient {
|
|
|
320
460
|
const model = this.config.geminiModel || DEFAULT_GEMINI_MODEL;
|
|
321
461
|
const ai = new GoogleGenAI({ apiKey });
|
|
322
462
|
|
|
323
|
-
// Convert history for Gemini
|
|
324
|
-
const geminiHistory = this.history.slice(
|
|
463
|
+
// Convert history for Gemini, ensuring parts are correctly structured
|
|
464
|
+
const geminiHistory = this.history.slice(-16).map(m => ({
|
|
325
465
|
role: m.role === 'assistant' ? 'model' : 'user',
|
|
326
|
-
parts: [{ text: m.content }]
|
|
466
|
+
parts: [{ text: String(m.content || '') }]
|
|
327
467
|
}));
|
|
328
468
|
|
|
329
|
-
const lastMessage = this.history[this.history.length - 1].content;
|
|
469
|
+
const lastMessage = String(this.history[this.history.length - 1].content || '');
|
|
330
470
|
|
|
331
471
|
const chat = ai.chats.create({
|
|
332
472
|
model,
|
|
@@ -342,6 +482,29 @@ class UnifiedAgentClient {
|
|
|
342
482
|
}
|
|
343
483
|
}
|
|
344
484
|
|
|
485
|
+
async function getAgentDecision(client, observation, options = {}) {
|
|
486
|
+
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
487
|
+
const step = options.step || 0;
|
|
488
|
+
|
|
489
|
+
let rawText = await client.sendMessage(observation);
|
|
490
|
+
for (let attempt = 0; attempt <= MAX_JSON_REPAIR_ATTEMPTS; attempt++) {
|
|
491
|
+
try {
|
|
492
|
+
return extractJson(rawText);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (attempt === MAX_JSON_REPAIR_ATTEMPTS) {
|
|
495
|
+
throw new Error(`Agent returned invalid JSON after ${MAX_JSON_REPAIR_ATTEMPTS + 1} attempts: ${error.message}`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
onProgress({ step, phase: 'repairing', action: 'json_repair', message: `invalid JSON response, requesting repair (${attempt + 1}/${MAX_JSON_REPAIR_ATTEMPTS})` });
|
|
499
|
+
rawText = await client.sendMessage([
|
|
500
|
+
'Your previous response was not valid JSON for Code Mode.',
|
|
501
|
+
'Reply again with valid JSON only, following the required schema exactly.',
|
|
502
|
+
`Previous response:\n${truncate(rawText, 4000)}`
|
|
503
|
+
].join('\n'));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
345
508
|
function detectPackageManager(workspaceRoot) {
|
|
346
509
|
if (fs.existsSync(path.join(workspaceRoot, 'package-lock.json'))) return 'npm';
|
|
347
510
|
if (fs.existsSync(path.join(workspaceRoot, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
@@ -384,12 +547,17 @@ async function getGitContext(workspaceRoot) {
|
|
|
384
547
|
return { isRepo: true, branch, status, diffSummary };
|
|
385
548
|
}
|
|
386
549
|
|
|
387
|
-
async function buildInitialObservation(task, workspaceRoot) {
|
|
550
|
+
async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
388
551
|
const session = readWorkspaceSession(workspaceRoot);
|
|
389
552
|
const gitContext = await getGitContext(workspaceRoot);
|
|
390
553
|
const testCommands = detectTestCommands(workspaceRoot);
|
|
391
554
|
|
|
555
|
+
const contextStr = history.length > 0
|
|
556
|
+
? `Recent Context:\n${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}\n`
|
|
557
|
+
: '';
|
|
558
|
+
|
|
392
559
|
return [
|
|
560
|
+
contextStr,
|
|
393
561
|
`Task: ${task}`,
|
|
394
562
|
`Workspace: ${workspaceRoot}`,
|
|
395
563
|
`Git branch: ${gitContext.branch}`,
|
|
@@ -409,28 +577,39 @@ async function buildInitialObservation(task, workspaceRoot) {
|
|
|
409
577
|
|
|
410
578
|
async function executeCodeTask(task, options = {}) {
|
|
411
579
|
const workspaceRoot = path.resolve(options.cwd || process.cwd());
|
|
580
|
+
const history = options.history || [];
|
|
412
581
|
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
413
582
|
const requestApproval = typeof options.requestApproval === 'function'
|
|
414
583
|
? options.requestApproval
|
|
415
584
|
: async () => true;
|
|
585
|
+
const askUser = typeof options.askUser === 'function'
|
|
586
|
+
? options.askUser
|
|
587
|
+
: async (q) => `User didn't answer: ${q}`;
|
|
416
588
|
const config = readConfig();
|
|
417
|
-
const provider = options.provider ||
|
|
589
|
+
const provider = options.provider || selectSupportedCodeProvider(config);
|
|
418
590
|
const client = new UnifiedAgentClient(provider, config);
|
|
419
591
|
|
|
420
|
-
let observation = await buildInitialObservation(task, workspaceRoot);
|
|
592
|
+
let observation = await buildInitialObservation(task, workspaceRoot, history);
|
|
421
593
|
|
|
422
594
|
let finalSummary = '';
|
|
423
595
|
let finalVerification = '';
|
|
424
596
|
let finalSessionSummary = '';
|
|
597
|
+
let executedSteps = 0;
|
|
425
598
|
|
|
426
599
|
for (let step = 1; step <= MAX_AGENT_STEPS; step++) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const decision =
|
|
600
|
+
executedSteps = step;
|
|
601
|
+
onProgress({ step, phase: 'thinking', action: 'thinking' });
|
|
602
|
+
const decision = await getAgentDecision(client, observation, { onProgress, step });
|
|
430
603
|
const action = decision.action;
|
|
431
604
|
const input = decision.input || {};
|
|
432
605
|
|
|
433
|
-
|
|
606
|
+
// Immediately show the agent's thought/reasoning
|
|
607
|
+
onProgress({
|
|
608
|
+
step,
|
|
609
|
+
phase: 'acting',
|
|
610
|
+
action: 'thinking',
|
|
611
|
+
thought: decision.thought
|
|
612
|
+
});
|
|
434
613
|
|
|
435
614
|
if (action === 'finish') {
|
|
436
615
|
finalSessionSummary = input.sessionSummary || input.summary || task;
|
|
@@ -445,75 +624,130 @@ async function executeCodeTask(task, options = {}) {
|
|
|
445
624
|
}
|
|
446
625
|
|
|
447
626
|
let toolResult = '';
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
627
|
+
try {
|
|
628
|
+
switch (action) {
|
|
629
|
+
case 'web_search':
|
|
630
|
+
toolResult = await webSearch(input.query);
|
|
631
|
+
break;
|
|
632
|
+
case 'list_files':
|
|
633
|
+
toolResult = await listFiles(workspaceRoot, input.path || '.');
|
|
634
|
+
break;
|
|
635
|
+
case 'read_file':
|
|
636
|
+
toolResult = readFileRange(workspaceRoot, input.path, input.startLine, input.endLine);
|
|
637
|
+
break;
|
|
638
|
+
case 'search_code':
|
|
639
|
+
toolResult = await searchCode(workspaceRoot, input.query);
|
|
640
|
+
break;
|
|
641
|
+
case 'find_path':
|
|
642
|
+
toolResult = await findPaths(workspaceRoot, input.query, input.type);
|
|
643
|
+
break;
|
|
644
|
+
case 'run_shell': {
|
|
645
|
+
const approved = await requestApproval({
|
|
646
|
+
type: 'shell',
|
|
647
|
+
label: input.command,
|
|
648
|
+
preview: input.command
|
|
649
|
+
});
|
|
650
|
+
if (!approved) {
|
|
651
|
+
toolResult = `User denied shell command: ${input.command}`;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
toolResult = await runShell(workspaceRoot, input.command);
|
|
466
655
|
break;
|
|
467
656
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
toolResult =
|
|
657
|
+
case 'apply_patch': {
|
|
658
|
+
const patchInput = input.patch || {};
|
|
659
|
+
const approved = await requestApproval({
|
|
660
|
+
type: 'patch',
|
|
661
|
+
label: patchInput.path,
|
|
662
|
+
preview: formatPatchPreview(patchInput)
|
|
663
|
+
});
|
|
664
|
+
if (!approved) {
|
|
665
|
+
toolResult = `User denied patch for ${patchInput.path}`;
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
toolResult = applyPatch(workspaceRoot, patchInput);
|
|
480
669
|
break;
|
|
481
670
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
toolResult =
|
|
671
|
+
case 'write_file': {
|
|
672
|
+
const approved = await requestApproval({
|
|
673
|
+
type: 'write_file',
|
|
674
|
+
label: input.path,
|
|
675
|
+
preview: `${input.path}\n${truncate(input.content || '', 800)}`
|
|
676
|
+
});
|
|
677
|
+
if (!approved) {
|
|
678
|
+
toolResult = `User denied full file write for ${input.path}`;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
toolResult = writeFile(workspaceRoot, input.path, input.content);
|
|
493
682
|
break;
|
|
494
683
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
684
|
+
case 'ask_user': {
|
|
685
|
+
const answer = await askUser(input.question);
|
|
686
|
+
toolResult = `User answered: ${answer}`;
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
case 'open_url':
|
|
690
|
+
case 'open_app':
|
|
691
|
+
case 'open_file':
|
|
692
|
+
case 'open_folder':
|
|
693
|
+
case 'create_folder':
|
|
694
|
+
case 'system_info':
|
|
695
|
+
case 'system_automation': {
|
|
696
|
+
// Delegate to existing automation logic
|
|
697
|
+
toolResult = await executeAction({
|
|
698
|
+
type: action,
|
|
699
|
+
target: input.target
|
|
700
|
+
});
|
|
701
|
+
break;
|
|
702
|
+
} default:
|
|
703
|
+
throw new Error(`Unsupported action: ${action}`);
|
|
704
|
+
} } catch (e) {
|
|
705
|
+
toolResult = `Error: ${e.message}`;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Log the finished step with result
|
|
709
|
+
let resultSummary = '';
|
|
710
|
+
if (action === 'search_code') {
|
|
711
|
+
const matches = (toolResult.match(/\n/g) || []).length;
|
|
712
|
+
resultSummary = ` -> Found ${matches} matches`;
|
|
713
|
+
} else if (action === 'run_shell') {
|
|
714
|
+
resultSummary = ` -> Exit code 0`; // Simplified
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
onProgress({
|
|
718
|
+
step,
|
|
719
|
+
phase: 'finished',
|
|
720
|
+
action,
|
|
721
|
+
target: (input.path || input.command || input.query || '') + resultSummary,
|
|
722
|
+
thought: decision.thought
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// Format tool result to be more readable and structured for the agent
|
|
726
|
+
let formattedToolResult = toolResult;
|
|
727
|
+
if (action === 'list_files' || action === 'find_path') {
|
|
728
|
+
formattedToolResult = `Result of ${action}:\n---\n${toolResult}\n---`;
|
|
500
729
|
}
|
|
501
730
|
|
|
502
731
|
observation = [
|
|
503
732
|
`Previous thought: ${decision.thought || '(none)'}`,
|
|
504
733
|
`Action: ${action}`,
|
|
505
734
|
'Observation:',
|
|
506
|
-
|
|
507
|
-
].join('\n');
|
|
508
|
-
}
|
|
735
|
+
formattedToolResult
|
|
736
|
+
].join('\n'); }
|
|
509
737
|
|
|
510
|
-
// Check for Agent Collaboration (Review)
|
|
511
|
-
if (config.enableAgentCollaboration
|
|
738
|
+
// Check for Agent Collaboration (Review) - Disabled by default to save tokens
|
|
739
|
+
if (config.enableAgentCollaboration === true && executedSteps > 8 && finalSummary) {
|
|
512
740
|
const availableProviders = getAvailableProviders(config);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
741
|
+
// Exclude providers that often need special local setup or are slow/unreliable for tiny reviews
|
|
742
|
+
const altProviders = availableProviders.filter(p => p !== provider && p !== 'ollama' && p !== 'huggingface' && p !== 'local_openai');
|
|
743
|
+
|
|
744
|
+
// Fallback to provider itself if no other good ones exist, or pick the best available
|
|
745
|
+
const reviewerProvider = altProviders.length > 0
|
|
746
|
+
? altProviders[0]
|
|
747
|
+
: (availableProviders.includes('gemini') ? 'gemini' : availableProviders[0]);
|
|
748
|
+
|
|
749
|
+
if (reviewerProvider && finalSummary) {
|
|
750
|
+
onProgress({ phase: 'reviewing', action: 'reviewer_start', message: `Invoking Reviewer Agent (${reviewerProvider})...` });
|
|
517
751
|
|
|
518
752
|
const reviewerClient = new UnifiedAgentClient(reviewerProvider, config);
|
|
519
753
|
reviewerClient.systemInstruction = CODE_AGENT_PROMPT + "\n\nYou are the Reviewer Agent. Review the primary agent's changes, test output, and verification. If you spot a critical bug, point it out. Otherwise, confirm it looks good. Return JSON with action: 'finish' and your review in the 'summary' field.";
|
|
@@ -527,7 +761,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
527
761
|
|
|
528
762
|
finalSummary += `\n\n[Review by ${reviewerProvider}]\n${reviewInput.summary || reviewDecision.thought || 'Looks good.'}`;
|
|
529
763
|
} catch (e) {
|
|
530
|
-
onProgress(`Reviewer Agent failed: ${e.message}`);
|
|
764
|
+
onProgress({ phase: 'reviewing', action: 'reviewer_error', message: `Reviewer Agent failed: ${e.message}` });
|
|
531
765
|
}
|
|
532
766
|
}
|
|
533
767
|
}
|
|
@@ -536,7 +770,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
536
770
|
return {
|
|
537
771
|
summary: finalSummary,
|
|
538
772
|
verification: finalVerification,
|
|
539
|
-
steps:
|
|
773
|
+
steps: executedSteps
|
|
540
774
|
};
|
|
541
775
|
}
|
|
542
776
|
|
|
@@ -549,8 +783,18 @@ async function executeCodeTask(task, options = {}) {
|
|
|
549
783
|
return {
|
|
550
784
|
summary: 'Stopped after reaching the maximum number of agent steps.',
|
|
551
785
|
verification: 'Agent limit reached before explicit completion.',
|
|
552
|
-
steps: MAX_AGENT_STEPS
|
|
786
|
+
steps: executedSteps || MAX_AGENT_STEPS
|
|
553
787
|
};
|
|
554
788
|
}
|
|
555
789
|
|
|
556
|
-
module.exports = {
|
|
790
|
+
module.exports = {
|
|
791
|
+
executeCodeTask,
|
|
792
|
+
_helpers: {
|
|
793
|
+
extractJson,
|
|
794
|
+
selectSupportedCodeProvider,
|
|
795
|
+
findPaths,
|
|
796
|
+
listFiles,
|
|
797
|
+
searchCode,
|
|
798
|
+
walkDirectory
|
|
799
|
+
}
|
|
800
|
+
};
|
package/src/CLI/list_features.js
CHANGED
|
@@ -22,11 +22,13 @@ function displayFeatures() {
|
|
|
22
22
|
const commands = [
|
|
23
23
|
{ cmd: 'mint', desc: 'Start interactive chat session (Default)' },
|
|
24
24
|
{ cmd: 'mint code "<task>"', desc: 'Run workspace-aware coding agent in current directory' },
|
|
25
|
+
{ cmd: 'mint mcp', desc: 'Manage Model Context Protocol (MCP) servers' },
|
|
26
|
+
{ cmd: 'mint task "<task>"', desc: 'Queue an autonomous task for the background agent' },
|
|
25
27
|
{ cmd: 'mint onboard', desc: 'Run setup wizard (API Key, Model, Daemon)' },
|
|
26
28
|
{ cmd: 'mint agent', desc: 'Run Mint as a background agent (Headless)' },
|
|
27
29
|
{ cmd: 'mint list', desc: 'Show this features & commands list' }
|
|
28
30
|
];
|
|
29
|
-
commands.forEach(c => console.log(` - ${colors.cyan}${c.cmd.padEnd(
|
|
31
|
+
commands.forEach(c => console.log(` - ${colors.cyan}${c.cmd.padEnd(18)}${colors.reset} : ${c.desc}`));
|
|
30
32
|
|
|
31
33
|
console.log(`\n${colors.bright}AI Core Actions (Automation):${colors.reset}`);
|
|
32
34
|
const actions = [
|