natureco-cli 5.46.0 → 5.47.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/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to NatureCo CLI will be documented in this file.
4
4
 
5
+ ## [5.47.0] - 2026-07-09 — "TEK BEYIN: her kanalda aynı kişilik + aynı hafıza"
6
+
7
+ ### Fixed
8
+ - **Split-brain: Telegram/WhatsApp'taki bot, terminaldekinden FARKLI kişilik ve hafızaya sahipti (kritik).** Mesajlaşma kanalları (Telegram, WhatsApp, Signal, IRC, Mattermost, iMessage, SMS) sabit İngilizce "You are a helpful X assistant" prompt'u + neredeyse boş legacy `universal-provider.json` hafızasıyla düz API passthrough'u kullanıyordu; terminal ise workflow orchestrator üzerinden gerçek personayı (`botName`) ve kullanıcı hafızasını (`<user>.json` + tree digest) alıyordu. Sonuç: terminalde her şeyi hatırlayan bot, Telegram'da kişiliksiz ve hafızasızdı.
9
+
10
+ ### Added
11
+ - **`src/utils/channel-brain.js` — tek beyin köprüsü.** Allow-list'teki (güvenilir) gönderenden gelen kanal mesajı artık terminaldekiyle **AYNI** workflow ajanına gider: aynı sistem mesajı, aynı persona, aynı kalıcı hafıza (flat + ağaç), aynı araçlar, aynı `memory_write`/`memory_tree` kayıt yolları. Kanal yalnızca taşıma katmanı; kişilik ve hafıza kanaldan bağımsız. Kanal-içi kısa-süre konuşma geçmişi `~/.natureco/channel-history/<kanal>_<sohbet>.json`'da tutulur (son 40 mesaj; modele son 12'si gider).
12
+ - Yanıtlar terminaldekiyle aynı model-adı temizliğinden geçer ("Ben MiniMax M2.5" → "Ben <botName>") ve Telegram 4096 limitine göre parçalanarak gönderilir (`chunkText`).
13
+ - 13 yeni regresyon testi (`test/utils/channel-brain.test.js`): sanitize desenleri, chunk bölme, workflow'a giden çağrı şekli, kanal-içi süreklilik, geçmiş sınırı, path-traversal güvenliği.
14
+
15
+ ### Security
16
+ - **Güvenilmeyen gönderen artık araçsız da (`noTools`).** Eski kanal yolu `sendMessage` varsayılanıyla tool tanımlarını da gönderiyordu; v5.43'ün "anonim kanala kişisel hafıza sızmaz" kuralına ek olarak artık araç erişimi de verilmiyor. Telegram gating'i de ortak `channelGate`'e taşındı (aynı semantik: allow-list doluysa dışındakiler engellenir, boşsa yanıt-ama-güvenilmez).
17
+ - Kanallardaki eski regex tabanlı `extractMemoryFromMessage` otomatik hafıza yazımı kaldırıldı (v5.40 dersi: regex çıkarımı yanlış fact üretir; güvenilir yol zaten ajanın bilinçli `memory_write`/`memory_tree` kayıtlarını kullanıyor).
18
+
19
+ ### Docs
20
+ - **foldTr'nin ı/i çakışması bilinçli taviz olarak belgelendi.** `src/utils/tr-text.js` JSDoc'una not eklendi: foldTr İ/I/ı/i'yi tek forma indirdiği için yalnız casing değil ı/i ayrımını da kaldırır → "kıl" (hair) ile "kil" (clay) aynılaşır. Bir arama/hafıza safety-net'i için "yanlış pozitif" < "yanlış negatif" olduğundan bilinçli tercih; birebir imla gereken yerde KULLANILMAMALI. 2 niyet-testi eklendi (regresyon değil). **Davranış değişmedi, sürüm yükseltmesi yok.**
21
+
5
22
  ## [5.46.0] - 2026-07-08 — "MEMORY: otomatik hijyen — yazma-anı dedup/çelişki uyarısı + oturum-sonu ipucu"
6
23
 
7
24
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.46.0",
3
+ "version": "5.47.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"
@@ -435,46 +435,35 @@ async function startWhatsAppProvider(sessionDir, config) {
435
435
  log('whatsapp', `Inbound message +${sender} -> +${ownNumber} (${cleanCommand.length} chars)`, 'cyan');
436
436
 
437
437
  try {
438
- const { sendMessage } = require('../utils/api');
439
- const { getMemoryPrompt, extractMemoryFromMessage, addMemoryEntry } = require('../utils/memory');
440
- const { getConfig, saveConfig } = require('../utils/config');
441
- const cfg = getConfig();
442
-
443
- log('whatsapp', 'Sending to AI provider...', 'cyan');
444
-
445
- // Use WhatsApp-specific conversation ID for persistent history
446
- const conversationId = `whatsapp_${sender.replace(/\D/g, '')}`;
447
-
448
- // Use same botId as terminal for shared memory
449
- const botId = 'universal-provider';
450
- const memoryPrompt = getMemoryPrompt(botId);
451
-
452
- // WhatsApp system prompt with memory
453
- let systemPrompt = `You are a helpful WhatsApp assistant. Keep responses concise and friendly. Use emojis when appropriate. If users ask for file operations or system commands, politely explain that those features are available in the terminal version.`;
454
-
455
- if (memoryPrompt) {
456
- systemPrompt += '\n\n' + memoryPrompt;
438
+ // v5.47 TEK BEYIN: allow-list'teki gonderen (veya sahibin kendi cihazi) =
439
+ // guvenilir terminaldekiyle ayni ajan. Aksi halde hafizasiz hafif yol.
440
+ const trusted = (msg.key.fromMe && isLID) ||
441
+ (allowedNumbers.length > 0 && allowedNumbers.some(n => numberMatches(n, sender)));
442
+ let reply = '';
443
+
444
+ if (trusted) {
445
+ log('whatsapp', 'Trusted sender unified agent brain', 'cyan');
446
+ const { runBrain } = require('../utils/channel-brain');
447
+ reply = await runBrain({ channel: 'whatsapp', chatKey: sender.replace(/\D/g, ''), text: cleanCommand });
448
+ } else {
449
+ log('whatsapp', 'Untrusted sender → memory-less lightweight path', 'gray');
450
+ const { sendMessage } = require('../utils/api');
451
+ const { getConfig } = require('../utils/config');
452
+ const cfg = getConfig();
453
+ const systemPrompt = `You are a helpful WhatsApp assistant. Keep responses concise and friendly. Use emojis when appropriate.`;
454
+ const response = await sendMessage(cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', cleanCommand, `whatsapp_${sender.replace(/\D/g, '')}`, systemPrompt, { noTools: true });
455
+ reply = response?.reply || response?.message || '';
457
456
  }
458
-
459
- const response = await sendMessage(cfg.providerApiKey || cfg.apiKey || '', botId, cleanCommand, conversationId, systemPrompt);
460
- const reply = response?.reply || response?.message || '';
461
-
457
+
462
458
  if (reply) {
463
459
  log('whatsapp', 'Sending reply...', 'cyan');
464
-
460
+
465
461
  await sock.sendMessage(msg.key.remoteJid, { text: reply });
466
-
462
+
467
463
  // Store last bot reply to prevent loop
468
464
  lastBotReply = reply;
469
-
465
+
470
466
  log('whatsapp', `Reply sent (${reply.length} chars)`, 'green');
471
-
472
- // Extract and save memory from user message
473
- const memoryEntries = extractMemoryFromMessage(messageText);
474
- for (const entry of memoryEntries) {
475
- addMemoryEntry(botId, entry.key, entry.value);
476
- log('whatsapp', `Memory saved: ${entry.key} = ${entry.value}`, 'gray');
477
- }
478
467
  } else {
479
468
  log('whatsapp', 'No reply from provider', 'yellow');
480
469
  }
@@ -672,54 +661,46 @@ async function startTelegramProvider(config) {
672
661
  return;
673
662
  }
674
663
 
675
- // Access control - check allowed chats
676
- const allowedChats = config.telegramAllowedChats || [];
677
- if (allowedChats.length > 0 && !allowedChats.includes(String(chatId))) {
664
+ // v5.47 TEK BEYIN: gonderen dogrulamasi channelGate ile (ayni semantik: allow-list
665
+ // doluysa disindakiler engellenir; bos ise herkes yanit alir ama guvenilmez sayilir).
666
+ const gate = channelGate(config, 'telegram', String(chatId));
667
+ if (!gate.allowed) {
678
668
  log('telegram', `Blocked message from chat ${chatId} (not in allowed list)`, 'yellow');
679
669
  return;
680
670
  }
681
-
671
+
682
672
  log('telegram', `Inbound message from chat ${chatId} (${messageText.length} chars)`, 'cyan');
683
-
673
+
684
674
  try {
685
- const { sendMessage } = require('../utils/api');
686
- const { getMemoryPrompt, extractMemoryFromMessage, addMemoryEntry } = require('../utils/memory');
687
- const { getConfig, saveConfig } = require('../utils/config');
688
- const cfg = getConfig();
689
-
690
- log('telegram', 'Sending to AI provider...', 'cyan');
691
-
692
- // Use Telegram-specific conversation ID for persistent history
693
- const conversationId = `telegram_${chatId}`;
694
-
695
- // Use same botId as terminal for shared memory
696
- const botId = 'universal-provider';
697
- const memoryPrompt = getMemoryPrompt(botId);
698
-
699
- // Telegram system prompt with memory
700
- let systemPrompt = `You are a helpful Telegram assistant. Keep responses concise and friendly. Use emojis when appropriate. If users ask for file operations or system commands, politely explain that those features are available in the terminal version.`;
701
-
702
- if (memoryPrompt) {
703
- systemPrompt += '\n\n' + memoryPrompt;
704
- }
705
-
706
675
  const cleanCommand = stripSlashPrefix(messageText);
707
- const response = await sendMessage(cfg.providerApiKey || cfg.apiKey || '', botId, cleanCommand, conversationId, systemPrompt);
708
- const reply = response?.reply || response?.message || '';
676
+ let reply = '';
677
+
678
+ if (gate.trusted) {
679
+ // Sahip: terminaldekiyle AYNI ajan — ayni persona, ayni kalici hafiza, ayni araclar
680
+ log('telegram', 'Trusted sender → unified agent brain', 'cyan');
681
+ const { runBrain } = require('../utils/channel-brain');
682
+ reply = await runBrain({ channel: 'telegram', chatKey: String(chatId), text: cleanCommand });
683
+ } else {
684
+ // Guvenilmeyen gonderen: hafizasiz + aracsiz hafif yol (v5.43 kurali)
685
+ log('telegram', 'Untrusted sender → memory-less lightweight path', 'gray');
686
+ const { sendMessage } = require('../utils/api');
687
+ const { getConfig } = require('../utils/config');
688
+ const cfg = getConfig();
689
+ const systemPrompt = `You are a helpful Telegram assistant. Keep responses concise and friendly. Use emojis when appropriate.`;
690
+ const response = await sendMessage(cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', cleanCommand, `telegram_${chatId}`, systemPrompt, { noTools: true });
691
+ reply = response?.reply || response?.message || '';
692
+ }
709
693
 
710
694
  if (reply) {
711
695
  log('telegram', 'Sending reply...', 'cyan');
712
-
713
- await bot.sendMessage(chatId, reply);
714
-
715
- log('telegram', `Reply sent (${reply.length} chars)`, 'green');
716
-
717
- // Extract and save memory from user message
718
- const memoryEntries = extractMemoryFromMessage(messageText);
719
- for (const entry of memoryEntries) {
720
- addMemoryEntry(botId, entry.key, entry.value);
721
- log('telegram', `Memory saved: ${entry.key} = ${entry.value}`, 'gray');
696
+
697
+ // Telegram mesaj limiti 4096 — uzun yaniti parcalara bol
698
+ const { chunkText } = require('../utils/channel-brain');
699
+ for (const part of chunkText(reply, 4000)) {
700
+ await bot.sendMessage(chatId, part);
722
701
  }
702
+
703
+ log('telegram', `Reply sent (${reply.length} chars)`, 'green');
723
704
  } else {
724
705
  log('telegram', 'No reply from provider', 'yellow');
725
706
  }
@@ -958,34 +939,26 @@ async function processSignalEnvelope(envelope, config) {
958
939
 
959
940
  // Get AI response
960
941
  try {
961
- const { sendMessage } = require('../utils/api');
962
- const { getMemoryPrompt, extractMemoryFromMessage, addMemoryEntry } = require('../utils/memory');
963
- const { getConfig, saveConfig } = require('../utils/config');
964
- const cfg = getConfig();
965
-
966
- const conversationId = `signal_${sender}`;
967
- const botId = 'universal-provider';
968
- const memoryPrompt = gate.trusted ? getMemoryPrompt(botId) : '';
969
-
970
- let systemPrompt = `You are a helpful Signal assistant. Keep responses concise.`;
971
- if (memoryPrompt) {
972
- systemPrompt += '\n\n' + memoryPrompt;
942
+ // v5.47 TEK BEYIN: guvenilir gonderen terminaldekiyle ayni ajani kullanir
943
+ let reply = '';
944
+ if (gate.trusted) {
945
+ const { runBrain } = require('../utils/channel-brain');
946
+ reply = await runBrain({ channel: 'signal', chatKey: sender, text: messageText });
947
+ } else {
948
+ const { sendMessage } = require('../utils/api');
949
+ const { getConfig } = require('../utils/config');
950
+ const cfg = getConfig();
951
+ const systemPrompt = `You are a helpful Signal assistant. Keep responses concise.`;
952
+ const response = await sendMessage(
953
+ cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', messageText,
954
+ `signal_${sender}`, systemPrompt, { noTools: true }
955
+ );
956
+ reply = response?.reply || response?.message || '';
973
957
  }
974
958
 
975
- const response = await sendMessage(
976
- cfg.providerApiKey || cfg.apiKey || '', botId, messageText,
977
- conversationId, systemPrompt
978
- );
979
- const reply = response?.reply || response?.message || '';
980
-
981
959
  if (reply) {
982
960
  await sendSignalMessage(config, sender, reply);
983
961
  log('signal', `Reply sent to ${sender} (${reply.length} chars)`, 'green');
984
-
985
- const memoryEntries = extractMemoryFromMessage(messageText);
986
- for (const entry of memoryEntries) {
987
- addMemoryEntry(botId, entry.key, entry.value);
988
- }
989
962
  }
990
963
  } catch (err) {
991
964
  log('signal', `Response error: ${err.message}`, 'red');
@@ -1215,25 +1188,25 @@ async function processIrcMessage(config, sender, target, text, socket) {
1215
1188
  const gate = channelGate(config, 'irc', sender);
1216
1189
  if (!gate.allowed) { log('irc', `Blocked (allow-list): ${sender}`, 'yellow'); return; }
1217
1190
  try {
1218
- const { sendMessage } = require('../utils/api');
1219
- const { getMemoryPrompt } = require('../utils/memory');
1220
- const { getConfig, saveConfig } = require('../utils/config');
1221
- const cfg = getConfig();
1222
-
1223
- const conversationId = `irc_${sender}_${target}`;
1224
- const botId = 'universal-provider';
1225
- const memoryPrompt = gate.trusted ? getMemoryPrompt(botId) : '';
1226
-
1227
- let systemPrompt = `You are a helpful IRC assistant. Keep responses concise. Use IRC-friendly formatting.`;
1228
- if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
1229
-
1230
1191
  // IRC handler's parameter is `text` (see signature above), not `messageText`.
1231
1192
  const cleanCommand = stripSlashPrefix(text);
1232
- const response = await sendMessage(
1233
- cfg.providerApiKey || cfg.apiKey || '', botId, cleanCommand,
1234
- conversationId, systemPrompt
1235
- );
1236
- const reply = response?.reply || response?.message || '';
1193
+
1194
+ // v5.47 TEK BEYIN: guvenilir gonderen terminaldekiyle ayni ajani kullanir
1195
+ let reply = '';
1196
+ if (gate.trusted) {
1197
+ const { runBrain } = require('../utils/channel-brain');
1198
+ reply = await runBrain({ channel: 'irc', chatKey: `${sender}_${target}`, text: cleanCommand });
1199
+ } else {
1200
+ const { sendMessage } = require('../utils/api');
1201
+ const { getConfig } = require('../utils/config');
1202
+ const cfg = getConfig();
1203
+ const systemPrompt = `You are a helpful IRC assistant. Keep responses concise. Use IRC-friendly formatting.`;
1204
+ const response = await sendMessage(
1205
+ cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', cleanCommand,
1206
+ `irc_${sender}_${target}`, systemPrompt, { noTools: true }
1207
+ );
1208
+ reply = response?.reply || response?.message || '';
1209
+ }
1237
1210
 
1238
1211
  if (reply) {
1239
1212
  const ircTarget = target.startsWith('#') ? target : sender;
@@ -1404,24 +1377,22 @@ async function handleMattermostPost(data, broadcast, config) {
1404
1377
  const gate = channelGate(config, 'mattermost', senderId);
1405
1378
  if (!gate.allowed) { log('mattermost', `Blocked (allow-list): ${senderId}`, 'yellow'); return; }
1406
1379
 
1407
- // Get AI response
1408
- const { sendMessage } = require('../utils/api');
1409
- const { getMemoryPrompt } = require('../utils/memory');
1410
- const { getConfig, saveConfig } = require('../utils/config');
1411
- const cfg = getConfig();
1412
-
1413
- const conversationId = `mattermost_${channelId}`;
1414
- const botId = 'universal-provider';
1415
- const memoryPrompt = gate.trusted ? getMemoryPrompt(botId) : '';
1416
-
1417
- let systemPrompt = `You are a helpful Mattermost assistant. Keep responses concise.`;
1418
- if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
1419
-
1420
- const response = await sendMessage(
1421
- cfg.providerApiKey || cfg.apiKey || '', botId, messageText,
1422
- conversationId, systemPrompt
1423
- );
1424
- const reply = response?.reply || response?.message || '';
1380
+ // Get AI response — v5.47 TEK BEYIN: guvenilir gonderen ayni ajani kullanir
1381
+ let reply = '';
1382
+ if (gate.trusted) {
1383
+ const { runBrain } = require('../utils/channel-brain');
1384
+ reply = await runBrain({ channel: 'mattermost', chatKey: String(channelId), text: messageText });
1385
+ } else {
1386
+ const { sendMessage } = require('../utils/api');
1387
+ const { getConfig } = require('../utils/config');
1388
+ const cfg = getConfig();
1389
+ const systemPrompt = `You are a helpful Mattermost assistant. Keep responses concise.`;
1390
+ const response = await sendMessage(
1391
+ cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', messageText,
1392
+ `mattermost_${channelId}`, systemPrompt, { noTools: true }
1393
+ );
1394
+ reply = response?.reply || response?.message || '';
1395
+ }
1425
1396
 
1426
1397
  if (reply) {
1427
1398
  await sendMattermostMessage(config, channelId, reply);
@@ -1607,26 +1578,27 @@ async function processImessageMessage(msg, config) {
1607
1578
  }
1608
1579
  log('imessage', `Slash command: /${cleanCommand.slice(0, 60)}`, 'yellow');
1609
1580
 
1610
- const { sendMessage } = require('../utils/api');
1611
- const { getMemoryPrompt } = require('../utils/memory');
1612
- const { getConfig, saveConfig } = require('../utils/config');
1581
+ const { getConfig } = require('../utils/config');
1613
1582
  const cfg = getConfig();
1614
1583
 
1615
1584
  // v5.43 GÜVENLİK: gönderen doğrulaması + hafıza sızıntısı önleme
1616
1585
  const gate = channelGate(cfg, 'imessage', sender);
1617
1586
  if (!gate.allowed) { log('imessage', `Blocked (allow-list): ${sender}`, 'yellow'); return; }
1618
- const conversationId = `imessage_${sender}`;
1619
- const botId = 'universal-provider';
1620
- const memoryPrompt = gate.trusted ? getMemoryPrompt(botId) : '';
1621
1587
 
1622
- let systemPrompt = `You are a helpful iMessage assistant. Keep responses concise.`;
1623
- if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
1624
-
1625
- const response = await sendMessage(
1626
- cfg.providerApiKey || cfg.apiKey || '', botId, cleanCommand,
1627
- conversationId, systemPrompt
1628
- );
1629
- const reply = response?.reply || response?.message || '';
1588
+ // v5.47 TEK BEYIN: guvenilir gonderen terminaldekiyle ayni ajani kullanir
1589
+ let reply = '';
1590
+ if (gate.trusted) {
1591
+ const { runBrain } = require('../utils/channel-brain');
1592
+ reply = await runBrain({ channel: 'imessage', chatKey: sender, text: cleanCommand });
1593
+ } else {
1594
+ const { sendMessage } = require('../utils/api');
1595
+ const systemPrompt = `You are a helpful iMessage assistant. Keep responses concise.`;
1596
+ const response = await sendMessage(
1597
+ cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', cleanCommand,
1598
+ `imessage_${sender}`, systemPrompt, { noTools: true }
1599
+ );
1600
+ reply = response?.reply || response?.message || '';
1601
+ }
1630
1602
 
1631
1603
  if (reply) {
1632
1604
  execSync(`${global.imessageProvider.imsgPath} send --to "${sender}" --text "${reply.replace(/"/g, '\\"')}" 2>/dev/null`, {
@@ -1785,29 +1757,30 @@ async function handleSmsWebhook(config, body, req) {
1785
1757
 
1786
1758
  // Get AI response
1787
1759
  try {
1788
- const { sendMessage } = require('../utils/api');
1789
- const { getMemoryPrompt } = require('../utils/memory');
1790
- const { getConfig, saveConfig } = require('../utils/config');
1791
- const cfg = getConfig();
1792
-
1793
1760
  // v5.43 GÜVENLİK: gönderen doğrulaması + hafıza sızıntısı önleme
1794
1761
  const gate = channelGate(config, 'sms', from);
1795
1762
  if (!gate.allowed) return { status: 200, body: { ok: true, blocked: true } };
1796
- const conversationId = `sms_${from}`;
1797
- const botId = 'universal-provider';
1798
- const memoryPrompt = gate.trusted ? getMemoryPrompt(botId) : '';
1799
-
1800
- let systemPrompt = `You are a helpful SMS assistant. Keep responses concise (SMS format).`;
1801
- if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
1802
1763
 
1803
1764
  // SMS handler binds the inbound to `text` (Twilio webhook body), unlike
1804
1765
  // Telegram/IRC which use `messageText`.
1805
1766
  const cleanCommand = stripSlashPrefix(text);
1806
- const response = await sendMessage(
1807
- cfg.providerApiKey || cfg.apiKey || '', botId, cleanCommand,
1808
- conversationId, systemPrompt
1809
- );
1810
- const reply = response?.reply || response?.message || '';
1767
+
1768
+ // v5.47 TEK BEYIN: guvenilir gonderen terminaldekiyle ayni ajani kullanir
1769
+ let reply = '';
1770
+ if (gate.trusted) {
1771
+ const { runBrain } = require('../utils/channel-brain');
1772
+ reply = await runBrain({ channel: 'sms', chatKey: from, text: cleanCommand });
1773
+ } else {
1774
+ const { sendMessage } = require('../utils/api');
1775
+ const { getConfig } = require('../utils/config');
1776
+ const cfg = getConfig();
1777
+ const systemPrompt = `You are a helpful SMS assistant. Keep responses concise (SMS format).`;
1778
+ const response = await sendMessage(
1779
+ cfg.providerApiKey || cfg.apiKey || '', 'universal-provider', cleanCommand,
1780
+ `sms_${from}`, systemPrompt, { noTools: true }
1781
+ );
1782
+ reply = response?.reply || response?.message || '';
1783
+ }
1811
1784
 
1812
1785
  if (reply) {
1813
1786
  await sendSmsMessage(config, from, reply);
@@ -0,0 +1,135 @@
1
+ // TEK BEYIN (v5.47): Terminal (chat/repl) ile mesajlasma kanallari (Telegram/WhatsApp/
2
+ // Signal/IRC/Mattermost/iMessage/SMS) AYNI ajani calistirir.
3
+ //
4
+ // Onceki durum (split-brain): her kanal kendi sabit "You are a helpful X assistant"
5
+ // prompt'u + legacy 'universal-provider.json' hafizasi + duz sendMessage passthrough'u
6
+ // kullaniyordu. Terminaldeki bot ise workflow orchestrator uzerinden gercek personayi
7
+ // (config.botName + memory botName), kullanici hafizasini (<userName>.json + legacy
8
+ // default.json) ve agac-hafiza digest'ini aliyordu. Sonuc: ayni bot terminalde "Hinata"
9
+ // olarak her seyi hatirlarken Telegram'da kisiliksiz ve hafizasiz davraniyordu.
10
+ //
11
+ // Artik: allow-list'teki (guvenilir) gonderen = CLI sahibi kabul edilir ve mesaji
12
+ // workflow.execute'a gider — terminalle AYNI sistem mesaji, AYNI kalici hafiza
13
+ // (flat + tree), AYNI araclar, AYNI memory_write/memory_tree kayit yollari. Kanal
14
+ // yalnizca bir tasima katmanidir; kisilik ve hafiza kanaldan bagimsizdir.
15
+ //
16
+ // GUVENLIK: Bu modul YALNIZCA guvenilir (channelGate.trusted) gonderen icin
17
+ // cagirilmalidir — workflow yolu arac calistirabilir (dosya, bash...). Guvenilmeyen
18
+ // gonderen eski hafizasiz passthrough yolunda kalir (v5.43 kurali: anonim kanala
19
+ // kisisel hafiza sizmaz, arac erisimi verilmez).
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const os = require('os');
23
+
24
+ // Kanal-bazli kisa-sure konusma gecmisi (terminal REPL'in oturum gecmisinin muadili).
25
+ // Kalici bilgi burada YASAMAZ (o memory_write/memory_tree'de) — bu sadece "az once ne
26
+ // konustuk" baglami. Kanal basina ayri dosya: Telegram sohbeti ile SMS sohbeti ayri
27
+ // akislardir; ortak olan kisilik + kalici hafizadir.
28
+ const HISTORY_DIR = path.join(os.homedir(), '.natureco', 'channel-history');
29
+ const MAX_HISTORY = 40; // diskte saklanan mesaj sayisi (user+assistant toplam)
30
+ const SEND_HISTORY = 12; // modele gonderilen son mesaj sayisi
31
+
32
+ function historyFile(channel, chatKey) {
33
+ const safe = String(chatKey).replace(/[^a-zA-Z0-9_+.-]/g, '_');
34
+ return path.join(HISTORY_DIR, `${channel}_${safe}.json`);
35
+ }
36
+
37
+ function loadHistory(channel, chatKey) {
38
+ try {
39
+ const arr = JSON.parse(fs.readFileSync(historyFile(channel, chatKey), 'utf8'));
40
+ return Array.isArray(arr) ? arr.slice(-MAX_HISTORY) : [];
41
+ } catch { return []; }
42
+ }
43
+
44
+ function saveHistory(channel, chatKey, history) {
45
+ try {
46
+ fs.mkdirSync(HISTORY_DIR, { recursive: true });
47
+ fs.writeFileSync(historyFile(channel, chatKey), JSON.stringify(history.slice(-MAX_HISTORY), null, 2), 'utf8');
48
+ } catch { /* gecmis kritik degil — kaydedilemezse sohbet yine calisir */ }
49
+ }
50
+
51
+ // repl.js'teki model-adi temizligiyle ayni desenler: model kendini "MiniMax/Claude/GPT"
52
+ // olarak tanitirsa kullanicinin verdigi bot adina cevrilir (kisilik tutarliligi).
53
+ function sanitizeReply(text, botName) {
54
+ let out = String(text == null ? '' : text);
55
+ // ONCE model-adi token'lari (repl.js ile ayni sira) — "Ben ..." kaliplarindan once
56
+ // kosmali, yoksa "Ben MiniMax M2.5" gibi ifadelerde ".5" artigi kalir.
57
+ out = out.replace(/\bMiniMax[-\s\w.\d]*/gi, botName);
58
+ out = out.replace(/\bM2\.5[-\s\w.\d]*/gi, botName);
59
+ out = out.replace(/\bM2[\s\-.\w\d]*/gi, botName);
60
+ out = out.replace(/\bClaude[-\s\w.\d]*/gi, botName);
61
+ out = out.replace(/Ben\s+GPT[^.!?,;:\n]*/gi, 'Ben ' + botName);
62
+ out = out.replace(/Ben\s+Asistan[\s\w.]*/gi, 'Ben ' + botName);
63
+ out = out.replace(/\*\*(?:GPT)[^*]*\*\*/gi, '**' + botName + '**');
64
+ return out;
65
+ }
66
+
67
+ // Uzun yaniti tasima katmaninin limitine bol (orn. Telegram 4096). Satir sinirinda
68
+ // bolmeye calisir; tek satir bile limiti asarsa sert keser.
69
+ function chunkText(text, maxLen) {
70
+ const chunks = [];
71
+ let rest = String(text == null ? '' : text);
72
+ while (rest.length > maxLen) {
73
+ let cut = rest.lastIndexOf('\n', maxLen);
74
+ if (cut < maxLen * 0.5) cut = maxLen;
75
+ chunks.push(rest.slice(0, cut));
76
+ rest = rest.slice(cut).replace(/^\n/, '');
77
+ }
78
+ if (rest) chunks.push(rest);
79
+ return chunks;
80
+ }
81
+
82
+ /**
83
+ * Guvenilir kanal mesajini terminaldekiyle AYNI ajana calistirir.
84
+ * @param {object} p
85
+ * @param {string} p.channel 'telegram' | 'whatsapp' | 'signal' | ...
86
+ * @param {string} p.chatKey kanal icindeki sohbet kimligi (chatId, numara, nick)
87
+ * @param {string} p.text kullanici mesaji
88
+ * @param {object} [deps] test icin enjekte edilebilir bagimliliklar
89
+ * @returns {Promise<string>} bot yaniti ('' donerse gonderilecek bir sey yok)
90
+ */
91
+ async function runBrain({ channel, chatKey, text }, deps = {}) {
92
+ const workflow = deps.workflow || require('../tools/workflow');
93
+ const getConfig = deps.getConfig || require('./config').getConfig;
94
+ const cfg = getConfig();
95
+ const botName = cfg.botName || 'Asistan';
96
+
97
+ const history = loadHistory(channel, chatKey);
98
+ const wf = await workflow.execute({
99
+ action: 'run',
100
+ task: text,
101
+ conversationHistory: history.slice(-SEND_HISTORY),
102
+ stream: false, // kanallar TTY degil — canli akis yok, tam yanit doner
103
+ });
104
+
105
+ let reply = '';
106
+ if (wf && wf.reply != null && String(wf.reply).trim()) {
107
+ reply = String(wf.reply);
108
+ } else if (wf && wf.status === 'completed' && Array.isArray(wf.results) && wf.results.length > 0) {
109
+ // Tool-calls yolu (OpenAI/Anthropic) plan+adim sonucu dondurebilir; kisa ozet uret
110
+ const lines = wf.results.map(r => {
111
+ const t = r.tool || r.name || '?';
112
+ const s = r.status === 'done' ? '✓' : '✗';
113
+ return `${s} ${t}`;
114
+ });
115
+ reply = 'Görev tamamlandı:\n' + lines.join('\n');
116
+ } else if (wf && wf.success === false) {
117
+ reply = 'Bir sorun oluştu: ' + (wf.error || 'yanıt alınamadı');
118
+ }
119
+
120
+ reply = sanitizeReply(reply, botName).trim();
121
+
122
+ if (reply) {
123
+ history.push({ role: 'user', content: text });
124
+ history.push({ role: 'assistant', content: reply });
125
+ saveHistory(channel, chatKey, history);
126
+ }
127
+ return reply;
128
+ }
129
+
130
+ module.exports = {
131
+ runBrain,
132
+ sanitizeReply,
133
+ chunkText,
134
+ _internal: { loadHistory, saveHistory, historyFile, HISTORY_DIR, MAX_HISTORY, SEND_HISTORY },
135
+ };
@@ -21,6 +21,17 @@
21
21
  * frequent), and collapsing them would create wrong matches. Only the notorious
22
22
  * i-dot case-folding trap is normalized.
23
23
  *
24
+ * BİLİNÇLİ TAVİZ — ı/i ÇAKIŞMASI:
25
+ * İ/i ve I/ı, Türkçe'de birbirinin büyük/küçük hali DEĞİL — iki ayrı harf çiftidir
26
+ * (İ↔i noktalı, I↔ı noktasız). foldTr bu dördünü tek forma indirdiği için, yalnızca
27
+ * casing'i değil, ı/i ayrımını da kaldırıyor. Sonuç: "kıl" (hair) ve "kil" (clay)
28
+ * gibi anlamca farklı kelimeler foldTr sonrası aynı olur ("kil"). Bu bilinçli bir
29
+ * tercih: bir arama/hafıza safety-net'i için "yanlış pozitif" (fazladan eşleşme),
30
+ * "yanlış negatif"ten (İstanbul'u hiç bulamamak gibi) daha az zararlı kabul edildi.
31
+ * Bu foldTr'yi tam bir yazım denetleyicisi değil, "bulunabilirlik öncelikli" bir
32
+ * normalize fonksiyonu yapıyor — birebir imla eşleştirmesi gereken bir yerde
33
+ * (örn. kullanıcıya gösterilecek metin, otomatik düzeltme) KULLANILMAMALI.
34
+ *
24
35
  * Used by both the agent's live recall (memory_tree search/remove) and the human CLI
25
36
  * search (memory-lint searchTree) so matching behaves identically everywhere.
26
37
  */