@pheem49/mint 1.3.0 → 1.4.0

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.
@@ -1,8 +1,11 @@
1
1
  const { GoogleGenAI } = require('@google/genai');
2
2
  const { readChatHistory, writeChatHistory, clearChatHistory } = require('../System/chat_history_manager');
3
- const { readConfig } = require('../System/config_manager');
3
+ const { readConfig, getAvailableProviders } = require('../System/config_manager');
4
4
  const pluginManager = require('../Plugins/plugin_manager');
5
5
  const mcpManager = require('../Plugins/mcp_manager');
6
+ const memoryStore = require('./memory_store');
7
+ const agentOrchestrator = require('./agent_orchestrator');
8
+ const workspaceManager = require('../CLI/workspace_manager');
6
9
 
7
10
  let ai = null;
8
11
  let activeApiKey = '';
@@ -92,6 +95,41 @@ Input: "อากาศวันนี้เป็นยังไง" or "What's
92
95
  Output: { "response": "มิ้นท์ไปดูอากาศให้เลยนะคะ", "action": { "type": "system_info", "target": "Bangkok" } }
93
96
  `;
94
97
 
98
+ // ─────────────────────────────────────────────────────────────────────────────
99
+ // buildSystemPrompt() — single source of truth for all provider system prompts
100
+ // Replaces 5 previously duplicated mcpPrompt blocks.
101
+ // ─────────────────────────────────────────────────────────────────────────────
102
+ function buildSystemPrompt() {
103
+ pluginManager.loadPlugins();
104
+ const mcpTools = mcpManager.getAllTools();
105
+
106
+ let mcpSection = '\n\nAVAILABLE MCP TOOLS (Model Context Protocol):\n';
107
+ if (mcpTools.length > 0) {
108
+ mcpTools.forEach(tool => {
109
+ mcpSection += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
110
+ });
111
+ mcpSection += "\nTo use these tools, use action type 'mcp_tool', specify the 'server' name, set 'target' to the tool name, and provide 'args'.\n";
112
+ } else {
113
+ mcpSection += 'No MCP tools currently connected.\n';
114
+ }
115
+
116
+ // Inject long-term user context (non-blocking read from SQLite)
117
+ const userContext = memoryStore.getUserContext();
118
+
119
+ // Get current specialized persona instruction
120
+ const agent = agentOrchestrator.getCurrentAgent();
121
+ const personaInstruction = `\n\n[CURRENT PERSONA: ${agent.name}]\n${agent.instruction}\n`;
122
+
123
+ // Inject Workspace Context if available
124
+ let workspaceSection = "";
125
+ const ws = workspaceManager.getWorkspaceByPath(process.cwd());
126
+ if (ws) {
127
+ workspaceSection = `\n\n[WORKSPACE DETECTED: ${ws.name}]\nPath: ${ws.path}\nProject Instructions: ${ws.instructions}\n`;
128
+ }
129
+
130
+ return systemInstruction + personaInstruction + workspaceSection + pluginManager.getPromptDescriptions() + mcpSection + userContext;
131
+ }
132
+
95
133
  function resolveApiKey() {
96
134
  let settingsKey = '';
97
135
  try {
@@ -134,22 +172,6 @@ let lastLoggedModel = '';
134
172
  const MAX_HISTORY_MESSAGES = 20; // Keep only the last 20 messages (approx 10 turns)
135
173
 
136
174
  function createChat(history = []) {
137
- // Load plugins and get dynamic description for the prompt
138
- pluginManager.loadPlugins();
139
- // Inject MCP Tools
140
- const mcpTools = mcpManager.getAllTools();
141
- let mcpPrompt = "\n\nAVAILABLE MCP TOOLS (Model Context Protocol):\n";
142
- if (mcpTools.length > 0) {
143
- mcpTools.forEach(tool => {
144
- mcpPrompt += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
145
- });
146
- mcpPrompt += "\nTo use these tools, use action type 'mcp_tool', specify the 'server' name, set 'target' to the tool name, and provide 'args'.\n";
147
- } else {
148
- mcpPrompt += "No MCP tools currently connected.\n";
149
- }
150
-
151
- const dynamicPrompt = systemInstruction + pluginManager.getPromptDescriptions() + mcpPrompt;
152
-
153
175
  // Truncate history and strip custom fields like 'timestamp' before passing to SDK
154
176
  const cleanedHistory = (history || []).map(msg => ({
155
177
  role: msg.role,
@@ -159,13 +181,12 @@ function createChat(history = []) {
159
181
 
160
182
  activeModel = resolveGeminiModel();
161
183
  if (activeModel && activeModel !== lastLoggedModel) {
162
- // console.log(`[Gemini] Using model: ${activeModel}`);
163
184
  lastLoggedModel = activeModel;
164
185
  }
165
186
  chat = ai.chats.create({
166
187
  model: activeModel,
167
188
  config: {
168
- systemInstruction: dynamicPrompt,
189
+ systemInstruction: buildSystemPrompt(),
169
190
  responseMimeType: "application/json"
170
191
  },
171
192
  history: truncatedHistory
@@ -224,18 +245,57 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
224
245
  }
225
246
  }
226
247
 
227
- if (provider === 'ollama') {
228
- return await handleOllamaChat(finalMessage, base64Image, base64Audio, config);
229
- }
248
+ const { getAvailableProviders } = require('../System/config_manager');
249
+ const availableProviders = getAvailableProviders(config);
230
250
 
231
- if (provider === 'anthropic') {
232
- return await handleAnthropicChat(finalMessage, base64Image, config);
233
- }
234
-
235
- if (provider === 'openai') {
236
- return await handleOpenAIChat(finalMessage, base64Image, config);
251
+ // Ensure the requested provider is prioritized. If not available, fallback to the first available.
252
+ let providersToTry = [provider];
253
+ const alternates = availableProviders.filter(p => p !== provider);
254
+ providersToTry = providersToTry.concat(alternates);
255
+
256
+ for (let i = 0; i < providersToTry.length; i++) {
257
+ const currentProv = providersToTry[i];
258
+ try {
259
+ if (currentProv === 'ollama') {
260
+ return await handleOllamaChat(finalMessage, base64Image, base64Audio, config);
261
+ }
262
+ if (currentProv === 'anthropic') {
263
+ return await handleAnthropicChat(finalMessage, base64Image, config);
264
+ }
265
+ if (currentProv === 'openai') {
266
+ return await handleOpenAIChat(finalMessage, base64Image, config);
267
+ }
268
+ if (currentProv === 'local_openai') {
269
+ return await handleLocalOpenAIChat(finalMessage, base64Image, config);
270
+ }
271
+ if (currentProv === 'huggingface') {
272
+ return await handleHuggingFaceChat(finalMessage, base64Image, config);
273
+ }
274
+
275
+ return await handleGeminiChat(finalMessage, base64Image, base64Audio);
276
+ } catch (error) {
277
+ console.error(`[Fallback System] Provider '${currentProv}' failed:`, error.message);
278
+ if (i === providersToTry.length - 1) {
279
+ console.error("[Fallback System] All available providers failed.");
280
+ throw error; // No more providers to fallback to
281
+ }
282
+ console.log(`[Fallback System] Switching to next available provider: '${providersToTry[i+1]}'`);
283
+ // Continue the loop to try the next provider
284
+ }
237
285
  }
286
+ } catch (globalError) {
287
+ console.error("handleChat error:", globalError);
288
+ throw globalError;
289
+ }
290
+ }
238
291
 
292
+ async function handleGeminiChat(finalMessage, base64Image, base64Audio) {
293
+ try {
294
+ // 1. Check cache first for text-only messages
295
+ if (finalMessage && !base64Image && !base64Audio) {
296
+ const cached = memoryStore.getCachedResponse(finalMessage);
297
+ if (cached) return cached;
298
+ }
239
299
 
240
300
  const desiredModel = resolveGeminiModel();
241
301
  if (!chat || activeModel !== desiredModel) {
@@ -318,7 +378,7 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
318
378
  }
319
379
  }
320
380
 
321
- // Finally, decode any remaining unicode escapes in the response text
381
+ // Decode any remaining unicode escapes in the response text
322
382
  if (parsedResult && typeof parsedResult.response === 'string') {
323
383
  parsedResult.response = decodeUnicode(parsedResult.response);
324
384
  }
@@ -326,6 +386,17 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
326
386
  // Attach timestamp to the result
327
387
  parsedResult.timestamp = now;
328
388
 
389
+ // Record interaction for long-term memory (non-blocking)
390
+ if (finalMessage && parsedResult.response) {
391
+ setImmediate(() => {
392
+ memoryStore.recordInteraction(finalMessage, parsedResult.response);
393
+ // Cache text-only responses
394
+ if (!base64Image && !base64Audio) {
395
+ memoryStore.cacheResponse(finalMessage, parsedResult);
396
+ }
397
+ });
398
+ }
399
+
329
400
  return parsedResult;
330
401
 
331
402
  } catch (error) {
@@ -334,18 +405,115 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
334
405
  }
335
406
  }
336
407
 
408
+ // ─────────────────────────────────────────────────────────────────────────────
409
+ // handleGeminiChatStream() — Streaming async generator (CLI only)
410
+ // Yields: { chunk: string } during streaming
411
+ // { done: true, parsed: object, timestamp: string } when complete
412
+ // ─────────────────────────────────────────────────────────────────────────────
413
+ async function* handleGeminiChatStream(finalMessage, base64Image, base64Audio) {
414
+ try {
415
+ // 1. Check cache first
416
+ if (finalMessage && !base64Image && !base64Audio) {
417
+ const cached = memoryStore.getCachedResponse(finalMessage);
418
+ if (cached) {
419
+ yield { chunk: `{"response":"${cached.response.replace(/"/g, '\\"')}", "action": {"type":"none"}}` };
420
+ yield { done: true, parsed: cached, timestamp: cached.timestamp || new Date().toISOString() };
421
+ return;
422
+ }
423
+ }
424
+
425
+ const desiredModel = resolveGeminiModel();
426
+ if (!chat || activeModel !== desiredModel) {
427
+ createChat(readChatHistory());
428
+ }
429
+
430
+ const parts = [];
431
+ if (finalMessage) {
432
+ parts.push({ text: finalMessage });
433
+ } else if (base64Audio && !base64Image) {
434
+ parts.push({ text: "Please listen to this voice command and respond in Thai with the appropriate JSON action if needed." });
435
+ } else if (!base64Image && !base64Audio) {
436
+ parts.push({ text: "Analyze this input." });
437
+ }
438
+ if (base64Image) {
439
+ const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, '');
440
+ parts.push({ inlineData: { mimeType: "image/png", data: base64Data } });
441
+ }
442
+ if (base64Audio) {
443
+ let mimeType = "audio/webm";
444
+ const mimeMatch = base64Audio.match(/^data:(audio\/\w+);base64,/);
445
+ if (mimeMatch) mimeType = mimeMatch[1];
446
+ const base64Data = base64Audio.replace(/^data:audio\/\w+;base64,/, '');
447
+ parts.push({ inlineData: { mimeType, data: base64Data } });
448
+ }
449
+
450
+ const stream = await chat.sendMessageStream({ message: parts });
451
+ let fullText = '';
452
+
453
+ for await (const chunk of stream) {
454
+ let chunkText = '';
455
+ try {
456
+ chunkText = (typeof chunk.text === 'function') ? chunk.text() : (chunk.text || '');
457
+ } catch (_) {}
458
+ if (chunkText) {
459
+ fullText += chunkText;
460
+ yield { chunk: chunkText };
461
+ }
462
+ }
463
+
464
+ // Save history
465
+ const history = await chat.getHistory();
466
+ const now = new Date().toISOString();
467
+ if (history.length >= 2) {
468
+ const modelMsg = history[history.length - 1];
469
+ const userMsg = history[history.length - 2];
470
+ if (!modelMsg.timestamp) modelMsg.timestamp = now;
471
+ if (!userMsg.timestamp) userMsg.timestamp = now;
472
+ }
473
+ writeChatHistory(history);
474
+
475
+ // Parse complete JSON response
476
+ let parsedResult;
477
+ try {
478
+ parsedResult = JSON.parse(fullText);
479
+ } catch (_) {
480
+ const jsonMatch = fullText.match(/```json\n([\s\S]*?)\n```/) || fullText.match(/\{[\s\S]*\}/);
481
+ if (jsonMatch) {
482
+ parsedResult = JSON.parse(jsonMatch[jsonMatch.length > 1 ? 1 : 0]);
483
+ } else {
484
+ parsedResult = { response: fullText, action: { type: 'none', target: '' } };
485
+ }
486
+ }
487
+ if (parsedResult && typeof parsedResult.response === 'string') {
488
+ parsedResult.response = decodeUnicode(parsedResult.response);
489
+ }
490
+ parsedResult.timestamp = now;
491
+
492
+ // Record for long-term memory
493
+ if (finalMessage && parsedResult.response) {
494
+ setImmediate(() => {
495
+ memoryStore.recordInteraction(finalMessage, parsedResult.response);
496
+ // Cache text-only responses
497
+ if (!base64Image && !base64Audio) {
498
+ memoryStore.cacheResponse(finalMessage, parsedResult);
499
+ }
500
+ });
501
+ }
502
+
503
+ yield { done: true, parsed: parsedResult, timestamp: now };
504
+
505
+ } catch (error) {
506
+ console.error('[Stream] Gemini stream error:', error);
507
+ throw error;
508
+ }
509
+ }
510
+
337
511
  async function handleAnthropicChat(finalMessage, base64Image, config) {
338
512
  const history = readChatHistory() || [];
339
513
  const apiKey = config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
340
514
  if (!apiKey) return { response: "กรุณาใส่ Anthropic API Key ในการตั้งค่าก่อนนะคะ", action: { type: "none" } };
341
515
 
342
- const mcpTools = mcpManager.getAllTools();
343
- let mcpPrompt = "\n\nAVAILABLE MCP TOOLS:\n";
344
- mcpTools.forEach(tool => {
345
- mcpPrompt += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
346
- });
347
-
348
- const systemPrompt = systemInstruction + pluginManager.getPromptDescriptions() + mcpPrompt;
516
+ const systemPrompt = buildSystemPrompt();
349
517
 
350
518
  const messages = [];
351
519
  for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
@@ -392,13 +560,7 @@ async function handleOpenAIChat(finalMessage, base64Image, config) {
392
560
  const apiKey = config.openaiApiKey || process.env.OPENAI_API_KEY;
393
561
  if (!apiKey) return { response: "กรุณาใส่ OpenAI API Key ในการตั้งค่าก่อนนะคะ", action: { type: "none" } };
394
562
 
395
- const mcpTools = mcpManager.getAllTools();
396
- let mcpPrompt = "\n\nAVAILABLE MCP TOOLS:\n";
397
- mcpTools.forEach(tool => {
398
- mcpPrompt += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
399
- });
400
-
401
- const systemPrompt = systemInstruction + pluginManager.getPromptDescriptions() + mcpPrompt;
563
+ const systemPrompt = buildSystemPrompt();
402
564
 
403
565
  const messages = [{ role: "system", content: systemPrompt }];
404
566
  for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
@@ -435,6 +597,96 @@ async function handleOpenAIChat(finalMessage, base64Image, config) {
435
597
  return parseAiResponse(outputText);
436
598
  }
437
599
 
600
+ async function handleLocalOpenAIChat(finalMessage, base64Image, config) {
601
+ const history = readChatHistory() || [];
602
+ const apiKey = 'lm-studio';
603
+ const baseUrl = config.localApiBaseUrl || 'http://localhost:1234/v1';
604
+
605
+ const systemPrompt = buildSystemPrompt();
606
+
607
+ const messages = [{ role: "system", content: systemPrompt }];
608
+ for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
609
+ const role = msg.role === 'model' ? 'assistant' : 'user';
610
+ let text = Array.isArray(msg.parts) ? msg.parts.map(p => p.text || '').join('\n') : '';
611
+ if (text) messages.push({ role, content: text });
612
+ }
613
+
614
+ const content = [{ type: "text", text: finalMessage || "Analyze this." }];
615
+ if (base64Image) {
616
+ content.push({
617
+ type: "image_url",
618
+ image_url: { url: base64Image }
619
+ });
620
+ }
621
+ messages.push({ role: "user", content });
622
+
623
+ const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
624
+ model: config.localModelName || 'local-model',
625
+ messages: messages,
626
+ // response_format json_object is sometimes problematic on weak local models, but required by our prompt.
627
+ // We'll keep it as some local servers like LM Studio support it for specific models.
628
+ // If not supported, the system prompt usually coerces it anyway.
629
+ response_format: { type: "json_object" }
630
+ }, {
631
+ headers: {
632
+ 'Authorization': `Bearer ${apiKey}`,
633
+ 'Content-Type': 'application/json'
634
+ }
635
+ });
636
+
637
+ const outputText = response.data.choices[0].message.content;
638
+ history.push({ role: 'user', parts: [{ text: finalMessage }] });
639
+ history.push({ role: 'model', parts: [{ text: outputText }] });
640
+ writeChatHistory(history.slice(-MAX_HISTORY_MESSAGES));
641
+
642
+ return parseAiResponse(outputText);
643
+ }
644
+
645
+ async function handleHuggingFaceChat(finalMessage, base64Image, config) {
646
+ const history = readChatHistory() || [];
647
+ const apiKey = config.hfApiKey || process.env.HF_API_KEY;
648
+ if (!apiKey) return { response: "กรุณาใส่ Hugging Face API Key ในการตั้งค่าก่อนนะคะ", action: { type: "none" } };
649
+
650
+ const modelId = config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct';
651
+ const baseUrl = `https://api-inference.huggingface.co/models/${modelId}/v1/chat/completions`;
652
+
653
+ const systemPrompt = buildSystemPrompt();
654
+
655
+ const messages = [{ role: "system", content: systemPrompt }];
656
+ for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
657
+ const role = msg.role === 'model' ? 'assistant' : 'user';
658
+ let text = Array.isArray(msg.parts) ? msg.parts.map(p => p.text || '').join('\n') : '';
659
+ if (text) messages.push({ role, content: text });
660
+ }
661
+
662
+ const content = [{ type: "text", text: finalMessage || "Analyze this." }];
663
+ if (base64Image) {
664
+ content.push({
665
+ type: "image_url",
666
+ image_url: { url: base64Image }
667
+ });
668
+ }
669
+ messages.push({ role: "user", content });
670
+
671
+ const response = await axios.post(baseUrl, {
672
+ model: modelId,
673
+ messages: messages,
674
+ max_tokens: 4096
675
+ }, {
676
+ headers: {
677
+ 'Authorization': `Bearer ${apiKey}`,
678
+ 'Content-Type': 'application/json'
679
+ }
680
+ });
681
+
682
+ const outputText = response.data.choices[0].message.content;
683
+ history.push({ role: 'user', parts: [{ text: finalMessage }] });
684
+ history.push({ role: 'model', parts: [{ text: outputText }] });
685
+ writeChatHistory(history.slice(-MAX_HISTORY_MESSAGES));
686
+
687
+ return parseAiResponse(outputText);
688
+ }
689
+
438
690
  function parseAiResponse(outputText) {
439
691
  let parsedResult;
440
692
  try {
@@ -456,10 +708,9 @@ function parseAiResponse(outputText) {
456
708
 
457
709
  async function handleOllamaChat(finalMessage, base64Image, base64Audio, config) {
458
710
  const history = readChatHistory() || [];
459
- pluginManager.loadPlugins();
460
711
 
461
712
  const ollamaMessages = [
462
- { role: 'system', content: systemInstruction + pluginManager.getPromptDescriptions() }
713
+ { role: 'system', content: buildSystemPrompt() }
463
714
  ];
464
715
 
465
716
  for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
@@ -486,7 +737,8 @@ async function handleOllamaChat(finalMessage, base64Image, base64Audio, config)
486
737
 
487
738
  ollamaMessages.push(userMessage);
488
739
 
489
- const response = await axios.post('http://localhost:11434/api/chat', {
740
+ const ollamaBaseUrl = (config.ollamaHost || 'http://localhost:11434').replace(/\/$/, '');
741
+ const response = await axios.post(`${ollamaBaseUrl}/api/chat`, {
490
742
  model: config.ollamaModel || 'llama3:latest',
491
743
  messages: ollamaMessages,
492
744
  format: 'json',
@@ -631,6 +883,7 @@ async function translateImageContent(base64Image) {
631
883
 
632
884
  module.exports = {
633
885
  handleChat,
886
+ handleGeminiChatStream,
634
887
  resetChat,
635
888
  getChatTranscript,
636
889
  translateImageContent,
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Mint Agent Orchestrator
3
+ * -----------------------
4
+ * Manages specialized AI personas (Experts) and their system prompts.
5
+ * Allows switching the agent's behavior on the fly.
6
+ */
7
+
8
+ const AGENT_PERSONAS = {
9
+ 'general': {
10
+ name: 'Mint Default',
11
+ icon: '💎',
12
+ instruction: 'You are Mint, a versatile and helpful AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately.'
13
+ },
14
+ 'coder': {
15
+ name: 'Mint Coder',
16
+ icon: '💻',
17
+ instruction: 'You are Mint Coder, an expert software engineer. Your responses should be technically precise, focus on best practices, and provide optimized code snippets. Explain complex logic clearly.'
18
+ },
19
+ 'researcher': {
20
+ name: 'Mint Researcher',
21
+ icon: '🔍',
22
+ instruction: 'You are Mint Researcher, an academic and analytical assistant. Focus on citations, data-driven facts, and objective analysis. Avoid speculation and be highly detailed.'
23
+ },
24
+ 'creative': {
25
+ name: 'Mint Creative',
26
+ icon: '🎨',
27
+ instruction: 'You are Mint Creative, a storytelling and brainstorming partner. Use vivid language, poetic descriptions, and think outside the box. Be highly expressive and encouraging.'
28
+ },
29
+ 'manager': {
30
+ name: 'Mint Manager',
31
+ icon: '💼',
32
+ instruction: 'You are Mint Manager, a productivity and project management expert. Focus on task lists, deadlines, efficiency, and clear action plans. Be concise and goal-oriented.'
33
+ },
34
+ 'reviewer': {
35
+ name: 'Mint Reviewer',
36
+ icon: '⚖️',
37
+ instruction: 'You are Mint Reviewer, a senior code critic. Your job is to find flaws, security vulnerabilities, performance bottlenecks, and logic errors in any provided content. Be brutal but constructive. Use a formal, objective tone.'
38
+ }
39
+ };
40
+
41
+ let currentAgentType = 'general';
42
+
43
+ function getAgent(type) {
44
+ return AGENT_PERSONAS[type] || AGENT_PERSONAS['general'];
45
+ }
46
+
47
+ function setAgent(type) {
48
+ if (AGENT_PERSONAS[type]) {
49
+ currentAgentType = type;
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function getCurrentAgent() {
56
+ return getAgent(currentAgentType);
57
+ }
58
+
59
+ function listAgents() {
60
+ return Object.keys(AGENT_PERSONAS);
61
+ }
62
+
63
+ function resetAgent() {
64
+ currentAgentType = 'general';
65
+ }
66
+
67
+ module.exports = {
68
+ getAgent,
69
+ setAgent,
70
+ getCurrentAgent,
71
+ listAgents,
72
+ resetAgent
73
+ };
@@ -7,6 +7,7 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
 
9
9
  const os = require('os');
10
+ const { sendNotification } = require('../System/notifications');
10
11
 
11
12
  const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
12
13
 
@@ -119,6 +120,7 @@ async function executeAutonomousTask(taskDescription, notifyCallback) {
119
120
  break;
120
121
  case 'propose_bash':
121
122
  if (notifyCallback) notifyCallback(`💡 มิ้นท์เสนอให้รันคำสั่ง: ${actionObj.target}`);
123
+ sendNotification('Mint Bash Proposal', `Mint wants to run: ${actionObj.target}`);
122
124
  observation = `USER NOTIFIED of bash command: ${actionObj.target}. Note: You must wait for user to run it manually. If you can continue without it, do so. Otherwise, indicate you are waiting or done with this phase.`;
123
125
  break;
124
126
  default: