beast-agent 1.9.0 → 2.0.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 (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -130
  3. package/bin/beast-agent.js +137 -137
  4. package/package.json +1 -1
  5. package/scripts/fix-electron.js +138 -138
  6. package/scripts/release.js +137 -131
  7. package/scripts/swap-electron.js +40 -40
  8. package/src/agent/agentdefs.js +122 -122
  9. package/src/agent/bots.js +580 -580
  10. package/src/agent/bus.js +389 -389
  11. package/src/agent/computeruse.js +200 -200
  12. package/src/agent/config.js +284 -284
  13. package/src/agent/discord.js +332 -332
  14. package/src/agent/engine.js +75 -10
  15. package/src/agent/kb.js +123 -123
  16. package/src/agent/llm.js +430 -430
  17. package/src/agent/logger.js +90 -90
  18. package/src/agent/mcp.js +427 -427
  19. package/src/agent/mem0.js +605 -605
  20. package/src/agent/memory.js +427 -427
  21. package/src/agent/mqueue.js +124 -124
  22. package/src/agent/pdf.js +20 -20
  23. package/src/agent/research.js +133 -133
  24. package/src/agent/scripts/news.py +113 -113
  25. package/src/agent/scripts/stealthsearch.py +30 -30
  26. package/src/agent/scripts/websearch.py +225 -225
  27. package/src/agent/searxng.js +325 -325
  28. package/src/agent/seeds/brainstorming/SKILL.md +90 -90
  29. package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
  30. package/src/agent/seeds/executing-plans/SKILL.md +60 -60
  31. package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
  32. package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
  33. package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
  34. package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
  35. package/src/agent/seeds/writing-plans/SKILL.md +162 -162
  36. package/src/agent/seeds/writing-skills/SKILL.md +229 -229
  37. package/src/agent/skills.js +652 -652
  38. package/src/agent/store.js +378 -378
  39. package/src/agent/telegram.js +155 -155
  40. package/src/agent/tokens.js +39 -39
  41. package/src/agent/usage.js +125 -125
  42. package/src/agent/watchers.js +312 -312
  43. package/src/agent/watext.js +80 -80
  44. package/src/agent/whatsapp.js +555 -555
  45. package/src/cron.js +255 -255
  46. package/src/main.js +348 -6
  47. package/src/preload.js +9 -0
  48. package/src/renderer/browserPreload.js +73 -73
  49. package/src/renderer/i18n.js +4 -0
  50. package/src/renderer/index.html +448 -409
  51. package/src/renderer/renderer.js +824 -41
  52. package/src/renderer/style.css +264 -5
package/src/agent/bots.js CHANGED
@@ -1,580 +1,580 @@
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
- /* ---------- registry ---------- */
102
-
103
- let REG = null;
104
-
105
- function loadRegistry() {
106
- if (REG) return REG;
107
- try {
108
- REG = JSON.parse(fs.readFileSync(registryFile(), 'utf8'));
109
- } catch {
110
- REG = null;
111
- }
112
- if (!REG || !Array.isArray(REG.bots)) REG = { bots: [adminBot()] };
113
- /* beast her zaman ilk ve admin */
114
- const beast = REG.bots.find((b) => b && b.id === 'beast');
115
- if (!beast) REG.bots.unshift(adminBot());
116
- else Object.assign(beast, { ...adminBot(), ...beast, id: 'beast', admin: true, name: beast.name || 'Beast' });
117
- saveRegistry();
118
- ensureDirs();
119
- return REG;
120
- }
121
-
122
- function saveRegistry() {
123
- try {
124
- fs.mkdirSync(beastRoot(), { recursive: true });
125
- fs.writeFileSync(registryFile(), JSON.stringify(REG, null, 2));
126
- } catch {}
127
- }
128
-
129
- function ensureDirs() {
130
- for (const b of REG.bots) {
131
- const d = botDir(b.id);
132
- try {
133
- for (const sub of ['logs', 'chat', 'plugins', 'watchers']) fs.mkdirSync(path.join(d, sub), { recursive: true });
134
- const cfgP = path.join(d, 'config.json');
135
- if (!fs.existsSync(cfgP)) fs.writeFileSync(cfgP, JSON.stringify(botConfigOf(b), null, 2));
136
- const yetP = path.join(d, 'yetkiler.json');
137
- if (!fs.existsSync(yetP)) fs.writeFileSync(yetP, JSON.stringify(botPermsOf(b), null, 2));
138
- /* bot hafızası ayarlardaki yapıyla aynı: SOUL.md + MEMORY.md + USER.md
139
- (eski memory.md varsa içeriği MEMORY.md'ye taşınır) */
140
- const legacyP = path.join(d, 'memory.md');
141
- const legacy = fs.existsSync(legacyP) ? fs.readFileSync(legacyP, 'utf8') : '';
142
- const memP = path.join(d, 'MEMORY.md');
143
- if (!fs.existsSync(memP)) fs.writeFileSync(memP, legacy || '');
144
- for (const f of ['SOUL.md', 'USER.md']) {
145
- const p = path.join(d, f);
146
- if (!fs.existsSync(p)) fs.writeFileSync(p, '');
147
- }
148
- } catch {}
149
- }
150
- }
151
-
152
- function botConfigOf(b) {
153
- return {
154
- id: b.id,
155
- name: b.name,
156
- icon: b.icon,
157
- admin: !!b.admin,
158
- code: b.code || '',
159
- prompt: b.prompt || '',
160
- browser: { default: b.browserDefault || 'dahili', extCommand: b.extCommand || '' },
161
- plugins: b.plugins || [],
162
- createdAt: b.createdAt,
163
- };
164
- }
165
-
166
- function botPermsOf(b) {
167
- return {
168
- id: b.id,
169
- perm: b.perm || 'all',
170
- skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) },
171
- seeBots: b.seeBots || [],
172
- extBrowser: b.extBrowser !== false,
173
- };
174
- }
175
-
176
- /* ---------- log ---------- */
177
-
178
- function logChange(id, text) {
179
- try {
180
- const d = botDir(id);
181
- fs.mkdirSync(path.join(d, 'logs'), { recursive: true });
182
- fs.appendFileSync(path.join(d, 'logs', 'changes.log'), `[${new Date().toISOString()}] ${text}\n`);
183
- } catch {}
184
- }
185
-
186
- /* ---------- public API ---------- */
187
-
188
- function list() {
189
- loadRegistry();
190
- return REG.bots.map((b) => ({ ...b, skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) } }));
191
- }
192
-
193
- function get(id) {
194
- loadRegistry();
195
- return REG.bots.find((b) => b.id === String(id || '')) || null;
196
- }
197
-
198
- function add({ name, icon, prompt }) {
199
- loadRegistry();
200
- const n = String(name || '').trim().slice(0, 40);
201
- if (!n) return { ok: false, error: 'bot adı zorunlu' };
202
- /* İLK HARF ZORUNLU: bot adı harf karakteriyle başlamalı */
203
- if (!/^\p{L}/u.test(n)) return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
204
- if (REG.bots.length >= MAX_BOTS) return { ok: false, error: `en fazla ${MAX_BOTS} bot olabilir (1 admin + ${MAX_BOTS - 1} müşteri)` };
205
- let id = slugify(n);
206
- while (get(id)) id = slugify(n) + '-' + uid().slice(-4);
207
- const bot = {
208
- id,
209
- name: n,
210
- icon: ICONS.includes(icon) ? icon : '🤖',
211
- admin: false,
212
- code: '', // ensureBotCodes hemen altında benzersiz 5 hane atar
213
- prompt: String(prompt || '').slice(0, 4000),
214
- perm: 'all',
215
- vis: true, // sohbet görünürlüğü (admin matristen kapatır)
216
- skills: { ...DEFAULT_SKILLS },
217
- seeBots: [], // varsayılan: hiçbir bot baka botu göremez
218
- extBrowser: false, // dış tarayıcı sadece yetkilide
219
- browserDefault: 'dahili',
220
- extCommand: '',
221
- plugins: [],
222
- createdAt: nowIso(),
223
- };
224
- REG.bots.push(bot);
225
- ensureBotCodes();
226
- saveRegistry();
227
- ensureDirs();
228
- logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon}, code=${bot.code})`);
229
- return { ok: true, bot: { ...bot } };
230
- }
231
-
232
- /* ---------- 5 HANELİ BENZERSİZ BOT KODU ----------
233
- Her bot oluşurken 10000-99999 arası rastgele kod alır; duplicate kontrolü
234
- zorunlu (tüm botlara karşı). Açılışta kodu eksik/çakışık botlara da atanır. */
235
- function ensureBotCodes() {
236
- loadRegistry();
237
- let dirty = false;
238
- const used = () => new Set(REG.bots.map((b) => String(b.code || '')).filter((c) => /^\d{5}$/.test(c)));
239
- const seen = new Set();
240
- for (const b of REG.bots) {
241
- const cur = String(b.code || '');
242
- if (/^\d{5}$/.test(cur) && !seen.has(cur)) { seen.add(cur); continue; }
243
- const u = used();
244
- let c;
245
- do { c = String(10000 + Math.floor(Math.random() * 90000)); } while (u.has(c));
246
- logChange(b.id, `bot kodu atandı: ${c}${cur ? ` (eskisi geçersizdi: ${cur})` : ''}`);
247
- b.code = c;
248
- seen.add(c);
249
- dirty = true;
250
- }
251
- if (dirty) saveRegistry();
252
- return dirty;
253
- }
254
-
255
- function byCode(code) {
256
- const c = String(code || '').replace(/\D/g, '');
257
- if (!/^\d{5}$/.test(c)) return null;
258
- loadRegistry();
259
- return REG.bots.find((b) => String(b.code || '') === c) || null;
260
- }
261
-
262
- function update(id, patch) {
263
- loadRegistry();
264
- const b = get(id);
265
- if (!b) return { ok: false, error: 'bot yok' };
266
- /* İLK HARF ZORUNLU: ad değişikliği harf karakteriyle başlamalı —
267
- diğer alanlara dokunmadan reddet */
268
- if (patch && typeof patch.name === 'string' && patch.name.trim() &&
269
- !/^\p{L}/u.test(patch.name.trim())) {
270
- return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
271
- }
272
- const changes = [];
273
- if (patch && typeof patch === 'object') {
274
- if (typeof patch.name === 'string' && patch.name.trim() && patch.name.trim() !== b.name) {
275
- changes.push(`name: "${b.name}" → "${patch.name.trim().slice(0, 40)}"`);
276
- b.name = patch.name.trim().slice(0, 40);
277
- }
278
- if (typeof patch.icon === 'string' && ICONS.includes(patch.icon) && patch.icon !== b.icon) {
279
- changes.push(`icon: ${b.icon} → ${patch.icon}`);
280
- b.icon = patch.icon;
281
- }
282
- if (typeof patch.prompt === 'string' && patch.prompt !== b.prompt) {
283
- changes.push('prompt güncellendi');
284
- b.prompt = String(patch.prompt).slice(0, 4000);
285
- }
286
- if (patch.perm !== undefined) {
287
- /* 'all' tek başına tüm araçları verir; web/read/chat çoklu seçilebilir
288
- (['web','read'] gibi dizi ya da 'web,read' gibi string kabul edilir) */
289
- const PERMS_ALL = ['all', 'web', 'read', 'chat'];
290
- let nextPerm = null;
291
- if (Array.isArray(patch.perm)) {
292
- const picked = [...new Set(patch.perm.map((x) => String(x).trim()).filter((x) => PERMS_ALL.includes(x)))];
293
- nextPerm = picked.includes('all') ? 'all' : (picked.length ? picked : 'chat');
294
- } else if (typeof patch.perm === 'string') {
295
- const arr = patch.perm.split(',').map((x) => x.trim()).filter((x) => PERMS_ALL.includes(x));
296
- if (arr.length) nextPerm = arr.includes('all') ? 'all' : (arr.length === 1 ? arr[0] : arr);
297
- }
298
- if (nextPerm !== null && JSON.stringify(nextPerm) !== JSON.stringify(b.perm)) {
299
- const fp = (p) => (Array.isArray(p) ? '[' + p.join('+') + ']' : String(p));
300
- changes.push(`perm: ${fp(b.perm)} → ${fp(nextPerm)}`);
301
- b.perm = nextPerm;
302
- }
303
- }
304
- if (patch.skills && typeof patch.skills === 'object') {
305
- const merged = { ...DEFAULT_SKILLS, ...(b.skills || {}) };
306
- for (const k of Object.keys(DEFAULT_SKILLS)) {
307
- if (typeof patch.skills[k] === 'boolean' && patch.skills[k] !== merged[k]) {
308
- changes.push(`skill ${k}: ${merged[k]} → ${patch.skills[k]}`);
309
- merged[k] = patch.skills[k];
310
- }
311
- }
312
- b.skills = merged;
313
- }
314
- if (Array.isArray(patch.seeBots)) {
315
- const valid = patch.seeBots.map(String).filter((x) => x !== b.id && get(x));
316
- const next = [...new Set(valid)];
317
- if (JSON.stringify(next) !== JSON.stringify(b.seeBots || [])) {
318
- changes.push(`görebilir: [${(b.seeBots || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
319
- b.seeBots = next;
320
- }
321
- }
322
- if (typeof patch.extBrowser === 'boolean' && patch.extBrowser !== b.extBrowser) {
323
- changes.push(`dış tarayıcı yetkisi: ${b.extBrowser} → ${patch.extBrowser}`);
324
- b.extBrowser = patch.extBrowser;
325
- }
326
- if (typeof patch.vis === 'boolean' && patch.vis !== (b.vis !== false)) {
327
- changes.push(`sohbet görünürlüğü: ${b.vis !== false} → ${patch.vis}`);
328
- b.vis = patch.vis;
329
- }
330
- if (['dahili', 'dis'].includes(patch.browserDefault) && patch.browserDefault !== b.browserDefault) {
331
- changes.push(`varsayılan tarayıcı: ${b.browserDefault} → ${patch.browserDefault}`);
332
- b.browserDefault = patch.browserDefault;
333
- }
334
- if (typeof patch.extCommand === 'string' && patch.extCommand !== b.extCommand) {
335
- changes.push('dış tarayıcı komutu güncellendi');
336
- b.extCommand = String(patch.extCommand).slice(0, 200);
337
- }
338
- if (typeof patch.model === 'string') {
339
- /* bot bazlı model override — boş string = global seçim */
340
- const next = patch.model.trim().slice(0, 160);
341
- if (next !== (b.model || '')) {
342
- changes.push('model güncellendi');
343
- b.model = next;
344
- }
345
- }
346
- if (Array.isArray(patch.plugins)) {
347
- const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
348
- if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
349
- changes.push(`pluginler: [${(b.plugins || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
350
- b.plugins = next;
351
- }
352
- }
353
- }
354
- saveRegistry();
355
- ensureDirs();
356
- try {
357
- const d = botDir(b.id);
358
- fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify(botConfigOf(b), null, 2));
359
- fs.writeFileSync(path.join(d, 'yetkiler.json'), JSON.stringify(botPermsOf(b), null, 2));
360
- } catch {}
361
- for (const c of changes) logChange(b.id, `yetki/ayar değişikliği (admin): ${c}`);
362
- return { ok: true, bot: { ...b }, changed: changes.length };
363
- }
364
-
365
- function remove(id) {
366
- loadRegistry();
367
- const b = get(id);
368
- if (!b) return { ok: false, error: 'bot yok' };
369
- if (b.admin) return { ok: false, error: 'admin bot silinemez' };
370
- REG.bots = REG.bots.filter((x) => x.id !== b.id);
371
- saveRegistry();
372
- logChange(b.id, `bot SİLİNDİ — bağlı numaralar botsuz duruma düştü (beast'e yönlendirilir)`);
373
- /* klasör arşivlenir — veri kaybı olmasın */
374
- try {
375
- const d = botDir(b.id);
376
- if (fs.existsSync(d)) fs.renameSync(d, d + '-arsiv-' + Date.now().toString(36));
377
- } catch {}
378
- return { ok: true };
379
- }
380
-
381
- /* viewer botun target botu görüp göremeyeceği */
382
- function canSee(viewerId, targetId) {
383
- const v = get(viewerId);
384
- if (!v) return false;
385
- if (v.admin) return true; // admin her şeyi görür
386
- if (viewerId === targetId) return true;
387
- return (v.seeBots || []).includes(String(targetId));
388
- }
389
-
390
- /* botun hafıza dosyaları — ayarlardaki SOUL/USER/MEMORY üçlüsünün bot izole hali */
391
- const BOT_MEM_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md'];
392
-
393
- /* botun MEMORY.md'si (persona bloğuna giden kısım) */
394
- function readMemory(id, cap = 2000) {
395
- try {
396
- return fs.readFileSync(path.join(botDir(id), 'MEMORY.md'), 'utf8').slice(0, cap).trim();
397
- } catch {
398
- return '';
399
- }
400
- }
401
-
402
- function readMemoryFiles(id) {
403
- const d = botDir(id);
404
- const out = { soul: '', memory: '', user: '' };
405
- try { out.soul = fs.readFileSync(path.join(d, 'SOUL.md'), 'utf8'); } catch {}
406
- try { out.memory = fs.readFileSync(path.join(d, 'MEMORY.md'), 'utf8'); } catch {}
407
- try { out.user = fs.readFileSync(path.join(d, 'USER.md'), 'utf8'); } catch {}
408
- return out;
409
- }
410
-
411
- function writeMemoryFile(id, file, content) {
412
- const b = get(id);
413
- if (!b) return { ok: false, error: 'bot yok' };
414
- if (!BOT_MEM_FILES.includes(file)) return { ok: false, error: 'geçersiz dosya' };
415
- try {
416
- const d = botDir(id);
417
- fs.mkdirSync(d, { recursive: true });
418
- fs.writeFileSync(path.join(d, file), String(content ?? ''), 'utf8');
419
- /* admin MEMORY.md'yi elle değiştirdiyse botun mem0 store'unu yeniden kur */
420
- if (file === 'MEMORY.md') {
421
- try {
422
- const mem0 = require('./mem0');
423
- mem0.reindexFromLines('bot:' + id, String(content ?? '').split('\n').map((l) => l.replace(/^[-*]\s*/, '').trim()).filter(Boolean));
424
- } catch {}
425
- }
426
- logChange(id, `${file} admin tarafından güncellendi`);
427
- return { ok: true };
428
- } catch (e) {
429
- return { ok: false, error: String((e && e.message) || e) };
430
- }
431
- }
432
-
433
- /* botun MEMORY.md ayna dosyasının yolu (mem0 syncMirror kullanır) */
434
- function memPath(id) {
435
- return path.join(botDir(id), 'MEMORY.md');
436
- }
437
-
438
- /* ---------- BOT OTURUMU hafıza operasyonları ----------
439
- Bot oturumlarında memory_write/memory_search GLOBAL Beast hafızasına DEĞİL,
440
- botun kendi SOUL/USER/MEMORY dosyalarına gider (tam izolasyon). */
441
-
442
- function readMem(id, f) {
443
- if (!BOT_MEM_FILES.includes(f)) return '';
444
- try {
445
- return fs.readFileSync(path.join(botDir(id), f), 'utf8');
446
- } catch {
447
- return '';
448
- }
449
- }
450
-
451
- function memEntries(id) {
452
- return readMem(id, 'MEMORY.md')
453
- .split('\n')
454
- .map((l) => l.replace(/^[-*]\s*/, '').trim())
455
- .filter(Boolean);
456
- }
457
-
458
- function appendMem(id, text) {
459
- const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 500);
460
- if (!t) return { ok: false, error: 'empty' };
461
- const list = memEntries(id);
462
- const key = foldTr(t).replace(/[^a-z0-9]+/g, ' ').trim();
463
- if (list.some((l) => foldTr(l).replace(/[^a-z0-9]+/g, ' ').trim() === key)) {
464
- return { ok: true, duplicate: true };
465
- }
466
- try {
467
- fs.mkdirSync(botDir(id), { recursive: true });
468
- fs.appendFileSync(path.join(botDir(id), 'MEMORY.md'), '- ' + t + '\n');
469
- return { ok: true };
470
- } catch (e) {
471
- return { ok: false, error: String((e && e.message) || e) };
472
- }
473
- }
474
-
475
- /* botun USER.md'si: bota özel kullanıcı profili (aynı dedup/yenile mantığı) */
476
- function appendUserMem(id, text) {
477
- const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 300);
478
- if (!t) return { ok: false, error: 'empty' };
479
- try {
480
- const p = path.join(botDir(id), 'USER.md');
481
- fs.mkdirSync(botDir(id), { recursive: true });
482
- const lines = readMem(id, 'USER.md')
483
- .split('\n')
484
- .map((l) => l.replace(/^[-*]\s*/, '').trim())
485
- .filter(Boolean);
486
- /* profil anahtarı: "Konu: ..." satırlarında Konu — değişiklikte güncelle */
487
- const topicOf = (s) => {
488
- const c = foldTr(s).indexOf(':');
489
- return (c > 0 ? foldTr(s).slice(0, c) : foldTr(s)).replace(/[^a-z0-9]+/g, ' ').trim();
490
- };
491
- const topic = topicOf(t);
492
- const idx = lines.findIndex((l) => topicOf(l) === topic);
493
- if (idx >= 0) {
494
- if (lines[idx] === t) return { ok: true, duplicate: true };
495
- lines[idx] = t;
496
- } else {
497
- lines.push(t);
498
- }
499
- while (lines.length > 60) lines.shift();
500
- fs.writeFileSync(p, lines.map((l) => '- ' + l).join('\n') + '\n');
501
- return { ok: true, updated: idx >= 0 };
502
- } catch (e) {
503
- return { ok: false, error: String((e && e.message) || e) };
504
- }
505
- }
506
-
507
- function searchMem(id, query, limit = 8) {
508
- const list = memEntries(id);
509
- if (!list.length) return [];
510
- const qTokens = memory.tokenize(query);
511
- let rows;
512
- if (!qTokens.length) {
513
- rows = list.map((text, i) => ({ text, score: 0, i })).slice(-limit).reverse();
514
- } else {
515
- rows = list
516
- .map((text, i) => ({ text, i, score: Number(memory.scoreEntry(text, qTokens).toFixed(3)) }))
517
- .filter((r) => r.score > 0)
518
- .sort((a, b) => b.score - a.score)
519
- .slice(0, limit);
520
- }
521
- return rows.map(({ text, score }) => ({ score, text }));
522
- }
523
-
524
- function relevantMem(id, query, { maxRelevant = 6, maxRecent = 4, charCap = 2400 } = {}) {
525
- const list = memEntries(id);
526
- if (!list.length) return '';
527
- const qTokens = memory.tokenize(query);
528
- const picked = new Set();
529
- if (qTokens.length) {
530
- list
531
- .map((text, i) => ({ i, score: memory.scoreEntry(text, qTokens) }))
532
- .filter((r) => r.score > 0)
533
- .sort((a, b) => b.score - a.score)
534
- .slice(0, maxRelevant)
535
- .forEach((r) => picked.add(r.i));
536
- }
537
- for (let i = list.length - 1; i >= 0 && picked.size < maxRelevant + maxRecent; i--) picked.add(i);
538
- let out = '';
539
- for (const i of [...picked].sort((a, b) => a - b)) {
540
- const line = '- ' + list[i];
541
- if (out.length + line.length > charCap) break;
542
- out += (out ? '\n' : '') + line;
543
- }
544
- return out;
545
- }
546
-
547
- function readLog(id, cap = 20000) {
548
- try {
549
- return fs.readFileSync(path.join(botDir(id), 'logs', 'changes.log'), 'utf8').slice(-cap);
550
- } catch {
551
- return '';
552
- }
553
- }
554
-
555
- module.exports = {
556
- MAX_BOTS,
557
- ICONS,
558
- SKILL_LIST,
559
- PLUGIN_LIST,
560
- beastRoot,
561
- botDir,
562
- list,
563
- get,
564
- add,
565
- update,
566
- remove,
567
- canSee,
568
- ensureBotCodes,
569
- byCode,
570
- readMemory,
571
- readMemoryFiles,
572
- writeMemoryFile,
573
- readMem,
574
- memPath,
575
- appendMem,
576
- appendUserMem,
577
- searchMem,
578
- relevantMem,
579
- readLog,
580
- };
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
+ /* ---------- registry ---------- */
102
+
103
+ let REG = null;
104
+
105
+ function loadRegistry() {
106
+ if (REG) return REG;
107
+ try {
108
+ REG = JSON.parse(fs.readFileSync(registryFile(), 'utf8'));
109
+ } catch {
110
+ REG = null;
111
+ }
112
+ if (!REG || !Array.isArray(REG.bots)) REG = { bots: [adminBot()] };
113
+ /* beast her zaman ilk ve admin */
114
+ const beast = REG.bots.find((b) => b && b.id === 'beast');
115
+ if (!beast) REG.bots.unshift(adminBot());
116
+ else Object.assign(beast, { ...adminBot(), ...beast, id: 'beast', admin: true, name: beast.name || 'Beast' });
117
+ saveRegistry();
118
+ ensureDirs();
119
+ return REG;
120
+ }
121
+
122
+ function saveRegistry() {
123
+ try {
124
+ fs.mkdirSync(beastRoot(), { recursive: true });
125
+ fs.writeFileSync(registryFile(), JSON.stringify(REG, null, 2));
126
+ } catch {}
127
+ }
128
+
129
+ function ensureDirs() {
130
+ for (const b of REG.bots) {
131
+ const d = botDir(b.id);
132
+ try {
133
+ for (const sub of ['logs', 'chat', 'plugins', 'watchers']) fs.mkdirSync(path.join(d, sub), { recursive: true });
134
+ const cfgP = path.join(d, 'config.json');
135
+ if (!fs.existsSync(cfgP)) fs.writeFileSync(cfgP, JSON.stringify(botConfigOf(b), null, 2));
136
+ const yetP = path.join(d, 'yetkiler.json');
137
+ if (!fs.existsSync(yetP)) fs.writeFileSync(yetP, JSON.stringify(botPermsOf(b), null, 2));
138
+ /* bot hafızası ayarlardaki yapıyla aynı: SOUL.md + MEMORY.md + USER.md
139
+ (eski memory.md varsa içeriği MEMORY.md'ye taşınır) */
140
+ const legacyP = path.join(d, 'memory.md');
141
+ const legacy = fs.existsSync(legacyP) ? fs.readFileSync(legacyP, 'utf8') : '';
142
+ const memP = path.join(d, 'MEMORY.md');
143
+ if (!fs.existsSync(memP)) fs.writeFileSync(memP, legacy || '');
144
+ for (const f of ['SOUL.md', 'USER.md']) {
145
+ const p = path.join(d, f);
146
+ if (!fs.existsSync(p)) fs.writeFileSync(p, '');
147
+ }
148
+ } catch {}
149
+ }
150
+ }
151
+
152
+ function botConfigOf(b) {
153
+ return {
154
+ id: b.id,
155
+ name: b.name,
156
+ icon: b.icon,
157
+ admin: !!b.admin,
158
+ code: b.code || '',
159
+ prompt: b.prompt || '',
160
+ browser: { default: b.browserDefault || 'dahili', extCommand: b.extCommand || '' },
161
+ plugins: b.plugins || [],
162
+ createdAt: b.createdAt,
163
+ };
164
+ }
165
+
166
+ function botPermsOf(b) {
167
+ return {
168
+ id: b.id,
169
+ perm: b.perm || 'all',
170
+ skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) },
171
+ seeBots: b.seeBots || [],
172
+ extBrowser: b.extBrowser !== false,
173
+ };
174
+ }
175
+
176
+ /* ---------- log ---------- */
177
+
178
+ function logChange(id, text) {
179
+ try {
180
+ const d = botDir(id);
181
+ fs.mkdirSync(path.join(d, 'logs'), { recursive: true });
182
+ fs.appendFileSync(path.join(d, 'logs', 'changes.log'), `[${new Date().toISOString()}] ${text}\n`);
183
+ } catch {}
184
+ }
185
+
186
+ /* ---------- public API ---------- */
187
+
188
+ function list() {
189
+ loadRegistry();
190
+ return REG.bots.map((b) => ({ ...b, skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) } }));
191
+ }
192
+
193
+ function get(id) {
194
+ loadRegistry();
195
+ return REG.bots.find((b) => b.id === String(id || '')) || null;
196
+ }
197
+
198
+ function add({ name, icon, prompt }) {
199
+ loadRegistry();
200
+ const n = String(name || '').trim().slice(0, 40);
201
+ if (!n) return { ok: false, error: 'bot adı zorunlu' };
202
+ /* İLK HARF ZORUNLU: bot adı harf karakteriyle başlamalı */
203
+ if (!/^\p{L}/u.test(n)) return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
204
+ if (REG.bots.length >= MAX_BOTS) return { ok: false, error: `en fazla ${MAX_BOTS} bot olabilir (1 admin + ${MAX_BOTS - 1} müşteri)` };
205
+ let id = slugify(n);
206
+ while (get(id)) id = slugify(n) + '-' + uid().slice(-4);
207
+ const bot = {
208
+ id,
209
+ name: n,
210
+ icon: ICONS.includes(icon) ? icon : '🤖',
211
+ admin: false,
212
+ code: '', // ensureBotCodes hemen altında benzersiz 5 hane atar
213
+ prompt: String(prompt || '').slice(0, 4000),
214
+ perm: 'all',
215
+ vis: true, // sohbet görünürlüğü (admin matristen kapatır)
216
+ skills: { ...DEFAULT_SKILLS },
217
+ seeBots: [], // varsayılan: hiçbir bot baka botu göremez
218
+ extBrowser: false, // dış tarayıcı sadece yetkilide
219
+ browserDefault: 'dahili',
220
+ extCommand: '',
221
+ plugins: [],
222
+ createdAt: nowIso(),
223
+ };
224
+ REG.bots.push(bot);
225
+ ensureBotCodes();
226
+ saveRegistry();
227
+ ensureDirs();
228
+ logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon}, code=${bot.code})`);
229
+ return { ok: true, bot: { ...bot } };
230
+ }
231
+
232
+ /* ---------- 5 HANELİ BENZERSİZ BOT KODU ----------
233
+ Her bot oluşurken 10000-99999 arası rastgele kod alır; duplicate kontrolü
234
+ zorunlu (tüm botlara karşı). Açılışta kodu eksik/çakışık botlara da atanır. */
235
+ function ensureBotCodes() {
236
+ loadRegistry();
237
+ let dirty = false;
238
+ const used = () => new Set(REG.bots.map((b) => String(b.code || '')).filter((c) => /^\d{5}$/.test(c)));
239
+ const seen = new Set();
240
+ for (const b of REG.bots) {
241
+ const cur = String(b.code || '');
242
+ if (/^\d{5}$/.test(cur) && !seen.has(cur)) { seen.add(cur); continue; }
243
+ const u = used();
244
+ let c;
245
+ do { c = String(10000 + Math.floor(Math.random() * 90000)); } while (u.has(c));
246
+ logChange(b.id, `bot kodu atandı: ${c}${cur ? ` (eskisi geçersizdi: ${cur})` : ''}`);
247
+ b.code = c;
248
+ seen.add(c);
249
+ dirty = true;
250
+ }
251
+ if (dirty) saveRegistry();
252
+ return dirty;
253
+ }
254
+
255
+ function byCode(code) {
256
+ const c = String(code || '').replace(/\D/g, '');
257
+ if (!/^\d{5}$/.test(c)) return null;
258
+ loadRegistry();
259
+ return REG.bots.find((b) => String(b.code || '') === c) || null;
260
+ }
261
+
262
+ function update(id, patch) {
263
+ loadRegistry();
264
+ const b = get(id);
265
+ if (!b) return { ok: false, error: 'bot yok' };
266
+ /* İLK HARF ZORUNLU: ad değişikliği harf karakteriyle başlamalı —
267
+ diğer alanlara dokunmadan reddet */
268
+ if (patch && typeof patch.name === 'string' && patch.name.trim() &&
269
+ !/^\p{L}/u.test(patch.name.trim())) {
270
+ return { ok: false, error: 'bot adı harf ile başlamalı — ilk karakter harf olmalı' };
271
+ }
272
+ const changes = [];
273
+ if (patch && typeof patch === 'object') {
274
+ if (typeof patch.name === 'string' && patch.name.trim() && patch.name.trim() !== b.name) {
275
+ changes.push(`name: "${b.name}" → "${patch.name.trim().slice(0, 40)}"`);
276
+ b.name = patch.name.trim().slice(0, 40);
277
+ }
278
+ if (typeof patch.icon === 'string' && ICONS.includes(patch.icon) && patch.icon !== b.icon) {
279
+ changes.push(`icon: ${b.icon} → ${patch.icon}`);
280
+ b.icon = patch.icon;
281
+ }
282
+ if (typeof patch.prompt === 'string' && patch.prompt !== b.prompt) {
283
+ changes.push('prompt güncellendi');
284
+ b.prompt = String(patch.prompt).slice(0, 4000);
285
+ }
286
+ if (patch.perm !== undefined) {
287
+ /* 'all' tek başına tüm araçları verir; web/read/chat çoklu seçilebilir
288
+ (['web','read'] gibi dizi ya da 'web,read' gibi string kabul edilir) */
289
+ const PERMS_ALL = ['all', 'web', 'read', 'chat'];
290
+ let nextPerm = null;
291
+ if (Array.isArray(patch.perm)) {
292
+ const picked = [...new Set(patch.perm.map((x) => String(x).trim()).filter((x) => PERMS_ALL.includes(x)))];
293
+ nextPerm = picked.includes('all') ? 'all' : (picked.length ? picked : 'chat');
294
+ } else if (typeof patch.perm === 'string') {
295
+ const arr = patch.perm.split(',').map((x) => x.trim()).filter((x) => PERMS_ALL.includes(x));
296
+ if (arr.length) nextPerm = arr.includes('all') ? 'all' : (arr.length === 1 ? arr[0] : arr);
297
+ }
298
+ if (nextPerm !== null && JSON.stringify(nextPerm) !== JSON.stringify(b.perm)) {
299
+ const fp = (p) => (Array.isArray(p) ? '[' + p.join('+') + ']' : String(p));
300
+ changes.push(`perm: ${fp(b.perm)} → ${fp(nextPerm)}`);
301
+ b.perm = nextPerm;
302
+ }
303
+ }
304
+ if (patch.skills && typeof patch.skills === 'object') {
305
+ const merged = { ...DEFAULT_SKILLS, ...(b.skills || {}) };
306
+ for (const k of Object.keys(DEFAULT_SKILLS)) {
307
+ if (typeof patch.skills[k] === 'boolean' && patch.skills[k] !== merged[k]) {
308
+ changes.push(`skill ${k}: ${merged[k]} → ${patch.skills[k]}`);
309
+ merged[k] = patch.skills[k];
310
+ }
311
+ }
312
+ b.skills = merged;
313
+ }
314
+ if (Array.isArray(patch.seeBots)) {
315
+ const valid = patch.seeBots.map(String).filter((x) => x !== b.id && get(x));
316
+ const next = [...new Set(valid)];
317
+ if (JSON.stringify(next) !== JSON.stringify(b.seeBots || [])) {
318
+ changes.push(`görebilir: [${(b.seeBots || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
319
+ b.seeBots = next;
320
+ }
321
+ }
322
+ if (typeof patch.extBrowser === 'boolean' && patch.extBrowser !== b.extBrowser) {
323
+ changes.push(`dış tarayıcı yetkisi: ${b.extBrowser} → ${patch.extBrowser}`);
324
+ b.extBrowser = patch.extBrowser;
325
+ }
326
+ if (typeof patch.vis === 'boolean' && patch.vis !== (b.vis !== false)) {
327
+ changes.push(`sohbet görünürlüğü: ${b.vis !== false} → ${patch.vis}`);
328
+ b.vis = patch.vis;
329
+ }
330
+ if (['dahili', 'dis'].includes(patch.browserDefault) && patch.browserDefault !== b.browserDefault) {
331
+ changes.push(`varsayılan tarayıcı: ${b.browserDefault} → ${patch.browserDefault}`);
332
+ b.browserDefault = patch.browserDefault;
333
+ }
334
+ if (typeof patch.extCommand === 'string' && patch.extCommand !== b.extCommand) {
335
+ changes.push('dış tarayıcı komutu güncellendi');
336
+ b.extCommand = String(patch.extCommand).slice(0, 200);
337
+ }
338
+ if (typeof patch.model === 'string') {
339
+ /* bot bazlı model override — boş string = global seçim */
340
+ const next = patch.model.trim().slice(0, 160);
341
+ if (next !== (b.model || '')) {
342
+ changes.push('model güncellendi');
343
+ b.model = next;
344
+ }
345
+ }
346
+ if (Array.isArray(patch.plugins)) {
347
+ const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
348
+ if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
349
+ changes.push(`pluginler: [${(b.plugins || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
350
+ b.plugins = next;
351
+ }
352
+ }
353
+ }
354
+ saveRegistry();
355
+ ensureDirs();
356
+ try {
357
+ const d = botDir(b.id);
358
+ fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify(botConfigOf(b), null, 2));
359
+ fs.writeFileSync(path.join(d, 'yetkiler.json'), JSON.stringify(botPermsOf(b), null, 2));
360
+ } catch {}
361
+ for (const c of changes) logChange(b.id, `yetki/ayar değişikliği (admin): ${c}`);
362
+ return { ok: true, bot: { ...b }, changed: changes.length };
363
+ }
364
+
365
+ function remove(id) {
366
+ loadRegistry();
367
+ const b = get(id);
368
+ if (!b) return { ok: false, error: 'bot yok' };
369
+ if (b.admin) return { ok: false, error: 'admin bot silinemez' };
370
+ REG.bots = REG.bots.filter((x) => x.id !== b.id);
371
+ saveRegistry();
372
+ logChange(b.id, `bot SİLİNDİ — bağlı numaralar botsuz duruma düştü (beast'e yönlendirilir)`);
373
+ /* klasör arşivlenir — veri kaybı olmasın */
374
+ try {
375
+ const d = botDir(b.id);
376
+ if (fs.existsSync(d)) fs.renameSync(d, d + '-arsiv-' + Date.now().toString(36));
377
+ } catch {}
378
+ return { ok: true };
379
+ }
380
+
381
+ /* viewer botun target botu görüp göremeyeceği */
382
+ function canSee(viewerId, targetId) {
383
+ const v = get(viewerId);
384
+ if (!v) return false;
385
+ if (v.admin) return true; // admin her şeyi görür
386
+ if (viewerId === targetId) return true;
387
+ return (v.seeBots || []).includes(String(targetId));
388
+ }
389
+
390
+ /* botun hafıza dosyaları — ayarlardaki SOUL/USER/MEMORY üçlüsünün bot izole hali */
391
+ const BOT_MEM_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md'];
392
+
393
+ /* botun MEMORY.md'si (persona bloğuna giden kısım) */
394
+ function readMemory(id, cap = 2000) {
395
+ try {
396
+ return fs.readFileSync(path.join(botDir(id), 'MEMORY.md'), 'utf8').slice(0, cap).trim();
397
+ } catch {
398
+ return '';
399
+ }
400
+ }
401
+
402
+ function readMemoryFiles(id) {
403
+ const d = botDir(id);
404
+ const out = { soul: '', memory: '', user: '' };
405
+ try { out.soul = fs.readFileSync(path.join(d, 'SOUL.md'), 'utf8'); } catch {}
406
+ try { out.memory = fs.readFileSync(path.join(d, 'MEMORY.md'), 'utf8'); } catch {}
407
+ try { out.user = fs.readFileSync(path.join(d, 'USER.md'), 'utf8'); } catch {}
408
+ return out;
409
+ }
410
+
411
+ function writeMemoryFile(id, file, content) {
412
+ const b = get(id);
413
+ if (!b) return { ok: false, error: 'bot yok' };
414
+ if (!BOT_MEM_FILES.includes(file)) return { ok: false, error: 'geçersiz dosya' };
415
+ try {
416
+ const d = botDir(id);
417
+ fs.mkdirSync(d, { recursive: true });
418
+ fs.writeFileSync(path.join(d, file), String(content ?? ''), 'utf8');
419
+ /* admin MEMORY.md'yi elle değiştirdiyse botun mem0 store'unu yeniden kur */
420
+ if (file === 'MEMORY.md') {
421
+ try {
422
+ const mem0 = require('./mem0');
423
+ mem0.reindexFromLines('bot:' + id, String(content ?? '').split('\n').map((l) => l.replace(/^[-*]\s*/, '').trim()).filter(Boolean));
424
+ } catch {}
425
+ }
426
+ logChange(id, `${file} admin tarafından güncellendi`);
427
+ return { ok: true };
428
+ } catch (e) {
429
+ return { ok: false, error: String((e && e.message) || e) };
430
+ }
431
+ }
432
+
433
+ /* botun MEMORY.md ayna dosyasının yolu (mem0 syncMirror kullanır) */
434
+ function memPath(id) {
435
+ return path.join(botDir(id), 'MEMORY.md');
436
+ }
437
+
438
+ /* ---------- BOT OTURUMU hafıza operasyonları ----------
439
+ Bot oturumlarında memory_write/memory_search GLOBAL Beast hafızasına DEĞİL,
440
+ botun kendi SOUL/USER/MEMORY dosyalarına gider (tam izolasyon). */
441
+
442
+ function readMem(id, f) {
443
+ if (!BOT_MEM_FILES.includes(f)) return '';
444
+ try {
445
+ return fs.readFileSync(path.join(botDir(id), f), 'utf8');
446
+ } catch {
447
+ return '';
448
+ }
449
+ }
450
+
451
+ function memEntries(id) {
452
+ return readMem(id, 'MEMORY.md')
453
+ .split('\n')
454
+ .map((l) => l.replace(/^[-*]\s*/, '').trim())
455
+ .filter(Boolean);
456
+ }
457
+
458
+ function appendMem(id, text) {
459
+ const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 500);
460
+ if (!t) return { ok: false, error: 'empty' };
461
+ const list = memEntries(id);
462
+ const key = foldTr(t).replace(/[^a-z0-9]+/g, ' ').trim();
463
+ if (list.some((l) => foldTr(l).replace(/[^a-z0-9]+/g, ' ').trim() === key)) {
464
+ return { ok: true, duplicate: true };
465
+ }
466
+ try {
467
+ fs.mkdirSync(botDir(id), { recursive: true });
468
+ fs.appendFileSync(path.join(botDir(id), 'MEMORY.md'), '- ' + t + '\n');
469
+ return { ok: true };
470
+ } catch (e) {
471
+ return { ok: false, error: String((e && e.message) || e) };
472
+ }
473
+ }
474
+
475
+ /* botun USER.md'si: bota özel kullanıcı profili (aynı dedup/yenile mantığı) */
476
+ function appendUserMem(id, text) {
477
+ const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 300);
478
+ if (!t) return { ok: false, error: 'empty' };
479
+ try {
480
+ const p = path.join(botDir(id), 'USER.md');
481
+ fs.mkdirSync(botDir(id), { recursive: true });
482
+ const lines = readMem(id, 'USER.md')
483
+ .split('\n')
484
+ .map((l) => l.replace(/^[-*]\s*/, '').trim())
485
+ .filter(Boolean);
486
+ /* profil anahtarı: "Konu: ..." satırlarında Konu — değişiklikte güncelle */
487
+ const topicOf = (s) => {
488
+ const c = foldTr(s).indexOf(':');
489
+ return (c > 0 ? foldTr(s).slice(0, c) : foldTr(s)).replace(/[^a-z0-9]+/g, ' ').trim();
490
+ };
491
+ const topic = topicOf(t);
492
+ const idx = lines.findIndex((l) => topicOf(l) === topic);
493
+ if (idx >= 0) {
494
+ if (lines[idx] === t) return { ok: true, duplicate: true };
495
+ lines[idx] = t;
496
+ } else {
497
+ lines.push(t);
498
+ }
499
+ while (lines.length > 60) lines.shift();
500
+ fs.writeFileSync(p, lines.map((l) => '- ' + l).join('\n') + '\n');
501
+ return { ok: true, updated: idx >= 0 };
502
+ } catch (e) {
503
+ return { ok: false, error: String((e && e.message) || e) };
504
+ }
505
+ }
506
+
507
+ function searchMem(id, query, limit = 8) {
508
+ const list = memEntries(id);
509
+ if (!list.length) return [];
510
+ const qTokens = memory.tokenize(query);
511
+ let rows;
512
+ if (!qTokens.length) {
513
+ rows = list.map((text, i) => ({ text, score: 0, i })).slice(-limit).reverse();
514
+ } else {
515
+ rows = list
516
+ .map((text, i) => ({ text, i, score: Number(memory.scoreEntry(text, qTokens).toFixed(3)) }))
517
+ .filter((r) => r.score > 0)
518
+ .sort((a, b) => b.score - a.score)
519
+ .slice(0, limit);
520
+ }
521
+ return rows.map(({ text, score }) => ({ score, text }));
522
+ }
523
+
524
+ function relevantMem(id, query, { maxRelevant = 6, maxRecent = 4, charCap = 2400 } = {}) {
525
+ const list = memEntries(id);
526
+ if (!list.length) return '';
527
+ const qTokens = memory.tokenize(query);
528
+ const picked = new Set();
529
+ if (qTokens.length) {
530
+ list
531
+ .map((text, i) => ({ i, score: memory.scoreEntry(text, qTokens) }))
532
+ .filter((r) => r.score > 0)
533
+ .sort((a, b) => b.score - a.score)
534
+ .slice(0, maxRelevant)
535
+ .forEach((r) => picked.add(r.i));
536
+ }
537
+ for (let i = list.length - 1; i >= 0 && picked.size < maxRelevant + maxRecent; i--) picked.add(i);
538
+ let out = '';
539
+ for (const i of [...picked].sort((a, b) => a - b)) {
540
+ const line = '- ' + list[i];
541
+ if (out.length + line.length > charCap) break;
542
+ out += (out ? '\n' : '') + line;
543
+ }
544
+ return out;
545
+ }
546
+
547
+ function readLog(id, cap = 20000) {
548
+ try {
549
+ return fs.readFileSync(path.join(botDir(id), 'logs', 'changes.log'), 'utf8').slice(-cap);
550
+ } catch {
551
+ return '';
552
+ }
553
+ }
554
+
555
+ module.exports = {
556
+ MAX_BOTS,
557
+ ICONS,
558
+ SKILL_LIST,
559
+ PLUGIN_LIST,
560
+ beastRoot,
561
+ botDir,
562
+ list,
563
+ get,
564
+ add,
565
+ update,
566
+ remove,
567
+ canSee,
568
+ ensureBotCodes,
569
+ byCode,
570
+ readMemory,
571
+ readMemoryFiles,
572
+ writeMemoryFile,
573
+ readMem,
574
+ memPath,
575
+ appendMem,
576
+ appendUserMem,
577
+ searchMem,
578
+ relevantMem,
579
+ readLog,
580
+ };