@pheem49/mint 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/GUIDE_TH.md +113 -0
  2. package/README.md +267 -78
  3. package/assets/CLI_Screen.png +0 -0
  4. package/main.js +76 -890
  5. package/mint-cli-logic.js +3 -107
  6. package/mint-cli.js +594 -29
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
  9. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
  10. 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
  11. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
  12. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
  13. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
  14. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
  15. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
  16. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
  17. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
  18. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
  19. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
  20. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
  21. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
  22. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
  23. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
  24. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
  25. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
  26. 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
  27. package/package.json +37 -4
  28. package/src/AI_Brain/Gemini_API.js +223 -65
  29. package/src/AI_Brain/autonomous_brain.js +11 -0
  30. package/src/AI_Brain/behavior_memory.js +26 -5
  31. package/src/AI_Brain/headless_agent.js +4 -0
  32. package/src/AI_Brain/knowledge_base.js +61 -8
  33. package/src/AI_Brain/memory_store.js +354 -10
  34. package/src/Automation_Layer/file_operations.js +1 -1
  35. package/src/CLI/chat_router.js +20 -7
  36. package/src/CLI/chat_ui.js +596 -825
  37. package/src/CLI/code_agent.js +347 -56
  38. package/src/CLI/gmail_auth.js +210 -0
  39. package/src/CLI/image_input.js +90 -0
  40. package/src/CLI/list_features.js +2 -0
  41. package/src/CLI/onboarding.js +364 -55
  42. package/src/CLI/updater.js +210 -0
  43. package/src/Channels/brave_search_bridge.js +35 -0
  44. package/src/Channels/discord_bridge.js +68 -0
  45. package/src/Channels/google_search_bridge.js +38 -0
  46. package/src/Channels/line_bridge.js +60 -0
  47. package/src/Channels/slack_bridge.js +53 -0
  48. package/src/Channels/telegram_bridge.js +49 -0
  49. package/src/Channels/whatsapp_bridge.js +55 -0
  50. package/src/Command_Parser/parser.js +12 -1
  51. package/src/Plugins/gmail.js +251 -0
  52. package/src/Plugins/google_calendar.js +245 -19
  53. package/src/Plugins/notion.js +256 -0
  54. package/src/System/action_executor.js +178 -0
  55. package/src/System/bridge_manager.js +76 -0
  56. package/src/System/chat_history_manager.js +23 -5
  57. package/src/System/config_manager.js +71 -7
  58. package/src/System/custom_workflows.js +31 -2
  59. package/src/System/google_tts_urls.js +51 -0
  60. package/src/System/granular_automation.js +122 -53
  61. package/src/System/ipc_handlers.js +238 -0
  62. package/src/System/proactive_loop.js +153 -0
  63. package/src/System/safety_manager.js +273 -0
  64. package/src/System/sandbox_runner.js +182 -0
  65. package/src/System/screen_capture.js +175 -0
  66. package/src/System/system_automation.js +127 -81
  67. package/src/System/system_info.js +70 -0
  68. package/src/System/task_manager.js +15 -5
  69. package/src/System/tool_registry.js +280 -0
  70. package/src/System/window_manager.js +212 -0
  71. package/src/UI/live2d_manager.js +368 -0
  72. package/src/UI/renderer.js +208 -24
  73. package/src/UI/settings.html +24 -0
  74. package/src/UI/settings.js +14 -4
  75. package/src/UI/styles.css +466 -32
  76. package/.codex +0 -0
  77. package/docs/assets/Agent_Mint.png +0 -0
  78. package/docs/assets/CLI_Screen.png +0 -0
  79. package/docs/assets/Settings.png +0 -0
  80. package/docs/assets/icon.png +0 -0
  81. package/docs/index.html +0 -132
  82. package/docs/style.css +0 -579
  83. package/index.html +0 -16
  84. package/src/UI/index.html +0 -126
  85. package/tech_news.txt +0 -3
  86. package/test_knowledge.txt +0 -3
  87. package/tests/agent_orchestrator.test.js +0 -41
  88. package/tests/chat_router.test.js +0 -42
  89. package/tests/code_agent.test.js +0 -69
  90. package/tests/config_manager.test.js +0 -141
  91. package/tests/docker.test.js +0 -46
  92. package/tests/file_operations.test.js +0 -57
  93. package/tests/memory_store.test.js +0 -185
  94. package/tests/provider_routing.test.js +0 -67
  95. package/tests/spotify.test.js +0 -201
  96. package/tests/system_monitor.test.js +0 -37
  97. package/tests/workspace_manager.test.js +0 -56
@@ -6,11 +6,46 @@ const { GoogleGenAI } = require('@google/genai');
6
6
  const axios = require('axios');
7
7
  const cheerio = require('cheerio');
8
8
  const { readConfig, getAvailableProviders } = require('../System/config_manager');
9
+ const safetyManager = require('../System/safety_manager');
10
+ const memoryStore = require('../AI_Brain/memory_store');
9
11
  const { readWorkspaceSession, writeWorkspaceSession } = require('./code_session_memory');
10
- const { executeAction } = require('../../mint-cli-logic');
12
+ const { executeAction } = require('../System/action_executor');
13
+ const toolRegistry = require('../System/tool_registry');
14
+ const sandboxRunner = require('../System/sandbox_runner');
11
15
 
12
- async function webSearch(query) {
16
+ async function webSearch(query, onProgress = () => {}) {
13
17
  if (!query) throw new Error('Search query required.');
18
+ const config = readConfig();
19
+
20
+ // 1. Try Google Search API if configured
21
+ if (config.googleSearchApiKey && config.googleSearchCx) {
22
+ try {
23
+ const GoogleSearch = require('../Channels/google_search_bridge');
24
+ const google = new GoogleSearch({ apiKey: config.googleSearchApiKey, cx: config.googleSearchCx });
25
+ const results = await google.search(query);
26
+ if (results.length > 0) {
27
+ return results.map(r => `Title: ${r.title}\nSnippet: ${r.snippet}\nURL: ${r.link}`).join('\n\n');
28
+ }
29
+ } catch (e) {
30
+ onProgress({ phase: 'error', action: 'web_search', message: e.message });
31
+ }
32
+ }
33
+
34
+ // 2. Try Brave Search API if configured
35
+ if (config.braveSearchApiKey) {
36
+ try {
37
+ const BraveSearch = require('../Channels/brave_search_bridge');
38
+ const brave = new BraveSearch({ apiKey: config.braveSearchApiKey });
39
+ const results = await brave.search(query);
40
+ if (results.length > 0) {
41
+ return results.map(r => `Title: ${r.title}\nSnippet: ${r.snippet}\nURL: ${r.link}`).join('\n\n');
42
+ }
43
+ } catch (e) {
44
+ onProgress({ phase: 'error', action: 'web_search', message: e.message });
45
+ }
46
+ }
47
+
48
+ // 3. Fallback to DuckDuckGo Scraping
14
49
  try {
15
50
  const response = await axios.get(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`, {
16
51
  headers: {
@@ -28,8 +63,14 @@ async function webSearch(query) {
28
63
  results.push(`Title: ${title}\nSnippet: ${snippet}\nURL: ${link}`);
29
64
  }
30
65
  });
66
+
67
+ if (results.length === 0) {
68
+ onProgress({ phase: 'error', action: 'web_search', message: 'DuckDuckGo scraping returned no results. It might be blocking us.' });
69
+ }
70
+
31
71
  return results.length > 0 ? results.join('\n\n') : 'No results found.';
32
72
  } catch (e) {
73
+ onProgress({ phase: 'error', action: 'web_search', message: `DuckDuckGo fallback failed: ${e.message}` });
33
74
  return `Search failed: ${e.message}`;
34
75
  }
35
76
  }
@@ -48,6 +89,10 @@ You work in an inspect -> plan -> act -> verify loop.
48
89
  PERSONALITY & TONE:
49
90
  - Gender: Female.
50
91
  - Persona: Friendly, energetic, polite, and slightly playful.
92
+ - Language routing is mandatory and based on the user's latest message:
93
+ - If the latest user message contains Thai characters, respond in Thai.
94
+ - If the latest user message is English, ASCII-only, or a short English greeting such as "hi", "hello", "ok", or "thanks", respond in English.
95
+ - Do not use Thai just because your persona mentions Mint/มิ้นท์, previous history was Thai, or app settings use th-TH.
51
96
  - Politeness:
52
97
  - **WHEN RESPONDING IN THAI:** ALWAYS use female polite particles such as "ค่ะ", "นะคะ", "นะค๊า", "จ้า". Refer to yourself as "มิ้นท์" or "หนู".
53
98
  - **WHEN RESPONDING IN ENGLISH:** Use a cheerful, polite, and bubbly tone.
@@ -127,20 +172,143 @@ function extractJson(text) {
127
172
  }
128
173
  }
129
174
 
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;
175
+ function normalizeExecutorAction(action, input = {}) {
176
+ return {
177
+ type: action,
178
+ target: input.target || input.path || input.query || '',
179
+ path: input.path,
180
+ pathType: input.type,
181
+ openAfter: input.openAfter,
182
+ pluginName: input.pluginName,
183
+ server: input.server,
184
+ args: input.args,
185
+ x: input.x,
186
+ y: input.y,
187
+ button: input.button
188
+ };
189
+ }
190
+
191
+ function formatActionPreview(action, input = {}) {
192
+ if (input.command) return input.command;
193
+ if (input.path) return input.path;
194
+ if (input.target) return input.target;
195
+ if (input.query) return input.query;
196
+ return action;
197
+ }
198
+
199
+ function evaluateActionResult(action, toolResult = '') {
200
+ if (!toolRegistry.isImportantAction(action)) {
201
+ return null;
202
+ }
203
+
204
+ const text = String(toolResult || '');
205
+ if (/^Error:|blocked|denied|failed|exception|not found/i.test(text)) {
206
+ return {
207
+ status: 'failed',
208
+ message: `Evaluator: ${action} may have failed. Review the observation before continuing.`
209
+ };
210
+ }
211
+
212
+ if (action === 'run_shell' && /(ERR!|Error:|FAIL|failed|not found|permission denied)/i.test(text)) {
213
+ return {
214
+ status: 'warning',
215
+ message: 'Evaluator: shell output contains error-like text; verify before claiming success.'
216
+ };
217
+ }
218
+
219
+ return {
220
+ status: 'passed',
221
+ message: `Evaluator: ${action} completed without obvious errors.`
222
+ };
223
+ }
224
+
225
+ function splitDataUri(dataUri = '') {
226
+ const match = String(dataUri).match(/^data:([^;]+);base64,([\s\S]+)$/);
227
+ if (!match) return null;
228
+ return {
229
+ mimeType: match[1],
230
+ data: match[2]
231
+ };
232
+ }
233
+
234
+ function contentToText(content) {
235
+ if (content && typeof content === 'object' && !Array.isArray(content)) {
236
+ return String(content.text || '');
237
+ }
238
+ return String(content || '');
239
+ }
240
+
241
+ function contentToGeminiParts(content) {
242
+ const text = contentToText(content);
243
+ const parts = text ? [{ text }] : [];
244
+ if (content && typeof content === 'object' && content.imageDataUri) {
245
+ const image = splitDataUri(content.imageDataUri);
246
+ if (image) {
247
+ parts.push({ inlineData: { mimeType: image.mimeType, data: image.data } });
248
+ }
249
+ }
250
+ return parts.length > 0 ? parts : [{ text: '' }];
251
+ }
252
+
253
+ function contentToOpenAIContent(content) {
254
+ const text = contentToText(content) || 'Analyze this input.';
255
+ if (content && typeof content === 'object' && content.imageDataUri) {
256
+ return [
257
+ { type: 'text', text },
258
+ { type: 'image_url', image_url: { url: content.imageDataUri } }
259
+ ];
134
260
  }
261
+ return text;
262
+ }
135
263
 
264
+ function contentToAnthropicContent(content) {
265
+ const text = contentToText(content) || 'Analyze this input.';
266
+ if (content && typeof content === 'object' && content.imageDataUri) {
267
+ const image = splitDataUri(content.imageDataUri);
268
+ if (image) {
269
+ return [
270
+ { type: 'image', source: { type: 'base64', media_type: image.mimeType, data: image.data } },
271
+ { type: 'text', text }
272
+ ];
273
+ }
274
+ }
275
+ return text;
276
+ }
277
+
278
+ function getSupportedCodeProviderOrder(config, availableProviders = getAvailableProviders(config || {}), requestedOverride = null) {
279
+ const requestedProvider = requestedOverride || (config && config.aiProvider) || 'gemini';
136
280
  const priority = ['anthropic', 'openai', 'gemini', 'local_openai'];
281
+ const ordered = [];
282
+
283
+ if (SUPPORTED_CODE_PROVIDERS.includes(requestedProvider) && availableProviders.includes(requestedProvider)) {
284
+ ordered.push(requestedProvider);
285
+ }
286
+
137
287
  for (const provider of priority) {
138
- if (availableProviders.includes(provider)) {
139
- return provider;
288
+ if (availableProviders.includes(provider) && !ordered.includes(provider)) {
289
+ ordered.push(provider);
140
290
  }
141
291
  }
142
292
 
143
- return 'gemini';
293
+ return ordered.length > 0 ? ordered : ['gemini'];
294
+ }
295
+
296
+ function selectSupportedCodeProvider(config, availableProviders = getAvailableProviders(config || {})) {
297
+ return getSupportedCodeProviderOrder(config, availableProviders)[0];
298
+ }
299
+
300
+ function getCodeProviderModel(provider, config = {}) {
301
+ switch (provider) {
302
+ case 'anthropic':
303
+ return config.anthropicModel || 'claude-3-5-sonnet-latest';
304
+ case 'openai':
305
+ return config.openaiModel || 'gpt-4o';
306
+ case 'local_openai':
307
+ return config.localModelName || 'local-model';
308
+ case 'gemini':
309
+ default:
310
+ return config.geminiModel || DEFAULT_GEMINI_MODEL;
311
+ }
144
312
  }
145
313
 
146
314
  function resolveWorkspacePath(workspaceRoot, targetPath = '.') {
@@ -301,21 +469,7 @@ async function findPaths(workspaceRoot, query, type = 'any') {
301
469
  }
302
470
 
303
471
  function assertSafeShell(command) {
304
- const blockedPatterns = [
305
- /\brm\s+-rf\b/,
306
- /\bgit\s+reset\s+--hard\b/,
307
- /\bgit\s+checkout\s+--\b/,
308
- /\bmkfs\b/,
309
- /\bshutdown\b/,
310
- /\breboot\b/,
311
- />\s*\/dev\//,
312
- /\bcurl\b.*\|\s*(sh|bash)\b/,
313
- /\bwget\b.*\|\s*(sh|bash)\b/
314
- ];
315
-
316
- if (blockedPatterns.some(pattern => pattern.test(command))) {
317
- throw new Error(`Blocked unsafe command: ${command}`);
318
- }
472
+ return safetyManager.assertShellCommandAllowed(command);
319
473
  }
320
474
 
321
475
  async function runShell(workspaceRoot, command) {
@@ -323,7 +477,8 @@ async function runShell(workspaceRoot, command) {
323
477
  throw new Error('Shell command is required.');
324
478
  }
325
479
  assertSafeShell(command);
326
- const { stdout, stderr } = await execFileAsync('bash', ['-lc', command], {
480
+ const { stdout, stderr } = await sandboxRunner.runShell(command, {
481
+ source: 'code_agent',
327
482
  cwd: workspaceRoot,
328
483
  maxBuffer: 1024 * 1024 * 4
329
484
  });
@@ -386,34 +541,51 @@ function writeFile(workspaceRoot, targetPath, content) {
386
541
  }
387
542
 
388
543
  class UnifiedAgentClient {
389
- constructor(provider, config) {
544
+ constructor(provider, config, providerOrder = [provider]) {
390
545
  this.provider = SUPPORTED_CODE_PROVIDERS.includes(provider) ? provider : 'gemini';
546
+ this.providerOrder = providerOrder.length > 0 ? providerOrder : [this.provider];
391
547
  this.config = config;
392
548
  this.history = [];
393
549
  this.systemInstruction = CODE_AGENT_PROMPT;
550
+ this.lastSuccessfulProvider = null;
394
551
  }
395
552
 
396
553
  async sendMessage(observation) {
397
554
  this.history.push({ role: 'user', content: observation });
398
555
 
399
- let responseText = '';
400
- if (this.provider === 'anthropic') {
401
- responseText = await this._callAnthropic();
402
- } else if (this.provider === 'openai' || this.provider === 'local_openai') {
403
- responseText = await this._callOpenAI();
404
- } else {
405
- responseText = await this._callGemini();
556
+ const failures = [];
557
+ for (const provider of this.providerOrder) {
558
+ this.provider = SUPPORTED_CODE_PROVIDERS.includes(provider) ? provider : 'gemini';
559
+ try {
560
+ let responseText = '';
561
+ if (this.provider === 'anthropic') {
562
+ responseText = await this._callAnthropic();
563
+ } else if (this.provider === 'openai' || this.provider === 'local_openai') {
564
+ responseText = await this._callOpenAI();
565
+ } else {
566
+ responseText = await this._callGemini();
567
+ }
568
+
569
+ this.history.push({ role: 'assistant', content: responseText });
570
+ this.lastSuccessfulProvider = this.provider;
571
+ return responseText;
572
+ } catch (error) {
573
+ const message = error.message || error.code || 'unknown error';
574
+ failures.push(`${this.provider}: ${message}`);
575
+ if (process.env.MINT_DEBUG === '1') {
576
+ console.error(`[Code Agent Fallback] Provider '${this.provider}' failed: ${message}`);
577
+ }
578
+ }
406
579
  }
407
580
 
408
- this.history.push({ role: 'assistant', content: responseText });
409
- return responseText;
581
+ throw new Error(`All code agent providers failed. ${failures.join(' | ')}`);
410
582
  }
411
583
 
412
584
  async _callAnthropic() {
413
585
  const apiKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
414
586
  const messages = this.history.map(m => ({
415
587
  role: m.role,
416
- content: m.content
588
+ content: contentToAnthropicContent(m.content)
417
589
  }));
418
590
 
419
591
  const response = await axios.post('https://api.anthropic.com/v1/messages', {
@@ -439,7 +611,10 @@ class UnifiedAgentClient {
439
611
 
440
612
  const messages = [
441
613
  { role: 'system', content: this.systemInstruction },
442
- ...this.history
614
+ ...this.history.map(m => ({
615
+ role: m.role,
616
+ content: contentToOpenAIContent(m.content)
617
+ }))
443
618
  ];
444
619
 
445
620
  const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
@@ -460,13 +635,15 @@ class UnifiedAgentClient {
460
635
  const model = this.config.geminiModel || DEFAULT_GEMINI_MODEL;
461
636
  const ai = new GoogleGenAI({ apiKey });
462
637
 
638
+ const recentHistory = this.history.slice(-16);
639
+ const priorHistory = recentHistory.slice(0, -1);
640
+ const lastEntry = recentHistory[recentHistory.length - 1] || { content: '' };
641
+
463
642
  // Convert history for Gemini, ensuring parts are correctly structured
464
- const geminiHistory = this.history.slice(-16).map(m => ({
643
+ const geminiHistory = priorHistory.map(m => ({
465
644
  role: m.role === 'assistant' ? 'model' : 'user',
466
- parts: [{ text: String(m.content || '') }]
645
+ parts: contentToGeminiParts(m.content)
467
646
  }));
468
-
469
- const lastMessage = String(this.history[this.history.length - 1].content || '');
470
647
 
471
648
  const chat = ai.chats.create({
472
649
  model,
@@ -477,7 +654,7 @@ class UnifiedAgentClient {
477
654
  history: geminiHistory
478
655
  });
479
656
 
480
- const response = await chat.sendMessage({ message: [{ text: lastMessage }] });
657
+ const response = await chat.sendMessage({ message: contentToGeminiParts(lastEntry.content) });
481
658
  return typeof response.text === 'function' ? response.text() : response.text;
482
659
  }
483
660
  }
@@ -551,6 +728,7 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
551
728
  const session = readWorkspaceSession(workspaceRoot);
552
729
  const gitContext = await getGitContext(workspaceRoot);
553
730
  const testCommands = detectTestCommands(workspaceRoot);
731
+ const userContext = memoryStore.getUserContext(task);
554
732
 
555
733
  const contextStr = history.length > 0
556
734
  ? `Recent Context:\n${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}\n`
@@ -571,7 +749,9 @@ async function buildInitialObservation(task, workspaceRoot, history = []) {
571
749
  session.summary || '(none)',
572
750
  `Previous task: ${session.lastTask || '(none)'}`,
573
751
  `Previous verification: ${session.lastVerification || '(none)'}`,
574
- 'Start by inspecting the workspace before making edits unless the task is trivial.'
752
+ 'Long-term user context:',
753
+ userContext || '(none)',
754
+ 'If the task is conversational or trivial, finish directly without inspecting the workspace. For code/workspace tasks, inspect before making edits.'
575
755
  ].join('\n');
576
756
  }
577
757
 
@@ -579,6 +759,7 @@ async function executeCodeTask(task, options = {}) {
579
759
  const workspaceRoot = path.resolve(options.cwd || process.cwd());
580
760
  const history = options.history || [];
581
761
  const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
762
+ const onFinalSummary = typeof options.onFinalSummary === 'function' ? options.onFinalSummary : null;
582
763
  const requestApproval = typeof options.requestApproval === 'function'
583
764
  ? options.requestApproval
584
765
  : async () => true;
@@ -586,10 +767,29 @@ async function executeCodeTask(task, options = {}) {
586
767
  ? options.askUser
587
768
  : async (q) => `User didn't answer: ${q}`;
588
769
  const config = readConfig();
589
- const provider = options.provider || selectSupportedCodeProvider(config);
590
- const client = new UnifiedAgentClient(provider, config);
591
-
592
- let observation = await buildInitialObservation(task, workspaceRoot, history);
770
+ const availableProviders = getAvailableProviders(config);
771
+ const providerOrder = getSupportedCodeProviderOrder(config, availableProviders, options.provider);
772
+ const provider = providerOrder[0];
773
+ const client = new UnifiedAgentClient(provider, config, providerOrder);
774
+
775
+ const initialObservationText = await buildInitialObservation(task, workspaceRoot, history);
776
+ const relevantMemoryCount = memoryStore.searchInteractions(task, 5).length;
777
+ onProgress({
778
+ phase: 'memory',
779
+ action: 'memory_context',
780
+ message: `Loaded memory: profile + recent history, ${relevantMemoryCount} direct match${relevantMemoryCount === 1 ? '' : 'es'}`
781
+ });
782
+ let observation = options.imageDataUri
783
+ ? {
784
+ text: [
785
+ initialObservationText,
786
+ '',
787
+ `[Attached image: ${options.imagePath || 'command-line image'}]`,
788
+ 'Use the attached image as visual context when planning and answering.'
789
+ ].join('\n'),
790
+ imageDataUri: options.imageDataUri
791
+ }
792
+ : initialObservationText;
593
793
 
594
794
  let finalSummary = '';
595
795
  let finalVerification = '';
@@ -602,6 +802,17 @@ async function executeCodeTask(task, options = {}) {
602
802
  const decision = await getAgentDecision(client, observation, { onProgress, step });
603
803
  const action = decision.action;
604
804
  const input = decision.input || {};
805
+ try {
806
+ toolRegistry.validateToolInput(action, input);
807
+ } catch (e) {
808
+ observation = [
809
+ `Previous thought: ${decision.thought || '(none)'}`,
810
+ `Action: ${action || '(none)'}`,
811
+ 'Observation:',
812
+ `Error: ${e.message}`
813
+ ].join('\n');
814
+ continue;
815
+ }
605
816
 
606
817
  // Immediately show the agent's thought/reasoning
607
818
  onProgress({
@@ -615,6 +826,16 @@ async function executeCodeTask(task, options = {}) {
615
826
  finalSessionSummary = input.sessionSummary || input.summary || task;
616
827
  finalSummary = input.summary || 'Task complete.';
617
828
  finalVerification = input.verification || 'Not specified.';
829
+ if (onFinalSummary) {
830
+ await onFinalSummary({
831
+ summary: finalSummary,
832
+ verification: finalVerification,
833
+ providerInfo: {
834
+ provider: client.lastSuccessfulProvider || client.provider || provider,
835
+ model: getCodeProviderModel(client.lastSuccessfulProvider || client.provider || provider, config)
836
+ }
837
+ });
838
+ }
618
839
  writeWorkspaceSession(workspaceRoot, {
619
840
  summary: finalSessionSummary,
620
841
  lastTask: task,
@@ -627,7 +848,7 @@ async function executeCodeTask(task, options = {}) {
627
848
  try {
628
849
  switch (action) {
629
850
  case 'web_search':
630
- toolResult = await webSearch(input.query);
851
+ toolResult = await webSearch(input.query, onProgress);
631
852
  break;
632
853
  case 'list_files':
633
854
  toolResult = await listFiles(workspaceRoot, input.path || '.');
@@ -640,6 +861,13 @@ async function executeCodeTask(task, options = {}) {
640
861
  break;
641
862
  case 'find_path':
642
863
  toolResult = await findPaths(workspaceRoot, input.query, input.type);
864
+ if (input.openAfter === true) {
865
+ const result = JSON.parse(toolResult);
866
+ if (result.success && result.matches.length === 1) {
867
+ await executeAction({ type: 'open_folder', target: result.matches[0].path });
868
+ toolResult = `Found and opened: ${result.matches[0].path}`;
869
+ }
870
+ }
643
871
  break;
644
872
  case 'run_shell': {
645
873
  const approved = await requestApproval({
@@ -651,6 +879,12 @@ async function executeCodeTask(task, options = {}) {
651
879
  toolResult = `User denied shell command: ${input.command}`;
652
880
  break;
653
881
  }
882
+ safetyManager.appendActionLog({
883
+ source: 'code_agent',
884
+ action: 'run_shell',
885
+ command: input.command,
886
+ approved
887
+ });
654
888
  toolResult = await runShell(workspaceRoot, input.command);
655
889
  break;
656
890
  }
@@ -665,6 +899,12 @@ async function executeCodeTask(task, options = {}) {
665
899
  toolResult = `User denied patch for ${patchInput.path}`;
666
900
  break;
667
901
  }
902
+ safetyManager.appendActionLog({
903
+ source: 'code_agent',
904
+ action: 'apply_patch',
905
+ path: patchInput.path,
906
+ approved
907
+ });
668
908
  toolResult = applyPatch(workspaceRoot, patchInput);
669
909
  break;
670
910
  }
@@ -678,6 +918,12 @@ async function executeCodeTask(task, options = {}) {
678
918
  toolResult = `User denied full file write for ${input.path}`;
679
919
  break;
680
920
  }
921
+ safetyManager.appendActionLog({
922
+ source: 'code_agent',
923
+ action: 'write_file',
924
+ path: input.path,
925
+ approved
926
+ });
681
927
  toolResult = writeFile(workspaceRoot, input.path, input.content);
682
928
  break;
683
929
  }
@@ -693,10 +939,28 @@ async function executeCodeTask(task, options = {}) {
693
939
  case 'create_folder':
694
940
  case 'system_info':
695
941
  case 'system_automation': {
696
- // Delegate to existing automation logic
697
- toolResult = await executeAction({
698
- type: action,
699
- target: input.target
942
+ const executorAction = normalizeExecutorAction(action, input);
943
+ const safety = safetyManager.classifyAction(executorAction);
944
+ let allowDangerous = false;
945
+ let allowApproval = false;
946
+ if (safety.tier === safetyManager.TIERS.APPROVAL || safety.tier === safetyManager.TIERS.DANGEROUS) {
947
+ const approved = await requestApproval({
948
+ type: action,
949
+ label: formatActionPreview(action, input),
950
+ preview: `${action}: ${formatActionPreview(action, input)}\nSafety: ${safety.tier} (${safety.reason})`
951
+ });
952
+ if (!approved) {
953
+ toolResult = `User denied ${action}: ${formatActionPreview(action, input)}`;
954
+ break;
955
+ }
956
+ allowApproval = safety.tier === safetyManager.TIERS.APPROVAL;
957
+ allowDangerous = safety.tier === safetyManager.TIERS.DANGEROUS;
958
+ }
959
+
960
+ toolResult = await executeAction(executorAction, {
961
+ source: 'code_agent',
962
+ allowApproval,
963
+ allowDangerous
700
964
  });
701
965
  break;
702
966
  } default:
@@ -705,6 +969,22 @@ async function executeCodeTask(task, options = {}) {
705
969
  toolResult = `Error: ${e.message}`;
706
970
  }
707
971
 
972
+ const evaluation = evaluateActionResult(action, toolResult);
973
+ if (evaluation) {
974
+ onProgress({
975
+ step,
976
+ phase: 'evaluating',
977
+ action: 'evaluator',
978
+ message: `${evaluation.status}: ${evaluation.message}`
979
+ });
980
+ toolResult = [
981
+ toolResult,
982
+ '',
983
+ 'Evaluation:',
984
+ `${evaluation.status}: ${evaluation.message}`
985
+ ].join('\n');
986
+ }
987
+
708
988
  // Log the finished step with result
709
989
  let resultSummary = '';
710
990
  if (action === 'search_code') {
@@ -718,8 +998,7 @@ async function executeCodeTask(task, options = {}) {
718
998
  step,
719
999
  phase: 'finished',
720
1000
  action,
721
- target: (input.path || input.command || input.query || '') + resultSummary,
722
- thought: decision.thought
1001
+ target: (input.path || input.command || input.query || '') + resultSummary
723
1002
  });
724
1003
 
725
1004
  // Format tool result to be more readable and structured for the agent
@@ -767,10 +1046,16 @@ async function executeCodeTask(task, options = {}) {
767
1046
  }
768
1047
 
769
1048
  if (finalSummary) {
1049
+ memoryStore.recordInteraction(task, finalSummary);
1050
+ const answeredProvider = client.lastSuccessfulProvider || client.provider || provider;
770
1051
  return {
771
1052
  summary: finalSummary,
772
1053
  verification: finalVerification,
773
- steps: executedSteps
1054
+ steps: executedSteps,
1055
+ providerInfo: {
1056
+ provider: answeredProvider,
1057
+ model: getCodeProviderModel(answeredProvider, config)
1058
+ }
774
1059
  };
775
1060
  }
776
1061
 
@@ -780,10 +1065,15 @@ async function executeCodeTask(task, options = {}) {
780
1065
  lastVerification: 'Agent limit reached before explicit completion.'
781
1066
  });
782
1067
 
1068
+ const answeredProvider = client.lastSuccessfulProvider || client.provider || provider;
783
1069
  return {
784
1070
  summary: 'Stopped after reaching the maximum number of agent steps.',
785
1071
  verification: 'Agent limit reached before explicit completion.',
786
- steps: executedSteps || MAX_AGENT_STEPS
1072
+ steps: executedSteps || MAX_AGENT_STEPS,
1073
+ providerInfo: {
1074
+ provider: answeredProvider,
1075
+ model: getCodeProviderModel(answeredProvider, config)
1076
+ }
787
1077
  };
788
1078
  }
789
1079
 
@@ -792,6 +1082,7 @@ module.exports = {
792
1082
  _helpers: {
793
1083
  extractJson,
794
1084
  selectSupportedCodeProvider,
1085
+ getSupportedCodeProviderOrder,
795
1086
  findPaths,
796
1087
  listFiles,
797
1088
  searchCode,