beast-agent 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -0,0 +1,495 @@
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
+ prompt: '',
88
+ perm: 'all',
89
+ skills: { ...DEFAULT_SKILLS, run_command: true },
90
+ seeBots: [], // admin her şeyi görür
91
+ extBrowser: true, // dış tarayıcı yetkisi
92
+ browserDefault: 'dahili',
93
+ extCommand: '',
94
+ plugins: [],
95
+ createdAt: nowIso(),
96
+ };
97
+ }
98
+
99
+ /* ---------- registry ---------- */
100
+
101
+ let REG = null;
102
+
103
+ function loadRegistry() {
104
+ if (REG) return REG;
105
+ try {
106
+ REG = JSON.parse(fs.readFileSync(registryFile(), 'utf8'));
107
+ } catch {
108
+ REG = null;
109
+ }
110
+ if (!REG || !Array.isArray(REG.bots)) REG = { bots: [adminBot()] };
111
+ /* beast her zaman ilk ve admin */
112
+ const beast = REG.bots.find((b) => b && b.id === 'beast');
113
+ if (!beast) REG.bots.unshift(adminBot());
114
+ else Object.assign(beast, { ...adminBot(), ...beast, id: 'beast', admin: true, name: beast.name || 'Beast' });
115
+ saveRegistry();
116
+ ensureDirs();
117
+ return REG;
118
+ }
119
+
120
+ function saveRegistry() {
121
+ try {
122
+ fs.mkdirSync(beastRoot(), { recursive: true });
123
+ fs.writeFileSync(registryFile(), JSON.stringify(REG, null, 2));
124
+ } catch {}
125
+ }
126
+
127
+ function ensureDirs() {
128
+ for (const b of REG.bots) {
129
+ const d = botDir(b.id);
130
+ try {
131
+ for (const sub of ['logs', 'chat', 'plugins', 'watchers']) fs.mkdirSync(path.join(d, sub), { recursive: true });
132
+ const cfgP = path.join(d, 'config.json');
133
+ if (!fs.existsSync(cfgP)) fs.writeFileSync(cfgP, JSON.stringify(botConfigOf(b), null, 2));
134
+ const yetP = path.join(d, 'yetkiler.json');
135
+ if (!fs.existsSync(yetP)) fs.writeFileSync(yetP, JSON.stringify(botPermsOf(b), null, 2));
136
+ /* bot hafızası ayarlardaki yapıyla aynı: SOUL.md + MEMORY.md + USER.md
137
+ (eski memory.md varsa içeriği MEMORY.md'ye taşınır) */
138
+ const legacyP = path.join(d, 'memory.md');
139
+ const legacy = fs.existsSync(legacyP) ? fs.readFileSync(legacyP, 'utf8') : '';
140
+ const memP = path.join(d, 'MEMORY.md');
141
+ if (!fs.existsSync(memP)) fs.writeFileSync(memP, legacy || '');
142
+ for (const f of ['SOUL.md', 'USER.md']) {
143
+ const p = path.join(d, f);
144
+ if (!fs.existsSync(p)) fs.writeFileSync(p, '');
145
+ }
146
+ } catch {}
147
+ }
148
+ }
149
+
150
+ function botConfigOf(b) {
151
+ return {
152
+ id: b.id,
153
+ name: b.name,
154
+ icon: b.icon,
155
+ admin: !!b.admin,
156
+ prompt: b.prompt || '',
157
+ browser: { default: b.browserDefault || 'dahili', extCommand: b.extCommand || '' },
158
+ plugins: b.plugins || [],
159
+ createdAt: b.createdAt,
160
+ };
161
+ }
162
+
163
+ function botPermsOf(b) {
164
+ return {
165
+ id: b.id,
166
+ perm: b.perm || 'all',
167
+ skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) },
168
+ seeBots: b.seeBots || [],
169
+ extBrowser: b.extBrowser !== false,
170
+ };
171
+ }
172
+
173
+ /* ---------- log ---------- */
174
+
175
+ function logChange(id, text) {
176
+ try {
177
+ const d = botDir(id);
178
+ fs.mkdirSync(path.join(d, 'logs'), { recursive: true });
179
+ fs.appendFileSync(path.join(d, 'logs', 'changes.log'), `[${new Date().toISOString()}] ${text}\n`);
180
+ } catch {}
181
+ }
182
+
183
+ /* ---------- public API ---------- */
184
+
185
+ function list() {
186
+ loadRegistry();
187
+ return REG.bots.map((b) => ({ ...b, skills: { ...DEFAULT_SKILLS, ...(b.skills || {}) } }));
188
+ }
189
+
190
+ function get(id) {
191
+ loadRegistry();
192
+ return REG.bots.find((b) => b.id === String(id || '')) || null;
193
+ }
194
+
195
+ function add({ name, icon, prompt }) {
196
+ loadRegistry();
197
+ const n = String(name || '').trim().slice(0, 40);
198
+ if (!n) return { ok: false, error: 'bot adı zorunlu' };
199
+ if (REG.bots.length >= MAX_BOTS) return { ok: false, error: `en fazla ${MAX_BOTS} bot olabilir (1 admin + ${MAX_BOTS - 1} müşteri)` };
200
+ let id = slugify(n);
201
+ while (get(id)) id = slugify(n) + '-' + uid().slice(-4);
202
+ const bot = {
203
+ id,
204
+ name: n,
205
+ icon: ICONS.includes(icon) ? icon : '🤖',
206
+ admin: false,
207
+ prompt: String(prompt || '').slice(0, 4000),
208
+ perm: 'all',
209
+ skills: { ...DEFAULT_SKILLS },
210
+ seeBots: [], // varsayılan: hiçbir bot baka botu göremez
211
+ extBrowser: false, // dış tarayıcı sadece yetkilide
212
+ browserDefault: 'dahili',
213
+ extCommand: '',
214
+ plugins: [],
215
+ createdAt: nowIso(),
216
+ };
217
+ REG.bots.push(bot);
218
+ saveRegistry();
219
+ ensureDirs();
220
+ logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon})`);
221
+ return { ok: true, bot: { ...bot } };
222
+ }
223
+
224
+ function update(id, patch) {
225
+ loadRegistry();
226
+ const b = get(id);
227
+ if (!b) return { ok: false, error: 'bot yok' };
228
+ const changes = [];
229
+ if (patch && typeof patch === 'object') {
230
+ if (typeof patch.name === 'string' && patch.name.trim() && patch.name.trim() !== b.name) {
231
+ changes.push(`name: "${b.name}" → "${patch.name.trim().slice(0, 40)}"`);
232
+ b.name = patch.name.trim().slice(0, 40);
233
+ }
234
+ if (typeof patch.icon === 'string' && ICONS.includes(patch.icon) && patch.icon !== b.icon) {
235
+ changes.push(`icon: ${b.icon} → ${patch.icon}`);
236
+ b.icon = patch.icon;
237
+ }
238
+ if (typeof patch.prompt === 'string' && patch.prompt !== b.prompt) {
239
+ changes.push('prompt güncellendi');
240
+ b.prompt = String(patch.prompt).slice(0, 4000);
241
+ }
242
+ if (['all', 'web', 'read', 'chat'].includes(patch.perm) && patch.perm !== b.perm) {
243
+ changes.push(`perm: ${b.perm} → ${patch.perm}`);
244
+ b.perm = patch.perm;
245
+ }
246
+ if (patch.skills && typeof patch.skills === 'object') {
247
+ const merged = { ...DEFAULT_SKILLS, ...(b.skills || {}) };
248
+ for (const k of Object.keys(DEFAULT_SKILLS)) {
249
+ if (typeof patch.skills[k] === 'boolean' && patch.skills[k] !== merged[k]) {
250
+ changes.push(`skill ${k}: ${merged[k]} → ${patch.skills[k]}`);
251
+ merged[k] = patch.skills[k];
252
+ }
253
+ }
254
+ b.skills = merged;
255
+ }
256
+ if (Array.isArray(patch.seeBots)) {
257
+ const valid = patch.seeBots.map(String).filter((x) => x !== b.id && get(x));
258
+ const next = [...new Set(valid)];
259
+ if (JSON.stringify(next) !== JSON.stringify(b.seeBots || [])) {
260
+ changes.push(`görebilir: [${(b.seeBots || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
261
+ b.seeBots = next;
262
+ }
263
+ }
264
+ if (typeof patch.extBrowser === 'boolean' && patch.extBrowser !== b.extBrowser) {
265
+ changes.push(`dış tarayıcı yetkisi: ${b.extBrowser} → ${patch.extBrowser}`);
266
+ b.extBrowser = patch.extBrowser;
267
+ }
268
+ if (['dahili', 'dis'].includes(patch.browserDefault) && patch.browserDefault !== b.browserDefault) {
269
+ changes.push(`varsayılan tarayıcı: ${b.browserDefault} → ${patch.browserDefault}`);
270
+ b.browserDefault = patch.browserDefault;
271
+ }
272
+ if (typeof patch.extCommand === 'string' && patch.extCommand !== b.extCommand) {
273
+ changes.push('dış tarayıcı komutu güncellendi');
274
+ b.extCommand = String(patch.extCommand).slice(0, 200);
275
+ }
276
+ if (Array.isArray(patch.plugins)) {
277
+ const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
278
+ if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
279
+ changes.push(`pluginler: [${(b.plugins || []).join(', ') || '—'}] → [${next.join(', ') || '—'}]`);
280
+ b.plugins = next;
281
+ }
282
+ }
283
+ }
284
+ saveRegistry();
285
+ ensureDirs();
286
+ try {
287
+ const d = botDir(b.id);
288
+ fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify(botConfigOf(b), null, 2));
289
+ fs.writeFileSync(path.join(d, 'yetkiler.json'), JSON.stringify(botPermsOf(b), null, 2));
290
+ } catch {}
291
+ for (const c of changes) logChange(b.id, `yetki/ayar değişikliği (admin): ${c}`);
292
+ return { ok: true, bot: { ...b }, changed: changes.length };
293
+ }
294
+
295
+ function remove(id) {
296
+ loadRegistry();
297
+ const b = get(id);
298
+ if (!b) return { ok: false, error: 'bot yok' };
299
+ if (b.admin) return { ok: false, error: 'admin bot silinemez' };
300
+ REG.bots = REG.bots.filter((x) => x.id !== b.id);
301
+ saveRegistry();
302
+ logChange(b.id, `bot SİLİNDİ — bağlı numaralar botsuz duruma düştü (beast'e yönlendirilir)`);
303
+ /* klasör arşivlenir — veri kaybı olmasın */
304
+ try {
305
+ const d = botDir(b.id);
306
+ if (fs.existsSync(d)) fs.renameSync(d, d + '-arsiv-' + Date.now().toString(36));
307
+ } catch {}
308
+ return { ok: true };
309
+ }
310
+
311
+ /* viewer botun target botu görüp göremeyeceği */
312
+ function canSee(viewerId, targetId) {
313
+ const v = get(viewerId);
314
+ if (!v) return false;
315
+ if (v.admin) return true; // admin her şeyi görür
316
+ if (viewerId === targetId) return true;
317
+ return (v.seeBots || []).includes(String(targetId));
318
+ }
319
+
320
+ /* botun hafıza dosyaları — ayarlardaki SOUL/USER/MEMORY üçlüsünün bot izole hali */
321
+ const BOT_MEM_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md'];
322
+
323
+ /* botun MEMORY.md'si (persona bloğuna giden kısım) */
324
+ function readMemory(id, cap = 2000) {
325
+ try {
326
+ return fs.readFileSync(path.join(botDir(id), 'MEMORY.md'), 'utf8').slice(0, cap).trim();
327
+ } catch {
328
+ return '';
329
+ }
330
+ }
331
+
332
+ function readMemoryFiles(id) {
333
+ const d = botDir(id);
334
+ const out = { soul: '', memory: '', user: '' };
335
+ try { out.soul = fs.readFileSync(path.join(d, 'SOUL.md'), 'utf8'); } catch {}
336
+ try { out.memory = fs.readFileSync(path.join(d, 'MEMORY.md'), 'utf8'); } catch {}
337
+ try { out.user = fs.readFileSync(path.join(d, 'USER.md'), 'utf8'); } catch {}
338
+ return out;
339
+ }
340
+
341
+ function writeMemoryFile(id, file, content) {
342
+ const b = get(id);
343
+ if (!b) return { ok: false, error: 'bot yok' };
344
+ if (!BOT_MEM_FILES.includes(file)) return { ok: false, error: 'geçersiz dosya' };
345
+ try {
346
+ const d = botDir(id);
347
+ fs.mkdirSync(d, { recursive: true });
348
+ fs.writeFileSync(path.join(d, file), String(content ?? ''), 'utf8');
349
+ logChange(id, `${file} admin tarafından güncellendi`);
350
+ return { ok: true };
351
+ } catch (e) {
352
+ return { ok: false, error: String((e && e.message) || e) };
353
+ }
354
+ }
355
+
356
+ /* ---------- BOT OTURUMU hafıza operasyonları ----------
357
+ Bot oturumlarında memory_write/memory_search GLOBAL Beast hafızasına DEĞİL,
358
+ botun kendi SOUL/USER/MEMORY dosyalarına gider (tam izolasyon). */
359
+
360
+ function readMem(id, f) {
361
+ if (!BOT_MEM_FILES.includes(f)) return '';
362
+ try {
363
+ return fs.readFileSync(path.join(botDir(id), f), 'utf8');
364
+ } catch {
365
+ return '';
366
+ }
367
+ }
368
+
369
+ function memEntries(id) {
370
+ return readMem(id, 'MEMORY.md')
371
+ .split('\n')
372
+ .map((l) => l.replace(/^[-*]\s*/, '').trim())
373
+ .filter(Boolean);
374
+ }
375
+
376
+ function appendMem(id, text) {
377
+ const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 500);
378
+ if (!t) return { ok: false, error: 'empty' };
379
+ const list = memEntries(id);
380
+ const key = foldTr(t).replace(/[^a-z0-9]+/g, ' ').trim();
381
+ if (list.some((l) => foldTr(l).replace(/[^a-z0-9]+/g, ' ').trim() === key)) {
382
+ return { ok: true, duplicate: true };
383
+ }
384
+ try {
385
+ fs.mkdirSync(botDir(id), { recursive: true });
386
+ fs.appendFileSync(path.join(botDir(id), 'MEMORY.md'), '- ' + t + '\n');
387
+ return { ok: true };
388
+ } catch (e) {
389
+ return { ok: false, error: String((e && e.message) || e) };
390
+ }
391
+ }
392
+
393
+ /* botun USER.md'si: bota özel kullanıcı profili (aynı dedup/yenile mantığı) */
394
+ function appendUserMem(id, text) {
395
+ const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 300);
396
+ if (!t) return { ok: false, error: 'empty' };
397
+ try {
398
+ const p = path.join(botDir(id), 'USER.md');
399
+ fs.mkdirSync(botDir(id), { recursive: true });
400
+ const lines = readMem(id, 'USER.md')
401
+ .split('\n')
402
+ .map((l) => l.replace(/^[-*]\s*/, '').trim())
403
+ .filter(Boolean);
404
+ /* profil anahtarı: "Konu: ..." satırlarında Konu — değişiklikte güncelle */
405
+ const topicOf = (s) => {
406
+ const c = foldTr(s).indexOf(':');
407
+ return (c > 0 ? foldTr(s).slice(0, c) : foldTr(s)).replace(/[^a-z0-9]+/g, ' ').trim();
408
+ };
409
+ const topic = topicOf(t);
410
+ const idx = lines.findIndex((l) => topicOf(l) === topic);
411
+ if (idx >= 0) {
412
+ if (lines[idx] === t) return { ok: true, duplicate: true };
413
+ lines[idx] = t;
414
+ } else {
415
+ lines.push(t);
416
+ }
417
+ while (lines.length > 60) lines.shift();
418
+ fs.writeFileSync(p, lines.map((l) => '- ' + l).join('\n') + '\n');
419
+ return { ok: true, updated: idx >= 0 };
420
+ } catch (e) {
421
+ return { ok: false, error: String((e && e.message) || e) };
422
+ }
423
+ }
424
+
425
+ function searchMem(id, query, limit = 8) {
426
+ const list = memEntries(id);
427
+ if (!list.length) return [];
428
+ const qTokens = memory.tokenize(query);
429
+ let rows;
430
+ if (!qTokens.length) {
431
+ rows = list.map((text, i) => ({ text, score: 0, i })).slice(-limit).reverse();
432
+ } else {
433
+ rows = list
434
+ .map((text, i) => ({ text, i, score: Number(memory.scoreEntry(text, qTokens).toFixed(3)) }))
435
+ .filter((r) => r.score > 0)
436
+ .sort((a, b) => b.score - a.score)
437
+ .slice(0, limit);
438
+ }
439
+ return rows.map(({ text, score }) => ({ score, text }));
440
+ }
441
+
442
+ function relevantMem(id, query, { maxRelevant = 6, maxRecent = 4, charCap = 2400 } = {}) {
443
+ const list = memEntries(id);
444
+ if (!list.length) return '';
445
+ const qTokens = memory.tokenize(query);
446
+ const picked = new Set();
447
+ if (qTokens.length) {
448
+ list
449
+ .map((text, i) => ({ i, score: memory.scoreEntry(text, qTokens) }))
450
+ .filter((r) => r.score > 0)
451
+ .sort((a, b) => b.score - a.score)
452
+ .slice(0, maxRelevant)
453
+ .forEach((r) => picked.add(r.i));
454
+ }
455
+ for (let i = list.length - 1; i >= 0 && picked.size < maxRelevant + maxRecent; i--) picked.add(i);
456
+ let out = '';
457
+ for (const i of [...picked].sort((a, b) => a - b)) {
458
+ const line = '- ' + list[i];
459
+ if (out.length + line.length > charCap) break;
460
+ out += (out ? '\n' : '') + line;
461
+ }
462
+ return out;
463
+ }
464
+
465
+ function readLog(id, cap = 20000) {
466
+ try {
467
+ return fs.readFileSync(path.join(botDir(id), 'logs', 'changes.log'), 'utf8').slice(-cap);
468
+ } catch {
469
+ return '';
470
+ }
471
+ }
472
+
473
+ module.exports = {
474
+ MAX_BOTS,
475
+ ICONS,
476
+ SKILL_LIST,
477
+ PLUGIN_LIST,
478
+ beastRoot,
479
+ botDir,
480
+ list,
481
+ get,
482
+ add,
483
+ update,
484
+ remove,
485
+ canSee,
486
+ readMemory,
487
+ readMemoryFiles,
488
+ writeMemoryFile,
489
+ readMem,
490
+ appendMem,
491
+ appendUserMem,
492
+ searchMem,
493
+ relevantMem,
494
+ readLog,
495
+ };