beast-agent 0.23.9 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,8 @@
7
7
 
8
8
  const { spawn, spawnSync } = require('child_process');
9
9
  const path = require('path');
10
+ const fs = require('fs');
11
+ const os = require('os');
10
12
 
11
13
  /* kaldırma modu: uygulamayı kaldırır ama KİŞİSEL VERİLERİ korur
12
14
  (%APPDATA%\beast: config.yaml, .env, oturumlar, hafıza, WhatsApp eşlemesi, yedekler) */
@@ -47,43 +49,54 @@ if (process.argv[2] === 'uninstall') {
47
49
  process.exit(ur.status || 0);
48
50
  }
49
51
 
50
- /* güncelleme modu: çalışan Beast'i kapat (dosya kilidi EBUSY vermesin) → npm güncelle → yeniden başlat */
52
+ /* güncelleme modu: çalışan Beast'i kapat (dosya kilidi EBUSY vermesin) → npm güncelle → yeniden başlat.
53
+ npm install DETACHED çalışır: terminali/uygulamayı kapatmak update'i boğmaz. */
51
54
  if (process.argv[2] === 'update') {
52
55
  const isWin = process.platform === 'win32';
53
- console.log('\u27F3 beast-agent g\u00FCncelleniyor\u2026');
56
+ console.log('\u27F3 beast-agent g\u00FCncelleniyor\u2026 (arka planda s\u00FCrer — bu pencereyi kapabilirsin)');
54
57
  if (isWin) {
55
58
  try {
56
59
  spawnSync('powershell.exe', ['-NoProfile', '-Command',
57
60
  "Get-Process electron -ErrorAction SilentlyContinue | Where-Object { $_.Path -like '*node_modules*beast-agent*' } | Stop-Process -Force"],
58
61
  { stdio: 'ignore' });
59
62
  console.log('\u2022 \u00E7al\u0131\u015Fan Beast kapat\u0131ld\u0131 (varsa)');
60
- spawnSync('powershell.exe', ['-NoProfile', '-Command', 'Start-Sleep -Seconds 2'], { stdio: 'ignore' });
61
63
  } catch {}
62
- } else {
63
- try { spawnSync('pkill', ['-f', 'node_modules/beast-agent'], { stdio: 'ignore' }); } catch {}
64
- }
65
- /* dosya kilidi (EBUSY) bazen ilk denemede patlar — 5 deneme hakkı */
66
- let ok = false;
67
- for (let i = 1; i <= 5 && !ok; i++) {
68
- const r = spawnSync('npm', ['install', '-g', 'beast-agent@latest'], { stdio: 'inherit', shell: isWin });
69
- ok = r.status === 0;
70
- if (!ok && i < 5) {
71
- console.log(` \u2022 deneme ${i}/5 ba\u015Far\u0131s\u0131z (dosya kilidi olabilir) \u2014 3 sn sonra tekrar\u2026`);
72
- if (isWin) spawnSync('powershell.exe', ['-NoProfile', '-Command', 'Start-Sleep -Seconds 3'], { stdio: 'ignore' });
73
- else spawnSync('sleep', ['3']);
74
- }
75
- }
76
- if (!ok) {
77
- console.log('\n\u2717 g\u00FCncelleme ba\u015Far\u0131s\u0131z \u2014 elle: npm install -g beast-agent@latest');
78
- process.exit(1);
64
+ /* detached PS helper: npm install (5 deneme) + electron.exe ile yeniden başlatma */
65
+ const ps = [
66
+ "$ErrorActionPreference = 'Continue'",
67
+ "$Log = Join-Path $env:APPDATA 'beast\\update.log'",
68
+ "function L([string]$m) { try { Add-Content -LiteralPath $Log -Value (\"[\" + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + \"] \" + $m) } catch {} }",
69
+ "L '=== beast update (terminal) basladi ==='",
70
+ "$ok = $false",
71
+ "for ($i = 1; $i -le 5 -and -not $ok; $i++) {",
72
+ " L \"npm install -g beast-agent@latest (deneme $i)\"",
73
+ " & npm.cmd install -g beast-agent@latest 2>&1 | ForEach-Object { L \" npm: $_\" }",
74
+ " if ($LASTEXITCODE -eq 0) { $ok = $true } else { Start-Sleep -Seconds 3 }",
75
+ "}",
76
+ "if (-not $ok) { L 'HATA: npm install basarisiz'; exit 1 }",
77
+ "L 'npm install tamam - yeniden baslatma'",
78
+ "$prefix = Join-Path $env:APPDATA 'npm'",
79
+ "$appDir = Join-Path $prefix 'node_modules\\beast-agent'",
80
+ "$exe = Join-Path $appDir 'node_modules\\electron\\dist\\electron.exe'",
81
+ "if (-not (Test-Path $exe)) { $exe = Join-Path $prefix 'node_modules\\electron\\dist\\electron.exe' }",
82
+ "Start-Process -FilePath $exe -ArgumentList ('\"' + $appDir + '\"')",
83
+ "L '=== beast update bitti ==='",
84
+ ].join('\r\n');
85
+ const os = require('os');
86
+ const psFile = path.join(os.tmpdir(), 'beast-update-helper.ps1');
87
+ fs.writeFileSync(psFile, ps, 'utf8');
88
+ spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psFile],
89
+ { detached: true, stdio: 'ignore', windowsHide: true }).unref();
90
+ console.log('\u2022 npm install arka planda s\u00FCr\u00FCyor (2-4 dk) \u2014 bitince uygulama kendili\u011Finden a\u00E7\u0131l\u0131r');
91
+ console.log(' durum: %APPDATA%\\beast\\update.log');
92
+ process.exit(0);
79
93
  }
80
- console.log('\n\u2713 beast-agent g\u00FCncellendi \u2014 uygulama ba\u015Flat\u0131l\u0131yor\u2026');
81
- try {
82
- const electron = require('electron');
83
- if (typeof electron === 'string') {
84
- spawn(electron, [path.resolve(__dirname, '..')], { stdio: 'ignore', detached: true }).unref();
85
- }
86
- } catch {}
94
+ try { spawnSync('pkill', ['-f', 'node_modules/beast-agent'], { stdio: 'ignore' }); } catch {}
95
+ const sh =
96
+ 'ok=0; for n in 1 2 3 4 5; do npm install -g beast-agent@latest && ok=1 && break; sleep 3; done; ' +
97
+ 'if [ $ok -eq 1 ]; then nohup beast-agent >/dev/null 2>&1 & fi';
98
+ spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' }).unref();
99
+ console.log('\u2022 arka planda s\u00FCr\u00FCyor \u2014 bitince uygulama a\u00E7\u0131l\u0131r');
87
100
  process.exit(0);
88
101
  }
89
102
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.23.9",
4
+ "version": "0.24.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/agent/bots.js CHANGED
@@ -84,8 +84,10 @@ function adminBot() {
84
84
  name: 'Beast',
85
85
  icon: '🦁',
86
86
  admin: true,
87
+ code: '', // 5 haneli benzersiz bot kodu (ensureBotCodes atar)
87
88
  prompt: '',
88
89
  perm: 'all',
90
+ vis: true, // sohbet görünürlüğü: ana sohbete davet edilebilir
89
91
  skills: { ...DEFAULT_SKILLS, run_command: true },
90
92
  seeBots: [], // admin her şeyi görür
91
93
  extBrowser: true, // dış tarayıcı yetkisi
@@ -153,6 +155,7 @@ function botConfigOf(b) {
153
155
  name: b.name,
154
156
  icon: b.icon,
155
157
  admin: !!b.admin,
158
+ code: b.code || '',
156
159
  prompt: b.prompt || '',
157
160
  browser: { default: b.browserDefault || 'dahili', extCommand: b.extCommand || '' },
158
161
  plugins: b.plugins || [],
@@ -204,8 +207,10 @@ function add({ name, icon, prompt }) {
204
207
  name: n,
205
208
  icon: ICONS.includes(icon) ? icon : '🤖',
206
209
  admin: false,
210
+ code: '', // ensureBotCodes hemen altında benzersiz 5 hane atar
207
211
  prompt: String(prompt || '').slice(0, 4000),
208
212
  perm: 'all',
213
+ vis: true, // sohbet görünürlüğü (admin matristen kapatır)
209
214
  skills: { ...DEFAULT_SKILLS },
210
215
  seeBots: [], // varsayılan: hiçbir bot baka botu göremez
211
216
  extBrowser: false, // dış tarayıcı sadece yetkilide
@@ -215,12 +220,43 @@ function add({ name, icon, prompt }) {
215
220
  createdAt: nowIso(),
216
221
  };
217
222
  REG.bots.push(bot);
223
+ ensureBotCodes();
218
224
  saveRegistry();
219
225
  ensureDirs();
220
- logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon})`);
226
+ logChange(id, `bot oluşturuldu (name="${n}", icon=${bot.icon}, code=${bot.code})`);
221
227
  return { ok: true, bot: { ...bot } };
222
228
  }
223
229
 
230
+ /* ---------- 5 HANELİ BENZERSİZ BOT KODU ----------
231
+ Her bot oluşurken 10000-99999 arası rastgele kod alır; duplicate kontrolü
232
+ zorunlu (tüm botlara karşı). Açılışta kodu eksik/çakışık botlara da atanır. */
233
+ function ensureBotCodes() {
234
+ loadRegistry();
235
+ let dirty = false;
236
+ const used = () => new Set(REG.bots.map((b) => String(b.code || '')).filter((c) => /^\d{5}$/.test(c)));
237
+ const seen = new Set();
238
+ for (const b of REG.bots) {
239
+ const cur = String(b.code || '');
240
+ if (/^\d{5}$/.test(cur) && !seen.has(cur)) { seen.add(cur); continue; }
241
+ const u = used();
242
+ let c;
243
+ do { c = String(10000 + Math.floor(Math.random() * 90000)); } while (u.has(c));
244
+ logChange(b.id, `bot kodu atandı: ${c}${cur ? ` (eskisi geçersizdi: ${cur})` : ''}`);
245
+ b.code = c;
246
+ seen.add(c);
247
+ dirty = true;
248
+ }
249
+ if (dirty) saveRegistry();
250
+ return dirty;
251
+ }
252
+
253
+ function byCode(code) {
254
+ const c = String(code || '').replace(/\D/g, '');
255
+ if (!/^\d{5}$/.test(c)) return null;
256
+ loadRegistry();
257
+ return REG.bots.find((b) => String(b.code || '') === c) || null;
258
+ }
259
+
224
260
  function update(id, patch) {
225
261
  loadRegistry();
226
262
  const b = get(id);
@@ -279,6 +315,10 @@ function update(id, patch) {
279
315
  changes.push(`dış tarayıcı yetkisi: ${b.extBrowser} → ${patch.extBrowser}`);
280
316
  b.extBrowser = patch.extBrowser;
281
317
  }
318
+ if (typeof patch.vis === 'boolean' && patch.vis !== (b.vis !== false)) {
319
+ changes.push(`sohbet görünürlüğü: ${b.vis !== false} → ${patch.vis}`);
320
+ b.vis = patch.vis;
321
+ }
282
322
  if (['dahili', 'dis'].includes(patch.browserDefault) && patch.browserDefault !== b.browserDefault) {
283
323
  changes.push(`varsayılan tarayıcı: ${b.browserDefault} → ${patch.browserDefault}`);
284
324
  b.browserDefault = patch.browserDefault;
@@ -497,6 +537,8 @@ module.exports = {
497
537
  update,
498
538
  remove,
499
539
  canSee,
540
+ ensureBotCodes,
541
+ byCode,
500
542
  readMemory,
501
543
  readMemoryFiles,
502
544
  writeMemoryFile,
@@ -333,6 +333,41 @@ class Engine {
333
333
  else this.sessionTools.delete(id);
334
334
  }
335
335
 
336
+ /* ANA SOHBETE DAVETLİ BOTLAR: oturuma guest bot ekle/çıkar (persist) */
337
+ setSessionGuests(sessionId, guests) {
338
+ const s = this._load(String(sessionId || ''));
339
+ if (!s) return false;
340
+ const items = (Array.isArray(guests) ? guests : [])
341
+ .slice(0, 4)
342
+ .map((g) => ({ id: String((g && g.id) || ''), code: String((g && g.code) || ''), name: String((g && g.name) || '').slice(0, 40) }))
343
+ .filter((g) => g.id && g.code);
344
+ s.guests = items;
345
+ try {
346
+ const file = this._file(s.id);
347
+ const lines = fs.readFileSync(file, 'utf8').split('\n').filter((l) => l.trim());
348
+ const kept = lines.filter((l) => {
349
+ try { return JSON.parse(l).t !== 'guests'; } catch { return true; }
350
+ });
351
+ const out = kept.join('\n') + (items.length ? '\n' + JSON.stringify({ t: 'guests', items }) : '') + '\n';
352
+ const tmp = file + '.tmp';
353
+ fs.writeFileSync(tmp, out);
354
+ fs.renameSync(tmp, file);
355
+ } catch {}
356
+ return true;
357
+ }
358
+
359
+ /* Davetli botlar system prompt bloğu — ajan bot_dm ile onlara danışır */
360
+ _guestsBlock(session) {
361
+ const g = Array.isArray(session && session.guests) ? session.guests : [];
362
+ if (!g.length) return '';
363
+ const lines = g.map((x) => `- ${x.name} (kod: ${x.code})`).join('\n');
364
+ return (
365
+ `# DAVETLİ BOTLAR\n` +
366
+ `Bu sohbete şu botlar davet edildi. Kullanıcı onlara hitap ederse veya uzmanlıkları gereken bir konu olursa ` +
367
+ `bot_dm aracıyla (to=5 haneli kod) onlara yaz; aldığın cevabı [BotAdı] etiketiyle kullanıcıya aktar:\n${lines}`
368
+ );
369
+ }
370
+
336
371
  /* Oturumun bağlı olduğu MÜŞTERİ botu (admin/seasız → null → global hafıza) */
337
372
  _sessionBotCtx(session) {
338
373
  if (!session || !session.botId || typeof this.resolveBot !== 'function') return null;
@@ -907,6 +942,14 @@ class Engine {
907
942
  } else if (rec.t === 'notes') {
908
943
  session.notes = String(rec.text || '');
909
944
  session.notesAt = Number(rec.at) || 0;
945
+ } else if (rec.t === 'botdm') {
946
+ /* botlar arası DM oturumu — admin izleyebilir, sidebar'da görünmez */
947
+ session.isBotDm = true;
948
+ session.dmA = String(rec.a || '');
949
+ session.dmB = String(rec.b || '');
950
+ } else if (rec.t === 'guests') {
951
+ /* ana sohbete davetli botlar */
952
+ session.guests = Array.isArray(rec.items) ? rec.items : [];
910
953
  } else if (rec.t === 'msg') {
911
954
  delete rec.t;
912
955
  session.messages.push(rec);
@@ -1042,6 +1085,8 @@ class Engine {
1042
1085
  updatedAt: s.updatedAt,
1043
1086
  count: s.messages.length,
1044
1087
  isBg,
1088
+ isBotDm: !!s.isBotDm,
1089
+ guests: Array.isArray(s.guests) ? s.guests : [],
1045
1090
  bgStatus: isBg ? (job ? job.status : null) : null,
1046
1091
  };
1047
1092
  }
@@ -1058,6 +1103,7 @@ class Engine {
1058
1103
  const id = f.replace(/\.jsonl$/, '');
1059
1104
  const v = this._view(this._load(id));
1060
1105
  if (v.isBg) continue;
1106
+ if (v.isBotDm) continue; // botlar arası DM — yalnız admin DM Log ekranında
1061
1107
  out.push(v);
1062
1108
  }
1063
1109
  out.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
@@ -2150,6 +2196,8 @@ class Engine {
2150
2196
  /* bot skill kısıtı: bota verilen yetkiye göre araç seti daraltılır */
2151
2197
  const toolLimit = this.sessionTools.get(String(session.id));
2152
2198
  if (toolLimit) activeTools = activeTools.filter((t) => toolLimit.has(t.function.name));
2199
+ /* DM oturumlarında bot_dm KAPALI — botlar botlara DM açıp döngü kuramaz */
2200
+ if (session.isBotDm) activeTools = activeTools.filter((t) => t.function.name !== 'bot_dm');
2153
2201
  if (session.bgJob) {
2154
2202
  /* arka plan ajanı: yönetim araçlarını görmez — işini bitirsin */
2155
2203
  activeTools = activeTools.filter((t) => !BG_HIDDEN_TOOLS.has(t.function.name));
@@ -2172,6 +2220,8 @@ class Engine {
2172
2220
  /* bot kimliği: bota bağlı oturumlarda kişilik + izolasyon kuralları */
2173
2221
  const botBlock = this._botSystemBlock(session);
2174
2222
  if (botBlock) system += '\n\n' + botBlock;
2223
+ const guestBlock = this._guestsBlock(session);
2224
+ if (guestBlock) system += '\n\n' + guestBlock;
2175
2225
  if (session.notes) {
2176
2226
  system +=
2177
2227
  `\n\n# OTURUM NOTLARI (oturum ${session.code || '?'} — bu oturumun önceki konuşma özeti; birebir geçmişi buradan hatırla)\n` +
@@ -2270,7 +2320,7 @@ class Engine {
2270
2320
  /* #21 bg ajanı: görev sonu ekstra LLM çağrıları YOK — hızlı kapansın.
2271
2321
  Beast Code (bcCode) oturumları da aynı şekilde ANINDA kapansın:
2272
2322
  not/hafıza/skill yansıtma çağrıları yapılmaz → done hemen gelir */
2273
- if (!session.bgJob && !session.bcCode) {
2323
+ if (!session.bgJob && !session.bcCode && !session.isBotDm) {
2274
2324
  const r = await this._updateSessionNotes(session, ctrl.signal);
2275
2325
  if (r && r.ok) {
2276
2326
  emitSafe(this, sid, { type: 'status', status: `oturum notları güncellendi (${session.code || ''})` });
@@ -2279,13 +2329,13 @@ class Engine {
2279
2329
  }
2280
2330
  }
2281
2331
  /* otomatik memory döngüsü: kalıcı değerli bilgiyi MEMORY.md'ye düşür */
2282
- if (!ctrl.signal.aborted && !session.bgJob && !session.bcCode) {
2332
+ if (!ctrl.signal.aborted && !session.bgJob && !session.bcCode && !session.isBotDm) {
2283
2333
  try {
2284
2334
  await this._autoMemory(session, ctrl.signal);
2285
2335
  } catch {}
2286
2336
  }
2287
2337
  /* deneyimden skill doğurma (#2): 5+ yeni araç çağrısı biriktiyse yansıt */
2288
- if (this.reflection.enabled && !ctrl.signal.aborted && !session.bgJob && !session.bcCode) {
2338
+ if (this.reflection.enabled && !ctrl.signal.aborted && !session.bgJob && !session.bcCode && !session.isBotDm) {
2289
2339
  try {
2290
2340
  const made = await this._maybeReflectSkill(session);
2291
2341
  if (made) emitSafe(this, sid, { type: 'status', status: `skill taslağı doğdu: ${made}` });
@@ -2541,6 +2591,119 @@ class Engine {
2541
2591
  return r.ok ? skills.slugify(draft.name) : null;
2542
2592
  }
2543
2593
 
2594
+ /* ---------- BOTLAR ARASI DM (FEATURE) ----------
2595
+ Admin bot, 5 haneli kodla başka bota özel mesaj atar; hedef botun cevabı
2596
+ senkron döner. İzolasyon: DM turları gizli pair oturumunda yürür, bot_dm
2597
+ DM oturumlarında KAPALI (döngü koruması). Tüm trafik admin DM Log'ta. */
2598
+ async _botDm(args, sessionId, signal) {
2599
+ const bots = require('./bots');
2600
+ const code = String((args && args.to) || '').replace(/\D/g, '');
2601
+ const message = String((args && args.message) || '').slice(0, 6000);
2602
+ if (!/^\d{5}$/.test(code)) return { ok: false, error: 'geçersiz kod — 5 haneli bot kodu gir' };
2603
+ if (!message.trim()) return { ok: false, error: 'mesaj boş' };
2604
+
2605
+ const session = this.cache.get(String(sessionId)) || this._load(String(sessionId));
2606
+ const senderBotId = (session && session.botId) || 'beast';
2607
+ const senderBot = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
2608
+ const senderIsAdmin = !session.botId || !!(senderBot && senderBot.admin);
2609
+ if (!senderIsAdmin) {
2610
+ return { ok: false, error: 'yetki yok — botlar arası DM yalnız yönetici (admin) bot tarafından açılabilir' };
2611
+ }
2612
+
2613
+ const target = bots.byCode(code);
2614
+ if (!target) return { ok: false, error: `bu kotta bot yok: ${code}` };
2615
+ if (target.id === senderBotId) return { ok: false, error: 'kendine DM atılamaz' };
2616
+
2617
+ /* deterministik pair oturumu: dm + (küçük kod + büyük kod) — aynı çift aynı oturum */
2618
+ const codes = [String(senderBot ? senderBot.code || '' : ''), String(target.code || '')]
2619
+ .map((c) => (/^\d{5}$/.test(c) ? c : '00000'))
2620
+ .sort();
2621
+ const pairId = 'dm' + codes[0] + codes[1];
2622
+
2623
+ let pair = this.cache.get(pairId) || this._load(pairId);
2624
+ const fresh = !pair.isBotDm && !pair.messages.length;
2625
+ if (!pair.isBotDm) {
2626
+ try {
2627
+ fs.appendFileSync(this._file(pairId), JSON.stringify({ t: 'botdm', a: senderBotId, b: target.id, at: nowIso() }) + '\n');
2628
+ } catch {}
2629
+ pair.isBotDm = true;
2630
+ pair.dmA = senderBotId;
2631
+ pair.dmB = target.id;
2632
+ }
2633
+ /* hedef botun kimliğiyle çalışsın; izolasyon _botSystemBlock'tan gelir */
2634
+ this.setSessionBot(pairId, target.id);
2635
+ this.setSessionPerm(pairId, target.perm || 'all');
2636
+
2637
+ const senderName = senderBot ? senderBot.name : 'Beast';
2638
+ const senderCode = senderBot && /^\d{5}$/.test(String(senderBot.code || '')) ? senderBot.code : codes[0];
2639
+ const prefix = `[BOT DM — gönderen bot: ${senderName} (kod ${senderCode}) — cevabını kısa ve net ver, araç kullanman gerekmiyorsa kullanma]`;
2640
+ const before = pair.messages.length;
2641
+ const sent = this.send(pairId, { text: prefix + '\n' + message });
2642
+ if (!sent) return { ok: false, error: 'DM oturumu başlatılamadı' };
2643
+
2644
+ /* tur bitene kadar bekle (ctrl kaydı düşer), en fazla 120 sn */
2645
+ const t0 = Date.now();
2646
+ while (Date.now() - t0 < 120000) {
2647
+ if (signal && signal.aborted) return { ok: false, error: 'iptal edildi' };
2648
+ await new Promise((r) => setTimeout(r, 400));
2649
+ if (!this.ctrls.has(pairId)) break;
2650
+ }
2651
+ if (this.ctrls.has(pairId)) {
2652
+ try { this.interrupt(pairId); } catch {}
2653
+ return { ok: false, error: 'hedef bot zaman aşımına uğradı (120 sn)' };
2654
+ }
2655
+ const after = this.cache.get(pairId) || pair;
2656
+ const newMsgs = after.messages.slice(before);
2657
+ const lastA = [...newMsgs].reverse().find((m) => m.role === 'assistant' && m.content && !(m.tool_calls && m.tool_calls.length));
2658
+ const reply = lastA ? String(typeof lastA.content === 'string' ? lastA.content : '(medya içerikli cevap)').slice(0, 12000) : '';
2659
+ if (!reply.trim()) return { ok: false, error: 'hedef bot cevap vermedi' };
2660
+ return { ok: true, from: target.name, code: target.code, reply };
2661
+ }
2662
+
2663
+ /* ADMIN İZLEME: tüm botlar arası DM oturumlarının listesi */
2664
+ listBotDmSessions() {
2665
+ const bots = require('./bots');
2666
+ let files = [];
2667
+ try {
2668
+ files = fs.readdirSync(this.sessionsDir).filter((f) => f.startsWith('dm') && f.endsWith('.jsonl'));
2669
+ } catch {}
2670
+ const out = [];
2671
+ for (const f of files) {
2672
+ const id = f.replace(/\.jsonl$/, '');
2673
+ const s = this._load(id);
2674
+ if (!s.isBotDm || !s.messages.length) continue;
2675
+ const a = bots.get(s.dmA);
2676
+ const b = bots.get(s.dmB);
2677
+ out.push({
2678
+ id,
2679
+ a: s.dmA,
2680
+ b: s.dmB,
2681
+ aName: a ? a.name : s.dmA,
2682
+ bName: b ? b.name : s.dmB,
2683
+ aCode: (a && a.code) || '—',
2684
+ bCode: (b && b.code) || '—',
2685
+ count: s.messages.length,
2686
+ updatedAt: s.updatedAt,
2687
+ });
2688
+ }
2689
+ out.sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
2690
+ return out;
2691
+ }
2692
+
2693
+ /* ADMIN İZLEME: bir DM oturumunun tam dökümü */
2694
+ readBotDm(id) {
2695
+ const s = this._load(String(id || ''));
2696
+ if (!s || !s.isBotDm) return { ok: false, error: 'DM oturumu yok' };
2697
+ return {
2698
+ ok: true,
2699
+ id: s.id,
2700
+ messages: s.messages.map((m) => ({
2701
+ role: m.role,
2702
+ content: typeof m.content === 'string' ? m.content : '[ek/medya]',
2703
+ })),
2704
+ };
2705
+ }
2706
+
2544
2707
  /* ---------- alt-agent ---------- */
2545
2708
 
2546
2709
  async _subagent(task, context, parentSignal, sessionId) {
@@ -2781,6 +2944,14 @@ class Engine {
2781
2944
  const result = await this._subagent(task, String(args.context || ''), signal, sessionId);
2782
2945
  return JSON.stringify({ ok: true, task, result: String(result).slice(0, 12000) });
2783
2946
  }
2947
+ if (name === 'bot_dm') {
2948
+ const dmS = this.cache.get(String(sessionId));
2949
+ if (dmS && dmS.isBotDm) {
2950
+ return JSON.stringify({ ok: false, error: 'DM oturumunda bot_dm kullanılamaz (döngü koruması)' });
2951
+ }
2952
+ const r = await this._botDm(args, sessionId, signal);
2953
+ return JSON.stringify(r);
2954
+ }
2784
2955
  if (name === 'send_file') {
2785
2956
  if (typeof this.fileSend !== 'function') {
2786
2957
  return JSON.stringify({ ok: false, error: 'dosya gönderim köprüsü yok' });
@@ -3151,6 +3322,22 @@ const TOOLS = [
3151
3322
  },
3152
3323
  },
3153
3324
  },
3325
+ {
3326
+ type: 'function',
3327
+ function: {
3328
+ name: 'bot_dm',
3329
+ description:
3330
+ 'Send a direct message to ANOTHER BOT by its 5-digit code and receive its answer. Use when the user asks another bot something, invites bots into this chat, or a specialist bot should weigh in. Admin bot only.',
3331
+ parameters: {
3332
+ type: 'object',
3333
+ properties: {
3334
+ to: { type: 'string', description: "Target bot's 5-digit code, e.g. 48213" },
3335
+ message: { type: 'string', description: 'Message to send to that bot' },
3336
+ },
3337
+ required: ['to', 'message'],
3338
+ },
3339
+ },
3340
+ },
3154
3341
  {
3155
3342
  type: 'function',
3156
3343
  function: {
package/src/main.js CHANGED
@@ -1015,16 +1015,9 @@ async function tryWaSlash(jid, rawText, senderNum) {
1015
1015
  ? `*${cmd === 'deny' ? 'Reddedildi' : 'Onaylandı'}:* ${r.tool}${always ? ' — bu araç için bir daha sorulmayacak' : ''}`
1016
1016
  : 'Bekleyen onay yok.';
1017
1017
  } else if (cmd === 'update') {
1018
- /* /update — sürüm kontrol; /update now indirileni kur (npm modunda kendini günceller) */
1018
+ /* /update — sürüm kontrol. TEK DAĞITIM npm: uygulama içi kurulum YOK. */
1019
1019
  if (String(arg || '').toLowerCase() === 'now') {
1020
- if (isNpmMode()) {
1021
- npmUpdateNow(async (text) => { out = text; });
1022
- } else if (updateState.downloaded && autoUpdater) {
1023
- out = `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`;
1024
- setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
1025
- } else {
1026
- out = 'İndirilmiş sürüm yok — önce `/update` yaz.';
1027
- }
1020
+ out = NPM_ONLY_TEXT;
1028
1021
  } else {
1029
1022
  updateReplies.jids.add(jid);
1030
1023
  await runUpdateCommand(async (text) => { out = text; });
@@ -2309,18 +2302,16 @@ function ensureDesktopShortcut() {
2309
2302
  app.whenReady().then(() => {
2310
2303
  // Tailscale modu: paketli uygulamada Windows ile otomatik başlat (sessiz, tepside)
2311
2304
  if (app.isPackaged) {
2305
+ /* DAĞITIM KARARI: EXE/portable kurulum desteklenmiyor — tek yol npm.
2306
+ EXE kendini startup'a YAZMAZ (eski portable kayıtları da temizlenir). */
2312
2307
  try {
2313
- // portable exe Temp'e açılır; gerçek yolu PORTABLE_EXECUTABLE_FILE verir
2314
- const exe = process.env.PORTABLE_EXECUTABLE_FILE || process.execPath;
2315
- app.setLoginItemSettings({
2316
- openAtLogin: true,
2317
- path: exe,
2318
- args: ['--hidden'],
2308
+ const k = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
2309
+ spawn('reg.exe', ['delete', k, '/v', 'electron.app.Beast Agent', '/f'], { stdio: 'ignore', windowsHide: true }).unref();
2310
+ spawn('reg.exe', ['query', k, '/v', 'electron.app.Beast Agent'], { stdio: 'ignore', windowsHide: true }).on('exit', (code) => {
2311
+ if (code !== 0) log.info('main', 'EXE modunda çalışıyor — startup kaydı temizlendi (npm kurulumuna geçin)');
2319
2312
  });
2320
- log.info('main', `Startup kaydı açık: ${exe} (--hidden, açılışta tepside)`);
2321
- } catch (e) {
2322
- log.error('main', 'Startup kaydı başarısız: ' + String((e && e.message) || e));
2323
- }
2313
+ } catch {}
2314
+ log.info('main', '⚠ EXE/portable mod desteklenmiyor — tek dağıtım: npm i -g beast-agent');
2324
2315
  } else if (!app.isPackaged && /node_modules[\\/]beast-agent/i.test(String(app.getAppPath()))) {
2325
2316
  /* npm (global) kurulum modu: startup kaydı + masaüstü kısayolu */
2326
2317
  try {
@@ -2337,6 +2328,7 @@ app.whenReady().then(() => {
2337
2328
  }
2338
2329
  reloadBackend();
2339
2330
  syncWhitelist(); // bot sistemi: whitelist.json aynası ilk açılışta garanti
2331
+ try { bots.ensureBotCodes(); } catch {} // her bota benzersiz 5 haneli kod garanti
2340
2332
  createSplash();
2341
2333
  createWindow();
2342
2334
  log.info('main', 'Beast Agent başlatıldı');
@@ -3333,14 +3325,7 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
3333
3325
  const a = t.slice(7).trim().toLowerCase();
3334
3326
  updateReplies.sids.add(String(sessionId || ''));
3335
3327
  if (a === 'now') {
3336
- if (isNpmMode()) {
3337
- npmUpdateNow((text) => desktopEcho(sessionId, t, text));
3338
- } else if (updateState.downloaded && autoUpdater) {
3339
- desktopEcho(sessionId, t, `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`);
3340
- setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
3341
- } else {
3342
- desktopEcho(sessionId, t, 'İndirilmiş sürüm yok — önce `/update` yaz.');
3343
- }
3328
+ desktopEcho(sessionId, t, NPM_ONLY_TEXT);
3344
3329
  } else {
3345
3330
  runUpdateCommand((text) => desktopEcho(sessionId, t, text));
3346
3331
  }
@@ -4087,100 +4072,13 @@ ipcMain.handle('update:check', async () => {
4087
4072
  return out;
4088
4073
  });
4089
4074
 
4090
- /* npm kurulumunda KENDİ KENDİNİ GÜNCELLEME:
4091
- helper script %APPDATA%\beast'a yazılır (paket dizini değişse de yaşar) ve
4092
- detached çalışır: 1) uygulama PID'i tamamen çıkana kadar bekler (en çok 30 sn,
4093
- sonra zorla kapatır — EBUSY dosya kilidinin numarası), 2) npm install -g
4094
- beast-agent@latest — kilide karşı 5 deneme, 3) tüm çıktı update.log'a yazılır,
4095
- 4) beast-agent komut shim'i üzerinden yeniden başlatır (electron sürümü
4096
- değişse de yol bozulmaz). Sonra app.quit(). */
4097
- function npmSelfUpdate() {
4098
- try {
4099
- fs.mkdirSync(APP_DIR, { recursive: true });
4100
- const pid = String(process.pid);
4101
- if (process.platform === 'win32') {
4102
- const ps = [
4103
- "param([int]$ProcId = 0)",
4104
- "$ErrorActionPreference = 'Continue'",
4105
- "$Log = Join-Path $env:APPDATA 'beast\\update.log'",
4106
- "function L([string]$m) { try { Add-Content -LiteralPath $Log -Value (\"[\" + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + \"] \" + $m) } catch {} }",
4107
- "L \"=== self-update basladi (app pid: $ProcId) ===\"",
4108
- "if ($ProcId -gt 0) {",
4109
- " $deadline = (Get-Date).AddSeconds(30)",
4110
- " while ((Get-Date) -lt $deadline) {",
4111
- " if (-not (Get-Process -Id $ProcId -ErrorAction SilentlyContinue)) { break }",
4112
- " Start-Sleep -Milliseconds 500",
4113
- " }",
4114
- " if (Get-Process -Id $ProcId -ErrorAction SilentlyContinue) {",
4115
- " L 'uygulama hala acik - zorla kapatiliyor'",
4116
- " try { Stop-Process -Id $ProcId -Force } catch {}",
4117
- " Start-Sleep -Seconds 2",
4118
- " }",
4119
- "}",
4120
- "L 'uygulama kapandi, npm install basliyor'",
4121
- "$npmCmd = (Get-Command 'npm.cmd' -ErrorAction SilentlyContinue).Source",
4122
- "if (-not $npmCmd) { $npmCmd = Join-Path $env:APPDATA 'npm\\npm.cmd' }",
4123
- "$ok = $false",
4124
- "for ($i = 1; $i -le 5; $i++) {",
4125
- " if ($ok) { break }",
4126
- " L \"npm install -g beast-agent@latest (deneme $i)\"",
4127
- " $out = & $npmCmd install -g beast-agent@latest 2>&1",
4128
- " $code = $LASTEXITCODE",
4129
- " foreach ($line in @($out)) { L \" npm: $line\" }",
4130
- " if ($code -eq 0) { $ok = $true } else { Start-Sleep -Seconds 3 }",
4131
- "}",
4132
- "if (-not $ok) {",
4133
- " L 'HATA: npm install 5 denemede basarisiz - uygulama yeniden baslatilmiyor'",
4134
- " exit 1",
4135
- "}",
4136
- "L 'npm install tamam, yeniden baslatma'",
4137
- "$prefix = (& $npmCmd prefix -g 2>$null)",
4138
- "if (-not $prefix) { $prefix = Join-Path $env:APPDATA 'npm' }",
4139
- "$appDir = Join-Path $prefix 'node_modules\\beast-agent'",
4140
- "$exe = Join-Path $appDir 'node_modules\\electron\\dist\\electron.exe'",
4141
- "if (-not (Test-Path $exe)) { $exe = Join-Path $prefix 'node_modules\\electron\\dist\\electron.exe' }",
4142
- "if (Test-Path $exe) {",
4143
- " L \"dogrudan electron: $exe\"",
4144
- " # WindowStyle Hidden KULLANMA: gizli bayragi Chromium miras alir, ilk pencere tray'de kaybolur",
4145
- " Start-Process -FilePath $exe -ArgumentList \"`\"$appDir`\"\"",
4146
- "} else {",
4147
- " $shim = Get-Command 'beast-agent.cmd' -ErrorAction SilentlyContinue",
4148
- " if ($shim) {",
4149
- " L \"shim uzerinden: $($shim.Source)\"",
4150
- " Start-Process -FilePath $shim.Source",
4151
- " } else {",
4152
- " L 'HATA: electron.exe ve shim bulunamadi - yeniden baslatma yapilamadi'",
4153
- " exit 1",
4154
- " }",
4155
- "}",
4156
- "L '=== self-update bitti ==='",
4157
- ].join('\r\n');
4158
- const psFile = path.join(APP_DIR, 'update-helper.ps1');
4159
- fs.writeFileSync(psFile, ps, 'utf8');
4160
- spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psFile, '-ProcId', pid],
4161
- { detached: true, stdio: 'ignore', windowsHide: true }).unref();
4162
- } else {
4163
- const sh =
4164
- `i=0; while [ $i -lt 60 ] && kill -0 ${pid} 2>/dev/null; do i=$((i+1)); sleep 0.5; done; ` +
4165
- 'ok=0; for n in 1 2 3 4 5; do npm install -g beast-agent@latest && ok=1 && break; sleep 3; done; ' +
4166
- 'if [ $ok -eq 1 ]; then nohup beast-agent >/dev/null 2>&1 & fi';
4167
- spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' }).unref();
4168
- }
4169
- log.info('main', 'npm self-update: helper bırakıldı, uygulama kapatılıyor');
4170
- } catch (e) {
4171
- log.error('main', 'npm self-update hatası: ' + String((e && e.message) || e));
4172
- }
4173
- setTimeout(() => { try { app.quit(); } catch {} }, 400);
4174
- }
4075
+ /* TEK DAĞITIM POLİTİKASI: uygulama içi self-update KALDIRILDI (v0.24.0).
4076
+ Güncelleme yalnız: uygulamayı kapat "npm i -g beast-agent@latest" → tekrar aç. */
4175
4077
 
4176
- ipcMain.handle('update:install', () => {
4177
- if (isNpmMode()) { npmSelfUpdate(); return { ok: true, npm: true }; }
4178
- if (!app.isPackaged) return { ok: false, error: 'Geliştirme modu — güncelleme npm / GitHub Releases üzerinden yapılır' };
4179
- if (!updateState.downloaded) return { ok: false, error: 'indirilmiş sürüm yok — önce /update' };
4180
- try { autoUpdater.quitAndInstall(); return { ok: true }; } catch (e) {
4181
- return { ok: false, error: String((e && e.message) || e) };
4182
- }
4183
- });
4078
+ ipcMain.handle('update:install', () => ({
4079
+ ok: false,
4080
+ error: NPM_ONLY_TEXT,
4081
+ }));
4184
4082
 
4185
4083
  ipcMain.handle('update:setAuto', (_e, cfg) => {
4186
4084
  if (cfg && typeof cfg.autoCheck === 'boolean') settings.autoCheckUpdate = cfg.autoCheck;
@@ -4194,14 +4092,19 @@ ipcMain.handle('update:setAuto', (_e, cfg) => {
4194
4092
 
4195
4093
  /* /update komutu (masaüstü + WA): hedefi kaydet, kontrol başlat */
4196
4094
  /* npm modunda güncelle-şimdi: sürüm kontrolü + numaralarıyla bildir + kendi kendini güncelle */
4095
+ /* TEK DAĞITIM POLİTİKASI: exe/installer YOK, uygulama içi self-update YOK.
4096
+ Güncelleme yalnız: uygulamayı kapat → "npm i -g beast-agent@latest" → tekrar aç. */
4097
+ const NPM_ONLY_TEXT =
4098
+ 'Tek dağıtım npm\u2019dir — uygulama içi güncelleme yok.\n' +
4099
+ 'Güncellemek için:\n1) Uygulamayı kapat\n2) Terminalde: npm i -g beast-agent@latest\n3) Tekrar aç';
4100
+
4197
4101
  function npmUpdateNow(reply /* fn(text) */) {
4198
4102
  const current = app.getVersion();
4199
4103
  getNpmLatest(true).then((v) => {
4200
4104
  if (v && isNewerVersion(v, current)) {
4201
4105
  updateState.available = true;
4202
4106
  updateState.version = v;
4203
- reply(`🔄 *Güncelleniyor*\nMevcut: v${current}\nYeni: v${v}\nKapanıp yeniden açılıyorum…`);
4204
- setTimeout(() => npmSelfUpdate(), 1500);
4107
+ reply(`🔄 *Yeni sürüm var*\nMevcut: v${current}\nYeni: v${v}\n\n${NPM_ONLY_TEXT}`);
4205
4108
  } else {
4206
4109
  reply(`✅ *Güncelsin* — v${current} zaten en son sürüm.`);
4207
4110
  }
@@ -4209,30 +4112,8 @@ function npmUpdateNow(reply /* fn(text) */) {
4209
4112
  }
4210
4113
 
4211
4114
  async function runUpdateCommand(reply /* fn(text) */) {
4212
- const current = app.getVersion();
4213
- if (isNpmMode()) return npmUpdateNow(reply);
4214
- if (!autoUpdater) {
4215
- reply('Updater bu modda kullanılamıyor. Yeni sürüm: github.com/algokodcom/beast-agent/releases');
4216
- return;
4217
- }
4218
- /* önce npm registry'den sürüm bilgisi — kullanıcıya numaraları söyle */
4219
- const v = await getNpmLatest(true);
4220
- if (v && isNewerVersion(v, current)) {
4221
- updateState.available = true;
4222
- updateState.version = v;
4223
- emitUpdateEvent();
4224
- reply(`🔄 *Yeni sürüm bulundu*\nMevcut: v${current}\nYeni: v${v}\nİndiriliyor… (kurulum için: /update now)`);
4225
- try { await autoUpdater.checkForUpdates(); } catch (e) {
4226
- reply('İndirme başlatılamadı: ' + String((e && e.message) || e));
4227
- }
4228
- } else if (v) {
4229
- reply(`✅ *Güncelsin* — v${current} en son sürüm.`);
4230
- } else {
4231
- reply(`🔍 Kontrol ediliyor (mevcut sürüm v${current})…`);
4232
- try { await autoUpdater.checkForUpdates(); } catch (e) {
4233
- reply('Kontrol başarısız: ' + String((e && e.message) || e));
4234
- }
4235
- }
4115
+ /* yalnız KONTROL + yol gösterme — kurulum yapmaz */
4116
+ return npmUpdateNow(reply);
4236
4117
  }
4237
4118
 
4238
4119
  /* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
@@ -5482,10 +5363,39 @@ ipcMain.handle('bots:list', () => botListWithNumbers());
5482
5363
 
5483
5364
  ipcMain.handle('bots:add', (_e, input) => {
5484
5365
  const r = bots.add(input || {});
5485
- if (r.ok) log.info('main', `yeni bot: ${r.bot.name} (${r.bot.id})`);
5366
+ if (r.ok) {
5367
+ log.info('main', `yeni bot: ${r.bot.name} (${r.bot.id}) kod=${r.bot.code}`);
5368
+ /* BOT KODU MAİL BİLDİRİMİ: bot oluşur oluşmez sahibine mail atılır */
5369
+ try {
5370
+ const cfg = emailCfg();
5371
+ if (cfg.host && cfg.user && cfg.pass) {
5372
+ emailSend({
5373
+ to: cfg.user,
5374
+ subject: `Beast Agent — yeni bot kuruldu: ${r.bot.name} (kod ${r.bot.code})`,
5375
+ body:
5376
+ `Yeni bot oluşturuldu.\n\n` +
5377
+ `Ad: ${r.bot.name}\n` +
5378
+ `Bot kodu: ${r.bot.code}\n` +
5379
+ `Zaman: ${new Date().toLocaleString('tr-TR')}\n\n` +
5380
+ `Bu 5 haneli kod botlar arası DM adresidir — bot_dm aracında 'to' olarak kullanılır.\n` +
5381
+ `Sol alttaki bot listesinde de görüntülenir.`,
5382
+ }).catch(() => {});
5383
+ }
5384
+ } catch {}
5385
+ }
5486
5386
  return { ...r, list: r.ok ? botListWithNumbers() : null };
5487
5387
  });
5488
5388
 
5389
+ /* Botlar arası DM izleme (admin) */
5390
+ ipcMain.handle('bots:dm:list', () => engine.listBotDmSessions());
5391
+ ipcMain.handle('bots:dm:read', (_e, id) => engine.readBotDm(id));
5392
+
5393
+ /* Ana sohbete davetli botlar */
5394
+ ipcMain.handle('sessions:guests:set', (_e, { sessionId, guests }) => {
5395
+ try { engine.setSessionGuests(sessionId, guests); } catch {}
5396
+ return { ok: true };
5397
+ });
5398
+
5489
5399
  ipcMain.handle('bots:update', (_e, { id, patch }) => {
5490
5400
  const p = patch || {};
5491
5401
  if (Array.isArray(p.numbers)) reassignBotNumbers(String(id || ''), p.numbers);
package/src/preload.js CHANGED
@@ -141,6 +141,9 @@ contextBridge.exposeInMainWorld('beast', {
141
141
  waListSessions: () => ipcRenderer.invoke('wa:sessions'),
142
142
  botsList: () => ipcRenderer.invoke('bots:list'),
143
143
  botsAdd: (input) => ipcRenderer.invoke('bots:add', input),
144
+ botsDmList: () => ipcRenderer.invoke('bots:dm:list'),
145
+ botsDmRead: (id) => ipcRenderer.invoke('bots:dm:read', id),
146
+ sessionSetGuests: (sessionId, guests) => ipcRenderer.invoke('sessions:guests:set', { sessionId, guests }),
144
147
  botsUpdate: (id, patch) => ipcRenderer.invoke('bots:update', { id, patch }),
145
148
  botsRemove: (id) => ipcRenderer.invoke('bots:remove', id),
146
149
  botsStats: () => ipcRenderer.invoke('bots:stats'),
@@ -165,6 +165,7 @@
165
165
  sk_h2: 'Skills',
166
166
  sk_sub: 'SKILL.md indeksi — tam metin model tarafından okunur',
167
167
  sk_auto: 'Otomatik skill: ajan öğrendiği yeni yöntemleri taslak onayı beklemeden kurar; bir skillin daha kolay/daha iyi yolunu bulursa mevcut skilli kendisi günceller',
168
+ up_npm_only: 'Tek dağıtım npm\u2019dir — uygulama içi kurulum butonu yoktur. Güncellemek için: uygulamayı kapat → terminalde "npm i -g beast-agent@latest" → tekrar aç. (İpucu: sohbete "beni güncelle" yazarsan adımları söyler.)',
168
169
  sk_rules_h2: 'Kalıcı Kurallar',
169
170
  sk_rules_sub: 'Agent\u2019ın her sohbette uyduğu talimatlar — WA\u2019dan /rule ile de eklenir',
170
171
  sk_no_rule: 'Henüz kural yok.',
@@ -443,6 +444,9 @@
443
444
  bot_msg_user: 'KULLANICI',
444
445
  bot_bind_title: 'Bu numarayı hangi bot karşılasın?',
445
446
  bot_sel_empty: '— bot seçme —',
447
+ bot_code_title: 'Botun benzersiz 5 haneli kodu — botlar arası DM adresi',
448
+ bot_vis: 'Ana sohbete davet edilebilir (sohbet görünürlüğü)',
449
+ tipGuestBot: 'Konuşmaya bot davet et — davetli botlara ajan bot_dm ile danışır',
446
450
  bot_switch_hint: 'Tıkla: bu botta çalış · ⚙: yönet',
447
451
  bot_switched: 'bottasın — sohbetler bu botun kimliğiyle gider',
448
452
  bot_manage: 'Botu yönet',
@@ -680,6 +684,7 @@
680
684
  sk_h2: 'Skills',
681
685
  sk_sub: 'SKILL.md index — full text is read by the model',
682
686
  sk_auto: 'Auto skills: the agent installs learned procedures without draft approval and updates existing skills when it finds an easier or better way',
687
+ up_npm_only: 'npm is the only distribution — there is no in-app install button. To update: close the app → run "npm i -g beast-agent@latest" in a terminal → reopen.',
683
688
  sk_rules_h2: 'Persistent Rules',
684
689
  sk_rules_sub: 'Instructions the agent follows in every chat — also addable via /rule on WA',
685
690
  sk_no_rule: 'No rules yet.',
@@ -1016,6 +1021,9 @@
1016
1021
  bot_msg_user: 'USER',
1017
1022
  bot_bind_title: 'Which bot should answer this number?',
1018
1023
  bot_sel_empty: '— no bot —',
1024
+ bot_code_title: "This bot's unique 5-digit code — the inter-bot DM address",
1025
+ bot_vis: 'Invitable to the main chat (chat visibility)',
1026
+ tipGuestBot: 'Invite a bot to this chat — the agent consults invited bots via bot_dm',
1019
1027
  bot_switch_hint: 'Click: work as this bot · ⚙: manage',
1020
1028
  bot_switched: 'active — chats run with this bot\u2019s identity',
1021
1029
  bot_manage: 'Manage bot',
@@ -95,8 +95,12 @@
95
95
  <div id="chips" hidden></div>
96
96
  <div id="slashMenu" hidden></div>
97
97
  <div id="composer">
98
- <button id="attachBtn" title="Dosya / resim ekle" data-i18n-title="tipAttach">+</button>
99
- <button id="micBtn" title="Sesli komut konuş, bırakınca yazıya çevirsin" data-i18n-title="tipMic"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><path d="M12 19v3"/></svg></button>
98
+ <div id="composerLeft" class="dd">
99
+ <button id="attachBtn" title="Dosya / resim ekle" data-i18n-title="tipAttach">+</button>
100
+ <button id="micBtn" title="Sesli komut — konuş, bırakınca yazıya çevirsin" data-i18n-title="tipMic"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><path d="M12 19v3"/></svg></button>
101
+ <button id="guestBotBtn" title="Konuşmaya bot davet et — davetli botlara ajan bot_dm ile danışır" data-i18n-title="tipGuestBot" hidden><span style="filter:grayscale(1)">🤖</span></button>
102
+ <div id="guestBotMenu" class="dd-menu" hidden></div>
103
+ </div>
100
104
  <textarea id="input" rows="1" placeholder="Bir şey sor ya da görev ver… (Enter: gönder · Shift+Enter: satır · /: komutlar)" data-i18n-ph="composerPh"></textarea>
101
105
  <button id="stopBtn" title="Durdur" hidden data-i18n-title="tipStop">■</button>
102
106
  <button id="sendBtn" title="Gönder" data-i18n-title="tipSend">&#x27A4;&#xFE0E;</button>
@@ -241,6 +245,7 @@
241
245
  <button class="btab" data-btab="watcher" data-i18n="bot_tab_watcher">Watcher</button>
242
246
  <button class="btab" data-btab="stats" data-i18n="bot_tab_stats">İstatistik</button>
243
247
  <button class="btab" data-btab="notes" data-i18n="bot_tab_notes">Notlar</button>
248
+ <button class="btab" data-btab="dmlog" hidden>DM Log</button>
244
249
  </nav>
245
250
  <div id="botPane"></div>
246
251
  </div>
@@ -497,12 +497,15 @@ async function renderSessions(list) {
497
497
  async function refreshSessions() {
498
498
  await renderSessions(await beast.listSessions());
499
499
  renderBotCards(); // bot kartlarındaki numara/sayı etiketleri de tazelensin
500
+ renderGuestBotMenu(); // davet menüsü (aktif bot/bot listesi değişmiş olabilir)
500
501
  }
501
502
 
502
503
  async function openSession(id) {
503
504
  activeId = id;
504
505
  streamEl = null;
505
506
  const s = await beast.openSession(id);
507
+ activeGuests = Array.isArray(s.guests) ? s.guests : [];
508
+ renderGuestBotMenu();
506
509
  els.msgs.innerHTML = '';
507
510
  showEmpty(s.messages.length === 0);
508
511
  renderTodos(s.todos || []);
@@ -1810,14 +1813,15 @@ async function renderUpdatePane(autoCheck) {
1810
1813
  if (!st) return;
1811
1814
  clearInterval(updatePaneTimer);
1812
1815
 
1813
- /* mod bazlı butonlar: npm → available, installer → downloaded, dev → sadece kontrol */
1816
+ /* mod bazlı butonlar: npm → kontrol, installer → kontrol, dev → sadece kontrol.
1817
+ TEK DAĞITIM npm — uygulama içi kurulum butonu KALDIRILDI. */
1814
1818
  const isDev = !st.packaged && !st.npm;
1815
1819
  let actions;
1816
1820
  if (st.npm) {
1817
1821
  actions = `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1818
- <button id="upInstall" class="btn ghost" ${st.available ? '' : 'disabled style="opacity:.45;cursor:default"'}>${_t('up_install_now')}</button>
1822
+ <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
1819
1823
  </div>
1820
- <div class="sub" style="margin-top:8px">${_t('up_npm_note')}</div>`;
1824
+ <div class="sub" style="margin-top:8px">${_t('up_npm_only')}</div>`;
1821
1825
  } else if (isDev) {
1822
1826
  actions = `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1823
1827
  <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
@@ -1826,9 +1830,8 @@ async function renderUpdatePane(autoCheck) {
1826
1830
  } else {
1827
1831
  actions = `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1828
1832
  <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
1829
- <button id="upInstall" class="btn ghost" ${st.downloaded ? '' : 'disabled style="opacity:.45;cursor:default"'}>${_t('up_install_now')}</button>
1830
1833
  </div>
1831
- <div class="sub" style="margin-top:8px">${_t('up_note')}</div>`;
1834
+ <div class="sub" style="margin-top:8px">${_t('up_npm_only')}</div>`;
1832
1835
  }
1833
1836
 
1834
1837
  pane.innerHTML =
@@ -1864,6 +1867,7 @@ async function renderUpdatePane(autoCheck) {
1864
1867
  if (r.ok && r.npm) toast(_t('up_npm_started'));
1865
1868
  else if (!r.ok && r.error) toast(r.error);
1866
1869
  });
1870
+ /* #upInstall butonu kaldırıldı — tek dağıtım npm (buton geri gelirse çalışsın diye koruma duruyor) */
1867
1871
 
1868
1872
  /* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
1869
1873
  updatePaneTimer = setInterval(() => {
@@ -2457,6 +2461,40 @@ let botsCache = [];
2457
2461
  let botPageId = null; // null = genel bakış (Tüm Botlar)
2458
2462
  let botPageStats = [];
2459
2463
  let activeBotId = 'beast'; // masaüstü UI'ının şu an hangi botta olduğu (varsayılan: ilk bot/Beast)
2464
+ let activeGuests = []; // aktif ana sohbete davetli botlar [{id, code, name}]
2465
+
2466
+ /* ANA SOHBETE BOT DAVETİ: yalnız admin bottayken görünür (composer'daki 🤖 ikonu);
2467
+ davetli botlar session'a kaydedilir, ajan bot_dm aracıyla onlara danışır */
2468
+ function renderGuestBotMenu() {
2469
+ const btn = $('#guestBotBtn');
2470
+ const menu = $('#guestBotMenu');
2471
+ if (!btn || !menu) return;
2472
+ const isAdmin = activeBotId === 'beast';
2473
+ const show = isAdmin && !!activeId;
2474
+ btn.style.display = show ? '' : 'none';
2475
+ if (!show) { menu.hidden = true; return; }
2476
+ const candidates = botsCache.filter((b) => !b.admin && b.vis !== false);
2477
+ menu.innerHTML = '';
2478
+ if (!candidates.length) {
2479
+ menu.innerHTML = '<div class="dd-item" style="pointer-events:none">Davet edilebilir bot yok</div>';
2480
+ return;
2481
+ }
2482
+ for (const b of candidates) {
2483
+ const invited = activeGuests.some((g) => g.id === b.id);
2484
+ const item = document.createElement('div');
2485
+ item.className = 'dd-item';
2486
+ item.innerHTML = `${b.icon} ${escapeHtml(b.name)} <span style="float:right;color:var(--muted)">${invited ? '✓ davetli' : '+ davet et'}</span>`;
2487
+ item.addEventListener('click', async () => {
2488
+ const next = invited ? activeGuests.filter((g) => g.id !== b.id) : [...activeGuests, { id: b.id, code: b.code || '', name: b.name }];
2489
+ activeGuests = next;
2490
+ await beast.sessionSetGuests(activeId, next).catch(() => {});
2491
+ renderGuestBotMenu();
2492
+ toast(invited ? `${b.name} davetten çıkarıldı` : `${b.name} sohbete davet edildi — ajan gerektiğinde ona danışacak`);
2493
+ menu.hidden = true;
2494
+ });
2495
+ menu.appendChild(item);
2496
+ }
2497
+ }
2460
2498
 
2461
2499
  async function refreshBots() {
2462
2500
  try {
@@ -2483,7 +2521,8 @@ function renderBotCards() {
2483
2521
  `<span class="bot-ico">${b.icon || '🤖'}</span>` +
2484
2522
  `<span class="bot-nm">${escapeHtml(b.name)}</span>` +
2485
2523
  `<span class="bot-gear" title="${_t('bot_manage')}">⚙</span>` +
2486
- `<span class="bot-tag">${b.admin ? 'ADMIN' : 'BOT'}</span>`;
2524
+ `<span class="bot-tag">${b.admin ? 'ADMIN' : 'BOT'}</span>` +
2525
+ (b.code ? `<span class="bot-code" title="${_t('bot_code_title')}">${escapeHtml(b.code)}</span>` : '');
2487
2526
  /* tıkla → UI o bota geçer; ⚙ → yönetim sayfası */
2488
2527
  row.addEventListener('click', () => switchBot(b.id));
2489
2528
  row.querySelector('.bot-gear').addEventListener('click', (e) => {
@@ -2576,6 +2615,9 @@ function renderBotPage() {
2576
2615
  head.querySelector('#botHeadIcon').textContent = b.icon || '🤖';
2577
2616
  head.querySelector('#botHeadName').textContent = b.name;
2578
2617
  $('#botHeadTag').textContent = b.admin ? 'ADMIN' : 'BOT';
2618
+ /* DM Log sekmesi yalnız admin botta görünür — botlar arası tüm trafiği o izler */
2619
+ const dmTab = document.querySelector('.btab[data-btab="dmlog"]');
2620
+ if (dmTab) dmTab.hidden = !b.admin;
2579
2621
  const active = document.querySelector('.btab.active');
2580
2622
  const tab = active ? active.dataset.btab : 'settings';
2581
2623
  if (tab === 'settings') renderBotSettings(pane, b);
@@ -2584,9 +2626,43 @@ function renderBotPage() {
2584
2626
  else if (tab === 'watcher') renderBotWatcher(pane, b);
2585
2627
  else if (tab === 'stats') renderBotStats(pane, b);
2586
2628
  else if (tab === 'notes') renderBotNotes(pane, b);
2629
+ else if (tab === 'dmlog') renderBotDmLog(pane, b);
2587
2630
  renderBotChats(b);
2588
2631
  }
2589
2632
 
2633
+ /* --- DM Log sekmesi (yalnız admin): botlar arası TÜM özel mesaj trafiği.
2634
+ Botlar birbirini göremez; admin her çiftin konuşmasını burada okur. --- */
2635
+ async function renderBotDmLog(pane, b) {
2636
+ pane.innerHTML = `<h2>Bot DM Log</h2><div class="sub">Botlar arası tüm özel mesaj trafiği — hangi bot kiminle, ne zaman, ne konuştu. Bu ekranı yalnız yönetici görür.</div>`;
2637
+ const list = await beast.botsDmList().catch(() => []);
2638
+ if (!list.length) {
2639
+ pane.insertAdjacentHTML('beforeend', '<p class="sub">Henüz botlar arası DM yok — admin botda bot_dm aracını kullanın.</p>');
2640
+ return;
2641
+ }
2642
+ for (const d of list) {
2643
+ const row = document.createElement('div');
2644
+ row.className = 'skill-row';
2645
+ row.innerHTML =
2646
+ `<div class="skill-name">${escapeHtml(d.aName)} (${escapeHtml(d.aCode)}) ⇄ ${escapeHtml(d.bName)} (${escapeHtml(d.bCode)})</div>` +
2647
+ `<div class="skill-desc">${d.count} mesaj · son: ${new Date(d.updatedAt).toLocaleString('tr-TR')}</div>` +
2648
+ `<button class="btn ghost" style="margin-top:6px;padding:3px 12px;font-size:12px">Dökümü aç/kapat</button>`;
2649
+ row.querySelector('button').addEventListener('click', async () => {
2650
+ const old = row.nextElementSibling;
2651
+ if (old && old.dataset && old.dataset.dmread === d.id) { old.remove(); return; }
2652
+ const r = await beast.botsDmRead(d.id).catch(() => ({ ok: false, error: 'ipc' }));
2653
+ const pre = document.createElement('pre');
2654
+ pre.dataset.dmread = d.id;
2655
+ pre.className = 'notes-body';
2656
+ pre.style.cssText = 'white-space:pre-wrap;max-height:340px;overflow:auto;margin-top:8px;border:1px solid var(--border);border-radius:8px;padding:10px';
2657
+ pre.textContent = r.ok
2658
+ ? r.messages.map((m) => `[${m.role === 'user' ? '→ hedef bot' : m.role === 'assistant' ? '← hedef bot' : m.role}] ${m.content}`).join('\n\n')
2659
+ : (r.error || 'okunamadı');
2660
+ row.after(pre);
2661
+ });
2662
+ pane.appendChild(row);
2663
+ }
2664
+ }
2665
+
2590
2666
  /* --- Notlar sekmesi: oturum notları — konuşma kodu, başlık, tarih ve özet metni.
2591
2667
  Admin bot (beast) tüm oturumları görür; müşteri botu yalnız kendi oturumlarını. --- */
2592
2668
  async function renderBotNotes(pane, b) {
@@ -2729,6 +2805,7 @@ function renderBotSettings(pane, b) {
2729
2805
  <div><label class="mem-label">${_t('bot_name')}</label><input id="bName" class="inp" value="${escapeHtml(b.name)}" maxlength="40"/></div>
2730
2806
  <div><label class="mem-label">${_t('bot_icon')}</label><div class="icon-pick" id="bIconPick">${iconBtns}</div></div>
2731
2807
  </div>
2808
+ <div class="sub" style="margin:2px 0 6px">BOT KODU: <b style="letter-spacing:2px">${escapeHtml(b.code || '—')}</b> — botlar arası DM adresi</div>
2732
2809
  <label class="mem-label">${_t('bot_prompt')}</label>
2733
2810
  <textarea id="bPrompt" class="mem-area" rows="5" placeholder="${_t('bot_prompt_ph')}">${escapeHtml(b.prompt || '')}</textarea>
2734
2811
  <div class="sub" style="margin-top:4px">${_t('bot_tpl_hint')}</div>
@@ -2748,6 +2825,9 @@ function renderBotSettings(pane, b) {
2748
2825
  <div class="bot-checks" style="margin-bottom:6px">
2749
2826
  <label><input type="checkbox" id="bExtBrowser" ${b.extBrowser ? 'checked' : ''}/> ${_t('bot_ext_browser')}</label>
2750
2827
  </div>
2828
+ <div class="bot-checks" style="margin-bottom:6px">
2829
+ <label><input type="checkbox" id="bVis" ${b.vis !== false ? 'checked' : ''}/> ${_t('bot_vis')}</label>
2830
+ </div>
2751
2831
  <div class="form-grid">
2752
2832
  <div>
2753
2833
  <label class="mem-label" style="margin-top:0">${_t('bot_def_browser')}</label>
@@ -2808,6 +2888,7 @@ function renderBotSettings(pane, b) {
2808
2888
  prompt: $('#bPrompt').value,
2809
2889
  seeBots: [...f.querySelectorAll('#bSee input:checked')].map((c) => c.dataset.see),
2810
2890
  extBrowser: $('#bExtBrowser').checked,
2891
+ vis: f.querySelector('#bVis') ? f.querySelector('#bVis').checked : true,
2811
2892
  browserDefault: $('#bBrDef').value,
2812
2893
  extCommand: $('#bBrCmd').value.trim(),
2813
2894
  numbers: (b.numbers || []).map((n) => n.num),
@@ -4396,6 +4477,18 @@ async function init() {
4396
4477
  if ($('#botClose')) $('#botClose').addEventListener('click', () => botOverlaySetOpen(false));
4397
4478
  if ($('#botOverviewBack')) $('#botOverviewBack').addEventListener('click', () => openBotPage(null));
4398
4479
  if ($('#botChip')) $('#botChip').addEventListener('click', () => openBotPage(activeBotId));
4480
+
4481
+ /* BOT DAVET MENÜSÜ: butona tıkla → aç/kapa; dışarı tık → kapa */
4482
+ if ($('#guestBotBtn') && $('#guestBotMenu')) {
4483
+ $('#guestBotBtn').addEventListener('click', (e) => {
4484
+ e.stopPropagation();
4485
+ renderGuestBotMenu();
4486
+ $('#guestBotMenu').hidden = !$('#guestBotMenu').hidden;
4487
+ });
4488
+ document.addEventListener('click', (e) => {
4489
+ if (!$('#guestBotMenu').hidden && !$('#guestBotMenu').contains(e.target)) $('#guestBotMenu').hidden = true;
4490
+ });
4491
+ }
4399
4492
  /* kalıcı aktif bot: restart sonrası aynı botun UI'ı açılır */
4400
4493
  beast.botsActiveGet().then((r) => { activeBotId = (r && r.id) || 'beast'; updateBotChip(); renderBotCards(); }).catch(() => {});
4401
4494
  document.querySelectorAll('.btab').forEach((b) =>
@@ -998,6 +998,23 @@ body.term-open #settingsOverlay { right: var(--tw, 520px); }
998
998
  }
999
999
  #attachBtn:hover { background: var(--panel); color: var(--accent); }
1000
1000
 
1001
+ /* composer sol grubu: + mikrofon bot-davet — yanyana boşluksuz */
1002
+ #composerLeft { display: flex; align-items: center; gap: 0; flex-shrink: 0; }
1003
+ #guestBotBtn {
1004
+ width: 32px; height: 32px;
1005
+ border-radius: 9px;
1006
+ border: none;
1007
+ cursor: pointer;
1008
+ font-size: 15px;
1009
+ line-height: 1;
1010
+ flex-shrink: 0;
1011
+ background: none;
1012
+ color: var(--muted);
1013
+ padding: 0;
1014
+ }
1015
+ #guestBotBtn:hover { background: var(--panel); color: var(--accent); }
1016
+ #guestBotMenu { width: 260px; left: 0; }
1017
+
1001
1018
  #micBtn {
1002
1019
  width: 32px; height: 32px;
1003
1020
  border-radius: 9px;
@@ -1725,6 +1742,16 @@ body.browser-open #toast { left: calc((100vw - var(--bw, 480px)) / 2); }
1725
1742
  .bot-card:hover { background: var(--panel2); color: var(--text); }
1726
1743
  .bot-card.active { background: var(--panel2); color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
1727
1744
  .bot-card .bot-ico { font-size: 16px; line-height: 1; flex-shrink: 0; }
1745
+ /* 5 haneli benzersiz bot kodu — bot adının yanında */
1746
+ .bot-card .bot-code {
1747
+ font-family: ui-monospace, Consolas, monospace;
1748
+ font-size: 10px;
1749
+ font-weight: 700;
1750
+ letter-spacing: 1.5px;
1751
+ color: var(--accent);
1752
+ opacity: 0.9;
1753
+ margin-left: 2px;
1754
+ }
1728
1755
  .bot-card .bot-gear {
1729
1756
  visibility: hidden;
1730
1757
  color: var(--muted);