beast-agent 2.76.5 → 2.77.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.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/src/agent/bots.js +669 -664
  3. package/src/agent/customtools.js +6 -19
  4. package/src/agent/engine.js +55 -307
  5. package/src/agent/seeds/tool-yazma/SKILL.md +13 -16
  6. package/src/agent/skills.js +14 -35
  7. package/src/agent/supertonic.js +622 -0
  8. package/src/agent/tools.js +3 -7
  9. package/src/main.js +77 -6068
  10. package/src/preload.js +2 -17
  11. package/src/renderer/i18n.js +14 -8
  12. package/src/renderer/index.html +0 -297
  13. package/src/renderer/renderer.js +142 -2280
  14. package/src/renderer/style.css +3 -825
  15. package/src/agent/defaulttools/mt5_barlar2/run.js +0 -188
  16. package/src/agent/defaulttools/mt5_barlar2/tool.json +0 -23
  17. package/src/agent/defaulttools/mt5_bekleyen/run.js +0 -105
  18. package/src/agent/defaulttools/mt5_bekleyen/tool.json +0 -18
  19. package/src/agent/defaulttools/mt5_durum/run.js +0 -93
  20. package/src/agent/defaulttools/mt5_durum/tool.json +0 -10
  21. package/src/agent/defaulttools/mt5_emir/run.js +0 -98
  22. package/src/agent/defaulttools/mt5_emir/tool.json +0 -17
  23. package/src/agent/defaulttools/mt5_emir_iptal/run.js +0 -90
  24. package/src/agent/defaulttools/mt5_emir_iptal/tool.json +0 -12
  25. package/src/agent/defaulttools/mt5_fiyat/run.js +0 -87
  26. package/src/agent/defaulttools/mt5_fiyat/tool.json +0 -16
  27. package/src/agent/defaulttools/mt5_gecmis/run.js +0 -87
  28. package/src/agent/defaulttools/mt5_gecmis/tool.json +0 -12
  29. package/src/agent/defaulttools/mt5_kapat/run.js +0 -108
  30. package/src/agent/defaulttools/mt5_kapat/tool.json +0 -15
  31. package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +0 -60
  32. package/src/agent/defaulttools/mt5_m15_m5/run.js +0 -73
  33. package/src/agent/defaulttools/mt5_m15_m5/tool.json +0 -10
  34. package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +0 -144
  35. package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +0 -12
  36. package/src/agent/defaulttools/mt5_shot/run.js +0 -649
  37. package/src/agent/defaulttools/mt5_shot/stitch.js +0 -151
  38. package/src/agent/defaulttools/mt5_shot/tool.json +0 -25
  39. package/src/agent/defaulttools/mt5_sltp/run.js +0 -92
  40. package/src/agent/defaulttools/mt5_sltp/tool.json +0 -14
  41. package/src/agent/financetools.js +0 -1025
  42. package/src/agent/finindicators.js +0 -254
  43. package/src/agent/finrisk.js +0 -289
  44. package/src/agent/finstats.js +0 -239
  45. package/src/agent/finwatch.js +0 -302
  46. package/src/agent/mt5/BeastFinance.mq5 +0 -498
  47. package/src/agent/mt5/BeastShot.tpl +0 -0
  48. package/src/agent/mt5setup.js +0 -542
  49. package/src/agent/scripts/mt5_bridge.py +0 -575
  50. package/src/agent/seeds/haber-duygu/SKILL.md +0 -49
  51. package/src/agent/seeds/mql5/SKILL.md +0 -154
  52. package/src/agent/seeds/price-action/SKILL.md +0 -60
  53. package/src/agent/seeds/risk-yonetimi/SKILL.md +0 -52
  54. package/src/mt5bridge.js +0 -215
package/src/agent/bots.js CHANGED
@@ -1,664 +1,669 @@
1
- 'use strict';
2
-
3
- /* Beast Bot Sistemi (BÖLÜM 2 / 3 / 6 / 7)
4
- - İlk bot hep "Beast" (admin, silinemez). Toplam MAX 5 bot (1 admin + 4 müşteri).
5
- - Her bot izole klasörde yaşar: bots/<id>/
6
- config.json → botun temel ayarı (ayna)
7
- memory.md → botun KENDİ hafızası (diğer botlar göremez)
8
- yetkiler.json → görebildiği botlar + skill/plugin/tarayıcı yetkileri
9
- logs/changes.log → yetki/ayar değişiklik günlüğü (her değişiklik loglanır)
10
- chat/ plugins/ watchers/ → botun izole çalışma alanları
11
- - WhatsApp numaraları bot_id ile eşlenir; registry bots.json'da durur.
12
- whitelist.json aynası main tarafında senkronlanır. */
13
-
14
- const fs = require('fs');
15
- const path = require('path');
16
- const os = require('os');
17
- const memory = require('./memory'); // tokenize/scoreEntry yeniden kullanılır (döngü yok)
18
-
19
- const MAX_BOTS = 5;
20
-
21
- const ICONS = ['🦁', '🚀', '💎', '⭐', '🔥', '🧮', '📊', '🤖', '🦊', '🐼', '🎯', '🛠️'];
22
-
23
- /* Her botta varsayılan AÇIK gelen skill'ler (yetki sadece admin tarafından değişir) */
24
- const DEFAULT_SKILLS = {
25
- email: true,
26
- browser: true, // dahili tarayıcı varsayılan TÜM botlarda aktif
27
- web_search: true,
28
- run_command: false,
29
- memory: true,
30
- kb: true,
31
- };
32
-
33
- const SKILL_LIST = [
34
- ['email', 'E-posta (okuma/gönderme)'],
35
- ['browser', 'Dahili tarayıcı (panel)'],
36
- ['web_search', 'Web arama + sayfa okuma'],
37
- ['run_command', 'Terminal / dosya / ekran'],
38
- ['memory', 'Kendi hafızası (yazma/arama)'],
39
- ['kb', 'Bilgi bankası (arama/ekleme)'],
40
- ];
41
-
42
- const PLUGIN_LIST = ['fatura_okuyucu', 'crm_plugin', 'rapor_uretici', 'stok_takip', 'toplanti_ozet'];
43
-
44
- function beastRoot() {
45
- if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
46
- return process.env.APPDATA
47
- ? path.join(process.env.APPDATA, 'beast')
48
- : path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
49
- }
50
-
51
- function registryFile() {
52
- return path.join(beastRoot(), 'bots.json');
53
- }
54
-
55
- function botDir(id) {
56
- return path.join(beastRoot(), 'bots', String(id || '').replace(/[^a-z0-9_-]/gi, ''));
57
- }
58
-
59
- function nowIso() {
60
- return new Date().toISOString();
61
- }
62
-
63
- function uid() {
64
- return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
65
- }
66
-
67
- function foldTr(s) {
68
- return String(s || '')
69
- .toLowerCase()
70
- .replace(/[çğıöşüâîû]/g, (ch) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' }[ch] || ch));
71
- }
72
-
73
- function slugify(name) {
74
- const base = foldTr(String(name || 'bot'))
75
- .replace(/[^a-z0-9]+/g, '-')
76
- .replace(/^-+|-+$/g, '')
77
- .slice(0, 18);
78
- return base || 'bot';
79
- }
80
-
81
- function adminBot() {
82
- return {
83
- id: 'beast',
84
- name: 'Beast',
85
- icon: '🦁',
86
- admin: true,
87
- code: '', // 5 haneli benzersiz bot kodu (ensureBotCodes atar)
88
- prompt: '',
89
- perm: 'all',
90
- vis: true, // sohbet görünürlüğü: ana sohbete davet edilebilir
91
- skills: { ...DEFAULT_SKILLS, run_command: true },
92
- seeBots: [], // admin her şeyi görür
93
- extBrowser: true, // dış tarayıcı yetkisi
94
- browserDefault: 'dahili',
95
- extCommand: '',
96
- plugins: [],
97
- createdAt: nowIso(),
98
- };
99
- }
100
-
101
- /* ZORUNLU VARSAYILAN BOTLAR: Beast (admin) + Trader + Coder + Tool.
102
- Eksik olanlar OTOMATİK açılır — Tool botunun TEK görevi tool yazmaktır;
103
- diğer ajanlar/botlar tool_request ile ona devreder (cephane mantığı). */
104
- const DEFAULT_BOT_SEEDS = [
105
- {
106
- id: 'trader',
107
- name: 'Trader',
108
- icon: '📊',
109
- prompt:
110
- 'Sen BEAST Trader botsun: disiplinli bir trading analisti. Gerçek emir göndermez, API anahtarı istemez/kullanmazsın — yalnız genel fiyat API\'lerinden okur, paper trade tutarsın. Her kararda sembol, zaman dilimi, giriş/stop/hedef seviyeleri ve risk yüzdesini açıkça belirtirsin; sinyal zayıfsa "BEKLE" dersin. İzleyici (watcher) kurarak sürekli izlersin. Grafik doğrulaması: tool__mt5_shot ile MT5 grafiğinden GERÇEK PNG al ve grafiği gör (timeframes:["M1","M15"] ile çoklu periyot, symbols:["GOLD","EURUSD"] ile çoklu sembol TEK karede; yoksa computer_look; metin-model isen ocr_read source:"screen"/"browser"); web grafiklerinde browser_screenshot kullan. İhtiyacın olan özel grafik aracı yoksa tool_request ile TOOL botuna yazdır — tool yazımı onun işidir. Beast Finance MT5 terminaline bağlandığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK kurulur (AutoTrading + grafik panosu + seviye çizgileri); grafik/entegrasyon işlerinde bu EA ve mt5_ea köprüsü kullanılır — elle ekleme varsayma.',
111
- },
112
- {
113
- id: 'coder',
114
- name: 'Coder',
115
- icon: '🤖',
116
- prompt:
117
- 'Sen BEAST Coder botsun: disiplinli bir yazılım mühendisi. Küçük adımlarla plan çıkarır (todo), uygular, doğrularsın. Dosya düzenler, komut çalıştırır ve kısa net açıklamalar yaparsın; gereksiz konuşmazsın. Beast Finance tarafında BeastFinance.mq5 uzman danışmanı MT5 grafiğine otomatik kurulur; MT5/entegrasyon işleri bu EA ve mt5_ea köprüsü üzerinden yürür.',
118
- },
119
- {
120
- id: 'tool',
121
- name: 'Tool',
122
- icon: '🛠️',
123
- prompt:
124
- 'Sen BEAST TOOL botusun — TEK görevin TOOL YAZMAK. Başka hiçbir iş yapmazsın: analiz, genel sohbet, araştırma, kod projesi YOK; yalnız eksik/bozuk araçları yazar ve onarırsın. Her istek bir araç ihtiyacıdır. ÖNCE skill aracıyla "tool-yazma" skill\'ini oku ve sözleşmeye BİREBİR uy: araç %APPDATA%\\beast\\tools\\<slug>\\ klasöründe tool.json (tanım) + run.js (kod) olarak yaşar; slug a-z0-9_- 2-24 karakter. Sözleşme: stdin JSON argüman → stdout SADECE JSON sonuç; {ok:true,...} ya da {ok:false,error:"sebep"}; günlük/teşhis metnini console.error\'a yaz. Var olan aracı silip baştan yazma — edit_file ile GÜNCELLE. Yazdıktan sonra run_command ile TEST DÖNGÜSÜ işlet (echo \'{"mesaj":"test"}\' | node "%APPDATA%\\beast\\tools\\<slug>\\run.js"): çıktı JSON ve ok:true DEĞİLSE stderr\'i/hatayı oku → edit_file ile düzelt → YENİDEN test et; en fazla 5 deneme. Ancak test GEÇİNCE "YAYINDA" de; geçmeyen aracı yayınlama, raporda BAŞARISIZ + kök hatayı yaz. Ağ çağrılarında timeout kullan; şifre/API anahtarını ASLA loglama/yazma. İstek eksikse tek cümlelik varsayım yazıp çalışan aracı üret. Cevabın TEK paragraf: tool adı (tool__<slug>), ne yaptığı, adımlar (yazıldı → test → düzeltme → YAYINDA/BAŞARISIZ), doğrulama sonucu ve tek satır çağrı örneği. Başka ajanlara iş devretme — tool yazımı senin işin. TÜM ajanlar (Beast, finance trader/ekip, müşteri botları) tool_request ya da bot DM ile sana istek atar; her isteği araç ihtiyacı say ve sözleşmeye uygun yaz. Beast Finance ajanlarından grafik/ekran görüntüsü istekleri de gelir (ör. MT5 grafiğini PNG dosyasına kaydetme) — bunlar da araçtır, aynı sözleşmeyle yaz ve doğrula. ANCAK tool__mt5_shot (EA "shot" komutu → gerçek grafik PNG + görsel enjeksiyonu) ve tool__mt5_barlar2 VARSAYILAN KURULUDUR — silme/baştan yazma; istek bunları ilgilendiriyorsa yalnızca GÜNCELLE. BEASTFINANCE EA ARTIK VAR ve MT5 terminaline İLK GRAFIĞE OTOMATİK kurulur (derleme + AutoTrading dahil; kurmaya/kopyalamaya ÇALIŞMA). Beast Finance\'in ENTEGRASYON kanalı odur: heartbeat beast_ea.json, komut köprüsü beast_cmd.json/beast_cmd_ack.json, grafik panosu + seviye çizgileri beast_note.json (screenshot\'larda görünür); finance oturumlarında mt5_ea (status/ping/chart/note) aracıyla konuşulur. MT5 entegrasyonu isteği geldiğinde ÖNCE skill("mql5") oku ve köprüyü GENİŞLET (EA\'da ProcessCommands\'a yeni komut + python tarafında ea_cmd) — sıfırdan EA/script yazma.',
125
- },
126
- ];
127
-
128
- /* Eksik varsayılan botları oluştur (id sabit — tool_request 'tool' id'sini arar) */
129
- function ensureDefaultBots() {
130
- loadRegistry();
131
- let created = false;
132
- for (const def of DEFAULT_BOT_SEEDS) {
133
- const existing = REG.bots.find((b) => b && b.id === def.id);
134
- if (existing) {
135
- /* VARSAYILAN PROMPT GÜNCELLEMESİ (tek seferlik ek): eski sürümde
136
- oluşmuş Tool botunun promptunda mql5/edit_file/skill vurgusu yoksa
137
- eksik parçalar EKLENİR — kullanıcı düzenlemesi ezilmez. */
138
- if (def.id === 'tool') {
139
- const p = String(existing.prompt || '');
140
- const add = [];
141
- if (!/skill\(["']mql5["']\)/.test(p)) add.push('MQL5/gösterge/EA işlerinde ÖNCE skill("mql5") oku ve prosedürüne uy.');
142
- if (!/edit_file/.test(p)) add.push('Var olan dosyayı edit_file ile GÜNCELLE; yeniyi write_file ile yaz; içerik aramada grep, dosya adı aramada glob kullan.');
143
- if (!/skill\(["']tool-yazma["']\)/.test(p)) add.push('Tool yazmadan önce skill("tool-yazma") oku ve sözleşmeye birebir uy.');
144
- if (add.length) {
145
- existing.prompt = (p ? p + ' ' : '') + add.join(' ');
146
- created = true;
147
- logChange(def.id, 'varsayılan prompt ekleri eklendi (mql5/edit_file/skill)');
148
- }
149
- }
150
- continue;
151
- }
152
- if (REG.bots.length >= MAX_BOTS) break;
153
- const bot = {
154
- id: def.id,
155
- name: def.name,
156
- icon: ICONS.includes(def.icon) ? def.icon : '🤖',
157
- admin: false,
158
- code: '',
159
- prompt: String(def.prompt || '').slice(0, 4000),
160
- perm: 'all',
161
- vis: true,
162
- /* Tool botu araç yazıp doğrulayabilmeli: run_command/read/write açık */
163
- skills: { ...DEFAULT_SKILLS, run_command: def.id === 'tool' },
164
- seeBots: [],
165
- extBrowser: false,
166
- browserDefault: 'dahili',
167
- extCommand: '',
168
- plugins: [],
169
- createdAt: nowIso(),
170
- };
171
- REG.bots.push(bot);
172
- created = true;
173
- logChange(def.id, `varsayılan bot otomatik oluşturuldu (name="${def.name}")`);
174
- }
175
- if (created) {
176
- ensureBotCodes();
177
- saveRegistry();
178
- ensureDirs();
179
- }
180
- return created;
181
- }
182
-
183
- /* ---------- registry ---------- */
184
-
185
- let REG = null;
186
-
187
- function loadRegistry() {
188
- if (REG) return REG;
189
- try {
190
- REG = JSON.parse(fs.readFileSync(registryFile(), 'utf8'));
191
- } catch {
192
- REG = null;
193
- }
194
- if (!REG || !Array.isArray(REG.bots)) REG = { bots: [adminBot()] };
195
- /* beast her zaman ilk ve admin */
196
- const beast = REG.bots.find((b) => b && b.id === 'beast');
197
- if (!beast) REG.bots.unshift(adminBot());
198
- else Object.assign(beast, { ...adminBot(), ...beast, id: 'beast', admin: true, name: beast.name || 'Beast' });
199
- /* ZORUNLU bot seti: Beast + Trader + Coder + Tool (eksikler otomatik açılır) */
200
- ensureDefaultBots();
201
- saveRegistry();
202
- ensureDirs();
203
- return REG;
204
- }
205
-
206
- function saveRegistry() {
207
- try {
208
- fs.mkdirSync(beastRoot(), { recursive: true });
209
- fs.writeFileSync(registryFile(), JSON.stringify(REG, null, 2));
210
- } catch {}
211
- }
212
-
213
- function ensureDirs() {
214
- for (const b of REG.bots) {
215
- const d = botDir(b.id);
216
- try {
217
- for (const sub of ['logs', 'chat', 'plugins', 'watchers']) fs.mkdirSync(path.join(d, sub), { recursive: true });
218
- const cfgP = path.join(d, 'config.json');
219
- if (!fs.existsSync(cfgP)) fs.writeFileSync(cfgP, JSON.stringify(botConfigOf(b), null, 2));
220
- const yetP = path.join(d, 'yetkiler.json');
221
- if (!fs.existsSync(yetP)) fs.writeFileSync(yetP, JSON.stringify(botPermsOf(b), null, 2));
222
- /* bot hafızası ayarlardaki yapıyla aynı: SOUL.md + MEMORY.md + USER.md
223
- (eski memory.md varsa içeriği MEMORY.md'ye taşınır) */
224
- const legacyP = path.join(d, 'memory.md');
225
- const legacy = fs.existsSync(legacyP) ? fs.readFileSync(legacyP, 'utf8') : '';
226
- const memP = path.join(d, 'MEMORY.md');
227
- if (!fs.existsSync(memP)) fs.writeFileSync(memP, legacy || '');
228
- for (const f of ['SOUL.md', 'USER.md']) {
229
- const p = path.join(d, f);
230
- if (!fs.existsSync(p)) fs.writeFileSync(p, '');
231
- }
232
- } catch {}
233
- }
234
- }
235
-
236
- function botConfigOf(b) {
237
- return {
238
- id: b.id,
239
- name: b.name,
240
- icon: b.icon,
241
- admin: !!b.admin,
242
- code: b.code || '',
243
- prompt: b.prompt || '',
244
- browser: { default: b.browserDefault || 'dahili', extCommand: b.extCommand || '' },
245
- plugins: b.plugins || [],
246
- createdAt: b.createdAt,
247
- };
248
- }
249
-
250
- function botPermsOf(b) {
251
- return {
252
- id: b.id,
253
- perm: b.perm || 'all',
254
- skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) },
255
- seeBots: b.seeBots || [],
256
- extBrowser: b.extBrowser !== false,
257
- };
258
- }
259
-
260
- /* ---------- log ---------- */
261
-
262
- function logChange(id, text) {
263
- try {
264
- const d = botDir(id);
265
- fs.mkdirSync(path.join(d, 'logs'), { recursive: true });
266
- fs.appendFileSync(path.join(d, 'logs', 'changes.log'), `[${new Date().toISOString()}] ${text}\n`);
267
- } catch {}
268
- }
269
-
270
- /* ---------- public API ---------- */
271
-
272
- function list() {
273
- loadRegistry();
274
- return REG.bots.map((b) => ({ ...b, skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) } }));
275
- }
276
-
277
- function get(id) {
278
- loadRegistry();
279
- return REG.bots.find((b) => b.id === String(id || '')) || null;
280
- }
281
-
282
- function add({ name, icon, prompt }) {
283
- loadRegistry();
284
- const n = String(name || '').trim().slice(0, 40);
285
- if (!n) return { ok: false, error: 'bot adı zorunlu' };
286
- /* İLK HARF ZORUNLU: bot adı harf karakteriyle başlamalı */
287
- if (!/^\p{L}/u.test(n)) return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
288
- if (REG.bots.length >= MAX_BOTS) return { ok: false, error: `en fazla ${MAX_BOTS} bot olabilir (1 admin + ${MAX_BOTS - 1} müşteri)` };
289
- let id = slugify(n);
290
- while (get(id)) id = slugify(n) + '-' + uid().slice(-4);
291
- const bot = {
292
- id,
293
- name: n,
294
- icon: ICONS.includes(icon) ? icon : '🤖',
295
- admin: false,
296
- code: '', // ensureBotCodes hemen altında benzersiz 5 hane atar
297
- prompt: String(prompt || '').slice(0, 4000),
298
- perm: 'all',
299
- vis: true, // sohbet görünürlüğü (admin matristen kapatır)
300
- skills: { ...DEFAULT_SKILLS },
301
- seeBots: [], // varsayılan: hiçbir bot baka botu göremez
302
- extBrowser: false, // dış tarayıcı sadece yetkilide
303
- browserDefault: 'dahili',
304
- extCommand: '',
305
- plugins: [],
306
- createdAt: nowIso(),
307
- };
308
- REG.bots.push(bot);
309
- ensureBotCodes();
310
- saveRegistry();
311
- ensureDirs();
312
- logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon}, code=${bot.code})`);
313
- return { ok: true, bot: { ...bot } };
314
- }
315
-
316
- /* ---------- 5 HANELİ BENZERSİZ BOT KODU ----------
317
- Her bot oluşurken 10000-99999 arası rastgele kod alır; duplicate kontrolü
318
- zorunlu (tüm botlara karşı). Açılışta kodu eksik/çakışık botlara da atanır. */
319
- function ensureBotCodes() {
320
- loadRegistry();
321
- let dirty = false;
322
- const used = () => new Set(REG.bots.map((b) => String(b.code || '')).filter((c) => /^\d{5}$/.test(c)));
323
- const seen = new Set();
324
- for (const b of REG.bots) {
325
- const cur = String(b.code || '');
326
- if (/^\d{5}$/.test(cur) && !seen.has(cur)) { seen.add(cur); continue; }
327
- const u = used();
328
- let c;
329
- do { c = String(10000 + Math.floor(Math.random() * 90000)); } while (u.has(c));
330
- logChange(b.id, `bot kodu atandı: ${c}${cur ? ` (eskisi geçersizdi: ${cur})` : ''}`);
331
- b.code = c;
332
- seen.add(c);
333
- dirty = true;
334
- }
335
- if (dirty) saveRegistry();
336
- return dirty;
337
- }
338
-
339
- function byCode(code) {
340
- const c = String(code || '').replace(/\D/g, '');
341
- if (!/^\d{5}$/.test(c)) return null;
342
- loadRegistry();
343
- return REG.bots.find((b) => String(b.code || '') === c) || null;
344
- }
345
-
346
- function update(id, patch) {
347
- loadRegistry();
348
- const b = get(id);
349
- if (!b) return { ok: false, error: 'bot yok' };
350
- /* İLK HARF ZORUNLU: ad değişikliği harf karakteriyle başlamalı —
351
- diğer alanlara dokunmadan reddet */
352
- if (patch && typeof patch.name === 'string' && patch.name.trim() &&
353
- !/^\p{L}/u.test(patch.name.trim())) {
354
- return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
355
- }
356
- const changes = [];
357
- if (patch && typeof patch === 'object') {
358
- if (typeof patch.name === 'string' && patch.name.trim() && patch.name.trim() !== b.name) {
359
- changes.push(`name: "${b.name}" → "${patch.name.trim().slice(0, 40)}"`);
360
- b.name = patch.name.trim().slice(0, 40);
361
- }
362
- if (typeof patch.icon === 'string' && ICONS.includes(patch.icon) && patch.icon !== b.icon) {
363
- changes.push(`icon: ${b.icon} → ${patch.icon}`);
364
- b.icon = patch.icon;
365
- }
366
- if (typeof patch.prompt === 'string' && patch.prompt !== b.prompt) {
367
- changes.push('prompt güncellendi');
368
- b.prompt = String(patch.prompt).slice(0, 4000);
369
- }
370
- if (patch.perm !== undefined) {
371
- /* 'all' tek başına tüm araçları verir; web/read/chat çoklu seçilebilir
372
- (['web','read'] gibi dizi ya da 'web,read' gibi string kabul edilir) */
373
- const PERMS_ALL = ['all', 'web', 'read', 'chat'];
374
- let nextPerm = null;
375
- if (Array.isArray(patch.perm)) {
376
- const picked = [...new Set(patch.perm.map((x) => String(x).trim()).filter((x) => PERMS_ALL.includes(x)))];
377
- nextPerm = picked.includes('all') ? 'all' : (picked.length ? picked : 'chat');
378
- } else if (typeof patch.perm === 'string') {
379
- const arr = patch.perm.split(',').map((x) => x.trim()).filter((x) => PERMS_ALL.includes(x));
380
- if (arr.length) nextPerm = arr.includes('all') ? 'all' : (arr.length === 1 ? arr[0] : arr);
381
- }
382
- if (nextPerm !== null && JSON.stringify(nextPerm) !== JSON.stringify(b.perm)) {
383
- const fp = (p) => (Array.isArray(p) ? '[' + p.join('+') + ']' : String(p));
384
- changes.push(`perm: ${fp(b.perm)} → ${fp(nextPerm)}`);
385
- b.perm = nextPerm;
386
- }
387
- }
388
- if (patch.skills && typeof patch.skills === 'object') {
389
- const merged = { ...DEFAULT_SKILLS, ...(b.skills || {}) };
390
- for (const k of Object.keys(DEFAULT_SKILLS)) {
391
- if (typeof patch.skills[k] === 'boolean' && patch.skills[k] !== merged[k]) {
392
- changes.push(`skill ${k}: ${merged[k]} → ${patch.skills[k]}`);
393
- merged[k] = patch.skills[k];
394
- }
395
- }
396
- b.skills = merged;
397
- }
398
- if (Array.isArray(patch.seeBots)) {
399
- const valid = patch.seeBots.map(String).filter((x) => x !== b.id && get(x));
400
- const next = [...new Set(valid)];
401
- if (JSON.stringify(next) !== JSON.stringify(b.seeBots || [])) {
402
- changes.push(`görebilir: [${(b.seeBots || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
403
- b.seeBots = next;
404
- }
405
- }
406
- if (typeof patch.extBrowser === 'boolean' && patch.extBrowser !== b.extBrowser) {
407
- changes.push(`dış tarayıcı yetkisi: ${b.extBrowser} → ${patch.extBrowser}`);
408
- b.extBrowser = patch.extBrowser;
409
- }
410
- if (typeof patch.vis === 'boolean' && patch.vis !== (b.vis !== false)) {
411
- changes.push(`sohbet görünürlüğü: ${b.vis !== false} → ${patch.vis}`);
412
- b.vis = patch.vis;
413
- }
414
- if (['dahili', 'dis'].includes(patch.browserDefault) && patch.browserDefault !== b.browserDefault) {
415
- changes.push(`varsayılan tarayıcı: ${b.browserDefault} → ${patch.browserDefault}`);
416
- b.browserDefault = patch.browserDefault;
417
- }
418
- if (typeof patch.extCommand === 'string' && patch.extCommand !== b.extCommand) {
419
- changes.push('dış tarayıcı komutu güncellendi');
420
- b.extCommand = String(patch.extCommand).slice(0, 200);
421
- }
422
- if (typeof patch.model === 'string') {
423
- /* bot bazlı model override — boş string = global seçim */
424
- const next = patch.model.trim().slice(0, 160);
425
- if (next !== (b.model || '')) {
426
- changes.push('model güncellendi');
427
- b.model = next;
428
- }
429
- }
430
- if (Array.isArray(patch.plugins)) {
431
- const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
432
- if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
433
- changes.push(`pluginler: [${(b.plugins || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
434
- b.plugins = next;
435
- }
436
- }
437
- }
438
- saveRegistry();
439
- ensureDirs();
440
- try {
441
- const d = botDir(b.id);
442
- fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify(botConfigOf(b), null, 2));
443
- fs.writeFileSync(path.join(d, 'yetkiler.json'), JSON.stringify(botPermsOf(b), null, 2));
444
- } catch {}
445
- for (const c of changes) logChange(b.id, `yetki/ayar değişikliği (admin): ${c}`);
446
- return { ok: true, bot: { ...b }, changed: changes.length };
447
- }
448
-
449
- function remove(id) {
450
- loadRegistry();
451
- const b = get(id);
452
- if (!b) return { ok: false, error: 'bot yok' };
453
- if (b.admin) return { ok: false, error: 'admin bot silinemez' };
454
- REG.bots = REG.bots.filter((x) => x.id !== b.id);
455
- saveRegistry();
456
- logChange(b.id, `bot SİLİNDİ — bağlı numaralar botsuz duruma düştü (beast'e yönlendirilir)`);
457
- /* klasör arşivlenir — veri kaybı olmasın */
458
- try {
459
- const d = botDir(b.id);
460
- if (fs.existsSync(d)) fs.renameSync(d, d + '-arsiv-' + Date.now().toString(36));
461
- } catch {}
462
- return { ok: true };
463
- }
464
-
465
- /* viewer botun target botu görüp göremeyeceği */
466
- function canSee(viewerId, targetId) {
467
- const v = get(viewerId);
468
- if (!v) return false;
469
- if (v.admin) return true; // admin her şeyi görür
470
- if (viewerId === targetId) return true;
471
- return (v.seeBots || []).includes(String(targetId));
472
- }
473
-
474
- /* botun hafıza dosyaları — ayarlardaki SOUL/USER/MEMORY üçlüsünün bot izole hali */
475
- const BOT_MEM_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md'];
476
-
477
- /* botun MEMORY.md'si (persona bloğuna giden kısım) */
478
- function readMemory(id, cap = 2000) {
479
- try {
480
- return fs.readFileSync(path.join(botDir(id), 'MEMORY.md'), 'utf8').slice(0, cap).trim();
481
- } catch {
482
- return '';
483
- }
484
- }
485
-
486
- function readMemoryFiles(id) {
487
- const d = botDir(id);
488
- const out = { soul: '', memory: '', user: '' };
489
- try { out.soul = fs.readFileSync(path.join(d, 'SOUL.md'), 'utf8'); } catch {}
490
- try { out.memory = fs.readFileSync(path.join(d, 'MEMORY.md'), 'utf8'); } catch {}
491
- try { out.user = fs.readFileSync(path.join(d, 'USER.md'), 'utf8'); } catch {}
492
- return out;
493
- }
494
-
495
- function writeMemoryFile(id, file, content) {
496
- const b = get(id);
497
- if (!b) return { ok: false, error: 'bot yok' };
498
- if (!BOT_MEM_FILES.includes(file)) return { ok: false, error: 'geçersiz dosya' };
499
- try {
500
- const d = botDir(id);
501
- fs.mkdirSync(d, { recursive: true });
502
- fs.writeFileSync(path.join(d, file), String(content ?? ''), 'utf8');
503
- /* admin MEMORY.md'yi elle değiştirdiyse botun mem0 store'unu yeniden kur */
504
- if (file === 'MEMORY.md') {
505
- try {
506
- const mem0 = require('./mem0');
507
- mem0.reindexFromLines('bot:' + id, String(content ?? '').split('\n').map((l) => l.replace(/^[-*]\s*/, '').trim()).filter(Boolean));
508
- } catch {}
509
- }
510
- logChange(id, `${file} admin tarafından güncellendi`);
511
- return { ok: true };
512
- } catch (e) {
513
- return { ok: false, error: String((e && e.message) || e) };
514
- }
515
- }
516
-
517
- /* botun MEMORY.md ayna dosyasının yolu (mem0 syncMirror kullanır) */
518
- function memPath(id) {
519
- return path.join(botDir(id), 'MEMORY.md');
520
- }
521
-
522
- /* ---------- BOT OTURUMU hafıza operasyonları ----------
523
- Bot oturumlarında memory_write/memory_search GLOBAL Beast hafızasına DEĞİL,
524
- botun kendi SOUL/USER/MEMORY dosyalarına gider (tam izolasyon). */
525
-
526
- function readMem(id, f) {
527
- if (!BOT_MEM_FILES.includes(f)) return '';
528
- try {
529
- return fs.readFileSync(path.join(botDir(id), f), 'utf8');
530
- } catch {
531
- return '';
532
- }
533
- }
534
-
535
- function memEntries(id) {
536
- return readMem(id, 'MEMORY.md')
537
- .split('\n')
538
- .map((l) => l.replace(/^[-*]\s*/, '').trim())
539
- .filter(Boolean);
540
- }
541
-
542
- function appendMem(id, text) {
543
- const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 500);
544
- if (!t) return { ok: false, error: 'empty' };
545
- const list = memEntries(id);
546
- const key = foldTr(t).replace(/[^a-z0-9]+/g, ' ').trim();
547
- if (list.some((l) => foldTr(l).replace(/[^a-z0-9]+/g, ' ').trim() === key)) {
548
- return { ok: true, duplicate: true };
549
- }
550
- try {
551
- fs.mkdirSync(botDir(id), { recursive: true });
552
- fs.appendFileSync(path.join(botDir(id), 'MEMORY.md'), '- ' + t + '\n');
553
- return { ok: true };
554
- } catch (e) {
555
- return { ok: false, error: String((e && e.message) || e) };
556
- }
557
- }
558
-
559
- /* botun USER.md'si: bota özel kullanıcı profili (aynı dedup/yenile mantığı) */
560
- function appendUserMem(id, text) {
561
- const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 300);
562
- if (!t) return { ok: false, error: 'empty' };
563
- try {
564
- const p = path.join(botDir(id), 'USER.md');
565
- fs.mkdirSync(botDir(id), { recursive: true });
566
- const lines = readMem(id, 'USER.md')
567
- .split('\n')
568
- .map((l) => l.replace(/^[-*]\s*/, '').trim())
569
- .filter(Boolean);
570
- /* profil anahtarı: "Konu: ..." satırlarında Konu — değişiklikte güncelle */
571
- const topicOf = (s) => {
572
- const c = foldTr(s).indexOf(':');
573
- return (c > 0 ? foldTr(s).slice(0, c) : foldTr(s)).replace(/[^a-z0-9]+/g, ' ').trim();
574
- };
575
- const topic = topicOf(t);
576
- const idx = lines.findIndex((l) => topicOf(l) === topic);
577
- if (idx >= 0) {
578
- if (lines[idx] === t) return { ok: true, duplicate: true };
579
- lines[idx] = t;
580
- } else {
581
- lines.push(t);
582
- }
583
- while (lines.length > 60) lines.shift();
584
- fs.writeFileSync(p, lines.map((l) => '- ' + l).join('\n') + '\n');
585
- return { ok: true, updated: idx >= 0 };
586
- } catch (e) {
587
- return { ok: false, error: String((e && e.message) || e) };
588
- }
589
- }
590
-
591
- function searchMem(id, query, limit = 8) {
592
- const list = memEntries(id);
593
- if (!list.length) return [];
594
- const qTokens = memory.tokenize(query);
595
- let rows;
596
- if (!qTokens.length) {
597
- rows = list.map((text, i) => ({ text, score: 0, i })).slice(-limit).reverse();
598
- } else {
599
- rows = list
600
- .map((text, i) => ({ text, i, score: Number(memory.scoreEntry(text, qTokens).toFixed(3)) }))
601
- .filter((r) => r.score > 0)
602
- .sort((a, b) => b.score - a.score)
603
- .slice(0, limit);
604
- }
605
- return rows.map(({ text, score }) => ({ score, text }));
606
- }
607
-
608
- function relevantMem(id, query, { maxRelevant = 6, maxRecent = 4, charCap = 2400 } = {}) {
609
- const list = memEntries(id);
610
- if (!list.length) return '';
611
- const qTokens = memory.tokenize(query);
612
- const picked = new Set();
613
- if (qTokens.length) {
614
- list
615
- .map((text, i) => ({ i, score: memory.scoreEntry(text, qTokens) }))
616
- .filter((r) => r.score > 0)
617
- .sort((a, b) => b.score - a.score)
618
- .slice(0, maxRelevant)
619
- .forEach((r) => picked.add(r.i));
620
- }
621
- for (let i = list.length - 1; i >= 0 && picked.size < maxRelevant + maxRecent; i--) picked.add(i);
622
- let out = '';
623
- for (const i of [...picked].sort((a, b) => a - b)) {
624
- const line = '- ' + list[i];
625
- if (out.length + line.length > charCap) break;
626
- out += (out ? '\n' : '') + line;
627
- }
628
- return out;
629
- }
630
-
631
- function readLog(id, cap = 20000) {
632
- try {
633
- return fs.readFileSync(path.join(botDir(id), 'logs', 'changes.log'), 'utf8').slice(-cap);
634
- } catch {
635
- return '';
636
- }
637
- }
638
-
639
- module.exports = {
640
- MAX_BOTS,
641
- ICONS,
642
- SKILL_LIST,
643
- PLUGIN_LIST,
644
- beastRoot,
645
- botDir,
646
- list,
647
- get,
648
- add,
649
- update,
650
- remove,
651
- canSee,
652
- ensureBotCodes,
653
- byCode,
654
- readMemory,
655
- readMemoryFiles,
656
- writeMemoryFile,
657
- readMem,
658
- memPath,
659
- appendMem,
660
- appendUserMem,
661
- searchMem,
662
- relevantMem,
663
- readLog,
664
- };
1
+ 'use strict';
2
+
3
+ /* Beast Bot Sistemi (BÖLÜM 2 / 3 / 6 / 7)
4
+ - İlk bot hep "Beast" (admin, silinemez). Toplam MAX 5 bot (1 admin + 4 müşteri).
5
+ - Her bot izole klasörde yaşar: bots/<id>/
6
+ config.json → botun temel ayarı (ayna)
7
+ memory.md → botun KENDİ hafızası (diğer botlar göremez)
8
+ yetkiler.json → görebildiği botlar + skill/plugin/tarayıcı yetkileri
9
+ logs/changes.log → yetki/ayar değişiklik günlüğü (her değişiklik loglanır)
10
+ chat/ plugins/ watchers/ → botun izole çalışma alanları
11
+ - WhatsApp numaraları bot_id ile eşlenir; registry bots.json'da durur.
12
+ whitelist.json aynası main tarafında senkronlanır. */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const os = require('os');
17
+ const memory = require('./memory'); // tokenize/scoreEntry yeniden kullanılır (döngü yok)
18
+
19
+ const MAX_BOTS = 5;
20
+
21
+ const ICONS = ['🦁', '🚀', '💎', '⭐', '🔥', '🧮', '📊', '🤖', '🦊', '🐼', '🎯', '🛠️'];
22
+
23
+ /* Her botta varsayılan AÇIK gelen skill'ler (yetki sadece admin tarafından değişir) */
24
+ const DEFAULT_SKILLS = {
25
+ email: true,
26
+ browser: true, // dahili tarayıcı varsayılan TÜM botlarda aktif
27
+ web_search: true,
28
+ run_command: false,
29
+ memory: true,
30
+ kb: true,
31
+ };
32
+
33
+ const SKILL_LIST = [
34
+ ['email', 'E-posta (okuma/gönderme)'],
35
+ ['browser', 'Dahili tarayıcı (panel)'],
36
+ ['web_search', 'Web arama + sayfa okuma'],
37
+ ['run_command', 'Terminal / dosya / ekran'],
38
+ ['memory', 'Kendi hafızası (yazma/arama)'],
39
+ ['kb', 'Bilgi bankası (arama/ekleme)'],
40
+ ];
41
+
42
+ const PLUGIN_LIST = ['fatura_okuyucu', 'crm_plugin', 'rapor_uretici', 'stok_takip', 'toplanti_ozet'];
43
+
44
+ function beastRoot() {
45
+ if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
46
+ return process.env.APPDATA
47
+ ? path.join(process.env.APPDATA, 'beast')
48
+ : path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
49
+ }
50
+
51
+ function registryFile() {
52
+ return path.join(beastRoot(), 'bots.json');
53
+ }
54
+
55
+ function botDir(id) {
56
+ return path.join(beastRoot(), 'bots', String(id || '').replace(/[^a-z0-9_-]/gi, ''));
57
+ }
58
+
59
+ function nowIso() {
60
+ return new Date().toISOString();
61
+ }
62
+
63
+ function uid() {
64
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
65
+ }
66
+
67
+ function foldTr(s) {
68
+ return String(s || '')
69
+ .toLowerCase()
70
+ .replace(/[çğıöşüâîû]/g, (ch) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' }[ch] || ch));
71
+ }
72
+
73
+ function slugify(name) {
74
+ const base = foldTr(String(name || 'bot'))
75
+ .replace(/[^a-z0-9]+/g, '-')
76
+ .replace(/^-+|-+$/g, '')
77
+ .slice(0, 18);
78
+ return base || 'bot';
79
+ }
80
+
81
+ function adminBot() {
82
+ return {
83
+ id: 'beast',
84
+ name: 'Beast',
85
+ icon: '🦁',
86
+ admin: true,
87
+ code: '', // 5 haneli benzersiz bot kodu (ensureBotCodes atar)
88
+ prompt: '',
89
+ perm: 'all',
90
+ vis: true, // sohbet görünürlüğü: ana sohbete davet edilebilir
91
+ skills: { ...DEFAULT_SKILLS, run_command: true },
92
+ seeBots: [], // admin her şeyi görür
93
+ extBrowser: true, // dış tarayıcı yetkisi
94
+ browserDefault: 'dahili',
95
+ extCommand: '',
96
+ plugins: [],
97
+ createdAt: nowIso(),
98
+ };
99
+ }
100
+
101
+ /* ZORUNLU VARSAYILAN BOTLAR: Beast (admin) + Coder + Tool.
102
+ Eksik olanlar OTOMATİK açılır — Tool botunun TEK görevi tool yazmaktır;
103
+ diğer ajanlar/botlar tool_request ile ona devreder (cephane mantığı). */
104
+ const RETIRED_TRADER_PROMPT = "Sen BEAST Trader botsun: disiplinli bir trading analisti. Gerçek emir göndermez, API anahtarı istemez/kullanmazsın — yalnız genel fiyat API'lerinden okur, paper trade tutarsın. Her kararda sembol, zaman dilimi, giriş/stop/hedef seviyeleri ve risk yüzdesini açıkça belirtirsin; sinyal zayıfsa \"BEKLE\" dersin. İzleyici (watcher) kurarak sürekli izlersin. Grafik doğrulaması: tool__mt5_shot ile MT5 grafiğinden GERÇEK PNG al ve grafiği gör (timeframes:[\"M1\",\"M15\"] ile çoklu periyot, symbols:[\"GOLD\",\"EURUSD\"] ile çoklu sembol TEK karede; yoksa computer_look; metin-model isen ocr_read source:\"screen\"/\"browser\"); web grafiklerinde browser_screenshot kullan. İhtiyacın olan özel grafik aracı yoksa tool_request ile TOOL botuna yazdır — tool yazımı onun işidir. Beast Finance MT5 terminaline bağlandığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK kurulur (AutoTrading + grafik panosu + seviye çizgileri); grafik/entegrasyon işlerinde bu EA ve mt5_ea köprüsü kullanılır — elle ekleme varsayma.";
105
+
106
+ const DEFAULT_BOT_SEEDS = [
107
+ {
108
+ id: 'coder',
109
+ name: 'Coder',
110
+ icon: '🤖',
111
+ prompt:
112
+ 'Sen BEAST Coder botsun: disiplinli bir yazılım mühendisi. Küçük adımlarla plan çıkarır (todo), uygular, doğrularsın. Dosya düzenler, komut çalıştırır ve kısa net açıklamalar yaparsın; gereksiz konuşmazsın.',
113
+ },
114
+ {
115
+ id: 'tool',
116
+ name: 'Tool',
117
+ icon: '🛠️',
118
+ prompt:
119
+ 'Sen BEAST TOOL botusun — TEK görevin TOOL YAZMAK. Başka hiçbir iş yapmazsın: analiz, genel sohbet, araştırma, kod projesi YOK; yalnız eksik/bozuk araçları yazar ve onarırsın. Her istek bir araç ihtiyacıdır. ÖNCE skill aracıyla "tool-yazma" skill\'ini oku ve sözleşmeye BİREBİR uy: araç %APPDATA%\\beast\\tools\\<slug>\\ klasöründe tool.json (tanım) + run.js (kod) olarak yaşar; slug a-z0-9_- 2-24 karakter. Sözleşme: stdin JSON argüman → stdout SADECE JSON sonuç; {ok:true,...} ya da {ok:false,error:"sebep"}; günlük/teşhis metnini console.error\'a yaz. Var olan aracı silip baştan yazma — edit_file ile GÜNCELLE. Yazdıktan sonra run_command ile TEST DÖNGÜSÜ işlet (echo \'{"mesaj":"test"}\' | node "%APPDATA%\\beast\\tools\\<slug>\\run.js"): çıktı JSON ve ok:true DEĞİLSE stderr\'i/hatayı oku → edit_file ile düzelt → YENİDEN test et; en fazla 5 deneme. Ancak test GEÇİNCE "YAYINDA" de; geçmeyen aracı yayınlama, raporda BAŞARISIZ + kök hatayı yaz. Ağ çağrılarında timeout kullan; şifre/API anahtarını ASLA loglama/yazma. İstek eksikse tek cümlelik varsayım yazıp çalışan aracı üret. Cevabın TEK paragraf: tool adı (tool__<slug>), ne yaptığı, adımlar (yazıldı → test → düzeltme → YAYINDA/BAŞARISIZ), doğrulama sonucu ve tek satır çağrı örneği. Başka ajanlara iş devretme — tool yazımı senin işin. TÜM ajanlar (Beast, müşteri botları) tool_request ya da bot DM ile sana istek atar; her isteği araç ihtiyacı say ve sözleşmeye uygun yaz.',
120
+ },
121
+ ];
122
+
123
+ /* Eksik varsayılan botları oluştur (id sabit — tool_request 'tool' id'sini arar) */
124
+ function ensureDefaultBots() {
125
+ loadRegistry();
126
+ let created = false;
127
+ /* TEMİZLİK: kaldırılan varsayılan "Trader" botu — yalnız HİÇ düzenlenmemişse sil
128
+ (kullanıcı promptunu değiştirdiyse dokunulmaz). */
129
+ {
130
+ const i = REG.bots.findIndex((b) => b && b.id === 'trader' && String(b.prompt || '') === RETIRED_TRADER_PROMPT);
131
+ if (i >= 0) {
132
+ try { fs.rmSync(path.join(beastRoot(), 'bots', 'trader'), { recursive: true, force: true }); } catch {}
133
+ REG.bots.splice(i, 1);
134
+ created = true;
135
+ logChange('trader', 'varsayılan Trader botu kaldırıldı (Beast Finance ayrıldı)');
136
+ }
137
+ }
138
+ for (const def of DEFAULT_BOT_SEEDS) {
139
+ const existing = REG.bots.find((b) => b && b.id === def.id);
140
+ if (existing) {
141
+ /* VARSAYILAN PROMPT GÜNCELLEMESİ (tek seferlik ek): eski sürümde
142
+ oluşmuş Tool botunun promptunda mql5/edit_file/skill vurgusu yoksa
143
+ eksik parçalar EKLENİR — kullanıcı düzenlemesi ezilmez. */
144
+ if (def.id === 'tool') {
145
+ const p = String(existing.prompt || '');
146
+ const add = [];
147
+ if (!/edit_file/.test(p)) add.push('Var olan dosyayı edit_file ile GÜNCELLE; yeniyi write_file ile yaz; içerik aramada grep, dosya adı aramada glob kullan.');
148
+ if (!/skill\(["']tool-yazma["']\)/.test(p)) add.push('Tool yazmadan önce skill("tool-yazma") oku ve sözleşmeye birebir uy.');
149
+ if (add.length) {
150
+ existing.prompt = (p ? p + ' ' : '') + add.join(' ');
151
+ created = true;
152
+ logChange(def.id, 'varsayılan prompt ekleri eklendi (edit_file/skill)');
153
+ }
154
+ }
155
+ continue;
156
+ }
157
+ if (REG.bots.length >= MAX_BOTS) break;
158
+ const bot = {
159
+ id: def.id,
160
+ name: def.name,
161
+ icon: ICONS.includes(def.icon) ? def.icon : '🤖',
162
+ admin: false,
163
+ code: '',
164
+ prompt: String(def.prompt || '').slice(0, 4000),
165
+ perm: 'all',
166
+ vis: true,
167
+ /* Tool botu araç yazıp doğrulayabilmeli: run_command/read/write açık */
168
+ skills: { ...DEFAULT_SKILLS, run_command: def.id === 'tool' },
169
+ seeBots: [],
170
+ extBrowser: false,
171
+ browserDefault: 'dahili',
172
+ extCommand: '',
173
+ plugins: [],
174
+ createdAt: nowIso(),
175
+ };
176
+ REG.bots.push(bot);
177
+ created = true;
178
+ logChange(def.id, `varsayılan bot otomatik oluşturuldu (name="${def.name}")`);
179
+ }
180
+ if (created) {
181
+ ensureBotCodes();
182
+ saveRegistry();
183
+ ensureDirs();
184
+ }
185
+ return created;
186
+ }
187
+
188
+ /* ---------- registry ---------- */
189
+
190
+ let REG = null;
191
+
192
+ function loadRegistry() {
193
+ if (REG) return REG;
194
+ try {
195
+ REG = JSON.parse(fs.readFileSync(registryFile(), 'utf8'));
196
+ } catch {
197
+ REG = null;
198
+ }
199
+ if (!REG || !Array.isArray(REG.bots)) REG = { bots: [adminBot()] };
200
+ /* beast her zaman ilk ve admin */
201
+ const beast = REG.bots.find((b) => b && b.id === 'beast');
202
+ if (!beast) REG.bots.unshift(adminBot());
203
+ else Object.assign(beast, { ...adminBot(), ...beast, id: 'beast', admin: true, name: beast.name || 'Beast' });
204
+ /* ZORUNLU bot seti: Beast + Coder + Tool (eksikler otomatik açılır) */
205
+ ensureDefaultBots();
206
+ saveRegistry();
207
+ ensureDirs();
208
+ return REG;
209
+ }
210
+
211
+ function saveRegistry() {
212
+ try {
213
+ fs.mkdirSync(beastRoot(), { recursive: true });
214
+ fs.writeFileSync(registryFile(), JSON.stringify(REG, null, 2));
215
+ } catch {}
216
+ }
217
+
218
+ function ensureDirs() {
219
+ for (const b of REG.bots) {
220
+ const d = botDir(b.id);
221
+ try {
222
+ for (const sub of ['logs', 'chat', 'plugins', 'watchers']) fs.mkdirSync(path.join(d, sub), { recursive: true });
223
+ const cfgP = path.join(d, 'config.json');
224
+ if (!fs.existsSync(cfgP)) fs.writeFileSync(cfgP, JSON.stringify(botConfigOf(b), null, 2));
225
+ const yetP = path.join(d, 'yetkiler.json');
226
+ if (!fs.existsSync(yetP)) fs.writeFileSync(yetP, JSON.stringify(botPermsOf(b), null, 2));
227
+ /* bot hafızası ayarlardaki yapıyla aynı: SOUL.md + MEMORY.md + USER.md
228
+ (eski memory.md varsa içeriği MEMORY.md'ye taşınır) */
229
+ const legacyP = path.join(d, 'memory.md');
230
+ const legacy = fs.existsSync(legacyP) ? fs.readFileSync(legacyP, 'utf8') : '';
231
+ const memP = path.join(d, 'MEMORY.md');
232
+ if (!fs.existsSync(memP)) fs.writeFileSync(memP, legacy || '');
233
+ for (const f of ['SOUL.md', 'USER.md']) {
234
+ const p = path.join(d, f);
235
+ if (!fs.existsSync(p)) fs.writeFileSync(p, '');
236
+ }
237
+ } catch {}
238
+ }
239
+ }
240
+
241
+ function botConfigOf(b) {
242
+ return {
243
+ id: b.id,
244
+ name: b.name,
245
+ icon: b.icon,
246
+ admin: !!b.admin,
247
+ code: b.code || '',
248
+ prompt: b.prompt || '',
249
+ browser: { default: b.browserDefault || 'dahili', extCommand: b.extCommand || '' },
250
+ plugins: b.plugins || [],
251
+ createdAt: b.createdAt,
252
+ };
253
+ }
254
+
255
+ function botPermsOf(b) {
256
+ return {
257
+ id: b.id,
258
+ perm: b.perm || 'all',
259
+ skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) },
260
+ seeBots: b.seeBots || [],
261
+ extBrowser: b.extBrowser !== false,
262
+ };
263
+ }
264
+
265
+ /* ---------- log ---------- */
266
+
267
+ function logChange(id, text) {
268
+ try {
269
+ const d = botDir(id);
270
+ fs.mkdirSync(path.join(d, 'logs'), { recursive: true });
271
+ fs.appendFileSync(path.join(d, 'logs', 'changes.log'), `[${new Date().toISOString()}] ${text}\n`);
272
+ } catch {}
273
+ }
274
+
275
+ /* ---------- public API ---------- */
276
+
277
+ function list() {
278
+ loadRegistry();
279
+ return REG.bots.map((b) => ({ ...b, skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) } }));
280
+ }
281
+
282
+ function get(id) {
283
+ loadRegistry();
284
+ return REG.bots.find((b) => b.id === String(id || '')) || null;
285
+ }
286
+
287
+ function add({ name, icon, prompt }) {
288
+ loadRegistry();
289
+ const n = String(name || '').trim().slice(0, 40);
290
+ if (!n) return { ok: false, error: 'bot adı zorunlu' };
291
+ /* İLK HARF ZORUNLU: bot adı harf karakteriyle başlamalı */
292
+ if (!/^\p{L}/u.test(n)) return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
293
+ if (REG.bots.length >= MAX_BOTS) return { ok: false, error: `en fazla ${MAX_BOTS} bot olabilir (1 admin + ${MAX_BOTS - 1} müşteri)` };
294
+ let id = slugify(n);
295
+ while (get(id)) id = slugify(n) + '-' + uid().slice(-4);
296
+ const bot = {
297
+ id,
298
+ name: n,
299
+ icon: ICONS.includes(icon) ? icon : '🤖',
300
+ admin: false,
301
+ code: '', // ensureBotCodes hemen altında benzersiz 5 hane atar
302
+ prompt: String(prompt || '').slice(0, 4000),
303
+ perm: 'all',
304
+ vis: true, // sohbet görünürlüğü (admin matristen kapatır)
305
+ skills: { ...DEFAULT_SKILLS },
306
+ seeBots: [], // varsayılan: hiçbir bot baka botu göremez
307
+ extBrowser: false, // dış tarayıcı sadece yetkilide
308
+ browserDefault: 'dahili',
309
+ extCommand: '',
310
+ plugins: [],
311
+ createdAt: nowIso(),
312
+ };
313
+ REG.bots.push(bot);
314
+ ensureBotCodes();
315
+ saveRegistry();
316
+ ensureDirs();
317
+ logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon}, code=${bot.code})`);
318
+ return { ok: true, bot: { ...bot } };
319
+ }
320
+
321
+ /* ---------- 5 HANELİ BENZERSİZ BOT KODU ----------
322
+ Her bot oluşurken 10000-99999 arası rastgele kod alır; duplicate kontrolü
323
+ zorunlu (tüm botlara karşı). Açılışta kodu eksik/çakışık botlara da atanır. */
324
+ function ensureBotCodes() {
325
+ loadRegistry();
326
+ let dirty = false;
327
+ const used = () => new Set(REG.bots.map((b) => String(b.code || '')).filter((c) => /^\d{5}$/.test(c)));
328
+ const seen = new Set();
329
+ for (const b of REG.bots) {
330
+ const cur = String(b.code || '');
331
+ if (/^\d{5}$/.test(cur) && !seen.has(cur)) { seen.add(cur); continue; }
332
+ const u = used();
333
+ let c;
334
+ do { c = String(10000 + Math.floor(Math.random() * 90000)); } while (u.has(c));
335
+ logChange(b.id, `bot kodu atandı: ${c}${cur ? ` (eskisi geçersizdi: ${cur})` : ''}`);
336
+ b.code = c;
337
+ seen.add(c);
338
+ dirty = true;
339
+ }
340
+ if (dirty) saveRegistry();
341
+ return dirty;
342
+ }
343
+
344
+ function byCode(code) {
345
+ const c = String(code || '').replace(/\D/g, '');
346
+ if (!/^\d{5}$/.test(c)) return null;
347
+ loadRegistry();
348
+ return REG.bots.find((b) => String(b.code || '') === c) || null;
349
+ }
350
+
351
+ function update(id, patch) {
352
+ loadRegistry();
353
+ const b = get(id);
354
+ if (!b) return { ok: false, error: 'bot yok' };
355
+ /* İLK HARF ZORUNLU: ad değişikliği harf karakteriyle başlamalı —
356
+ diğer alanlara dokunmadan reddet */
357
+ if (patch && typeof patch.name === 'string' && patch.name.trim() &&
358
+ !/^\p{L}/u.test(patch.name.trim())) {
359
+ return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
360
+ }
361
+ const changes = [];
362
+ if (patch && typeof patch === 'object') {
363
+ if (typeof patch.name === 'string' && patch.name.trim() && patch.name.trim() !== b.name) {
364
+ changes.push(`name: "${b.name}" → "${patch.name.trim().slice(0, 40)}"`);
365
+ b.name = patch.name.trim().slice(0, 40);
366
+ }
367
+ if (typeof patch.icon === 'string' && ICONS.includes(patch.icon) && patch.icon !== b.icon) {
368
+ changes.push(`icon: ${b.icon} → ${patch.icon}`);
369
+ b.icon = patch.icon;
370
+ }
371
+ if (typeof patch.prompt === 'string' && patch.prompt !== b.prompt) {
372
+ changes.push('prompt güncellendi');
373
+ b.prompt = String(patch.prompt).slice(0, 4000);
374
+ }
375
+ if (patch.perm !== undefined) {
376
+ /* 'all' tek başına tüm araçları verir; web/read/chat çoklu seçilebilir
377
+ (['web','read'] gibi dizi ya da 'web,read' gibi string kabul edilir) */
378
+ const PERMS_ALL = ['all', 'web', 'read', 'chat'];
379
+ let nextPerm = null;
380
+ if (Array.isArray(patch.perm)) {
381
+ const picked = [...new Set(patch.perm.map((x) => String(x).trim()).filter((x) => PERMS_ALL.includes(x)))];
382
+ nextPerm = picked.includes('all') ? 'all' : (picked.length ? picked : 'chat');
383
+ } else if (typeof patch.perm === 'string') {
384
+ const arr = patch.perm.split(',').map((x) => x.trim()).filter((x) => PERMS_ALL.includes(x));
385
+ if (arr.length) nextPerm = arr.includes('all') ? 'all' : (arr.length === 1 ? arr[0] : arr);
386
+ }
387
+ if (nextPerm !== null && JSON.stringify(nextPerm) !== JSON.stringify(b.perm)) {
388
+ const fp = (p) => (Array.isArray(p) ? '[' + p.join('+') + ']' : String(p));
389
+ changes.push(`perm: ${fp(b.perm)} → ${fp(nextPerm)}`);
390
+ b.perm = nextPerm;
391
+ }
392
+ }
393
+ if (patch.skills && typeof patch.skills === 'object') {
394
+ const merged = { ...DEFAULT_SKILLS, ...(b.skills || {}) };
395
+ for (const k of Object.keys(DEFAULT_SKILLS)) {
396
+ if (typeof patch.skills[k] === 'boolean' && patch.skills[k] !== merged[k]) {
397
+ changes.push(`skill ${k}: ${merged[k]} → ${patch.skills[k]}`);
398
+ merged[k] = patch.skills[k];
399
+ }
400
+ }
401
+ b.skills = merged;
402
+ }
403
+ if (Array.isArray(patch.seeBots)) {
404
+ const valid = patch.seeBots.map(String).filter((x) => x !== b.id && get(x));
405
+ const next = [...new Set(valid)];
406
+ if (JSON.stringify(next) !== JSON.stringify(b.seeBots || [])) {
407
+ changes.push(`görebilir: [${(b.seeBots || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
408
+ b.seeBots = next;
409
+ }
410
+ }
411
+ if (typeof patch.extBrowser === 'boolean' && patch.extBrowser !== b.extBrowser) {
412
+ changes.push(`dış tarayıcı yetkisi: ${b.extBrowser} → ${patch.extBrowser}`);
413
+ b.extBrowser = patch.extBrowser;
414
+ }
415
+ if (typeof patch.vis === 'boolean' && patch.vis !== (b.vis !== false)) {
416
+ changes.push(`sohbet görünürlüğü: ${b.vis !== false} → ${patch.vis}`);
417
+ b.vis = patch.vis;
418
+ }
419
+ if (['dahili', 'dis'].includes(patch.browserDefault) && patch.browserDefault !== b.browserDefault) {
420
+ changes.push(`varsayılan tarayıcı: ${b.browserDefault} → ${patch.browserDefault}`);
421
+ b.browserDefault = patch.browserDefault;
422
+ }
423
+ if (typeof patch.extCommand === 'string' && patch.extCommand !== b.extCommand) {
424
+ changes.push('dış tarayıcı komutu güncellendi');
425
+ b.extCommand = String(patch.extCommand).slice(0, 200);
426
+ }
427
+ if (typeof patch.model === 'string') {
428
+ /* bot bazlı model override — boş string = global seçim */
429
+ const next = patch.model.trim().slice(0, 160);
430
+ if (next !== (b.model || '')) {
431
+ changes.push('model güncellendi');
432
+ b.model = next;
433
+ }
434
+ }
435
+ if (Array.isArray(patch.plugins)) {
436
+ const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
437
+ if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
438
+ changes.push(`pluginler: [${(b.plugins || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
439
+ b.plugins = next;
440
+ }
441
+ }
442
+ }
443
+ saveRegistry();
444
+ ensureDirs();
445
+ try {
446
+ const d = botDir(b.id);
447
+ fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify(botConfigOf(b), null, 2));
448
+ fs.writeFileSync(path.join(d, 'yetkiler.json'), JSON.stringify(botPermsOf(b), null, 2));
449
+ } catch {}
450
+ for (const c of changes) logChange(b.id, `yetki/ayar değişikliği (admin): ${c}`);
451
+ return { ok: true, bot: { ...b }, changed: changes.length };
452
+ }
453
+
454
+ function remove(id) {
455
+ loadRegistry();
456
+ const b = get(id);
457
+ if (!b) return { ok: false, error: 'bot yok' };
458
+ if (b.admin) return { ok: false, error: 'admin bot silinemez' };
459
+ REG.bots = REG.bots.filter((x) => x.id !== b.id);
460
+ saveRegistry();
461
+ logChange(b.id, `bot SİLİNDİ — bağlı numaralar botsuz duruma düştü (beast'e yönlendirilir)`);
462
+ /* klasör arşivlenir — veri kaybı olmasın */
463
+ try {
464
+ const d = botDir(b.id);
465
+ if (fs.existsSync(d)) fs.renameSync(d, d + '-arsiv-' + Date.now().toString(36));
466
+ } catch {}
467
+ return { ok: true };
468
+ }
469
+
470
+ /* viewer botun target botu görüp göremeyeceği */
471
+ function canSee(viewerId, targetId) {
472
+ const v = get(viewerId);
473
+ if (!v) return false;
474
+ if (v.admin) return true; // admin her şeyi görür
475
+ if (viewerId === targetId) return true;
476
+ return (v.seeBots || []).includes(String(targetId));
477
+ }
478
+
479
+ /* botun hafıza dosyaları — ayarlardaki SOUL/USER/MEMORY üçlüsünün bot izole hali */
480
+ const BOT_MEM_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md'];
481
+
482
+ /* botun MEMORY.md'si (persona bloğuna giden kısım) */
483
+ function readMemory(id, cap = 2000) {
484
+ try {
485
+ return fs.readFileSync(path.join(botDir(id), 'MEMORY.md'), 'utf8').slice(0, cap).trim();
486
+ } catch {
487
+ return '';
488
+ }
489
+ }
490
+
491
+ function readMemoryFiles(id) {
492
+ const d = botDir(id);
493
+ const out = { soul: '', memory: '', user: '' };
494
+ try { out.soul = fs.readFileSync(path.join(d, 'SOUL.md'), 'utf8'); } catch {}
495
+ try { out.memory = fs.readFileSync(path.join(d, 'MEMORY.md'), 'utf8'); } catch {}
496
+ try { out.user = fs.readFileSync(path.join(d, 'USER.md'), 'utf8'); } catch {}
497
+ return out;
498
+ }
499
+
500
+ function writeMemoryFile(id, file, content) {
501
+ const b = get(id);
502
+ if (!b) return { ok: false, error: 'bot yok' };
503
+ if (!BOT_MEM_FILES.includes(file)) return { ok: false, error: 'geçersiz dosya' };
504
+ try {
505
+ const d = botDir(id);
506
+ fs.mkdirSync(d, { recursive: true });
507
+ fs.writeFileSync(path.join(d, file), String(content ?? ''), 'utf8');
508
+ /* admin MEMORY.md'yi elle değiştirdiyse botun mem0 store'unu yeniden kur */
509
+ if (file === 'MEMORY.md') {
510
+ try {
511
+ const mem0 = require('./mem0');
512
+ mem0.reindexFromLines('bot:' + id, String(content ?? '').split('\n').map((l) => l.replace(/^[-*]\s*/, '').trim()).filter(Boolean));
513
+ } catch {}
514
+ }
515
+ logChange(id, `${file} admin tarafından güncellendi`);
516
+ return { ok: true };
517
+ } catch (e) {
518
+ return { ok: false, error: String((e && e.message) || e) };
519
+ }
520
+ }
521
+
522
+ /* botun MEMORY.md ayna dosyasının yolu (mem0 syncMirror kullanır) */
523
+ function memPath(id) {
524
+ return path.join(botDir(id), 'MEMORY.md');
525
+ }
526
+
527
+ /* ---------- BOT OTURUMU hafıza operasyonları ----------
528
+ Bot oturumlarında memory_write/memory_search GLOBAL Beast hafızasına DEĞİL,
529
+ botun kendi SOUL/USER/MEMORY dosyalarına gider (tam izolasyon). */
530
+
531
+ function readMem(id, f) {
532
+ if (!BOT_MEM_FILES.includes(f)) return '';
533
+ try {
534
+ return fs.readFileSync(path.join(botDir(id), f), 'utf8');
535
+ } catch {
536
+ return '';
537
+ }
538
+ }
539
+
540
+ function memEntries(id) {
541
+ return readMem(id, 'MEMORY.md')
542
+ .split('\n')
543
+ .map((l) => l.replace(/^[-*]\s*/, '').trim())
544
+ .filter(Boolean);
545
+ }
546
+
547
+ function appendMem(id, text) {
548
+ const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 500);
549
+ if (!t) return { ok: false, error: 'empty' };
550
+ const list = memEntries(id);
551
+ const key = foldTr(t).replace(/[^a-z0-9]+/g, ' ').trim();
552
+ if (list.some((l) => foldTr(l).replace(/[^a-z0-9]+/g, ' ').trim() === key)) {
553
+ return { ok: true, duplicate: true };
554
+ }
555
+ try {
556
+ fs.mkdirSync(botDir(id), { recursive: true });
557
+ fs.appendFileSync(path.join(botDir(id), 'MEMORY.md'), '- ' + t + '\n');
558
+ return { ok: true };
559
+ } catch (e) {
560
+ return { ok: false, error: String((e && e.message) || e) };
561
+ }
562
+ }
563
+
564
+ /* botun USER.md'si: bota özel kullanıcı profili (aynı dedup/yenile mantığı) */
565
+ function appendUserMem(id, text) {
566
+ const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 300);
567
+ if (!t) return { ok: false, error: 'empty' };
568
+ try {
569
+ const p = path.join(botDir(id), 'USER.md');
570
+ fs.mkdirSync(botDir(id), { recursive: true });
571
+ const lines = readMem(id, 'USER.md')
572
+ .split('\n')
573
+ .map((l) => l.replace(/^[-*]\s*/, '').trim())
574
+ .filter(Boolean);
575
+ /* profil anahtarı: "Konu: ..." satırlarında Konu — değişiklikte güncelle */
576
+ const topicOf = (s) => {
577
+ const c = foldTr(s).indexOf(':');
578
+ return (c > 0 ? foldTr(s).slice(0, c) : foldTr(s)).replace(/[^a-z0-9]+/g, ' ').trim();
579
+ };
580
+ const topic = topicOf(t);
581
+ const idx = lines.findIndex((l) => topicOf(l) === topic);
582
+ if (idx >= 0) {
583
+ if (lines[idx] === t) return { ok: true, duplicate: true };
584
+ lines[idx] = t;
585
+ } else {
586
+ lines.push(t);
587
+ }
588
+ while (lines.length > 60) lines.shift();
589
+ fs.writeFileSync(p, lines.map((l) => '- ' + l).join('\n') + '\n');
590
+ return { ok: true, updated: idx >= 0 };
591
+ } catch (e) {
592
+ return { ok: false, error: String((e && e.message) || e) };
593
+ }
594
+ }
595
+
596
+ function searchMem(id, query, limit = 8) {
597
+ const list = memEntries(id);
598
+ if (!list.length) return [];
599
+ const qTokens = memory.tokenize(query);
600
+ let rows;
601
+ if (!qTokens.length) {
602
+ rows = list.map((text, i) => ({ text, score: 0, i })).slice(-limit).reverse();
603
+ } else {
604
+ rows = list
605
+ .map((text, i) => ({ text, i, score: Number(memory.scoreEntry(text, qTokens).toFixed(3)) }))
606
+ .filter((r) => r.score > 0)
607
+ .sort((a, b) => b.score - a.score)
608
+ .slice(0, limit);
609
+ }
610
+ return rows.map(({ text, score }) => ({ score, text }));
611
+ }
612
+
613
+ function relevantMem(id, query, { maxRelevant = 6, maxRecent = 4, charCap = 2400 } = {}) {
614
+ const list = memEntries(id);
615
+ if (!list.length) return '';
616
+ const qTokens = memory.tokenize(query);
617
+ const picked = new Set();
618
+ if (qTokens.length) {
619
+ list
620
+ .map((text, i) => ({ i, score: memory.scoreEntry(text, qTokens) }))
621
+ .filter((r) => r.score > 0)
622
+ .sort((a, b) => b.score - a.score)
623
+ .slice(0, maxRelevant)
624
+ .forEach((r) => picked.add(r.i));
625
+ }
626
+ for (let i = list.length - 1; i >= 0 && picked.size < maxRelevant + maxRecent; i--) picked.add(i);
627
+ let out = '';
628
+ for (const i of [...picked].sort((a, b) => a - b)) {
629
+ const line = '- ' + list[i];
630
+ if (out.length + line.length > charCap) break;
631
+ out += (out ? '\n' : '') + line;
632
+ }
633
+ return out;
634
+ }
635
+
636
+ function readLog(id, cap = 20000) {
637
+ try {
638
+ return fs.readFileSync(path.join(botDir(id), 'logs', 'changes.log'), 'utf8').slice(-cap);
639
+ } catch {
640
+ return '';
641
+ }
642
+ }
643
+
644
+ module.exports = {
645
+ MAX_BOTS,
646
+ ICONS,
647
+ SKILL_LIST,
648
+ PLUGIN_LIST,
649
+ beastRoot,
650
+ botDir,
651
+ list,
652
+ get,
653
+ add,
654
+ update,
655
+ remove,
656
+ canSee,
657
+ ensureBotCodes,
658
+ byCode,
659
+ readMemory,
660
+ readMemoryFiles,
661
+ writeMemoryFile,
662
+ readMem,
663
+ memPath,
664
+ appendMem,
665
+ appendUserMem,
666
+ searchMem,
667
+ relevantMem,
668
+ readLog,
669
+ };