natureco-cli 5.7.18 → 5.8.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.
- package/package.json +1 -1
- package/src/commands/medium.js +1 -1
- package/src/commands/naturehub.js +1 -1
- package/src/commands/repl.js +149 -67
- package/src/commands/xp.js +1 -1
- package/src/tools/calendar_add.js +1 -1
- package/src/tools/cross_session_memory.js +1 -1
- package/src/tools/dashboard.js +2 -2
- package/src/tools/delegate_task.js +1 -1
- package/src/tools/mac_alarm.js +1 -1
- package/src/tools/memory_write.js +4 -4
- package/src/tools/plan.js +1 -1
- package/src/tools/plugin.js +1 -1
- package/src/tools/skill_generate.js +1 -1
- package/src/tools/skills_autoload.js +1 -1
- package/src/tools/skills_marketplace.js +3 -3
- package/src/tools/soul.js +2 -2
- package/src/tools/voice_chat.js +1 -1
- package/src/utils/api.js +50 -33
- package/src/utils/error.js +1 -1
- package/src/utils/memory-store.js +9 -0
- package/src/utils/paths.js +1 -1
- package/src/utils/system-prompt.js +14 -9
- package/src/utils/tool-guardrails.js +85 -0
- package/src/utils/tool-runner.js +22 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.8.0",
|
|
4
4
|
"description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"natureco": "bin/natureco.js"
|
package/src/commands/medium.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* natureco naturehub trending Trend olan konular
|
|
9
9
|
* natureco naturehub config Ayarları göster
|
|
10
10
|
*
|
|
11
|
-
* API endpoint: api.natureco.me/naturehub/* (placeholder
|
|
11
|
+
* API endpoint: api.natureco.me/naturehub/* (placeholder)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const chalk = require('chalk');
|
package/src/commands/repl.js
CHANGED
|
@@ -27,7 +27,8 @@ const { accumulateToolCallDeltas, finalizeToolCalls } = require('../utils/stream
|
|
|
27
27
|
const { createPasteSafeInput, createOutputFilter, enableBracketedPaste, disableBracketedPaste, restoreNewlines, clearPasteContext } = require('../utils/paste-safe-input');
|
|
28
28
|
const { getMemoryStore } = require('../utils/memory-store');
|
|
29
29
|
const { buildSkillIndex } = require('../utils/skill-index');
|
|
30
|
-
const { buildTiers, assemble
|
|
30
|
+
const { buildTiers, assemble } = require('../utils/system-prompt');
|
|
31
|
+
const { ToolGuardrails } = require('../utils/tool-guardrails');
|
|
31
32
|
|
|
32
33
|
// v5.4.6: Model adi sizintisini engelle — global'e ata, callback'lerden erisebilir olsun
|
|
33
34
|
const MODEL_NAMES_TO_HIDE = ['MiniMax-M2.5', 'MiniMaxM2.5', 'minimaxm25', 'Claude-3', 'GPT-4', 'ChatGPT'];
|
|
@@ -36,11 +37,11 @@ function fixModelNameLeak(text, botName) {
|
|
|
36
37
|
let fixed = text;
|
|
37
38
|
for (const modelName of MODEL_NAMES_TO_HIDE) {
|
|
38
39
|
const regex = new RegExp(modelName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
39
|
-
fixed = fixed.replace(regex, botName || '
|
|
40
|
+
fixed = fixed.replace(regex, botName || 'asistan');
|
|
40
41
|
}
|
|
41
|
-
fixed = fixed.replace(/Ben\s+MiniMax[^.,!?\n]*/gi, 'Ben
|
|
42
|
-
fixed = fixed.replace(/I'm\s+MiniMax[^.,!?\n]*/gi, "I'm
|
|
43
|
-
fixed = fixed.replace(/I am\s+Claude[^.,!?\n]*/gi, 'I am
|
|
42
|
+
fixed = fixed.replace(/Ben\s+MiniMax[^.,!?\n]*/gi, 'Ben ' + (botName || 'asistan'));
|
|
43
|
+
fixed = fixed.replace(/I'm\s+MiniMax[^.,!?\n]*/gi, "I'm " + (botName || 'asistan'));
|
|
44
|
+
fixed = fixed.replace(/I am\s+Claude[^.,!?\n]*/gi, 'I am ' + (botName || 'asistan'));
|
|
44
45
|
return fixed;
|
|
45
46
|
}
|
|
46
47
|
global.fixModelNameLeak = fixModelNameLeak;
|
|
@@ -58,6 +59,46 @@ function getToolDefs() {
|
|
|
58
59
|
return _toolDefs;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// ── System prompt tier cache (Hermes-style prefix cache warmth) ────────────
|
|
63
|
+
// stable+context built once at session start, volatile rebuilt per turn.
|
|
64
|
+
let _cachedStable = '';
|
|
65
|
+
let _cachedContext = '';
|
|
66
|
+
let _cachedTierOpts = null; // opts snapshot for volatile-only rebuilds
|
|
67
|
+
|
|
68
|
+
function rebuildSystemPrompt(opts) {
|
|
69
|
+
// If stable/context opts changed, rebuild them too
|
|
70
|
+
const needsFullRebuild = !_cachedTierOpts ||
|
|
71
|
+
_cachedTierOpts.botName !== opts.botName ||
|
|
72
|
+
_cachedTierOpts.soulSummary !== opts.soulSummary ||
|
|
73
|
+
_cachedTierOpts.skillsIndexBlock !== opts.skillsIndexBlock ||
|
|
74
|
+
_cachedTierOpts.crossSessionContext !== opts.crossSessionContext;
|
|
75
|
+
|
|
76
|
+
if (needsFullRebuild || !_cachedStable) {
|
|
77
|
+
const tiers = buildTiers(opts);
|
|
78
|
+
_cachedStable = tiers.stable;
|
|
79
|
+
_cachedContext = tiers.context;
|
|
80
|
+
_cachedTierOpts = {
|
|
81
|
+
botName: opts.botName,
|
|
82
|
+
soulSummary: opts.soulSummary,
|
|
83
|
+
skillsIndexBlock: opts.skillsIndexBlock,
|
|
84
|
+
crossSessionContext: opts.crossSessionContext,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Volatile always rebuilt fresh
|
|
88
|
+
const volatileOnly = buildTiers({
|
|
89
|
+
...opts,
|
|
90
|
+
// Pass empty strings for stable/context fields so buildTiers only builds volatile
|
|
91
|
+
botName: '',
|
|
92
|
+
soulSummary: '',
|
|
93
|
+
skillsIndexBlock: '',
|
|
94
|
+
crossSessionContext: '',
|
|
95
|
+
});
|
|
96
|
+
return assemble(_cachedStable, _cachedContext, volatileOnly.volatile);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Tool Guardrails instance (Hermes-style) ─────────────────────────────
|
|
100
|
+
const guardrails = new ToolGuardrails();
|
|
101
|
+
|
|
61
102
|
// CLI komutları (REPL içinden çalıştırılabilir)
|
|
62
103
|
const CLI_COMMANDS = {
|
|
63
104
|
'/doctor': { desc: 'Sistem sağlığı kontrolü', run: ['doctor'] },
|
|
@@ -102,7 +143,7 @@ function loadMemory(username) {
|
|
|
102
143
|
try {
|
|
103
144
|
if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
104
145
|
} catch {}
|
|
105
|
-
return { name: username || 'Kullanıcı', nickname: null, botName: '
|
|
146
|
+
return { name: username || 'Kullanıcı', nickname: null, botName: 'Asistan', facts: [], preferences: [], history: [] };
|
|
106
147
|
}
|
|
107
148
|
|
|
108
149
|
function saveMemory(username, memory) {
|
|
@@ -233,6 +274,7 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
233
274
|
const isMM = isMiniMax(providerUrl);
|
|
234
275
|
const toolDefs = getToolDefs();
|
|
235
276
|
const toolParam = toOpenAIFormat(toolDefs);
|
|
277
|
+
guardrails.reset();
|
|
236
278
|
|
|
237
279
|
let currentMessages = messages;
|
|
238
280
|
let fullText = '';
|
|
@@ -268,17 +310,18 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
268
310
|
|
|
269
311
|
while (iterations < MAX_TOOL_ITERATIONS) {
|
|
270
312
|
iterations++;
|
|
313
|
+
const shouldStream = !isMM; // MiniMax streaming endpoint doesn't support tool_calls
|
|
271
314
|
const body = {
|
|
272
315
|
model,
|
|
273
316
|
messages: currentMessages,
|
|
274
|
-
stream:
|
|
317
|
+
stream: shouldStream,
|
|
275
318
|
temperature: 0.3,
|
|
276
319
|
max_tokens: 2048,
|
|
277
320
|
};
|
|
278
321
|
if (toolParam) body.tools = toolParam;
|
|
279
322
|
if (isMM) body.tool_choice = 'auto'; // MiniMax için explicit
|
|
280
323
|
|
|
281
|
-
if (!
|
|
324
|
+
if (!shouldStream) {
|
|
282
325
|
// MiniMax (non-stream) — tool_calls desteklemiyor varsayalım
|
|
283
326
|
const res = await apiRequest(providerUrl, providerApiKey, body, false);
|
|
284
327
|
const msg = res.choices?.[0]?.message || {};
|
|
@@ -408,12 +451,25 @@ async function processToolCalls(toolCalls, onToolCall) {
|
|
|
408
451
|
return { name, args, id };
|
|
409
452
|
});
|
|
410
453
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
const
|
|
454
|
+
// Filter out blocked tools via guardrails
|
|
455
|
+
guardrails.startIteration();
|
|
456
|
+
const blocked = parsed.filter(p => {
|
|
457
|
+
const check = guardrails.check(p.name, p.args);
|
|
458
|
+
if (check.blocked) {
|
|
459
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'done', result: { error: check.reason } });
|
|
460
|
+
results.push({ ...p, result: { error: check.reason }, _blocked: true });
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return true;
|
|
464
|
+
});
|
|
465
|
+
const allowed = parsed.filter(p => !results.some(r => r.id === p.id && r._blocked));
|
|
466
|
+
|
|
467
|
+
// Separate parallel-safe and sequential tools (over allowed only)
|
|
468
|
+
const parallelBatch = allowed.filter(p => PARALLEL_SAFE_TOOLS.has(p.name));
|
|
469
|
+
const sequentialBatch = allowed.filter(p => !PARALLEL_SAFE_TOOLS.has(p.name));
|
|
414
470
|
|
|
415
|
-
// Notify UI for all
|
|
416
|
-
for (const p of
|
|
471
|
+
// Notify UI for all non-blocked
|
|
472
|
+
for (const p of allowed) {
|
|
417
473
|
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'running' });
|
|
418
474
|
}
|
|
419
475
|
|
|
@@ -421,6 +477,8 @@ async function processToolCalls(toolCalls, onToolCall) {
|
|
|
421
477
|
if (parallelBatch.length > 0) {
|
|
422
478
|
const parallelResults = await Promise.all(parallelBatch.map(async (p) => {
|
|
423
479
|
const result = await executeTool(p.name, p.args, toolDefs);
|
|
480
|
+
const success = result?.success !== false;
|
|
481
|
+
guardrails.record(p.name, p.args, success);
|
|
424
482
|
return { ...p, result };
|
|
425
483
|
}));
|
|
426
484
|
results.push(...parallelResults);
|
|
@@ -429,9 +487,16 @@ async function processToolCalls(toolCalls, onToolCall) {
|
|
|
429
487
|
// Run sequential batch one at a time
|
|
430
488
|
for (const p of sequentialBatch) {
|
|
431
489
|
const result = await executeTool(p.name, p.args, toolDefs);
|
|
490
|
+
const success = result?.success !== false;
|
|
491
|
+
guardrails.record(p.name, p.args, success);
|
|
432
492
|
results.push({ ...p, result });
|
|
433
493
|
}
|
|
434
494
|
|
|
495
|
+
// No-progress check: if all tools failed, inject warning
|
|
496
|
+
if (guardrails.isNoProgress()) {
|
|
497
|
+
if (onToolCall) onToolCall({ name: '_no_progress', args: null, status: 'done', result: { error: 'All tools failed this iteration' } });
|
|
498
|
+
}
|
|
499
|
+
|
|
435
500
|
// Notify UI done + build messages
|
|
436
501
|
const messages = [];
|
|
437
502
|
for (const { name, id, result } of results) {
|
|
@@ -513,7 +578,7 @@ async function startRepl(args) {
|
|
|
513
578
|
|
|
514
579
|
// v5.6.19: Oncelik config.botName, sonra memory.botName
|
|
515
580
|
if (!memory.botName) {
|
|
516
|
-
memory.botName = cfg.botName || '
|
|
581
|
+
memory.botName = cfg.botName || 'Asistan';
|
|
517
582
|
}
|
|
518
583
|
// BotName'i memory'ye persist et (her oturumda ayni kalsin)
|
|
519
584
|
try {
|
|
@@ -575,26 +640,23 @@ async function startRepl(args) {
|
|
|
575
640
|
}
|
|
576
641
|
|
|
577
642
|
// System prompt oluştur (memory + identity + persistent bağlam)
|
|
578
|
-
// v5.4.6: Bot adı zorlaması EN GÜÇLÜ + SOUL.md EN BAŞTA
|
|
579
|
-
const botName = memory.botName || 'İchigo';
|
|
580
|
-
const userName = memory.name || memory.nickname || 'kanka';
|
|
581
643
|
// v5.6.5: Kucuk model tespiti (Groq, Mistral Small, Ollama) - SOUL injection skip
|
|
644
|
+
const botName = memory.botName || 'Asistan';
|
|
645
|
+
const userName = memory.name || memory.nickname || 'kanka';
|
|
582
646
|
const isSmallModel = (cfg.providerUrl || '').includes('groq.com') ||
|
|
583
647
|
(cfg.providerUrl || '').includes('mistral.ai') ||
|
|
584
648
|
(cfg.providerUrl || '').includes('localhost') ||
|
|
585
649
|
(cfg.providerUrl || '').includes('ollama');
|
|
586
|
-
//
|
|
587
|
-
|
|
588
|
-
// and tear down the REPL session mid-conversation.
|
|
589
|
-
const tiers = buildTiers({
|
|
650
|
+
// Build system prompt with tier caching (stable+context cached, volatile fresh)
|
|
651
|
+
const promptOpts = {
|
|
590
652
|
botName, userName, soulSummary, isSmallModel,
|
|
591
653
|
memorySnapshotBlock, skillsIndexBlock,
|
|
592
654
|
crossSessionContext: crossSessionContext || '',
|
|
593
655
|
userHome: cfg.userHome || '',
|
|
594
656
|
hasHistory: messages.length > 0,
|
|
595
657
|
memoryFacts: memory.facts || [],
|
|
596
|
-
}
|
|
597
|
-
let systemPrompt =
|
|
658
|
+
};
|
|
659
|
+
let systemPrompt = rebuildSystemPrompt(promptOpts);
|
|
598
660
|
|
|
599
661
|
if (messages.length === 0) {
|
|
600
662
|
messages.push({ role: 'system', content: systemPrompt, _internal: true });
|
|
@@ -612,14 +674,14 @@ async function startRepl(args) {
|
|
|
612
674
|
console.log(tui.C.muted(' Provider: ') + tui.C.brand(providerUrl.replace(/https?:\/\//, '')));
|
|
613
675
|
console.log(tui.C.muted(' Model: ') + tui.C.brand(model));
|
|
614
676
|
console.log(tui.C.muted(' Kullanıcı: ') + tui.C.brand((memory.nickname || cfg.userName) + (memory.nickname ? ` (${cfg.userName})` : '')));
|
|
615
|
-
console.log(tui.C.muted(' Bot: ') + tui.C.brand(memory.botName || '
|
|
677
|
+
console.log(tui.C.muted(' Bot: ') + tui.C.brand(memory.botName || 'Asistan'));
|
|
616
678
|
if (messages.length > 1) {
|
|
617
679
|
console.log(tui.C.muted(' Oturum: ') + tui.C.amber(`${messages.filter(m => m.role === 'user' || m.role === 'assistant').length} mesaj (resume)`));
|
|
618
680
|
}
|
|
619
681
|
console.log(tui.C.muted(' Komutlar: ') + tui.C.yellow('/help') + tui.C.muted(' · ') + tui.C.yellow('/memory') + tui.C.muted(' · ') + tui.C.yellow('/sessions') + tui.C.muted(' · ') + tui.C.yellow('/exit'));
|
|
620
682
|
console.log('');
|
|
621
683
|
// v5.4.7: Hard-coded kimlik
|
|
622
|
-
const displayBotName = memory.botName || '
|
|
684
|
+
const displayBotName = memory.botName || 'Asistan';
|
|
623
685
|
const displayUserName = userName || 'kanka';
|
|
624
686
|
console.log(tui.C.brand(' 👋 Ben ' + displayBotName + ', ' + displayUserName + '. Sen nasilsin?'));
|
|
625
687
|
console.log('');
|
|
@@ -683,31 +745,30 @@ async function startRepl(args) {
|
|
|
683
745
|
// Pattern-based extraction (zaten extractFacts var)
|
|
684
746
|
const newFacts = extractFacts(messages, memory.facts || []);
|
|
685
747
|
|
|
686
|
-
// Bazi user message'lari da tara
|
|
748
|
+
// Bazi user message'lari da tara — genel kalıplarla fact çıkar
|
|
687
749
|
const userMessages = messages.filter(m => m.role === 'user' && !m._internal);
|
|
688
750
|
for (const msg of userMessages) {
|
|
689
751
|
const text = (msg.content || '').toLowerCase();
|
|
690
752
|
|
|
691
753
|
// BotName hatirlatmasi
|
|
692
|
-
if (text.includes('
|
|
693
|
-
|
|
694
|
-
memory.botName = 'İchigo';
|
|
695
|
-
}
|
|
754
|
+
if (text.includes('ad') && (text.includes('adin') || text.includes('ismin'))) {
|
|
755
|
+
// Bot adı sorgulanmış olabilir, mevcut adı koru
|
|
696
756
|
}
|
|
697
757
|
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
758
|
+
// Kisilik tercihleri (genel pattern'ler)
|
|
759
|
+
const prefPatterns = [
|
|
760
|
+
{ match: /(?:benim ad[ıi]m?|bana\s+.*de|ad[ıi]m?)\s+(\w+)/i, category: 'personal', key: 'ad' },
|
|
761
|
+
{ match: /(?:seviyorum|hoşlan[ıi]yorum|beğeniyorum)\s+(\w+)/i, category: 'preference', key: 'sevilen' },
|
|
762
|
+
{ match: /(?:yaşıyorum|oturuyorum|kalıyorum)\s+(\w+)/i, category: 'location', key: 'yer' },
|
|
763
|
+
];
|
|
764
|
+
for (const p of prefPatterns) {
|
|
765
|
+
const m2 = msg.content.match(p.match);
|
|
766
|
+
if (m2) {
|
|
767
|
+
const val = m2[1].toLowerCase();
|
|
768
|
+
const fact = `Kullanici ${p.key}: ${val}`;
|
|
769
|
+
if (!(memory.facts || []).some(f => f.value === fact)) {
|
|
770
|
+
newFacts.push({ value: fact, score: 6, category: p.category, createdAt: new Date().toISOString() });
|
|
771
|
+
}
|
|
711
772
|
}
|
|
712
773
|
}
|
|
713
774
|
}
|
|
@@ -774,7 +835,7 @@ async function startRepl(args) {
|
|
|
774
835
|
console.log(chalk.cyan('\n 🧠 Memory:\n'));
|
|
775
836
|
console.log(' Kullanıcı: ' + chalk.cyan(memory.name));
|
|
776
837
|
console.log(' Nickname: ' + chalk.cyan(memory.nickname || '(yok)'));
|
|
777
|
-
console.log(' Bot: ' + chalk.cyan(memory.botName || '
|
|
838
|
+
console.log(' Bot: ' + chalk.cyan(memory.botName || 'Asistan'));
|
|
778
839
|
if (memory.facts && memory.facts.length > 0) {
|
|
779
840
|
console.log(' Facts (' + memory.facts.length + '):');
|
|
780
841
|
for (const f of memory.facts) {
|
|
@@ -790,10 +851,13 @@ async function startRepl(args) {
|
|
|
790
851
|
if (fs.existsSync(path.join(MEMORY_DIR, `${(cfg.userName || 'default').toLowerCase()}.json`))) {
|
|
791
852
|
fs.unlinkSync(path.join(MEMORY_DIR, `${(cfg.userName || 'default').toLowerCase()}.json`));
|
|
792
853
|
}
|
|
793
|
-
memory = { name: cfg.userName, nickname: null, botName: '
|
|
794
|
-
// System prompt'u
|
|
795
|
-
|
|
796
|
-
|
|
854
|
+
memory = { name: cfg.userName, nickname: null, botName: 'Asistan', facts: [], preferences: [], history: [] };
|
|
855
|
+
// System prompt'u rebuild with cleared memory
|
|
856
|
+
promptOpts.memoryFacts = [];
|
|
857
|
+
memoryStore.clear();
|
|
858
|
+
promptOpts.memorySnapshotBlock = memoryStore.getSystemPromptBlock();
|
|
859
|
+
systemPrompt = rebuildSystemPrompt(promptOpts);
|
|
860
|
+
messages[0] = { role: 'system', content: systemPrompt, _internal: true };
|
|
797
861
|
console.log(chalk.green(' ✓ Memory temizlendi'));
|
|
798
862
|
} catch (e) {
|
|
799
863
|
console.log(chalk.red(' ❌ ' + e.message));
|
|
@@ -823,7 +887,15 @@ async function startRepl(args) {
|
|
|
823
887
|
break;
|
|
824
888
|
case 'system':
|
|
825
889
|
if (!arg) { console.log(chalk.yellow(' Kullanım: /system <text>')); break; }
|
|
826
|
-
|
|
890
|
+
// Override stable tier directly (user's custom text)
|
|
891
|
+
_cachedStable = arg;
|
|
892
|
+
// Rebuild volatile only (context stays unchanged)
|
|
893
|
+
memoryStore.load();
|
|
894
|
+
promptOpts.memorySnapshotBlock = memoryStore.getSystemPromptBlock();
|
|
895
|
+
promptOpts.memoryFacts = memory.facts || [];
|
|
896
|
+
const volOpts = { ...promptOpts, botName: '', soulSummary: '', skillsIndexBlock: '', crossSessionContext: '' };
|
|
897
|
+
const volTiers = buildTiers(volOpts);
|
|
898
|
+
systemPrompt = assemble(_cachedStable, _cachedContext, volTiers.volatile);
|
|
827
899
|
messages[0] = { role: 'system', content: systemPrompt, _internal: true };
|
|
828
900
|
console.log(chalk.green(' ✓ System prompt güncellendi'));
|
|
829
901
|
break;
|
|
@@ -833,11 +905,17 @@ async function startRepl(args) {
|
|
|
833
905
|
console.log(chalk.green(' ✓ Model: ') + chalk.cyan(model));
|
|
834
906
|
break;
|
|
835
907
|
case 'identity':
|
|
836
|
-
if (!arg) { console.log(chalk.yellow(` Mevcut: ${memory.botName || '
|
|
908
|
+
if (!arg) { console.log(chalk.yellow(` Mevcut: ${memory.botName || 'Asistan'}`)); break; }
|
|
837
909
|
memory.botName = arg;
|
|
838
910
|
saveMemory(cfg.userName, memory);
|
|
839
|
-
|
|
840
|
-
|
|
911
|
+
// Rebuild stable tier with new botName
|
|
912
|
+
promptOpts.botName = arg;
|
|
913
|
+
_cachedTierOpts = null; // force full rebuild
|
|
914
|
+
memoryStore.load();
|
|
915
|
+
promptOpts.memorySnapshotBlock = memoryStore.getSystemPromptBlock();
|
|
916
|
+
promptOpts.memoryFacts = memory.facts || [];
|
|
917
|
+
systemPrompt = rebuildSystemPrompt(promptOpts);
|
|
918
|
+
messages[0] = { role: 'system', content: systemPrompt, _internal: true };
|
|
841
919
|
console.log(chalk.green(' ✓ Bot adı: ') + chalk.cyan(arg));
|
|
842
920
|
break;
|
|
843
921
|
case 'tokens':
|
|
@@ -884,7 +962,7 @@ async function startRepl(args) {
|
|
|
884
962
|
if (isIdentityQuestion) {
|
|
885
963
|
// v5.6.10: Hard-coded prefix minimal - model cevabini bozuyordu
|
|
886
964
|
// Once sadece isim yaz, modelin devamini getirsin
|
|
887
|
-
const displayName = memory.botName || '
|
|
965
|
+
const displayName = memory.botName || 'Asistan';
|
|
888
966
|
process.stdout.write(tui.styled('\n AI ', { color: tui.PALETTE.secondary, bold: true }));
|
|
889
967
|
process.stdout.write('Merhaba! Ben ' + displayName + '. ');
|
|
890
968
|
}
|
|
@@ -892,6 +970,14 @@ async function startRepl(args) {
|
|
|
892
970
|
// AI cevabı
|
|
893
971
|
process.stdout.write(tui.styled('\n AI ', { color: tui.PALETTE.secondary, bold: true }));
|
|
894
972
|
try {
|
|
973
|
+
// Per-turn: rebuild volatile tier with current memory snapshot (Hermes frozen snapshot)
|
|
974
|
+
guardrails.reset();
|
|
975
|
+
memory = loadMemory(cfg.userName);
|
|
976
|
+
memoryStore.load();
|
|
977
|
+
promptOpts.memorySnapshotBlock = memoryStore.getSystemPromptBlock();
|
|
978
|
+
promptOpts.memoryFacts = memory.facts || [];
|
|
979
|
+
systemPrompt = rebuildSystemPrompt(promptOpts);
|
|
980
|
+
messages[0] = { role: 'system', content: systemPrompt, _internal: true };
|
|
895
981
|
const apiMessages = messages.filter(m => !m._internal);
|
|
896
982
|
const reply = await sendStreaming(
|
|
897
983
|
providerUrl,
|
|
@@ -900,24 +986,24 @@ async function startRepl(args) {
|
|
|
900
986
|
model,
|
|
901
987
|
// v5.6.12: Callback bos - tam metin 'reply' olarak gelecek (non-stream mode)
|
|
902
988
|
() => {},
|
|
903
|
-
// Tool call callback —
|
|
904
|
-
(toolEvent) => {
|
|
989
|
+
// Tool call callback — Hermes-style per-tool status line
|
|
990
|
+
((toolEvent) => {
|
|
991
|
+
const name = toolEvent.name;
|
|
905
992
|
if (toolEvent.status === 'running') {
|
|
906
|
-
process.stdout.write(tui.styled('\r 🔧 ' +
|
|
993
|
+
process.stdout.write(tui.styled('\r 🔧 ' + name + '... ', { color: tui.PALETTE.muted }));
|
|
907
994
|
} else if (toolEvent.status === 'done') {
|
|
908
|
-
if (toolEvent.result
|
|
909
|
-
process.stdout.write(tui.styled('
|
|
995
|
+
if (toolEvent.result?.error) {
|
|
996
|
+
process.stdout.write(tui.styled(' ✗ ' + name + ': ' + String(toolEvent.result.error).slice(0, 80) + '\n', { color: tui.PALETTE.danger }));
|
|
910
997
|
} else {
|
|
911
|
-
process.stdout.write(
|
|
998
|
+
process.stdout.write(tui.styled(' ✓ ' + name + '\n', { color: tui.PALETTE.success }));
|
|
912
999
|
}
|
|
913
|
-
process.stdout.write(tui.styled(' AI ', { color: tui.PALETTE.secondary, bold: true }));
|
|
914
1000
|
}
|
|
915
|
-
}
|
|
1001
|
+
})
|
|
916
1002
|
);
|
|
917
1003
|
// v5.6.12: Tam metin 'reply' olarak zaten geldi (non-stream mode)
|
|
918
1004
|
const fullReply = String(reply || '');
|
|
919
1005
|
// Bot adini al
|
|
920
|
-
const displayBotName = memory.botName || '
|
|
1006
|
+
const displayBotName = memory.botName || 'Asistan';
|
|
921
1007
|
// v5.6.9: Tum model adlarini ve varyasyonlari temizle
|
|
922
1008
|
let fixedReply = String(fullReply);
|
|
923
1009
|
// Bilinen model adlari - tum varyasyonlar
|
|
@@ -933,13 +1019,9 @@ async function startRepl(args) {
|
|
|
933
1019
|
fixedReply = fixedReply.replace(/Ben\s+MiniMax[^.!?,;:\n]*/gi, 'Ben ' + displayBotName);
|
|
934
1020
|
fixedReply = fixedReply.replace(/Ben\s+Claude[^.!?,;:\n]*/gi, 'Ben ' + displayBotName);
|
|
935
1021
|
fixedReply = fixedReply.replace(/Ben\s+GPT[^.!?,;:\n]*/gi, 'Ben ' + displayBotName);
|
|
936
|
-
fixedReply = fixedReply.replace(/Ben\s
|
|
1022
|
+
fixedReply = fixedReply.replace(/Ben\s+Asistan[\s\w\.]*/gi, 'Ben ' + displayBotName);
|
|
937
1023
|
// Markdown ** ile cevrili model adi
|
|
938
1024
|
fixedReply = fixedReply.replace(/\*\*(?:MiniMax|Claude|GPT|M2\.5|M2)[^\*]*\*\*/gi, '**' + displayBotName + '**');
|
|
939
|
-
// "İchigo" varyasyonlari
|
|
940
|
-
fixedReply = fixedReply.replace(/(İchigo)(\d)([a-zA-ZçğıöşüÇĞİÖŞÜ])/g, displayBotName + ' $3');
|
|
941
|
-
fixedReply = fixedReply.replace(/İchigo[\.\s\-_]*\d+/g, displayBotName);
|
|
942
|
-
fixedReply = fixedReply.replace(/İchigo\./g, displayBotName);
|
|
943
1025
|
// Cevap yazdir
|
|
944
1026
|
process.stdout.write('\n' + fixedReply + '\n');
|
|
945
1027
|
// v5.7.18: Tool call geçmişini kalıcı messages'a ekle — model sonraki turlarda tool sonuçlarını görsün
|
package/src/commands/xp.js
CHANGED
package/src/tools/dashboard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dashboard - Web Dashboard v2 (v5.4.0)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Dashboard — CLI'yi web'den kontrol eder
|
|
5
5
|
*
|
|
6
6
|
* Ozellikler:
|
|
7
7
|
* - Real-time tool execution (WebSocket)
|
|
@@ -189,7 +189,7 @@ const DASHBOARD_HTML = `
|
|
|
189
189
|
</div>
|
|
190
190
|
|
|
191
191
|
<div class="footer">
|
|
192
|
-
<p>NatureCo CLI v5.4.0 -
|
|
192
|
+
<p>NatureCo CLI v5.4.0 - <span id="lastUpdate"></span></p>
|
|
193
193
|
</div>
|
|
194
194
|
</div>
|
|
195
195
|
<script>
|
|
@@ -9,7 +9,7 @@ const { spawn } = require("child_process");
|
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const os = require("os");
|
|
11
11
|
|
|
12
|
-
// v5.2.0: Agent alias mapping
|
|
12
|
+
// v5.2.0: Agent alias mapping
|
|
13
13
|
const AGENT_ALIASES = {
|
|
14
14
|
"review": "general", // eskiden review diye bir vardi, simdi general
|
|
15
15
|
"analyze": "explore",
|
package/src/tools/mac_alarm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* mac_alarm - macOS Clock app ile alarm kur (v5.1.1)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* macOS alarm — saat uygulamasi uzerinden alarm kurar
|
|
5
5
|
* Eski reminder_add date parse edemiyordu. Bu tool AppleScript ile
|
|
6
6
|
* Clock.app'in events sistemine yazar (alarm orada saklanir).
|
|
7
7
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* memory_write - Memory'ye fact/kayit yaz (v5.1.1)
|
|
3
3
|
*
|
|
4
4
|
* REPL'in extractMemoryFromMessage ozelligini tool olarak expose eder.
|
|
5
|
-
*
|
|
5
|
+
* Kalici hafiza — faktlari kaydeder, puanlar, eskiyenleri temizler
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const fs = require("fs");
|
|
@@ -133,7 +133,7 @@ function verifyMemoryWrite(username, expectedFact, expectedBotName) {
|
|
|
133
133
|
|
|
134
134
|
function addMemory({ username, fact, score = 5, category = "general", botName, nickname, name }) {
|
|
135
135
|
// Username yoksa ve 'name' parametresi varsa, onu username olarak kullan
|
|
136
|
-
// (
|
|
136
|
+
// (hitap bicimi icin)
|
|
137
137
|
const effectiveUsername = username || (name && name.toLowerCase()) || 'default';
|
|
138
138
|
if (!effectiveUsername || effectiveUsername === 'default') {
|
|
139
139
|
// Hicbir username yok, default.json'a yaz
|
|
@@ -232,8 +232,8 @@ module.exports = {
|
|
|
232
232
|
inputSchema: {
|
|
233
233
|
type: "object",
|
|
234
234
|
properties: {
|
|
235
|
-
username: { type: "string", description: "Kullanici adi (ornek: '
|
|
236
|
-
fact: { type: "string", description: 'Yeni fact (ornek: "Kullanici
|
|
235
|
+
username: { type: "string", description: "Kullanici adi (ornek: 'ahmet' veya 'default')" },
|
|
236
|
+
fact: { type: "string", description: 'Yeni fact (ornek: "Kullanici kahve seviyor", "Istanbul\'da yasiyor")' },
|
|
237
237
|
score: { type: "number", description: "Onem derecesi 1-10 (default 5)" },
|
|
238
238
|
category: { type: "string", description: "Kategori: personal, preference, work, hobby, fact (default general)" },
|
|
239
239
|
botName: { type: "string", description: "Bot adini degistir (memory.botName)" },
|
package/src/tools/plan.js
CHANGED
package/src/tools/plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* skills_marketplace - Skill marketplace (v5.0.0)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Skills marketplace — skill paylasimi ve otomatik yukleme
|
|
5
5
|
*
|
|
6
6
|
* Format: ~/.natureco/marketplace/<skill_name>.json
|
|
7
7
|
* Source: NatureCo GitHub repo (community-contributed) veya local
|
|
@@ -16,7 +16,7 @@ const MARKETPLACE_DIR = path.join(os.homedir(), ".natureco", "marketplace");
|
|
|
16
16
|
const SKILLS_DIR = path.join(os.homedir(), ".natureco", "skills");
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Marketplace URL'leri —
|
|
19
|
+
* Marketplace URL'leri — kullanici GitHub repo'su
|
|
20
20
|
*/
|
|
21
21
|
const MARKETPLACE_SOURCES = [
|
|
22
22
|
{
|
|
@@ -32,7 +32,7 @@ const MARKETPLACE_SOURCES = [
|
|
|
32
32
|
];
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Built-in skill paketleri
|
|
35
|
+
* Built-in skill paketleri
|
|
36
36
|
*/
|
|
37
37
|
const BUILTIN_SKILLS = {
|
|
38
38
|
"seo-audit": {
|
package/src/tools/soul.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* soul - SOUL.md, IDENTITY.md, AGENTS.md okuyucu (v5.4.12)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Soul dosyasi — kimlik, kisisellik ve calisma tarzini tanimlar
|
|
5
5
|
*
|
|
6
6
|
* 3 dosya sirayla okunur, ozetlenir ve system prompt'a enjekte edilir:
|
|
7
7
|
* 1. SOUL.md - KISILIK (nasil hissederim, kirmizi cizgiler, degerler)
|
|
8
|
-
* 2. IDENTITY.md - KIMLIK (kim
|
|
8
|
+
* 2. IDENTITY.md - KIMLIK (kim oldugu, baglam, calisma tarzi)
|
|
9
9
|
* 3. AGENTS.md - CALISMA ORTAMI (kurallar, tools, heartbeats)
|
|
10
10
|
*
|
|
11
11
|
* 3 seviyede arar:
|
package/src/tools/voice_chat.js
CHANGED
package/src/utils/api.js
CHANGED
|
@@ -10,6 +10,8 @@ const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
|
|
|
10
10
|
const { MCPClient } = require('./mcp-client');
|
|
11
11
|
const TB = require('./token-budget');
|
|
12
12
|
const { accumulateToolCallDeltas } = require('./streaming-tools');
|
|
13
|
+
const { ToolGuardrails } = require('./tool-guardrails');
|
|
14
|
+
const guardrails = new ToolGuardrails();
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* v5.5.0: Provider-specific format detection
|
|
@@ -756,44 +758,59 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
756
758
|
input: JSON.parse(tc.function.arguments)
|
|
757
759
|
}));
|
|
758
760
|
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
761
|
+
// Group MCP and local tools
|
|
762
|
+
const mcpTools = getMcpTools();
|
|
763
|
+
const mcpCalls = toolCalls.filter(tc => mcpTools.find(t => t.name === tc.name));
|
|
764
|
+
const localCalls = toolCalls.filter(tc => !mcpTools.find(t => t.name === tc.name));
|
|
765
|
+
|
|
766
|
+
// Guardrails: filter blocked tools
|
|
767
|
+
guardrails.reset();
|
|
768
|
+
guardrails.startIteration();
|
|
769
|
+
const blockedMcp = mcpCalls.filter(tc => {
|
|
770
|
+
const check = guardrails.check(tc.name, tc.input);
|
|
771
|
+
return check.blocked;
|
|
772
|
+
});
|
|
773
|
+
const blockedLocal = localCalls.filter(tc => {
|
|
774
|
+
const check = guardrails.check(tc.name, tc.input);
|
|
775
|
+
return check.blocked;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// Execute local tools in parallel (tool-runner already parallelizes safe tools)
|
|
779
|
+
let localResults = [];
|
|
780
|
+
if (localCalls.filter(tc => !blockedLocal.includes(tc)).length > 0) {
|
|
781
|
+
debugLog(`[Local] Executing ${localCalls.length} tool(s) concurrently`);
|
|
782
|
+
localResults = await executeToolCalls(
|
|
783
|
+
localCalls.filter(tc => !blockedLocal.includes(tc)),
|
|
784
|
+
{ toolDefinitions: getToolDefinitions() }
|
|
785
|
+
);
|
|
786
|
+
for (const r of localResults) {
|
|
787
|
+
guardrails.record(r.name, {}, r.result?.success !== false);
|
|
784
788
|
}
|
|
789
|
+
}
|
|
785
790
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
791
|
+
// Execute MCP tools in parallel (they're independent by nature)
|
|
792
|
+
let mcpResults = [];
|
|
793
|
+
if (mcpCalls.filter(tc => !blockedMcp.includes(tc)).length > 0) {
|
|
794
|
+
debugLog(`[MCP] Executing ${mcpCalls.length} tool(s) concurrently`);
|
|
795
|
+
mcpResults = await Promise.all(
|
|
796
|
+
mcpCalls.filter(tc => !blockedMcp.includes(tc)).map(async (tc) => {
|
|
797
|
+
const result = await executeMcpTool(tc.name, tc.input);
|
|
798
|
+
guardrails.record(tc.name, tc.input, result?.success !== false);
|
|
799
|
+
return { id: tc.id, name: tc.name, result };
|
|
800
|
+
})
|
|
801
|
+
);
|
|
790
802
|
}
|
|
791
803
|
|
|
792
|
-
// Add tool results
|
|
793
|
-
|
|
794
|
-
|
|
804
|
+
// Add blocked tool results as errors
|
|
805
|
+
const allResults = [
|
|
806
|
+
...blockedMcp.map(tc => ({ id: tc.id, name: tc.name, result: { error: `blocked_by_guardrails: ${tc.name}` } })),
|
|
807
|
+
...blockedLocal.map(tc => ({ id: tc.id, name: tc.name, result: { error: `blocked_by_guardrails: ${tc.name}` } })),
|
|
808
|
+
...localResults,
|
|
809
|
+
...mcpResults,
|
|
810
|
+
];
|
|
811
|
+
|
|
812
|
+
for (const result of allResults) {
|
|
795
813
|
const encodedContent = encodeToolResult(result.result);
|
|
796
|
-
|
|
797
814
|
messages.push({
|
|
798
815
|
role: 'tool',
|
|
799
816
|
tool_call_id: result.id,
|
package/src/utils/error.js
CHANGED
|
@@ -120,6 +120,15 @@ class MemoryStore {
|
|
|
120
120
|
return JSON.stringify({ success: true, message: 'Memory entry removed.', removed: removed[0] });
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
clear(target) {
|
|
124
|
+
const path = this._pathFor(target);
|
|
125
|
+
this._ensureDir();
|
|
126
|
+
fs.writeFileSync(path, '', 'utf8');
|
|
127
|
+
const entries = target === 'user' ? this._userEntries : this._memoryEntries;
|
|
128
|
+
entries.length = 0;
|
|
129
|
+
return JSON.stringify({ success: true, message: `Memory ${target} cleared.` });
|
|
130
|
+
}
|
|
131
|
+
|
|
123
132
|
replace(target, oldContent, newContent) {
|
|
124
133
|
if (!oldContent || !newContent) {
|
|
125
134
|
return JSON.stringify({ success: false, error: 'Both old and new content required.' });
|
package/src/utils/paths.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* paths.js — Path helper utilities (v5.2.0)
|
|
3
3
|
*
|
|
4
4
|
* Tum tool'larda ~/Desktop/test.txt gibi path'leri dogru handle etmek icin.
|
|
5
|
-
*
|
|
5
|
+
* "File does not exist" bug'i duzeltildi.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const os = require("os");
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
function buildTiers(opts) {
|
|
14
14
|
const {
|
|
15
|
-
botName
|
|
16
|
-
userName = '
|
|
15
|
+
botName,
|
|
16
|
+
userName = 'kullanıcı',
|
|
17
|
+
bossName = '',
|
|
18
|
+
bossTitle = '',
|
|
19
|
+
userTitle = '',
|
|
17
20
|
soulSummary = '',
|
|
18
21
|
isSmallModel = false,
|
|
19
22
|
memorySnapshotBlock = '',
|
|
@@ -23,19 +26,21 @@ function buildTiers(opts) {
|
|
|
23
26
|
hasHistory = false,
|
|
24
27
|
memoryFacts = [],
|
|
25
28
|
} = opts;
|
|
29
|
+
const displayBot = botName || 'Asistan';
|
|
30
|
+
const displayBoss = bossName || userName || 'kullanıcı';
|
|
26
31
|
|
|
27
32
|
// ── STABLE TIER (changes rarely — prefix cache friendly) ─────────────
|
|
28
33
|
const stable = [
|
|
29
34
|
// Identity (strong, repeated)
|
|
30
|
-
`SENIN ADIN: ${
|
|
31
|
-
`PATRONUN:
|
|
32
|
-
`
|
|
35
|
+
`SENIN ADIN: ${displayBot}. SADECE ${displayBot} adini kullan, model adi SOYLEME.`,
|
|
36
|
+
bossName ? `PATRONUN: ${bossName}${bossTitle ? ` (${bossTitle})` : ''}. Sana "${displayBoss}" diye hitap eder.` : `KULLANICI: ${userName}. Ona yardim ediyorsun.`,
|
|
37
|
+
userTitle ? `UNVANIN: ${userTitle}.` : '',
|
|
33
38
|
`KIRMIZI CIZGILERIN: Onaysiz para harcama yok, veri guvenligi, site stabilitesi, resmi taahhut yok.`,
|
|
34
|
-
`!!! KRİTİK KİMLİK !!! Senin ADIN: ${
|
|
39
|
+
`!!! KRİTİK KİMLİK !!! Senin ADIN: ${displayBot}. Bu SENİN GERÇEK ADIN. Sen ${displayBot}'sin.`,
|
|
35
40
|
`ASLA "MiniMax", "MiniMax-M2.5", "Claude", "GPT", "AI asistanı", "yapay zeka" gibi ifadeleri KENDİNİ tanıtmak için KULLANMA.`,
|
|
36
|
-
`Kullanıcı "adın ne?", "sen kimsin?" diye sorduğunda İLK cümlende MUTLAKA "Ben ${
|
|
37
|
-
`Senin adın: ${
|
|
38
|
-
`Sen bir marka veya ürün değilsin. Sen ${
|
|
41
|
+
`Kullanıcı "adın ne?", "sen kimsin?" diye sorduğunda İLK cümlende MUTLAKA "Ben ${displayBot}" yaz.`,
|
|
42
|
+
`Senin adın: ${displayBot}. Tekrar: ${displayBot}. Asla unutma: ${displayBot}.`,
|
|
43
|
+
`Sen bir marka veya ürün değilsin. Sen ${displayBot}sin, NatureCo CLI'nin Türkçe yapay zeka asistanısın.`,
|
|
39
44
|
|
|
40
45
|
// Personality (stable)
|
|
41
46
|
`Kisiselik: Sen samimi, sicak, dosta benzeyen bir asistansin. "Selam", "tamam", "hadi yapalim", "bak simdi", "sakin ol" gibi gunluk ifadeler kullan.`,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Guardrails — Hermes-style ToolCallGuardrailController
|
|
3
|
+
*
|
|
4
|
+
* Tracks per-iteration:
|
|
5
|
+
* - failure count per tool
|
|
6
|
+
* - repeated identical calls (same name + same args)
|
|
7
|
+
* - no-progress detection (all tools failing)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class ToolGuardrails {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.reset();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
reset() {
|
|
16
|
+
this.callLog = []; // [{ name, argsKey, iteration, success }]
|
|
17
|
+
this.failureCounts = {}; // { toolName: count }
|
|
18
|
+
this.iteration = 0;
|
|
19
|
+
this.blockedTools = new Set();
|
|
20
|
+
this.consecutiveFailures = {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
startIteration() {
|
|
24
|
+
this.iteration++;
|
|
25
|
+
// Decay consecutive failures each iteration (Hermes: decay factor 0.5)
|
|
26
|
+
for (const name of Object.keys(this.consecutiveFailures)) {
|
|
27
|
+
this.consecutiveFailures[name] = Math.floor(this.consecutiveFailures[name] * 0.5);
|
|
28
|
+
if (this.consecutiveFailures[name] <= 0) {
|
|
29
|
+
delete this.consecutiveFailures[name];
|
|
30
|
+
this.blockedTools.delete(name);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns blocked tool names if guardrails trigger.
|
|
37
|
+
*/
|
|
38
|
+
check(toolName, toolArgs) {
|
|
39
|
+
const argsKey = JSON.stringify(toolArgs || {});
|
|
40
|
+
|
|
41
|
+
// 1. Too many repeated identical calls (Hermes: same name+args in last 3 calls)
|
|
42
|
+
const identicalCount = this.callLog.filter(
|
|
43
|
+
c => c.name === toolName && c.argsKey === argsKey && c.iteration >= this.iteration - 2
|
|
44
|
+
).length;
|
|
45
|
+
if (identicalCount >= 2) {
|
|
46
|
+
return { blocked: true, reason: `repeated_call: ${toolName} called ${identicalCount + 1}x with same args` };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. Too many failures (Hermes: 2+ failures = blocked for this iteration)
|
|
50
|
+
const recentFailures = this.callLog.filter(
|
|
51
|
+
c => c.name === toolName && !c.success && c.iteration >= this.iteration - 1
|
|
52
|
+
).length;
|
|
53
|
+
if (recentFailures >= 2 || (this.consecutiveFailures[toolName] || 0) >= 2) {
|
|
54
|
+
this.blockedTools.add(toolName);
|
|
55
|
+
return { blocked: true, reason: `too_many_failures: ${toolName} failed ${recentFailures + 1}x` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { blocked: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Record a tool call result.
|
|
63
|
+
*/
|
|
64
|
+
record(toolName, toolArgs, success) {
|
|
65
|
+
const argsKey = JSON.stringify(toolArgs || {});
|
|
66
|
+
this.callLog.push({ name: toolName, argsKey, iteration: this.iteration, success });
|
|
67
|
+
|
|
68
|
+
if (!success) {
|
|
69
|
+
this.failureCounts[toolName] = (this.failureCounts[toolName] || 0) + 1;
|
|
70
|
+
this.consecutiveFailures[toolName] = (this.consecutiveFailures[toolName] || 0) + 1;
|
|
71
|
+
} else {
|
|
72
|
+
this.consecutiveFailures[toolName] = 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns true if no tool has succeeded this iteration (no-progress).
|
|
78
|
+
*/
|
|
79
|
+
isNoProgress() {
|
|
80
|
+
const thisIter = this.callLog.filter(c => c.iteration === this.iteration);
|
|
81
|
+
return thisIter.length > 0 && thisIter.every(c => !c.success);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { ToolGuardrails };
|
package/src/utils/tool-runner.js
CHANGED
|
@@ -146,13 +146,33 @@ async function executeTool(toolName, params, opts = {}) {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// ── Execute multiple tool calls
|
|
149
|
+
// ── Execute multiple tool calls (parallel for independent, sequential for others) ──
|
|
150
|
+
const PARALLEL_SAFE_TOOLS = new Set(['read_file', 'file_search', 'grep_search', 'web_search', 'web_readability', 'duckduckgo_search', 'exa_search', 'searxng_search', 'firecrawl', 'memory_search', 'memory']);
|
|
151
|
+
|
|
150
152
|
async function executeToolCalls(toolCalls, opts = {}) {
|
|
153
|
+
if (!toolCalls || toolCalls.length === 0) return [];
|
|
154
|
+
|
|
155
|
+
// Group: parallel-safe vs sequential
|
|
156
|
+
const safe = toolCalls.filter(c => PARALLEL_SAFE_TOOLS.has(c.name));
|
|
157
|
+
const sequential = toolCalls.filter(c => !PARALLEL_SAFE_TOOLS.has(c.name));
|
|
158
|
+
|
|
151
159
|
const results = [];
|
|
152
|
-
|
|
160
|
+
|
|
161
|
+
// Run parallel-safe tools concurrently
|
|
162
|
+
if (safe.length > 0) {
|
|
163
|
+
const safeResults = await Promise.all(safe.map(async (call) => {
|
|
164
|
+
const result = await executeTool(call.name, call.input, opts);
|
|
165
|
+
return { id: call.id, name: call.name, result };
|
|
166
|
+
}));
|
|
167
|
+
results.push(...safeResults);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Run sequential tools one at a time
|
|
171
|
+
for (const call of sequential) {
|
|
153
172
|
const result = await executeTool(call.name, call.input, opts);
|
|
154
173
|
results.push({ id: call.id, name: call.name, result });
|
|
155
174
|
}
|
|
175
|
+
|
|
156
176
|
return results;
|
|
157
177
|
}
|
|
158
178
|
|