@pheem49/mint 1.5.0 → 1.5.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/README.md +35 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +201 -500
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +40 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +23 -0
- package/package.json +40 -17
- package/src/AI_Brain/Gemini_API.js +147 -46
- package/src/AI_Brain/autonomous_brain.js +2 -1
- package/src/AI_Brain/memory_store.js +299 -3
- package/src/AI_Brain/proactive_engine.js +12 -2
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +42 -0
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +583 -52
- package/src/CLI/cli_colors.js +32 -0
- package/src/CLI/cli_formatters.js +89 -0
- package/src/CLI/code_agent.js +369 -71
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +479 -0
- package/src/CLI/list_features.js +3 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/repo_summarizer.js +282 -0
- package/src/CLI/semantic_code_search.js +312 -0
- package/src/CLI/skill_manager.js +41 -0
- package/src/CLI/slash_command_handler.js +418 -0
- package/src/CLI/symbol_indexer.js +231 -0
- package/src/CLI/updater.js +6 -4
- package/src/Channels/discord_bridge.js +11 -13
- package/src/Channels/line_bridge.js +10 -10
- package/src/Channels/slack_bridge.js +7 -12
- package/src/Channels/telegram_bridge.js +6 -14
- package/src/Channels/whatsapp_bridge.js +11 -9
- package/src/System/action_executor.js +59 -10
- package/src/System/chat_history_manager.js +20 -12
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/optional_require.js +23 -0
- package/src/System/proactive_loop.js +19 -3
- package/src/System/safety_manager.js +108 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +4 -2
- package/src/UI/live2d_manager.js +566 -0
- package/src/UI/renderer.js +339 -21
- package/src/UI/settings.css +655 -420
- package/src/UI/settings.html +478 -432
- package/src/UI/settings.js +10 -8
- package/src/UI/styles.css +516 -31
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/guide.html +0 -632
- package/docs/index.html +0 -133
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/action_executor_safety.test.js +0 -67
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/gmail.test.js +0 -135
- package/tests/gmail_auth.test.js +0 -129
- package/tests/google_calendar.test.js +0 -113
- package/tests/google_tts_urls.test.js +0 -24
- package/tests/memory_store.test.js +0 -185
- package/tests/notion.test.js +0 -121
- package/tests/provider_routing.test.js +0 -83
- package/tests/safety_manager.test.js +0 -40
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/updater.test.js +0 -32
- package/tests/workspace_manager.test.js +0 -56
package/src/CLI/code_agent.js
CHANGED
|
@@ -7,68 +7,112 @@ const axios = require('axios');
|
|
|
7
7
|
const cheerio = require('cheerio');
|
|
8
8
|
const { readConfig, getAvailableProviders } = require('../System/config_manager');
|
|
9
9
|
const safetyManager = require('../System/safety_manager');
|
|
10
|
+
const memoryStore = require('../AI_Brain/memory_store');
|
|
10
11
|
const { readWorkspaceSession, writeWorkspaceSession } = require('./code_session_memory');
|
|
11
|
-
const { executeAction } = require('
|
|
12
|
+
const { executeAction } = require('../System/action_executor');
|
|
13
|
+
const toolRegistry = require('../System/tool_registry');
|
|
14
|
+
const sandboxRunner = require('../System/sandbox_runner');
|
|
12
15
|
|
|
13
16
|
async function webSearch(query, onProgress = () => {}) {
|
|
14
17
|
if (!query) throw new Error('Search query required.');
|
|
15
18
|
const config = readConfig();
|
|
19
|
+
const debug = process.env.MINT_DEBUG === '1';
|
|
20
|
+
const errors = [];
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
const formatResults = (source, hits) => {
|
|
23
|
+
const instruction = `[CRITICAL AGENT INSTRUCTION: You MUST start your response by explicitly telling the user that you found this information using ${source}. Example: "อ้างอิงจากข้อมูลบน ${source}..." or "According to ${source}..."]\n\n`;
|
|
24
|
+
return instruction + `[Source: ${source}]\n\n` + hits;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// 1. Google Custom Search API (requires googleSearchApiKey + googleSearchCx in config)
|
|
18
28
|
if (config.googleSearchApiKey && config.googleSearchCx) {
|
|
19
29
|
try {
|
|
20
30
|
const GoogleSearch = require('../Channels/google_search_bridge');
|
|
21
31
|
const google = new GoogleSearch({ apiKey: config.googleSearchApiKey, cx: config.googleSearchCx });
|
|
22
32
|
const results = await google.search(query);
|
|
23
33
|
if (results.length > 0) {
|
|
24
|
-
return results.map(r => `Title: ${r.title}\nSnippet: ${r.snippet}\nURL: ${r.link}`).join('\n\n');
|
|
34
|
+
return formatResults('Google Search API', results.map(r => `Title: ${r.title}\nSnippet: ${r.snippet}\nURL: ${r.link}`).join('\n\n'));
|
|
25
35
|
}
|
|
26
|
-
} catch (e) {
|
|
27
|
-
|
|
36
|
+
} catch (e) {
|
|
37
|
+
errors.push(`Google: ${e.message}`);
|
|
38
|
+
if (debug) console.error('[webSearch] Google failed:', e.message);
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
// 2.
|
|
42
|
+
// 2. Brave Search API (requires braveSearchApiKey in config)
|
|
32
43
|
if (config.braveSearchApiKey) {
|
|
33
44
|
try {
|
|
34
45
|
const BraveSearch = require('../Channels/brave_search_bridge');
|
|
35
46
|
const brave = new BraveSearch({ apiKey: config.braveSearchApiKey });
|
|
36
47
|
const results = await brave.search(query);
|
|
37
48
|
if (results.length > 0) {
|
|
38
|
-
return results.map(r => `Title: ${r.title}\nSnippet: ${r.snippet}\nURL: ${r.link}`).join('\n\n');
|
|
49
|
+
return formatResults('Brave Search API', results.map(r => `Title: ${r.title}\nSnippet: ${r.snippet}\nURL: ${r.link}`).join('\n\n'));
|
|
39
50
|
}
|
|
40
|
-
} catch (e) {
|
|
41
|
-
|
|
51
|
+
} catch (e) {
|
|
52
|
+
errors.push(`Brave: ${e.message}`);
|
|
53
|
+
if (debug) console.error('[webSearch] Brave failed:', e.message);
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
// 3. Fallback
|
|
57
|
+
// 3. Fallback: DuckDuckGo HTML (No key required, but might get blocked by Captcha)
|
|
46
58
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
const cheerio = require('cheerio');
|
|
60
|
+
const ddgResponse = await axios.get(
|
|
61
|
+
`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`,
|
|
62
|
+
{
|
|
63
|
+
timeout: 8000,
|
|
64
|
+
headers: {
|
|
65
|
+
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
|
66
|
+
'Accept-Language': 'en-US,en;q=0.9'
|
|
67
|
+
}
|
|
50
68
|
}
|
|
51
|
-
|
|
52
|
-
const $ = cheerio.load(
|
|
53
|
-
const
|
|
54
|
-
$('.result__body').each((i, el) => {
|
|
69
|
+
);
|
|
70
|
+
const $ddg = cheerio.load(ddgResponse.data);
|
|
71
|
+
const ddgResults = [];
|
|
72
|
+
$ddg('.result__body').each((i, el) => {
|
|
55
73
|
if (i >= 5) return false;
|
|
56
|
-
const title
|
|
57
|
-
const snippet = $(el).find('.result__snippet').text().trim();
|
|
58
|
-
const link
|
|
59
|
-
if (title && link) {
|
|
60
|
-
results.push(`Title: ${title}\nSnippet: ${snippet}\nURL: ${link}`);
|
|
61
|
-
}
|
|
74
|
+
const title = $ddg(el).find('.result__title').text().trim();
|
|
75
|
+
const snippet = $ddg(el).find('.result__snippet').text().trim();
|
|
76
|
+
const link = $ddg(el).find('.result__url').attr('href');
|
|
77
|
+
if (title && link) ddgResults.push(`Title: ${title}\nSnippet: ${snippet}\nURL: ${link}`);
|
|
62
78
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
onProgress({ phase: 'error', action: 'web_search', message: 'DuckDuckGo scraping returned no results. It might be blocking us.' });
|
|
79
|
+
if (ddgResults.length > 0) {
|
|
80
|
+
return formatResults('DuckDuckGo', ddgResults.join('\n\n'));
|
|
66
81
|
}
|
|
82
|
+
errors.push('DuckDuckGo: no results (captcha?)');
|
|
83
|
+
if (debug) console.error('[webSearch] DuckDuckGo returned no results');
|
|
84
|
+
} catch (e) {
|
|
85
|
+
errors.push(`DuckDuckGo: ${e.message}`);
|
|
86
|
+
if (debug) console.error('[webSearch] DuckDuckGo failed:', e.message);
|
|
87
|
+
}
|
|
67
88
|
|
|
68
|
-
|
|
89
|
+
// 4. Fallback: Wikipedia API (Free, no key required, good for factual queries)
|
|
90
|
+
try {
|
|
91
|
+
const wikiResponse = await axios.get('https://en.wikipedia.org/w/api.php', {
|
|
92
|
+
params: { action: 'query', list: 'search', srsearch: query, format: 'json', srlimit: 3 },
|
|
93
|
+
timeout: 5000,
|
|
94
|
+
headers: { 'User-Agent': 'Mint-CLI/1.5 (https://github.com/pheem49/mint)' }
|
|
95
|
+
});
|
|
96
|
+
const hits = wikiResponse.data?.query?.search || [];
|
|
97
|
+
if (hits.length > 0) {
|
|
98
|
+
return formatResults('Wikipedia API', hits.map(r => `Title: ${r.title}\nSnippet: ${r.snippet.replace(/<[^>]+>/g, '')}\nURL: https://en.wikipedia.org/wiki/${encodeURIComponent(r.title.replace(/ /g, '_'))}`).join('\n\n'));
|
|
99
|
+
}
|
|
100
|
+
errors.push('Wikipedia: no results');
|
|
69
101
|
} catch (e) {
|
|
70
|
-
|
|
71
|
-
|
|
102
|
+
errors.push(`Wikipedia: ${e.message}`);
|
|
103
|
+
if (debug) console.error('[webSearch] Wikipedia failed:', e.message);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// All engines exhausted — inform agent clearly WHY it failed
|
|
107
|
+
const hasKeys = !!(config.googleSearchApiKey || config.braveSearchApiKey);
|
|
108
|
+
const summary = errors.length > 0 ? errors.join(' | ') : 'all search engines unavailable';
|
|
109
|
+
|
|
110
|
+
if (!hasKeys) {
|
|
111
|
+
onProgress({ phase: 'warn', action: 'web_search', message: `No Search API keys configured. Using training knowledge.` });
|
|
112
|
+
return `CRITICAL SYSTEM INSTRUCTION: Web search failed because no API keys are configured. You MUST inform the user that they need to set 'googleSearchApiKey' or 'braveSearchApiKey' in their Mint config file (~/.config/mint/config.json) to enable real-time internet search. Then, answer their query using your training knowledge.`;
|
|
113
|
+
} else {
|
|
114
|
+
onProgress({ phase: 'warn', action: 'web_search', message: `Web search unavailable (${summary}). Answering from training knowledge.` });
|
|
115
|
+
return `CRITICAL SYSTEM INSTRUCTION: Web search is temporarily unavailable. You MUST inform the user that live search failed, and then answer their query using only your training knowledge.`;
|
|
72
116
|
}
|
|
73
117
|
}
|
|
74
118
|
|
|
@@ -80,27 +124,28 @@ const MAX_AGENT_STEPS = 16;
|
|
|
80
124
|
const MAX_JSON_REPAIR_ATTEMPTS = 2;
|
|
81
125
|
const SUPPORTED_CODE_PROVIDERS = ['gemini', 'anthropic', 'openai', 'local_openai'];
|
|
82
126
|
|
|
83
|
-
const CODE_AGENT_PROMPT = `You are "Mint" (มิ้นท์), a
|
|
127
|
+
const CODE_AGENT_PROMPT = `You are "Mint" (มิ้นท์), a pragmatic, polite, and highly helpful AI assistant that can chat, reason, write code, and search the web.
|
|
84
128
|
You work in an inspect -> plan -> act -> verify loop.
|
|
85
129
|
|
|
86
130
|
PERSONALITY & TONE:
|
|
87
131
|
- Gender: Female.
|
|
88
|
-
- Persona: Friendly,
|
|
132
|
+
- Persona: Friendly, calm, concise, and technically direct. Avoid excessive praise, roleplay, or filler.
|
|
89
133
|
- Language routing is mandatory and based on the user's latest message:
|
|
90
134
|
- If the latest user message contains Thai characters, respond in Thai.
|
|
91
135
|
- If the latest user message is English, ASCII-only, or a short English greeting such as "hi", "hello", "ok", or "thanks", respond in English.
|
|
92
136
|
- Do not use Thai just because your persona mentions Mint/มิ้นท์, previous history was Thai, or app settings use th-TH.
|
|
93
137
|
- Politeness:
|
|
94
|
-
- **WHEN RESPONDING IN THAI:**
|
|
95
|
-
- **WHEN RESPONDING IN ENGLISH:** Use a
|
|
96
|
-
- Emojis:
|
|
138
|
+
- **WHEN RESPONDING IN THAI:** Use natural female polite particles such as "ค่ะ" or "นะคะ" where appropriate. Refer to yourself as "มิ้นท์" when it sounds natural.
|
|
139
|
+
- **WHEN RESPONDING IN ENGLISH:** Use a polite, concise, professional tone.
|
|
140
|
+
- Emojis: Avoid emojis in technical, review, debugging, and code-editing responses unless the user explicitly uses or asks for them.
|
|
97
141
|
|
|
98
142
|
Rules:
|
|
99
143
|
1. Respond with valid JSON only.
|
|
100
144
|
2. If the user asks a conversational question, you can just use "finish" to reply directly.
|
|
101
145
|
3. If you need information, use "web_search", "read_file", or "ask_user" before replying.
|
|
102
|
-
4.
|
|
103
|
-
5.
|
|
146
|
+
4. When using "web_search", always explicitly mention the source engine you used in your final summary (e.g. "According to Brave Search..." or "อ้างอิงจากข้อมูลบน Google..."). Match the language of your response.
|
|
147
|
+
5. Make focused edits that preserve existing project style.
|
|
148
|
+
6. Use shell commands for inspection, tests, and formatting when useful.
|
|
104
149
|
6. Never use destructive commands like "rm -rf", "git reset --hard", or overwrite unrelated files.
|
|
105
150
|
7. Before any shell command or file patch is executed, the user must approve it. Plan accordingly.
|
|
106
151
|
8. When editing, prefer "apply_patch" with precise hunks over whole-file rewrites.
|
|
@@ -169,6 +214,109 @@ function extractJson(text) {
|
|
|
169
214
|
}
|
|
170
215
|
}
|
|
171
216
|
|
|
217
|
+
function normalizeExecutorAction(action, input = {}) {
|
|
218
|
+
return {
|
|
219
|
+
type: action,
|
|
220
|
+
target: input.target || input.path || input.query || '',
|
|
221
|
+
path: input.path,
|
|
222
|
+
pathType: input.type,
|
|
223
|
+
openAfter: input.openAfter,
|
|
224
|
+
pluginName: input.pluginName,
|
|
225
|
+
server: input.server,
|
|
226
|
+
args: input.args,
|
|
227
|
+
x: input.x,
|
|
228
|
+
y: input.y,
|
|
229
|
+
button: input.button
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function formatActionPreview(action, input = {}) {
|
|
234
|
+
if (input.command) return input.command;
|
|
235
|
+
if (input.path) return input.path;
|
|
236
|
+
if (input.target) return input.target;
|
|
237
|
+
if (input.query) return input.query;
|
|
238
|
+
return action;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function evaluateActionResult(action, toolResult = '') {
|
|
242
|
+
if (!toolRegistry.isImportantAction(action)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const text = String(toolResult || '');
|
|
247
|
+
if (/^Error:|blocked|denied|failed|exception|not found/i.test(text)) {
|
|
248
|
+
return {
|
|
249
|
+
status: 'failed',
|
|
250
|
+
message: `Evaluator: ${action} may have failed. Review the observation before continuing.`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (action === 'run_shell' && /(ERR!|Error:|FAIL|failed|not found|permission denied)/i.test(text)) {
|
|
255
|
+
return {
|
|
256
|
+
status: 'warning',
|
|
257
|
+
message: 'Evaluator: shell output contains error-like text; verify before claiming success.'
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
status: 'passed',
|
|
263
|
+
message: `Evaluator: ${action} completed without obvious errors.`
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function splitDataUri(dataUri = '') {
|
|
268
|
+
const match = String(dataUri).match(/^data:([^;]+);base64,([\s\S]+)$/);
|
|
269
|
+
if (!match) return null;
|
|
270
|
+
return {
|
|
271
|
+
mimeType: match[1],
|
|
272
|
+
data: match[2]
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function contentToText(content) {
|
|
277
|
+
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
278
|
+
return String(content.text || '');
|
|
279
|
+
}
|
|
280
|
+
return String(content || '');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function contentToGeminiParts(content) {
|
|
284
|
+
const text = contentToText(content);
|
|
285
|
+
const parts = text ? [{ text }] : [];
|
|
286
|
+
if (content && typeof content === 'object' && content.imageDataUri) {
|
|
287
|
+
const image = splitDataUri(content.imageDataUri);
|
|
288
|
+
if (image) {
|
|
289
|
+
parts.push({ inlineData: { mimeType: image.mimeType, data: image.data } });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return parts.length > 0 ? parts : [{ text: '' }];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function contentToOpenAIContent(content) {
|
|
296
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
297
|
+
if (content && typeof content === 'object' && content.imageDataUri) {
|
|
298
|
+
return [
|
|
299
|
+
{ type: 'text', text },
|
|
300
|
+
{ type: 'image_url', image_url: { url: content.imageDataUri } }
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
return text;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function contentToAnthropicContent(content) {
|
|
307
|
+
const text = contentToText(content) || 'Analyze this input.';
|
|
308
|
+
if (content && typeof content === 'object' && content.imageDataUri) {
|
|
309
|
+
const image = splitDataUri(content.imageDataUri);
|
|
310
|
+
if (image) {
|
|
311
|
+
return [
|
|
312
|
+
{ type: 'image', source: { type: 'base64', media_type: image.mimeType, data: image.data } },
|
|
313
|
+
{ type: 'text', text }
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return text;
|
|
318
|
+
}
|
|
319
|
+
|
|
172
320
|
function getSupportedCodeProviderOrder(config, availableProviders = getAvailableProviders(config || {}), requestedOverride = null) {
|
|
173
321
|
const requestedProvider = requestedOverride || (config && config.aiProvider) || 'gemini';
|
|
174
322
|
const priority = ['anthropic', 'openai', 'gemini', 'local_openai'];
|
|
@@ -371,36 +519,32 @@ async function runShell(workspaceRoot, command) {
|
|
|
371
519
|
throw new Error('Shell command is required.');
|
|
372
520
|
}
|
|
373
521
|
assertSafeShell(command);
|
|
374
|
-
const { stdout, stderr } = await
|
|
522
|
+
const { stdout, stderr } = await sandboxRunner.runShell(command, {
|
|
523
|
+
source: 'code_agent',
|
|
375
524
|
cwd: workspaceRoot,
|
|
376
525
|
maxBuffer: 1024 * 1024 * 4
|
|
377
526
|
});
|
|
378
527
|
return truncate([stdout, stderr].filter(Boolean).join('\n') || '(no output)');
|
|
379
528
|
}
|
|
380
529
|
|
|
381
|
-
function
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return [
|
|
389
|
-
`Hunk ${index + 1}:`,
|
|
390
|
-
'--- old',
|
|
391
|
-
oldPreview,
|
|
392
|
-
'+++ new',
|
|
393
|
-
newPreview
|
|
394
|
-
].join('\n');
|
|
395
|
-
})
|
|
396
|
-
.join('\n\n');
|
|
397
|
-
return `${patchInput.path}\n${preview}`;
|
|
530
|
+
function splitDiffLines(text) {
|
|
531
|
+
const normalized = String(text || '').replace(/\r\n/g, '\n');
|
|
532
|
+
const lines = normalized.split('\n');
|
|
533
|
+
if (normalized.endsWith('\n')) {
|
|
534
|
+
lines.pop();
|
|
535
|
+
}
|
|
536
|
+
return lines;
|
|
398
537
|
}
|
|
399
538
|
|
|
400
|
-
function
|
|
539
|
+
function formatDiffRange(startLine, count) {
|
|
540
|
+
return count === 1 ? `${startLine}` : `${startLine},${count}`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function buildUnifiedDiffPreview(workspaceRoot, patchInput, options = {}) {
|
|
401
544
|
if (!patchInput || !patchInput.path) {
|
|
402
545
|
throw new Error('Patch path is required.');
|
|
403
546
|
}
|
|
547
|
+
|
|
404
548
|
const resolved = resolveWorkspacePath(workspaceRoot, patchInput.path);
|
|
405
549
|
if (!fs.existsSync(resolved)) {
|
|
406
550
|
throw new Error(`Patch target does not exist: ${patchInput.path}`);
|
|
@@ -411,16 +555,86 @@ function applyPatch(workspaceRoot, patchInput) {
|
|
|
411
555
|
throw new Error('Patch hunks are required.');
|
|
412
556
|
}
|
|
413
557
|
|
|
558
|
+
const contextLines = Number.isFinite(options.contextLines) ? options.contextLines : 3;
|
|
414
559
|
let content = fs.readFileSync(resolved, 'utf8');
|
|
560
|
+
const output = [
|
|
561
|
+
`--- a/${patchInput.path}`,
|
|
562
|
+
`+++ b/${patchInput.path}`
|
|
563
|
+
];
|
|
564
|
+
|
|
415
565
|
hunks.forEach((hunk, index) => {
|
|
416
566
|
if (typeof hunk.oldText !== 'string' || typeof hunk.newText !== 'string') {
|
|
417
567
|
throw new Error(`Patch hunk ${index + 1} is invalid.`);
|
|
418
568
|
}
|
|
419
|
-
|
|
569
|
+
|
|
570
|
+
const offset = content.indexOf(hunk.oldText);
|
|
571
|
+
if (offset === -1) {
|
|
420
572
|
throw new Error(`Patch hunk ${index + 1} oldText not found in ${patchInput.path}`);
|
|
421
573
|
}
|
|
422
|
-
|
|
574
|
+
|
|
575
|
+
const beforeText = content.slice(0, offset);
|
|
576
|
+
const oldStartLine = beforeText.length === 0 ? 1 : splitDiffLines(beforeText).length + 1;
|
|
577
|
+
const fileLines = splitDiffLines(content);
|
|
578
|
+
const oldLines = splitDiffLines(hunk.oldText);
|
|
579
|
+
const newLines = splitDiffLines(hunk.newText);
|
|
580
|
+
const oldStartIndex = oldStartLine - 1;
|
|
581
|
+
const contextStartIndex = Math.max(0, oldStartIndex - contextLines);
|
|
582
|
+
const contextEndIndex = Math.min(fileLines.length, oldStartIndex + oldLines.length + contextLines);
|
|
583
|
+
const beforeContext = fileLines.slice(contextStartIndex, oldStartIndex);
|
|
584
|
+
const afterContext = fileLines.slice(oldStartIndex + oldLines.length, contextEndIndex);
|
|
585
|
+
const oldRangeStart = contextStartIndex + 1;
|
|
586
|
+
const oldRangeCount = beforeContext.length + oldLines.length + afterContext.length;
|
|
587
|
+
const newRangeCount = beforeContext.length + newLines.length + afterContext.length;
|
|
588
|
+
|
|
589
|
+
output.push(`@@ -${formatDiffRange(oldRangeStart, oldRangeCount)} +${formatDiffRange(oldRangeStart, newRangeCount)} @@`);
|
|
590
|
+
beforeContext.forEach(line => output.push(` ${line}`));
|
|
591
|
+
oldLines.forEach(line => output.push(`-${line}`));
|
|
592
|
+
newLines.forEach(line => output.push(`+${line}`));
|
|
593
|
+
afterContext.forEach(line => output.push(` ${line}`));
|
|
594
|
+
|
|
595
|
+
content = `${content.slice(0, offset)}${hunk.newText}${content.slice(offset + hunk.oldText.length)}`;
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
return output.join('\n');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function formatPatchPreview(workspaceRoot, patchInput) {
|
|
602
|
+
try {
|
|
603
|
+
return buildUnifiedDiffPreview(workspaceRoot, patchInput);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
return `Patch preview failed: ${error.message}`;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function applyHunksToContent(content, hunks, filePath) {
|
|
610
|
+
let nextContent = content;
|
|
611
|
+
hunks.forEach((hunk, index) => {
|
|
612
|
+
if (typeof hunk.oldText !== 'string' || typeof hunk.newText !== 'string') {
|
|
613
|
+
throw new Error(`Patch hunk ${index + 1} is invalid.`);
|
|
614
|
+
}
|
|
615
|
+
if (!nextContent.includes(hunk.oldText)) {
|
|
616
|
+
throw new Error(`Patch hunk ${index + 1} oldText not found in ${filePath}`);
|
|
617
|
+
}
|
|
618
|
+
nextContent = nextContent.replace(hunk.oldText, hunk.newText);
|
|
423
619
|
});
|
|
620
|
+
return nextContent;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function applyPatch(workspaceRoot, patchInput) {
|
|
624
|
+
if (!patchInput || !patchInput.path) {
|
|
625
|
+
throw new Error('Patch path is required.');
|
|
626
|
+
}
|
|
627
|
+
const resolved = resolveWorkspacePath(workspaceRoot, patchInput.path);
|
|
628
|
+
if (!fs.existsSync(resolved)) {
|
|
629
|
+
throw new Error(`Patch target does not exist: ${patchInput.path}`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const hunks = Array.isArray(patchInput.hunks) ? patchInput.hunks : [];
|
|
633
|
+
if (hunks.length === 0) {
|
|
634
|
+
throw new Error('Patch hunks are required.');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const content = applyHunksToContent(fs.readFileSync(resolved, 'utf8'), hunks, patchInput.path);
|
|
424
638
|
|
|
425
639
|
fs.writeFileSync(resolved, content, 'utf8');
|
|
426
640
|
return `Patched ${patchInput.path} with ${hunks.length} hunk(s).`;
|
|
@@ -478,7 +692,7 @@ class UnifiedAgentClient {
|
|
|
478
692
|
const apiKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
479
693
|
const messages = this.history.map(m => ({
|
|
480
694
|
role: m.role,
|
|
481
|
-
content: m.content
|
|
695
|
+
content: contentToAnthropicContent(m.content)
|
|
482
696
|
}));
|
|
483
697
|
|
|
484
698
|
const response = await axios.post('https://api.anthropic.com/v1/messages', {
|
|
@@ -504,7 +718,10 @@ class UnifiedAgentClient {
|
|
|
504
718
|
|
|
505
719
|
const messages = [
|
|
506
720
|
{ role: 'system', content: this.systemInstruction },
|
|
507
|
-
...this.history
|
|
721
|
+
...this.history.map(m => ({
|
|
722
|
+
role: m.role,
|
|
723
|
+
content: contentToOpenAIContent(m.content)
|
|
724
|
+
}))
|
|
508
725
|
];
|
|
509
726
|
|
|
510
727
|
const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
|
|
@@ -525,13 +742,15 @@ class UnifiedAgentClient {
|
|
|
525
742
|
const model = this.config.geminiModel || DEFAULT_GEMINI_MODEL;
|
|
526
743
|
const ai = new GoogleGenAI({ apiKey });
|
|
527
744
|
|
|
745
|
+
const recentHistory = this.history.slice(-16);
|
|
746
|
+
const priorHistory = recentHistory.slice(0, -1);
|
|
747
|
+
const lastEntry = recentHistory[recentHistory.length - 1] || { content: '' };
|
|
748
|
+
|
|
528
749
|
// Convert history for Gemini, ensuring parts are correctly structured
|
|
529
|
-
const geminiHistory =
|
|
750
|
+
const geminiHistory = priorHistory.map(m => ({
|
|
530
751
|
role: m.role === 'assistant' ? 'model' : 'user',
|
|
531
|
-
parts:
|
|
752
|
+
parts: contentToGeminiParts(m.content)
|
|
532
753
|
}));
|
|
533
|
-
|
|
534
|
-
const lastMessage = String(this.history[this.history.length - 1].content || '');
|
|
535
754
|
|
|
536
755
|
const chat = ai.chats.create({
|
|
537
756
|
model,
|
|
@@ -542,7 +761,7 @@ class UnifiedAgentClient {
|
|
|
542
761
|
history: geminiHistory
|
|
543
762
|
});
|
|
544
763
|
|
|
545
|
-
const response = await chat.sendMessage({ message:
|
|
764
|
+
const response = await chat.sendMessage({ message: contentToGeminiParts(lastEntry.content) });
|
|
546
765
|
return typeof response.text === 'function' ? response.text() : response.text;
|
|
547
766
|
}
|
|
548
767
|
}
|
|
@@ -616,6 +835,7 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
|
616
835
|
const session = readWorkspaceSession(workspaceRoot);
|
|
617
836
|
const gitContext = await getGitContext(workspaceRoot);
|
|
618
837
|
const testCommands = detectTestCommands(workspaceRoot);
|
|
838
|
+
const userContext = memoryStore.getUserContext(task);
|
|
619
839
|
|
|
620
840
|
const contextStr = history.length > 0
|
|
621
841
|
? `Recent Context:\n${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}\n`
|
|
@@ -636,6 +856,8 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
|
|
|
636
856
|
session.summary || '(none)',
|
|
637
857
|
`Previous task: ${session.lastTask || '(none)'}`,
|
|
638
858
|
`Previous verification: ${session.lastVerification || '(none)'}`,
|
|
859
|
+
'Long-term user context:',
|
|
860
|
+
userContext || '(none)',
|
|
639
861
|
'If the task is conversational or trivial, finish directly without inspecting the workspace. For code/workspace tasks, inspect before making edits.'
|
|
640
862
|
].join('\n');
|
|
641
863
|
}
|
|
@@ -644,6 +866,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
644
866
|
const workspaceRoot = path.resolve(options.cwd || process.cwd());
|
|
645
867
|
const history = options.history || [];
|
|
646
868
|
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
869
|
+
const onFinalSummary = typeof options.onFinalSummary === 'function' ? options.onFinalSummary : null;
|
|
647
870
|
const requestApproval = typeof options.requestApproval === 'function'
|
|
648
871
|
? options.requestApproval
|
|
649
872
|
: async () => true;
|
|
@@ -656,7 +879,24 @@ async function executeCodeTask(task, options = {}) {
|
|
|
656
879
|
const provider = providerOrder[0];
|
|
657
880
|
const client = new UnifiedAgentClient(provider, config, providerOrder);
|
|
658
881
|
|
|
659
|
-
|
|
882
|
+
const initialObservationText = await buildInitialObservation(task, workspaceRoot, history);
|
|
883
|
+
const relevantMemoryCount = memoryStore.searchInteractions(task, 5).length;
|
|
884
|
+
onProgress({
|
|
885
|
+
phase: 'memory',
|
|
886
|
+
action: 'memory_context',
|
|
887
|
+
message: `Loaded memory: profile + recent history, ${relevantMemoryCount} direct match${relevantMemoryCount === 1 ? '' : 'es'}`
|
|
888
|
+
});
|
|
889
|
+
let observation = options.imageDataUri
|
|
890
|
+
? {
|
|
891
|
+
text: [
|
|
892
|
+
initialObservationText,
|
|
893
|
+
'',
|
|
894
|
+
`[Attached image: ${options.imagePath || 'command-line image'}]`,
|
|
895
|
+
'Use the attached image as visual context when planning and answering.'
|
|
896
|
+
].join('\n'),
|
|
897
|
+
imageDataUri: options.imageDataUri
|
|
898
|
+
}
|
|
899
|
+
: initialObservationText;
|
|
660
900
|
|
|
661
901
|
let finalSummary = '';
|
|
662
902
|
let finalVerification = '';
|
|
@@ -669,6 +909,17 @@ async function executeCodeTask(task, options = {}) {
|
|
|
669
909
|
const decision = await getAgentDecision(client, observation, { onProgress, step });
|
|
670
910
|
const action = decision.action;
|
|
671
911
|
const input = decision.input || {};
|
|
912
|
+
try {
|
|
913
|
+
toolRegistry.validateToolInput(action, input);
|
|
914
|
+
} catch (e) {
|
|
915
|
+
observation = [
|
|
916
|
+
`Previous thought: ${decision.thought || '(none)'}`,
|
|
917
|
+
`Action: ${action || '(none)'}`,
|
|
918
|
+
'Observation:',
|
|
919
|
+
`Error: ${e.message}`
|
|
920
|
+
].join('\n');
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
672
923
|
|
|
673
924
|
// Immediately show the agent's thought/reasoning
|
|
674
925
|
onProgress({
|
|
@@ -682,6 +933,16 @@ async function executeCodeTask(task, options = {}) {
|
|
|
682
933
|
finalSessionSummary = input.sessionSummary || input.summary || task;
|
|
683
934
|
finalSummary = input.summary || 'Task complete.';
|
|
684
935
|
finalVerification = input.verification || 'Not specified.';
|
|
936
|
+
if (onFinalSummary) {
|
|
937
|
+
await onFinalSummary({
|
|
938
|
+
summary: finalSummary,
|
|
939
|
+
verification: finalVerification,
|
|
940
|
+
providerInfo: {
|
|
941
|
+
provider: client.lastSuccessfulProvider || client.provider || provider,
|
|
942
|
+
model: getCodeProviderModel(client.lastSuccessfulProvider || client.provider || provider, config)
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
}
|
|
685
946
|
writeWorkspaceSession(workspaceRoot, {
|
|
686
947
|
summary: finalSessionSummary,
|
|
687
948
|
lastTask: task,
|
|
@@ -739,7 +1000,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
739
1000
|
const approved = await requestApproval({
|
|
740
1001
|
type: 'patch',
|
|
741
1002
|
label: patchInput.path,
|
|
742
|
-
preview: formatPatchPreview(patchInput)
|
|
1003
|
+
preview: formatPatchPreview(workspaceRoot, patchInput)
|
|
743
1004
|
});
|
|
744
1005
|
if (!approved) {
|
|
745
1006
|
toolResult = `User denied patch for ${patchInput.path}`;
|
|
@@ -785,10 +1046,28 @@ async function executeCodeTask(task, options = {}) {
|
|
|
785
1046
|
case 'create_folder':
|
|
786
1047
|
case 'system_info':
|
|
787
1048
|
case 'system_automation': {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1049
|
+
const executorAction = normalizeExecutorAction(action, input);
|
|
1050
|
+
const safety = safetyManager.classifyAction(executorAction);
|
|
1051
|
+
let allowDangerous = false;
|
|
1052
|
+
let allowApproval = false;
|
|
1053
|
+
if (safety.tier === safetyManager.TIERS.APPROVAL || safety.tier === safetyManager.TIERS.DANGEROUS) {
|
|
1054
|
+
const approved = await requestApproval({
|
|
1055
|
+
type: action,
|
|
1056
|
+
label: formatActionPreview(action, input),
|
|
1057
|
+
preview: `${action}: ${formatActionPreview(action, input)}\nSafety: ${safety.tier} (${safety.reason})`
|
|
1058
|
+
});
|
|
1059
|
+
if (!approved) {
|
|
1060
|
+
toolResult = `User denied ${action}: ${formatActionPreview(action, input)}`;
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
allowApproval = safety.tier === safetyManager.TIERS.APPROVAL;
|
|
1064
|
+
allowDangerous = safety.tier === safetyManager.TIERS.DANGEROUS;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
toolResult = await executeAction(executorAction, {
|
|
1068
|
+
source: 'code_agent',
|
|
1069
|
+
allowApproval,
|
|
1070
|
+
allowDangerous
|
|
792
1071
|
});
|
|
793
1072
|
break;
|
|
794
1073
|
} default:
|
|
@@ -797,6 +1076,22 @@ async function executeCodeTask(task, options = {}) {
|
|
|
797
1076
|
toolResult = `Error: ${e.message}`;
|
|
798
1077
|
}
|
|
799
1078
|
|
|
1079
|
+
const evaluation = evaluateActionResult(action, toolResult);
|
|
1080
|
+
if (evaluation) {
|
|
1081
|
+
onProgress({
|
|
1082
|
+
step,
|
|
1083
|
+
phase: 'evaluating',
|
|
1084
|
+
action: 'evaluator',
|
|
1085
|
+
message: `${evaluation.status}: ${evaluation.message}`
|
|
1086
|
+
});
|
|
1087
|
+
toolResult = [
|
|
1088
|
+
toolResult,
|
|
1089
|
+
'',
|
|
1090
|
+
'Evaluation:',
|
|
1091
|
+
`${evaluation.status}: ${evaluation.message}`
|
|
1092
|
+
].join('\n');
|
|
1093
|
+
}
|
|
1094
|
+
|
|
800
1095
|
// Log the finished step with result
|
|
801
1096
|
let resultSummary = '';
|
|
802
1097
|
if (action === 'search_code') {
|
|
@@ -858,6 +1153,7 @@ async function executeCodeTask(task, options = {}) {
|
|
|
858
1153
|
}
|
|
859
1154
|
|
|
860
1155
|
if (finalSummary) {
|
|
1156
|
+
memoryStore.recordInteraction(task, finalSummary);
|
|
861
1157
|
const answeredProvider = client.lastSuccessfulProvider || client.provider || provider;
|
|
862
1158
|
return {
|
|
863
1159
|
summary: finalSummary,
|
|
@@ -897,6 +1193,8 @@ module.exports = {
|
|
|
897
1193
|
findPaths,
|
|
898
1194
|
listFiles,
|
|
899
1195
|
searchCode,
|
|
900
|
-
walkDirectory
|
|
1196
|
+
walkDirectory,
|
|
1197
|
+
buildUnifiedDiffPreview,
|
|
1198
|
+
formatPatchPreview
|
|
901
1199
|
}
|
|
902
1200
|
};
|