natureco-cli 5.46.0 → 5.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/src/commands/gateway-server.js +132 -159
- package/src/utils/branding.js +4 -4
- package/src/utils/channel-brain.js +135 -0
- package/src/utils/tr-text.js +11 -0
- package/src/utils/tui.js +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to NatureCo CLI will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [5.47.1] - 2026-07-09 — "FIX: ASCII banner NATUREOO → NATURECO"
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Ana banner "NATUREOO" yazıyordu.** `src/utils/tui.js` ve `src/utils/branding.js`'deki ASCII logoda 7. harf (C) kapalı kutu (O) olarak çizilmişti; C'nin sağ tarafı açıldı. `chat.js`/`code.js`/README'lerdeki kopyalar zaten doğruydu.
|
|
9
|
+
|
|
10
|
+
## [5.47.0] - 2026-07-09 — "TEK BEYIN: her kanalda aynı kişilik + aynı hafıza"
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **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ı.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **`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).
|
|
17
|
+
- 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`).
|
|
18
|
+
- 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.
|
|
19
|
+
|
|
20
|
+
### Security
|
|
21
|
+
- **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).
|
|
22
|
+
- 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).
|
|
23
|
+
|
|
24
|
+
### Docs
|
|
25
|
+
- **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.**
|
|
26
|
+
|
|
5
27
|
## [5.46.0] - 2026-07-08 — "MEMORY: otomatik hijyen — yazma-anı dedup/çelişki uyarısı + oturum-sonu ipucu"
|
|
6
28
|
|
|
7
29
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.47.1",
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
//
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
708
|
-
|
|
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
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
)
|
|
1236
|
-
|
|
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
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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 {
|
|
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
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
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
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
)
|
|
1810
|
-
|
|
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);
|
package/src/utils/branding.js
CHANGED
|
@@ -24,10 +24,10 @@ const SMALL_LOGO = [
|
|
|
24
24
|
|
|
25
25
|
const FULL_LOGO = [
|
|
26
26
|
'███╗ ██╗ █████╗ ████████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ',
|
|
27
|
-
'████╗ ██║██╔══██╗╚══██╔══╝██║
|
|
28
|
-
'██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝█████╗ ██║
|
|
29
|
-
'██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║
|
|
30
|
-
'██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║
|
|
27
|
+
'████╗ ██║██╔══██╗╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔════╝ ██╔═══██╗',
|
|
28
|
+
'██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝█████╗ ██║ ██║ ██║',
|
|
29
|
+
'██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║ ██║ ██║',
|
|
30
|
+
'██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗╚██████╗ ╚██████╔╝',
|
|
31
31
|
'╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ',
|
|
32
32
|
];
|
|
33
33
|
|
|
@@ -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
|
+
};
|
package/src/utils/tr-text.js
CHANGED
|
@@ -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
|
*/
|
package/src/utils/tui.js
CHANGED
|
@@ -498,10 +498,10 @@ async function splash(options = {}) {
|
|
|
498
498
|
// Logo satırları (ASCII art)
|
|
499
499
|
const logo = [
|
|
500
500
|
'███╗ ██╗ █████╗ ████████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ',
|
|
501
|
-
'████╗ ██║██╔══██╗╚══██╔══╝██║
|
|
502
|
-
'██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝█████╗ ██║
|
|
503
|
-
'██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║
|
|
504
|
-
'██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║
|
|
501
|
+
'████╗ ██║██╔══██╗╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔════╝ ██╔═══██╗',
|
|
502
|
+
'██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝█████╗ ██║ ██║ ██║',
|
|
503
|
+
'██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║ ██║ ██║',
|
|
504
|
+
'██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗╚██████╗ ╚██████╔╝',
|
|
505
505
|
'╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ',
|
|
506
506
|
];
|
|
507
507
|
|