@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
|
@@ -93,17 +93,49 @@ function getDb() {
|
|
|
93
93
|
last_used DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
-- Raw episodic memories of user/assistant turns.
|
|
97
|
+
CREATE TABLE IF NOT EXISTS interaction_memories (
|
|
98
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
|
+
user_text TEXT NOT NULL,
|
|
100
|
+
ai_text TEXT NOT NULL,
|
|
101
|
+
keywords TEXT DEFAULT '',
|
|
102
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
103
|
+
);
|
|
104
|
+
|
|
96
105
|
-- Response Cache: For repetitive exact queries
|
|
97
106
|
CREATE TABLE IF NOT EXISTS response_cache (
|
|
98
107
|
query_hash TEXT PRIMARY KEY,
|
|
99
108
|
response TEXT NOT NULL,
|
|
100
109
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
101
110
|
);
|
|
111
|
+
|
|
112
|
+
-- Learned skill/instruction documents imported from local files.
|
|
113
|
+
CREATE TABLE IF NOT EXISTS learned_skills (
|
|
114
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
115
|
+
name TEXT NOT NULL,
|
|
116
|
+
source_path TEXT NOT NULL UNIQUE,
|
|
117
|
+
content TEXT NOT NULL,
|
|
118
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
119
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
120
|
+
);
|
|
102
121
|
`);
|
|
103
122
|
|
|
104
123
|
return dbInstance;
|
|
105
124
|
}
|
|
106
125
|
|
|
126
|
+
function ensureLearnedSkillsTable() {
|
|
127
|
+
getDb().exec(`
|
|
128
|
+
CREATE TABLE IF NOT EXISTS learned_skills (
|
|
129
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
130
|
+
name TEXT NOT NULL,
|
|
131
|
+
source_path TEXT NOT NULL UNIQUE,
|
|
132
|
+
content TEXT NOT NULL,
|
|
133
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
134
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
135
|
+
);
|
|
136
|
+
`);
|
|
137
|
+
}
|
|
138
|
+
|
|
107
139
|
// ── Profile helpers ────────────────────────────────────────────────────────
|
|
108
140
|
function setProfile(key, value) {
|
|
109
141
|
try {
|
|
@@ -203,6 +235,116 @@ function getTopPatterns(limit = 8) {
|
|
|
203
235
|
}
|
|
204
236
|
}
|
|
205
237
|
|
|
238
|
+
const MAX_INTERACTION_MEMORIES = 1000;
|
|
239
|
+
|
|
240
|
+
function stripRelevantMemoryBlock(text) {
|
|
241
|
+
return String(text || '')
|
|
242
|
+
.replace(/\n?\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\n?/g, '\n')
|
|
243
|
+
.replace(/^\s*\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\s*/g, '')
|
|
244
|
+
.replace(/\n?\[LOCAL KNOWLEDGE BASE - USE THIS CONTEXT TO ANSWER\][\s\S]*/g, '')
|
|
245
|
+
.trim();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function addInteractionMemory(userMessage, aiResponseText, keywords = []) {
|
|
249
|
+
try {
|
|
250
|
+
const db = getDb();
|
|
251
|
+
db.prepare(`
|
|
252
|
+
INSERT INTO interaction_memories (user_text, ai_text, keywords)
|
|
253
|
+
VALUES (?, ?, ?)
|
|
254
|
+
`).run(
|
|
255
|
+
String(userMessage || '').slice(0, 1200),
|
|
256
|
+
String(aiResponseText || '').slice(0, 1200),
|
|
257
|
+
keywords.join(',')
|
|
258
|
+
);
|
|
259
|
+
db.exec(`
|
|
260
|
+
DELETE FROM interaction_memories WHERE id NOT IN (
|
|
261
|
+
SELECT id FROM interaction_memories ORDER BY id DESC LIMIT ${MAX_INTERACTION_MEMORIES}
|
|
262
|
+
)
|
|
263
|
+
`);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error('[Memory] addInteractionMemory error:', err.message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getRecentInteractions(limit = 5) {
|
|
270
|
+
try {
|
|
271
|
+
return getDb()
|
|
272
|
+
.prepare('SELECT id, user_text, ai_text, keywords, created_at FROM interaction_memories ORDER BY id DESC LIMIT ?')
|
|
273
|
+
.all(limit);
|
|
274
|
+
} catch (_) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function deleteInteractionMemory(id) {
|
|
280
|
+
try {
|
|
281
|
+
const result = getDb().prepare('DELETE FROM interaction_memories WHERE id = ?').run(id);
|
|
282
|
+
return result.changes > 0;
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.error('[Memory] deleteInteractionMemory error:', err.message);
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function searchInteractions(query, limit = 8) {
|
|
290
|
+
try {
|
|
291
|
+
const keywords = extractKeywords(query);
|
|
292
|
+
const terms = keywords.length > 0 ? keywords : [String(query || '').trim()].filter(Boolean);
|
|
293
|
+
if (terms.length === 0) return [];
|
|
294
|
+
|
|
295
|
+
const rows = [];
|
|
296
|
+
const seen = new Set();
|
|
297
|
+
const stmt = getDb().prepare(`
|
|
298
|
+
SELECT id, user_text, ai_text, keywords, created_at
|
|
299
|
+
FROM interaction_memories
|
|
300
|
+
WHERE user_text LIKE ? OR ai_text LIKE ? OR keywords LIKE ?
|
|
301
|
+
ORDER BY id DESC
|
|
302
|
+
LIMIT ?
|
|
303
|
+
`);
|
|
304
|
+
|
|
305
|
+
for (const term of terms.slice(0, 5)) {
|
|
306
|
+
const like = `%${term}%`;
|
|
307
|
+
for (const row of stmt.all(like, like, like, limit)) {
|
|
308
|
+
if (!seen.has(row.id)) {
|
|
309
|
+
seen.add(row.id);
|
|
310
|
+
rows.push(row);
|
|
311
|
+
if (rows.length >= limit) return rows;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return rows;
|
|
316
|
+
} catch (_) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function clearInteractionMemories() {
|
|
322
|
+
try {
|
|
323
|
+
getDb().prepare('DELETE FROM interaction_memories').run();
|
|
324
|
+
} catch (err) {
|
|
325
|
+
console.error('[Memory] clearInteractionMemories error:', err.message);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function exportMemorySnapshot() {
|
|
330
|
+
try {
|
|
331
|
+
return {
|
|
332
|
+
profile: getAllProfile(),
|
|
333
|
+
session_memories: getRecentMemories(MAX_SESSION_MEMORIES),
|
|
334
|
+
usage_patterns: getTopPatterns(50),
|
|
335
|
+
interaction_memories: getRecentInteractions(MAX_INTERACTION_MEMORIES)
|
|
336
|
+
};
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error('[Memory] exportMemorySnapshot error:', err.message);
|
|
339
|
+
return {
|
|
340
|
+
profile: {},
|
|
341
|
+
session_memories: [],
|
|
342
|
+
usage_patterns: [],
|
|
343
|
+
interaction_memories: []
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
206
348
|
// ── Simple keyword extractor (no external deps) ────────────────────────────
|
|
207
349
|
const STOP_WORDS = new Set([
|
|
208
350
|
'ที่', 'ให้', 'และ', 'ของ', 'กับ', 'ใน', 'บน', 'เป็น', 'อยู่', 'มี', 'ได้', 'the', 'a', 'an',
|
|
@@ -220,6 +362,37 @@ function extractKeywords(text) {
|
|
|
220
362
|
.slice(0, 6);
|
|
221
363
|
}
|
|
222
364
|
|
|
365
|
+
function cleanProfileValue(value) {
|
|
366
|
+
return String(value || '')
|
|
367
|
+
.replace(/[.,!?;:()[\]{}"'`“”‘’]+$/g, '')
|
|
368
|
+
.replace(/(นะ|น่ะ|ครับ|ค่ะ|คะ|จ้า|จ๊ะ|ฮะ|ค้าบ|ค่า)+$/u, '')
|
|
369
|
+
.trim();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function extractUserName(text) {
|
|
373
|
+
const input = String(text || '').trim();
|
|
374
|
+
const patterns = [
|
|
375
|
+
/(?:ผม|ฉัน|ชั้น|หนู|เรา|ข้า|ดิฉัน)?\s*ชื่อ(?:เล่น)?\s*(?:คือ|ว่า|เป็น)?\s*([A-Za-z\u0E00-\u0E7F][A-Za-z\u0E00-\u0E7F\s]{0,40})/iu,
|
|
376
|
+
/(?:เรียก(?:ผม|ฉัน|ชั้น|หนู|เรา)?ว่า)\s*([A-Za-z\u0E00-\u0E7F][A-Za-z\u0E00-\u0E7F\s]{0,40})/iu,
|
|
377
|
+
/\bmy name is\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu,
|
|
378
|
+
/\bcall me\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu,
|
|
379
|
+
/\bi am\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu,
|
|
380
|
+
/\bi'm\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
for (const pattern of patterns) {
|
|
384
|
+
const match = input.match(pattern);
|
|
385
|
+
if (match && match[1]) {
|
|
386
|
+
const name = cleanProfileValue(match[1])
|
|
387
|
+
.split(/\s+(?:and|แล้ว|นะ|ครับ|ค่ะ|คะ)\s+/i)[0]
|
|
388
|
+
.trim();
|
|
389
|
+
if (name && name.length <= 40) return name;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return '';
|
|
394
|
+
}
|
|
395
|
+
|
|
223
396
|
// ── Main public API ────────────────────────────────────────────────────────
|
|
224
397
|
|
|
225
398
|
/**
|
|
@@ -233,12 +406,18 @@ function recordInteraction(userMessage, aiResponseText) {
|
|
|
233
406
|
// Extract keywords as usage patterns
|
|
234
407
|
const keywords = extractKeywords(userMessage);
|
|
235
408
|
keywords.forEach(kw => recordPattern(kw));
|
|
409
|
+
addInteractionMemory(userMessage, aiResponseText, keywords);
|
|
236
410
|
|
|
237
411
|
// Detect preferred language
|
|
238
412
|
const thaiRatio = (userMessage.match(/[\u0E00-\u0E7F]/g) || []).length / userMessage.length;
|
|
239
413
|
if (thaiRatio > 0.3) setProfile('preferred_language', 'thai');
|
|
240
414
|
else setProfile('preferred_language', 'english');
|
|
241
415
|
|
|
416
|
+
const userName = extractUserName(userMessage);
|
|
417
|
+
if (userName) {
|
|
418
|
+
setProfile('user_name', userName);
|
|
419
|
+
}
|
|
420
|
+
|
|
242
421
|
// Detect coding intent (update project activity)
|
|
243
422
|
const codingKeywords = ['code', 'fix', 'debug', 'function', 'class', 'import', 'script',
|
|
244
423
|
'แก้', 'เขียน', 'โค้ด', 'สคริปต์', 'ฟังก์ชัน'];
|
|
@@ -268,20 +447,86 @@ function saveSessionSummary(summary, tags = []) {
|
|
|
268
447
|
addSessionMemory(summary.trim(), tags);
|
|
269
448
|
}
|
|
270
449
|
|
|
450
|
+
function addLearnedSkill(name, sourcePath, content) {
|
|
451
|
+
const cleanName = String(name || '').trim() || path.basename(sourcePath || 'skill.md');
|
|
452
|
+
const cleanPath = path.resolve(String(sourcePath || ''));
|
|
453
|
+
const cleanContent = String(content || '').trim();
|
|
454
|
+
if (!cleanContent) {
|
|
455
|
+
throw new Error('Skill file is empty.');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const storedContent = cleanContent.slice(0, 12000);
|
|
459
|
+
ensureLearnedSkillsTable();
|
|
460
|
+
const db = getDb();
|
|
461
|
+
db.prepare(`
|
|
462
|
+
INSERT INTO learned_skills (name, source_path, content, created_at, updated_at)
|
|
463
|
+
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
464
|
+
ON CONFLICT(source_path) DO UPDATE SET
|
|
465
|
+
name = excluded.name,
|
|
466
|
+
content = excluded.content,
|
|
467
|
+
updated_at = CURRENT_TIMESTAMP
|
|
468
|
+
`).run(cleanName, cleanPath, storedContent);
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
name: cleanName,
|
|
472
|
+
source_path: cleanPath,
|
|
473
|
+
content_length: cleanContent.length,
|
|
474
|
+
stored_length: storedContent.length
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function getLearnedSkills(limit = 10) {
|
|
479
|
+
try {
|
|
480
|
+
ensureLearnedSkillsTable();
|
|
481
|
+
return getDb().prepare(`
|
|
482
|
+
SELECT id, name, source_path, content, created_at, updated_at
|
|
483
|
+
FROM learned_skills
|
|
484
|
+
ORDER BY updated_at DESC, id DESC
|
|
485
|
+
LIMIT ?
|
|
486
|
+
`).all(limit);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
console.error('[Memory] getLearnedSkills error:', err.message);
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function deleteLearnedSkill(identifier) {
|
|
494
|
+
try {
|
|
495
|
+
ensureLearnedSkillsTable();
|
|
496
|
+
const input = String(identifier || '').trim();
|
|
497
|
+
if (!input) return 0;
|
|
498
|
+
|
|
499
|
+
const db = getDb();
|
|
500
|
+
if (/^\d+$/.test(input)) {
|
|
501
|
+
return db.prepare('DELETE FROM learned_skills WHERE id = ?').run(Number(input)).changes;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const resolved = path.resolve(input);
|
|
505
|
+
return db.prepare('DELETE FROM learned_skills WHERE source_path = ? OR name = ?').run(resolved, input).changes;
|
|
506
|
+
} catch (err) {
|
|
507
|
+
console.error('[Memory] deleteLearnedSkill error:', err.message);
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
271
512
|
/**
|
|
272
513
|
* Returns a formatted context string to inject into the AI system prompt.
|
|
273
514
|
* Lightweight — no async calls.
|
|
274
515
|
*/
|
|
275
|
-
function getUserContext() {
|
|
516
|
+
function getUserContext(query = '') {
|
|
276
517
|
try {
|
|
277
518
|
const profile = getAllProfile();
|
|
278
519
|
const patterns = getTopPatterns(6);
|
|
279
520
|
const memories = getRecentMemories(3);
|
|
521
|
+
const interactions = getRecentInteractions(6);
|
|
522
|
+
const relevantInteractions = query ? searchInteractions(query, 5) : [];
|
|
280
523
|
|
|
281
524
|
const lines = ['\n\n[LONG-TERM USER CONTEXT — use this to personalize responses]'];
|
|
282
525
|
|
|
283
526
|
// Profile info
|
|
284
527
|
if (Object.keys(profile).length > 0) {
|
|
528
|
+
if (profile.user_name)
|
|
529
|
+
lines.push(`• User name: ${profile.user_name}`);
|
|
285
530
|
if (profile.preferred_language)
|
|
286
531
|
lines.push(`• Previously inferred language: ${profile.preferred_language} (do not override the current user message language)`);
|
|
287
532
|
if (profile.last_active_project)
|
|
@@ -306,6 +551,36 @@ function getUserContext() {
|
|
|
306
551
|
memories.forEach((m, i) => lines.push(` ${i + 1}. ${m.summary}`));
|
|
307
552
|
}
|
|
308
553
|
|
|
554
|
+
if (interactions.length > 0) {
|
|
555
|
+
lines.push('\nRecent remembered interactions:');
|
|
556
|
+
interactions.forEach((m, i) => {
|
|
557
|
+
lines.push(` ${i + 1}. User: ${m.user_text}`);
|
|
558
|
+
lines.push(` Mint: ${m.ai_text}`);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (relevantInteractions.length > 0) {
|
|
563
|
+
lines.push('\nRelevant remembered interactions for the current request:');
|
|
564
|
+
relevantInteractions.forEach((m, i) => {
|
|
565
|
+
lines.push(` ${i + 1}. User: ${m.user_text}`);
|
|
566
|
+
lines.push(` Mint: ${m.ai_text}`);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const learnedSkills = getLearnedSkills(8);
|
|
571
|
+
if (learnedSkills.length > 0) {
|
|
572
|
+
lines.push('\nLearned skill/instruction files:');
|
|
573
|
+
learnedSkills.forEach((skill, i) => {
|
|
574
|
+
lines.push(`\n ${i + 1}. ${skill.name}`);
|
|
575
|
+
lines.push(` Source: ${skill.source_path}`);
|
|
576
|
+
lines.push(' Content:');
|
|
577
|
+
lines.push(skill.content
|
|
578
|
+
.split('\n')
|
|
579
|
+
.map(line => ` ${line}`)
|
|
580
|
+
.join('\n'));
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
309
584
|
if (lines.length === 1) return ''; // nothing to add
|
|
310
585
|
lines.push('[END USER CONTEXT]\n');
|
|
311
586
|
return lines.join('\n');
|
|
@@ -324,7 +599,11 @@ function getCachedResponse(query) {
|
|
|
324
599
|
// Optional: check TTL (e.g., 24 hours)
|
|
325
600
|
const age = Date.now() - new Date(row.created_at).getTime();
|
|
326
601
|
if (age < 24 * 60 * 60 * 1000) {
|
|
327
|
-
|
|
602
|
+
const parsed = JSON.parse(row.response);
|
|
603
|
+
if (parsed && typeof parsed.response === 'string') {
|
|
604
|
+
parsed.response = stripRelevantMemoryBlock(parsed.response);
|
|
605
|
+
}
|
|
606
|
+
return parsed;
|
|
328
607
|
}
|
|
329
608
|
}
|
|
330
609
|
} catch (_) {}
|
|
@@ -334,11 +613,19 @@ function getCachedResponse(query) {
|
|
|
334
613
|
function cacheResponse(query, responseObj) {
|
|
335
614
|
try {
|
|
336
615
|
const hash = crypto.createHash('md5').update(query.trim().toLowerCase()).digest('hex');
|
|
616
|
+
const sanitized = (responseObj && typeof responseObj === 'object')
|
|
617
|
+
? {
|
|
618
|
+
...responseObj,
|
|
619
|
+
response: typeof responseObj.response === 'string'
|
|
620
|
+
? stripRelevantMemoryBlock(responseObj.response)
|
|
621
|
+
: responseObj.response
|
|
622
|
+
}
|
|
623
|
+
: responseObj;
|
|
337
624
|
getDb().prepare(`
|
|
338
625
|
INSERT INTO response_cache (query_hash, response, created_at)
|
|
339
626
|
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
340
627
|
ON CONFLICT(query_hash) DO UPDATE SET response = excluded.response, created_at = CURRENT_TIMESTAMP
|
|
341
|
-
`).run(hash, JSON.stringify(
|
|
628
|
+
`).run(hash, JSON.stringify(sanitized));
|
|
342
629
|
} catch (_) {}
|
|
343
630
|
}
|
|
344
631
|
|
|
@@ -358,7 +645,16 @@ module.exports = {
|
|
|
358
645
|
deleteProfile,
|
|
359
646
|
clearConversationScopedProfile,
|
|
360
647
|
getProfile,
|
|
648
|
+
getAllProfile,
|
|
649
|
+
addLearnedSkill,
|
|
650
|
+
getLearnedSkills,
|
|
651
|
+
deleteLearnedSkill,
|
|
361
652
|
getTopPatterns,
|
|
653
|
+
getRecentInteractions,
|
|
654
|
+
searchInteractions,
|
|
655
|
+
deleteInteractionMemory,
|
|
656
|
+
clearInteractionMemories,
|
|
657
|
+
exportMemorySnapshot,
|
|
362
658
|
getRecentMemories,
|
|
363
659
|
getCachedResponse,
|
|
364
660
|
cacheResponse,
|
|
@@ -5,9 +5,14 @@ const { readConfig } = require('../System/config_manager');
|
|
|
5
5
|
// Proactive Engine — Smart Suggestion Engine (Multi-Choice)
|
|
6
6
|
// ============================================================
|
|
7
7
|
|
|
8
|
-
const ai = new GoogleGenAI({});
|
|
9
8
|
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
10
9
|
let lastLoggedModel = '';
|
|
10
|
+
let _ai = null;
|
|
11
|
+
|
|
12
|
+
function getAi(apiKey) {
|
|
13
|
+
if (!_ai) _ai = new GoogleGenAI({ apiKey });
|
|
14
|
+
return _ai;
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
const PROACTIVE_SYSTEM_PROMPT = `You are a Smart Suggestion Engine built into a Desktop AI Agent called "Mint".
|
|
13
18
|
Your job: observe the user's screen + behavior, then offer MULTIPLE relevant quick-action options — NOT just one question.
|
|
@@ -89,11 +94,15 @@ function getMinSuggestionIntervalMs() {
|
|
|
89
94
|
*/
|
|
90
95
|
async function analyzeAndSuggest(base64Image, behaviorSummary) {
|
|
91
96
|
try {
|
|
92
|
-
const
|
|
97
|
+
const cfg = readConfig();
|
|
98
|
+
const apiKey = cfg.apiKey || process.env.GEMINI_API_KEY;
|
|
99
|
+
if (!apiKey) return null; // silently skip if no API key configured
|
|
100
|
+
const model = (cfg.geminiModel || '').trim() || DEFAULT_GEMINI_MODEL;
|
|
93
101
|
if (model && model !== lastLoggedModel) {
|
|
94
102
|
console.log(`[Gemini] Proactive Engine model: ${model}`);
|
|
95
103
|
lastLoggedModel = model;
|
|
96
104
|
}
|
|
105
|
+
const ai = getAi(apiKey);
|
|
97
106
|
|
|
98
107
|
const now = Date.now();
|
|
99
108
|
const minInterval = getMinSuggestionIntervalMs();
|
|
@@ -127,6 +136,7 @@ Rules: Only suggest if you see a clear opportunity. Return 2–4 relevant chips.
|
|
|
127
136
|
contents: [{ role: 'user', parts: userMessage }]
|
|
128
137
|
});
|
|
129
138
|
|
|
139
|
+
|
|
130
140
|
let parsed;
|
|
131
141
|
try {
|
|
132
142
|
parsed = JSON.parse(response.text);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { requireOptional } = require('../System/optional_require');
|
|
2
4
|
const { GoogleGenAI } = require('@google/genai');
|
|
3
5
|
const { readConfig } = require('../System/config_manager');
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
7
|
-
let lastLoggedModel = '';
|
|
7
|
+
|
|
8
8
|
|
|
9
9
|
const BROWSER_SYSTEM_PROMPT = `You are an Autonomous Browser Agent. Your goal is to fulfill the user's web instruction by driving a headless browser.
|
|
10
10
|
|
|
@@ -24,6 +24,9 @@ Actions:
|
|
|
24
24
|
|
|
25
25
|
You will receive the result of your previous action in the next message. If you get stuck or fail, try another approach or use "done" to report the failure.`;
|
|
26
26
|
|
|
27
|
+
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
28
|
+
let lastLoggedModel = '';
|
|
29
|
+
|
|
27
30
|
function resolveGeminiModel() {
|
|
28
31
|
try {
|
|
29
32
|
const cfg = readConfig();
|
|
@@ -35,11 +38,14 @@ function resolveGeminiModel() {
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
async function performWebAutomation(query) {
|
|
38
|
-
if (!query) return
|
|
41
|
+
if (!query) return 'No query provided.';
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
// Dynamic require — user must install puppeteer separately
|
|
44
|
+
const puppeteer = requireOptional('puppeteer', 'npm install puppeteer');
|
|
41
45
|
|
|
42
46
|
const config = readConfig();
|
|
47
|
+
const apiKey = config.apiKey || process.env.GEMINI_API_KEY;
|
|
48
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
43
49
|
const browserPath = config.automationBrowser;
|
|
44
50
|
|
|
45
51
|
let browser;
|
|
@@ -50,7 +56,6 @@ async function performWebAutomation(query) {
|
|
|
50
56
|
args: ['--start-maximized']
|
|
51
57
|
};
|
|
52
58
|
|
|
53
|
-
// If it's a specific path (like /usr/bin/firefox), set executablePath
|
|
54
59
|
if (browserPath && browserPath !== 'chromium') {
|
|
55
60
|
launchOptions.executablePath = browserPath;
|
|
56
61
|
if (browserPath.toLowerCase().includes('firefox')) {
|
|
@@ -59,9 +64,8 @@ async function performWebAutomation(query) {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
browser = await puppeteer.launch(launchOptions);
|
|
62
|
-
|
|
63
67
|
const page = await browser.newPage();
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
const model = resolveGeminiModel();
|
|
66
70
|
if (model && model !== lastLoggedModel) {
|
|
67
71
|
console.log(`[Gemini] Web Automation model: ${model}`);
|
|
@@ -72,12 +76,12 @@ async function performWebAutomation(query) {
|
|
|
72
76
|
model,
|
|
73
77
|
config: {
|
|
74
78
|
systemInstruction: BROWSER_SYSTEM_PROMPT,
|
|
75
|
-
responseMimeType:
|
|
79
|
+
responseMimeType: 'application/json'
|
|
76
80
|
}
|
|
77
81
|
});
|
|
78
82
|
|
|
79
83
|
let currentObservation = `Goal: ${query}\nSystem Note: You have a blank browser page. What is your first action? Start by using "goto" to navigate to a relevant search engine or website.`;
|
|
80
|
-
|
|
84
|
+
|
|
81
85
|
let maxSteps = 10;
|
|
82
86
|
let step = 0;
|
|
83
87
|
|
|
@@ -87,27 +91,26 @@ async function performWebAutomation(query) {
|
|
|
87
91
|
console.log(`Observation:`, currentObservation.substring(0, 150) + (currentObservation.length > 150 ? '...' : ''));
|
|
88
92
|
|
|
89
93
|
const response = await chat.sendMessage({ message: currentObservation });
|
|
90
|
-
|
|
94
|
+
|
|
91
95
|
let parsed;
|
|
92
96
|
try {
|
|
93
97
|
const text = response.text;
|
|
94
98
|
const cleanText = text.replace(/^```json\n/, '').replace(/\n```$/, '').trim();
|
|
95
99
|
parsed = JSON.parse(cleanText);
|
|
96
100
|
} catch (e) {
|
|
97
|
-
console.error(
|
|
98
|
-
currentObservation =
|
|
101
|
+
console.error('Agent failed to return valid JSON:', response.text);
|
|
102
|
+
currentObservation = 'Error: Invalid JSON returned. Please reply with ONLY valid JSON matching the schema.';
|
|
99
103
|
continue;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
console.log(
|
|
103
|
-
console.log(
|
|
104
|
-
console.log(
|
|
106
|
+
console.log('Agent Thought:', parsed.thought);
|
|
107
|
+
console.log('Agent Action:', parsed.action);
|
|
108
|
+
console.log('Agent Target:', parsed.target);
|
|
105
109
|
|
|
106
110
|
const { action, target } = parsed;
|
|
107
111
|
|
|
108
112
|
if (action === 'done') {
|
|
109
|
-
console.log(
|
|
110
|
-
// Intentionally keeping the browser open so the user can see the page.
|
|
113
|
+
console.log('Agent finished with answer:', target);
|
|
111
114
|
return `🤖 Web Automation Result: ${target}`;
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -119,7 +122,7 @@ async function performWebAutomation(query) {
|
|
|
119
122
|
} else if (action === 'click') {
|
|
120
123
|
await page.waitForSelector(target, { timeout: 5000 });
|
|
121
124
|
await page.click(target);
|
|
122
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
125
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
123
126
|
const pageTitle = await page.title();
|
|
124
127
|
currentObservation = `Clicked element. Current page: ${pageTitle}. ` + await page.evaluate(() => document.body.innerText.substring(0, 1500));
|
|
125
128
|
} else if (action === 'eval') {
|
|
@@ -129,16 +132,15 @@ async function performWebAutomation(query) {
|
|
|
129
132
|
currentObservation = `Error: Unknown action type "${action}".`;
|
|
130
133
|
}
|
|
131
134
|
} catch (actionError) {
|
|
132
|
-
console.error(
|
|
135
|
+
console.error('Action execution failed:', actionError);
|
|
133
136
|
currentObservation = `Action failed: ${actionError.message}. Please try again or use another method (for instance, try a different CSS selector or just read the current page).`;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
return "Agent reached maximum steps (10) without finding a final answer.";
|
|
140
|
+
return 'Agent reached maximum steps (10) without finding a final answer.';
|
|
139
141
|
|
|
140
142
|
} catch (error) {
|
|
141
|
-
console.error(
|
|
143
|
+
console.error('Web Automation Error:', error);
|
|
142
144
|
if (browser) browser.close();
|
|
143
145
|
return `I encountered an overall error while automating the browser: ${error.message}`;
|
|
144
146
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const { colors } = require('./cli_colors');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prompts the user in the terminal to approve or deny a code-agent action.
|
|
8
|
+
* Used by the non-interactive `mint code <task>` command.
|
|
9
|
+
*
|
|
10
|
+
* @param {{ type: string, label?: string, preview?: string }} request
|
|
11
|
+
* @returns {Promise<boolean>} true = approved, false = denied
|
|
12
|
+
*/
|
|
13
|
+
async function requestCodeApproval(request) {
|
|
14
|
+
const typeLabel =
|
|
15
|
+
request.type === 'shell' ? 'Shell Command' :
|
|
16
|
+
request.type === 'patch' ? 'Patch Edit' :
|
|
17
|
+
'File Write';
|
|
18
|
+
|
|
19
|
+
console.log(`\n${colors.yellow}${colors.bright}[Approval Required]${colors.reset} ${typeLabel}`);
|
|
20
|
+
if (request.label) console.log(`${colors.gray}${request.label}${colors.reset}`);
|
|
21
|
+
if (request.preview) console.log(`${colors.gray}${request.preview}${colors.reset}\n`);
|
|
22
|
+
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const answer = await new Promise((resolve) => {
|
|
29
|
+
rl.question('Approve this action? [y/N]: ', (value) => {
|
|
30
|
+
rl.close();
|
|
31
|
+
resolve((value || '').trim().toLowerCase());
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const approved = answer === 'y' || answer === 'yes';
|
|
36
|
+
console.log(approved
|
|
37
|
+
? `${colors.mint}[Mint Code] Approved.${colors.reset}\n`
|
|
38
|
+
: `${colors.pink}[Mint Code] Denied.${colors.reset}\n`);
|
|
39
|
+
return approved;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { requestCodeApproval };
|
package/src/CLI/chat_router.js
CHANGED
|
@@ -187,6 +187,7 @@ async function runChatRoutedTask(input, context) {
|
|
|
187
187
|
}, 1000);
|
|
188
188
|
|
|
189
189
|
try {
|
|
190
|
+
let streamedFinalSummary = false;
|
|
190
191
|
const result = await executeCodeTask(text, {
|
|
191
192
|
cwd: process.cwd(),
|
|
192
193
|
requestApproval,
|
|
@@ -199,21 +200,32 @@ async function runChatRoutedTask(input, context) {
|
|
|
199
200
|
} else {
|
|
200
201
|
appendMessage('system', `[Code] ${typeof info === 'string' ? info : (info.action || info.phase)}`);
|
|
201
202
|
}
|
|
203
|
+
},
|
|
204
|
+
onFinalSummary: async (info) => {
|
|
205
|
+
if (typeof context.streamAssistantSentences !== 'function') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
clearInterval(timer);
|
|
209
|
+
setThinking(false);
|
|
210
|
+
streamedFinalSummary = true;
|
|
211
|
+
await context.streamAssistantSentences(info.summary, appendMessage, { providerInfo: info.providerInfo }, context.streamMessage);
|
|
202
212
|
}
|
|
203
213
|
});
|
|
204
214
|
clearInterval(timer);
|
|
205
215
|
setThinking(false);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
if (!streamedFinalSummary) {
|
|
217
|
+
appendMessage('assistant', [
|
|
218
|
+
`Code Mode finished.`,
|
|
219
|
+
result.summary,
|
|
220
|
+
`Verification: ${result.verification}`
|
|
221
|
+
].join('\n'), { providerInfo: result.providerInfo });
|
|
222
|
+
}
|
|
211
223
|
} catch (error) {
|
|
212
224
|
clearInterval(timer);
|
|
213
225
|
setThinking(false);
|
|
214
226
|
appendMessage('error', error.message);
|
|
215
227
|
} finally {
|
|
216
|
-
if (setMode) setMode('
|
|
228
|
+
if (setMode) setMode('Agent');
|
|
217
229
|
}
|
|
218
230
|
}
|
|
219
231
|
|