beast-agent 0.23.8 → 0.24.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.
@@ -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.8",
4
+ "version": "0.24.0",
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,
@@ -220,7 +220,10 @@ class Engine {
220
220
  this.tokRatio = 1; // gerçek prompt_tokens ile kalibre edilir
221
221
  this._codeIndex = new Map(); // kısa oturum kodu -> session id
222
222
  /* yansıma: oturumda 5+ yeni araç çağrısında skill taslağı denenir */
223
- this.reflection = { enabled: opts.reflection !== false, minTools: 5 };
223
+ this.reflection = { enabled: opts.reflection !== false, minTools: 3 };
224
+ /* OTOMATİK SKİLL SİSTEMİ: öğrenilen prosedür taslak onayı beklemeden
225
+ kurulu skill olur; mevcut skillin daha iyisi bulunursa üzerine günceller */
226
+ this.autoSkills = opts.autoSkills !== false;
224
227
  this.historyTokenBudget = Number(opts.historyTokenBudget) || HISTORY_TOKEN_BUDGET;
225
228
  this.browser = opts.browser || null; // dahili tarayıcı kancaları
226
229
  this.fileSend = opts.fileSend || null; // #26 dosya gönderim köprüsü (chat/WA)
@@ -330,6 +333,41 @@ class Engine {
330
333
  else this.sessionTools.delete(id);
331
334
  }
332
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
+
333
371
  /* Oturumun bağlı olduğu MÜŞTERİ botu (admin/seasız → null → global hafıza) */
334
372
  _sessionBotCtx(session) {
335
373
  if (!session || !session.botId || typeof this.resolveBot !== 'function') return null;
@@ -904,6 +942,14 @@ class Engine {
904
942
  } else if (rec.t === 'notes') {
905
943
  session.notes = String(rec.text || '');
906
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 : [];
907
953
  } else if (rec.t === 'msg') {
908
954
  delete rec.t;
909
955
  session.messages.push(rec);
@@ -1039,6 +1085,8 @@ class Engine {
1039
1085
  updatedAt: s.updatedAt,
1040
1086
  count: s.messages.length,
1041
1087
  isBg,
1088
+ isBotDm: !!s.isBotDm,
1089
+ guests: Array.isArray(s.guests) ? s.guests : [],
1042
1090
  bgStatus: isBg ? (job ? job.status : null) : null,
1043
1091
  };
1044
1092
  }
@@ -1055,6 +1103,7 @@ class Engine {
1055
1103
  const id = f.replace(/\.jsonl$/, '');
1056
1104
  const v = this._view(this._load(id));
1057
1105
  if (v.isBg) continue;
1106
+ if (v.isBotDm) continue; // botlar arası DM — yalnız admin DM Log ekranında
1058
1107
  out.push(v);
1059
1108
  }
1060
1109
  out.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
@@ -2147,6 +2196,8 @@ class Engine {
2147
2196
  /* bot skill kısıtı: bota verilen yetkiye göre araç seti daraltılır */
2148
2197
  const toolLimit = this.sessionTools.get(String(session.id));
2149
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');
2150
2201
  if (session.bgJob) {
2151
2202
  /* arka plan ajanı: yönetim araçlarını görmez — işini bitirsin */
2152
2203
  activeTools = activeTools.filter((t) => !BG_HIDDEN_TOOLS.has(t.function.name));
@@ -2169,6 +2220,8 @@ class Engine {
2169
2220
  /* bot kimliği: bota bağlı oturumlarda kişilik + izolasyon kuralları */
2170
2221
  const botBlock = this._botSystemBlock(session);
2171
2222
  if (botBlock) system += '\n\n' + botBlock;
2223
+ const guestBlock = this._guestsBlock(session);
2224
+ if (guestBlock) system += '\n\n' + guestBlock;
2172
2225
  if (session.notes) {
2173
2226
  system +=
2174
2227
  `\n\n# OTURUM NOTLARI (oturum ${session.code || '?'} — bu oturumun önceki konuşma özeti; birebir geçmişi buradan hatırla)\n` +
@@ -2267,7 +2320,7 @@ class Engine {
2267
2320
  /* #21 bg ajanı: görev sonu ekstra LLM çağrıları YOK — hızlı kapansın.
2268
2321
  Beast Code (bcCode) oturumları da aynı şekilde ANINDA kapansın:
2269
2322
  not/hafıza/skill yansıtma çağrıları yapılmaz → done hemen gelir */
2270
- if (!session.bgJob && !session.bcCode) {
2323
+ if (!session.bgJob && !session.bcCode && !session.isBotDm) {
2271
2324
  const r = await this._updateSessionNotes(session, ctrl.signal);
2272
2325
  if (r && r.ok) {
2273
2326
  emitSafe(this, sid, { type: 'status', status: `oturum notları güncellendi (${session.code || ''})` });
@@ -2276,13 +2329,13 @@ class Engine {
2276
2329
  }
2277
2330
  }
2278
2331
  /* otomatik memory döngüsü: kalıcı değerli bilgiyi MEMORY.md'ye düşür */
2279
- if (!ctrl.signal.aborted && !session.bgJob && !session.bcCode) {
2332
+ if (!ctrl.signal.aborted && !session.bgJob && !session.bcCode && !session.isBotDm) {
2280
2333
  try {
2281
2334
  await this._autoMemory(session, ctrl.signal);
2282
2335
  } catch {}
2283
2336
  }
2284
2337
  /* deneyimden skill doğurma (#2): 5+ yeni araç çağrısı biriktiyse yansıt */
2285
- 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) {
2286
2339
  try {
2287
2340
  const made = await this._maybeReflectSkill(session);
2288
2341
  if (made) emitSafe(this, sid, { type: 'status', status: `skill taslağı doğdu: ${made}` });
@@ -2479,12 +2532,25 @@ class Engine {
2479
2532
  );
2480
2533
  if (transcript.length < 400) return null; // çok kısa deneyim değmez
2481
2534
 
2535
+ /* OTOMATİK SKİLL SİSTEMİ: kurulu skill listesi de prompta girer —
2536
+ ajan "eski skillden daha kolay/better yol buldum" diyebilmesin, KARAR VERİP
2537
+ skilli GÜNCELLESİN. action: create (yeni) | update (mevcutu iyileştir) | none */
2538
+ const skills = require('./skills');
2539
+ const existing = skills
2540
+ .scan()
2541
+ .map((s) => `- ${s.name}: ${(s.description || '').slice(0, 100)}`)
2542
+ .join('\n');
2543
+
2482
2544
  const prompt =
2483
- 'Aşağıdaki agent oturumunda TEKRARLANABİLİR, yeniden kullanılabilir bir prosedür/bilgi ' +
2484
- 'birikti mi? Kullanıcıya özel geçici detaylar sayılmaz. Karar: evet ise SKILL.md taslağı üret.\n\n' +
2545
+ 'Aşağıdaki agent oturumunda TEKRARLANABİLİR, yeniden kullanılabilir bir prosedür/bilgi birikti mi?\n' +
2546
+ 'Bir kurulu skill, bu oturumda öğrenilen DAHA KOLAY/DAHA İYİ yöntemle güncellenmeyi hak ediyor mu?\n' +
2547
+ 'Kullanıcıya özel geçici detaylar (tarih, şehir, numara gibi) skill\u2019e YAZILMAZ — genel yöntem yazılır.\n\n' +
2548
+ '# KURULU SKİLLER\n' + (existing || '(yok)') + '\n\n' +
2485
2549
  'SADECE şu JSON formatında cevap ver, başka metin yok:\n' +
2486
- '{"create": true|false, "name": "kisa-kebab-ad", "description": "tek cümle ne işe yarar", ' +
2487
- '"body": "# Başlık\\n\\nmaddeler halinde adım adım prosedür"}\n\n' +
2550
+ '{"action": "none" | "create" | "update", "name": "kisa-kebab-ad", "description": "tek cümle ne işe yarar", "body": "# Başlık\\n\\nmaddeler halinde adım adım prosedür"}\n\n' +
2551
+ 'action=create → yeni skill (body: baştan sona tam prosedür)\n' +
2552
+ 'action=update → "name" MEVCUT skillin adı; body o skillin GÜNCELLENMİŞ TAM HALI (eskiden iyi olan adımları koru, yeni kolaylığı işle)\n' +
2553
+ 'action=none → değerlenecek bir şey yok\n\n' +
2488
2554
  '# OTURUM ÖZETİ\n' + transcript.slice(0, 6000);
2489
2555
 
2490
2556
  const res = await chatOnce(
@@ -2493,8 +2559,30 @@ class Engine {
2493
2559
  {}
2494
2560
  );
2495
2561
  const draft = parseReflectionJson(res.content || '');
2496
- if (!draft || !draft.create || !draft.name || !draft.body) return null;
2497
- const skills = require('./skills');
2562
+ if (!draft || !draft.name || !draft.body) return null;
2563
+
2564
+ const action = String(draft.action || (draft.create ? 'create' : 'none')).toLowerCase();
2565
+ if (action !== 'create' && action !== 'update') return null;
2566
+
2567
+ if (action === 'update') {
2568
+ /* mevcut skillin GÜNCELLENMİŞ hali — doğrudan üzerine (eski hali .bak) */
2569
+ const r = skills.upsertSkill({
2570
+ name: String(draft.name),
2571
+ description: String(draft.description || ''),
2572
+ body: String(draft.body),
2573
+ });
2574
+ return r.ok ? skills.slugify(draft.name) + ' (güncellendi)' : null;
2575
+ }
2576
+
2577
+ /* create: otomatik skill modu AÇIKSA taslak beklemeden direkt kur */
2578
+ if (this.autoSkills) {
2579
+ const r = skills.upsertSkill({
2580
+ name: String(draft.name),
2581
+ description: String(draft.description || ''),
2582
+ body: String(draft.body),
2583
+ });
2584
+ return r.ok ? skills.slugify(draft.name) : null;
2585
+ }
2498
2586
  const r = skills.addDraft({
2499
2587
  name: String(draft.name),
2500
2588
  description: String(draft.description || ''),
@@ -2503,6 +2591,119 @@ class Engine {
2503
2591
  return r.ok ? skills.slugify(draft.name) : null;
2504
2592
  }
2505
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
+
2506
2707
  /* ---------- alt-agent ---------- */
2507
2708
 
2508
2709
  async _subagent(task, context, parentSignal, sessionId) {
@@ -2743,6 +2944,14 @@ class Engine {
2743
2944
  const result = await this._subagent(task, String(args.context || ''), signal, sessionId);
2744
2945
  return JSON.stringify({ ok: true, task, result: String(result).slice(0, 12000) });
2745
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
+ }
2746
2955
  if (name === 'send_file') {
2747
2956
  if (typeof this.fileSend !== 'function') {
2748
2957
  return JSON.stringify({ ok: false, error: 'dosya gönderim köprüsü yok' });
@@ -3113,6 +3322,22 @@ const TOOLS = [
3113
3322
  },
3114
3323
  },
3115
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
+ },
3116
3341
  {
3117
3342
  type: 'function',
3118
3343
  function: {
@@ -487,6 +487,33 @@ function dropDraft(id) {
487
487
  }
488
488
  }
489
489
 
490
+ /* OTOMATIK SKILL SİSTEMİ: yansımadan doğan prosedürü DOĞRUDAN kurulu skill olarak
491
+ yazar (taslak onayı beklemeden). Aynı isimde skill varsa GÜNCELLENİR:
492
+ eski içerik .bak'a alınır, created korunur, updated damgası vurulur. */
493
+ function upsertSkill({ name, description, body }) {
494
+ try {
495
+ const nm = String(name || '').trim() || 'yetenek';
496
+ const folder = slugify(nm);
497
+ const d = path.join(dir(), folder);
498
+ const file = path.join(d, 'SKILL.md');
499
+ fs.mkdirSync(d, { recursive: true });
500
+ let fm = `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\n`;
501
+ let updated = false;
502
+ if (fs.existsSync(file)) {
503
+ const old = fs.readFileSync(file, 'utf8');
504
+ const oldFm = parseFrontmatter(old);
505
+ fm += `created: ${oldFm.created || oldFm.generatedAt || new Date().toISOString()}\n`;
506
+ fs.writeFileSync(file + '.bak', old);
507
+ updated = true;
508
+ }
509
+ fm += `updated: ${new Date().toISOString()}\n---\n\n`;
510
+ fs.writeFileSync(file, fm + String(body || '').trim() + '\n');
511
+ return { ok: true, folder, file, updated };
512
+ } catch (e) {
513
+ return { ok: false, error: String((e && e.message) || e) };
514
+ }
515
+ }
516
+
490
517
  /* Kurulu bir skill'e madde ekler (#3 kural hattı): "## Kurallar" altına yazılır. */
491
518
  function appendRuleToSkill(nameOrFolder, ruleText) {
492
519
  try {
@@ -520,4 +547,5 @@ module.exports = {
520
547
  acceptDraft,
521
548
  dropDraft,
522
549
  appendRuleToSkill,
550
+ upsertSkill,
523
551
  };
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; });
@@ -2071,6 +2064,9 @@ function reloadBackend() {
2071
2064
  thinkLevel: settings.thinkLevel || 0,
2072
2065
  fallout: settings.fallout || null,
2073
2066
  limits: settings.limits || null,
2067
+ /* OTOMATİK SKİLL SİSTEMİ: ayarlardan kapatılmadıysa öğrenilen prosedürler
2068
+ otomatik skill olur, mevcutların daha iyisi bulunursa güncellenir */
2069
+ autoSkills: settings.autoSkills !== false,
2074
2070
  approvals: settings.security && settings.security.approvals ? approvalsBridge : null,
2075
2071
  alwaysAllowTools: (settings.security && settings.security.alwaysAllow) || [],
2076
2072
  crashFile: FALLOUT_CRASH_FILE,
@@ -2306,18 +2302,16 @@ function ensureDesktopShortcut() {
2306
2302
  app.whenReady().then(() => {
2307
2303
  // Tailscale modu: paketli uygulamada Windows ile otomatik başlat (sessiz, tepside)
2308
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). */
2309
2307
  try {
2310
- // portable exe Temp'e açılır; gerçek yolu PORTABLE_EXECUTABLE_FILE verir
2311
- const exe = process.env.PORTABLE_EXECUTABLE_FILE || process.execPath;
2312
- app.setLoginItemSettings({
2313
- openAtLogin: true,
2314
- path: exe,
2315
- 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)');
2316
2312
  });
2317
- log.info('main', `Startup kaydı açık: ${exe} (--hidden, açılışta tepside)`);
2318
- } catch (e) {
2319
- log.error('main', 'Startup kaydı başarısız: ' + String((e && e.message) || e));
2320
- }
2313
+ } catch {}
2314
+ log.info('main', '⚠ EXE/portable mod desteklenmiyor — tek dağıtım: npm i -g beast-agent');
2321
2315
  } else if (!app.isPackaged && /node_modules[\\/]beast-agent/i.test(String(app.getAppPath()))) {
2322
2316
  /* npm (global) kurulum modu: startup kaydı + masaüstü kısayolu */
2323
2317
  try {
@@ -2334,6 +2328,7 @@ app.whenReady().then(() => {
2334
2328
  }
2335
2329
  reloadBackend();
2336
2330
  syncWhitelist(); // bot sistemi: whitelist.json aynası ilk açılışta garanti
2331
+ try { bots.ensureBotCodes(); } catch {} // her bota benzersiz 5 haneli kod garanti
2337
2332
  createSplash();
2338
2333
  createWindow();
2339
2334
  log.info('main', 'Beast Agent başlatıldı');
@@ -3330,14 +3325,7 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
3330
3325
  const a = t.slice(7).trim().toLowerCase();
3331
3326
  updateReplies.sids.add(String(sessionId || ''));
3332
3327
  if (a === 'now') {
3333
- if (isNpmMode()) {
3334
- npmUpdateNow((text) => desktopEcho(sessionId, t, text));
3335
- } else if (updateState.downloaded && autoUpdater) {
3336
- desktopEcho(sessionId, t, `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`);
3337
- setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
3338
- } else {
3339
- desktopEcho(sessionId, t, 'İndirilmiş sürüm yok — önce `/update` yaz.');
3340
- }
3328
+ desktopEcho(sessionId, t, NPM_ONLY_TEXT);
3341
3329
  } else {
3342
3330
  runUpdateCommand((text) => desktopEcho(sessionId, t, text));
3343
3331
  }
@@ -3828,6 +3816,16 @@ ipcMain.handle('memory:save', (_e, { file, content }) => memory.save(file, conte
3828
3816
 
3829
3817
  ipcMain.handle('skills:list', () => skillsMod.scan());
3830
3818
 
3819
+ /* OTOMATİK SKİLL SİSTEMİ: açıkken öğrenilen prosedürler direkt kurulur,
3820
+ mevcut skillin daha iyisi bulunursa güncellenir */
3821
+ ipcMain.handle('skills:auto:get', () => settings.autoSkills !== false);
3822
+ ipcMain.handle('skills:auto:set', (_e, v) => {
3823
+ settings.autoSkills = !!v;
3824
+ saveSettings();
3825
+ if (engine) engine.autoSkills = !!v;
3826
+ return settings.autoSkills;
3827
+ });
3828
+
3831
3829
  /* taslak skill'ler (#2 yansıma ürünleri) */
3832
3830
  ipcMain.handle('skills:drafts:list', () => skillsMod.listDrafts());
3833
3831
  ipcMain.handle('skills:drafts:accept', (_e, id) => {
@@ -4074,100 +4072,13 @@ ipcMain.handle('update:check', async () => {
4074
4072
  return out;
4075
4073
  });
4076
4074
 
4077
- /* npm kurulumunda KENDİ KENDİNİ GÜNCELLEME:
4078
- helper script %APPDATA%\beast'a yazılır (paket dizini değişse de yaşar) ve
4079
- detached çalışır: 1) uygulama PID'i tamamen çıkana kadar bekler (en çok 30 sn,
4080
- sonra zorla kapatır — EBUSY dosya kilidinin numarası), 2) npm install -g
4081
- beast-agent@latest — kilide karşı 5 deneme, 3) tüm çıktı update.log'a yazılır,
4082
- 4) beast-agent komut shim'i üzerinden yeniden başlatır (electron sürümü
4083
- değişse de yol bozulmaz). Sonra app.quit(). */
4084
- function npmSelfUpdate() {
4085
- try {
4086
- fs.mkdirSync(APP_DIR, { recursive: true });
4087
- const pid = String(process.pid);
4088
- if (process.platform === 'win32') {
4089
- const ps = [
4090
- "param([int]$ProcId = 0)",
4091
- "$ErrorActionPreference = 'Continue'",
4092
- "$Log = Join-Path $env:APPDATA 'beast\\update.log'",
4093
- "function L([string]$m) { try { Add-Content -LiteralPath $Log -Value (\"[\" + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + \"] \" + $m) } catch {} }",
4094
- "L \"=== self-update basladi (app pid: $ProcId) ===\"",
4095
- "if ($ProcId -gt 0) {",
4096
- " $deadline = (Get-Date).AddSeconds(30)",
4097
- " while ((Get-Date) -lt $deadline) {",
4098
- " if (-not (Get-Process -Id $ProcId -ErrorAction SilentlyContinue)) { break }",
4099
- " Start-Sleep -Milliseconds 500",
4100
- " }",
4101
- " if (Get-Process -Id $ProcId -ErrorAction SilentlyContinue) {",
4102
- " L 'uygulama hala acik - zorla kapatiliyor'",
4103
- " try { Stop-Process -Id $ProcId -Force } catch {}",
4104
- " Start-Sleep -Seconds 2",
4105
- " }",
4106
- "}",
4107
- "L 'uygulama kapandi, npm install basliyor'",
4108
- "$npmCmd = (Get-Command 'npm.cmd' -ErrorAction SilentlyContinue).Source",
4109
- "if (-not $npmCmd) { $npmCmd = Join-Path $env:APPDATA 'npm\\npm.cmd' }",
4110
- "$ok = $false",
4111
- "for ($i = 1; $i -le 5; $i++) {",
4112
- " if ($ok) { break }",
4113
- " L \"npm install -g beast-agent@latest (deneme $i)\"",
4114
- " $out = & $npmCmd install -g beast-agent@latest 2>&1",
4115
- " $code = $LASTEXITCODE",
4116
- " foreach ($line in @($out)) { L \" npm: $line\" }",
4117
- " if ($code -eq 0) { $ok = $true } else { Start-Sleep -Seconds 3 }",
4118
- "}",
4119
- "if (-not $ok) {",
4120
- " L 'HATA: npm install 5 denemede basarisiz - uygulama yeniden baslatilmiyor'",
4121
- " exit 1",
4122
- "}",
4123
- "L 'npm install tamam, yeniden baslatma'",
4124
- "$prefix = (& $npmCmd prefix -g 2>$null)",
4125
- "if (-not $prefix) { $prefix = Join-Path $env:APPDATA 'npm' }",
4126
- "$appDir = Join-Path $prefix 'node_modules\\beast-agent'",
4127
- "$exe = Join-Path $appDir 'node_modules\\electron\\dist\\electron.exe'",
4128
- "if (-not (Test-Path $exe)) { $exe = Join-Path $prefix 'node_modules\\electron\\dist\\electron.exe' }",
4129
- "if (Test-Path $exe) {",
4130
- " L \"dogrudan electron: $exe\"",
4131
- " # WindowStyle Hidden KULLANMA: gizli bayragi Chromium miras alir, ilk pencere tray'de kaybolur",
4132
- " Start-Process -FilePath $exe -ArgumentList \"`\"$appDir`\"\"",
4133
- "} else {",
4134
- " $shim = Get-Command 'beast-agent.cmd' -ErrorAction SilentlyContinue",
4135
- " if ($shim) {",
4136
- " L \"shim uzerinden: $($shim.Source)\"",
4137
- " Start-Process -FilePath $shim.Source",
4138
- " } else {",
4139
- " L 'HATA: electron.exe ve shim bulunamadi - yeniden baslatma yapilamadi'",
4140
- " exit 1",
4141
- " }",
4142
- "}",
4143
- "L '=== self-update bitti ==='",
4144
- ].join('\r\n');
4145
- const psFile = path.join(APP_DIR, 'update-helper.ps1');
4146
- fs.writeFileSync(psFile, ps, 'utf8');
4147
- spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psFile, '-ProcId', pid],
4148
- { detached: true, stdio: 'ignore', windowsHide: true }).unref();
4149
- } else {
4150
- const sh =
4151
- `i=0; while [ $i -lt 60 ] && kill -0 ${pid} 2>/dev/null; do i=$((i+1)); sleep 0.5; done; ` +
4152
- 'ok=0; for n in 1 2 3 4 5; do npm install -g beast-agent@latest && ok=1 && break; sleep 3; done; ' +
4153
- 'if [ $ok -eq 1 ]; then nohup beast-agent >/dev/null 2>&1 & fi';
4154
- spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' }).unref();
4155
- }
4156
- log.info('main', 'npm self-update: helper bırakıldı, uygulama kapatılıyor');
4157
- } catch (e) {
4158
- log.error('main', 'npm self-update hatası: ' + String((e && e.message) || e));
4159
- }
4160
- setTimeout(() => { try { app.quit(); } catch {} }, 400);
4161
- }
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ç. */
4162
4077
 
4163
- ipcMain.handle('update:install', () => {
4164
- if (isNpmMode()) { npmSelfUpdate(); return { ok: true, npm: true }; }
4165
- if (!app.isPackaged) return { ok: false, error: 'Geliştirme modu — güncelleme npm / GitHub Releases üzerinden yapılır' };
4166
- if (!updateState.downloaded) return { ok: false, error: 'indirilmiş sürüm yok — önce /update' };
4167
- try { autoUpdater.quitAndInstall(); return { ok: true }; } catch (e) {
4168
- return { ok: false, error: String((e && e.message) || e) };
4169
- }
4170
- });
4078
+ ipcMain.handle('update:install', () => ({
4079
+ ok: false,
4080
+ error: NPM_ONLY_TEXT,
4081
+ }));
4171
4082
 
4172
4083
  ipcMain.handle('update:setAuto', (_e, cfg) => {
4173
4084
  if (cfg && typeof cfg.autoCheck === 'boolean') settings.autoCheckUpdate = cfg.autoCheck;
@@ -4181,14 +4092,19 @@ ipcMain.handle('update:setAuto', (_e, cfg) => {
4181
4092
 
4182
4093
  /* /update komutu (masaüstü + WA): hedefi kaydet, kontrol başlat */
4183
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
+
4184
4101
  function npmUpdateNow(reply /* fn(text) */) {
4185
4102
  const current = app.getVersion();
4186
4103
  getNpmLatest(true).then((v) => {
4187
4104
  if (v && isNewerVersion(v, current)) {
4188
4105
  updateState.available = true;
4189
4106
  updateState.version = v;
4190
- reply(`🔄 *Güncelleniyor*\nMevcut: v${current}\nYeni: v${v}\nKapanıp yeniden açılıyorum…`);
4191
- setTimeout(() => npmSelfUpdate(), 1500);
4107
+ reply(`🔄 *Yeni sürüm var*\nMevcut: v${current}\nYeni: v${v}\n\n${NPM_ONLY_TEXT}`);
4192
4108
  } else {
4193
4109
  reply(`✅ *Güncelsin* — v${current} zaten en son sürüm.`);
4194
4110
  }
@@ -4196,30 +4112,8 @@ function npmUpdateNow(reply /* fn(text) */) {
4196
4112
  }
4197
4113
 
4198
4114
  async function runUpdateCommand(reply /* fn(text) */) {
4199
- const current = app.getVersion();
4200
- if (isNpmMode()) return npmUpdateNow(reply);
4201
- if (!autoUpdater) {
4202
- reply('Updater bu modda kullanılamıyor. Yeni sürüm: github.com/algokodcom/beast-agent/releases');
4203
- return;
4204
- }
4205
- /* önce npm registry'den sürüm bilgisi — kullanıcıya numaraları söyle */
4206
- const v = await getNpmLatest(true);
4207
- if (v && isNewerVersion(v, current)) {
4208
- updateState.available = true;
4209
- updateState.version = v;
4210
- emitUpdateEvent();
4211
- reply(`🔄 *Yeni sürüm bulundu*\nMevcut: v${current}\nYeni: v${v}\nİndiriliyor… (kurulum için: /update now)`);
4212
- try { await autoUpdater.checkForUpdates(); } catch (e) {
4213
- reply('İndirme başlatılamadı: ' + String((e && e.message) || e));
4214
- }
4215
- } else if (v) {
4216
- reply(`✅ *Güncelsin* — v${current} en son sürüm.`);
4217
- } else {
4218
- reply(`🔍 Kontrol ediliyor (mevcut sürüm v${current})…`);
4219
- try { await autoUpdater.checkForUpdates(); } catch (e) {
4220
- reply('Kontrol başarısız: ' + String((e && e.message) || e));
4221
- }
4222
- }
4115
+ /* yalnız KONTROL + yol gösterme — kurulum yapmaz */
4116
+ return npmUpdateNow(reply);
4223
4117
  }
4224
4118
 
4225
4119
  /* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
@@ -5469,10 +5363,39 @@ ipcMain.handle('bots:list', () => botListWithNumbers());
5469
5363
 
5470
5364
  ipcMain.handle('bots:add', (_e, input) => {
5471
5365
  const r = bots.add(input || {});
5472
- 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
+ }
5473
5386
  return { ...r, list: r.ok ? botListWithNumbers() : null };
5474
5387
  });
5475
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
+
5476
5399
  ipcMain.handle('bots:update', (_e, { id, patch }) => {
5477
5400
  const p = patch || {};
5478
5401
  if (Array.isArray(p.numbers)) reassignBotNumbers(String(id || ''), p.numbers);
package/src/preload.js CHANGED
@@ -25,6 +25,8 @@ contextBridge.exposeInMainWorld('beast', {
25
25
  clearNotes: (id) => ipcRenderer.invoke('notes:clear', id),
26
26
  saveMemory: (file, content) => ipcRenderer.invoke('memory:save', { file, content }),
27
27
  listSkills: () => ipcRenderer.invoke('skills:list'),
28
+ skillsGetAuto: () => ipcRenderer.invoke('skills:auto:get'),
29
+ skillsSetAuto: (v) => ipcRenderer.invoke('skills:auto:set', v),
28
30
  openSkillsFolder: () => ipcRenderer.invoke('skills:openFolder'),
29
31
  draftsList: () => ipcRenderer.invoke('skills:drafts:list'),
30
32
  draftAccept: (id) => ipcRenderer.invoke('skills:drafts:accept', id),
@@ -139,6 +141,9 @@ contextBridge.exposeInMainWorld('beast', {
139
141
  waListSessions: () => ipcRenderer.invoke('wa:sessions'),
140
142
  botsList: () => ipcRenderer.invoke('bots:list'),
141
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 }),
142
147
  botsUpdate: (id, patch) => ipcRenderer.invoke('bots:update', { id, patch }),
143
148
  botsRemove: (id) => ipcRenderer.invoke('bots:remove', id),
144
149
  botsStats: () => ipcRenderer.invoke('bots:stats'),
@@ -164,6 +164,8 @@
164
164
  mem_saved: 'Memory kaydedildi — SOUL.md dahil',
165
165
  sk_h2: 'Skills',
166
166
  sk_sub: 'SKILL.md indeksi — tam metin model tarafından okunur',
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.)',
167
169
  sk_rules_h2: 'Kalıcı Kurallar',
168
170
  sk_rules_sub: 'Agent\u2019ın her sohbette uyduğu talimatlar — WA\u2019dan /rule ile de eklenir',
169
171
  sk_no_rule: 'Henüz kural yok.',
@@ -442,6 +444,8 @@
442
444
  bot_msg_user: 'KULLANICI',
443
445
  bot_bind_title: 'Bu numarayı hangi bot karşılasın?',
444
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üğü)',
445
449
  bot_switch_hint: 'Tıkla: bu botta çalış · ⚙: yönet',
446
450
  bot_switched: 'bottasın — sohbetler bu botun kimliğiyle gider',
447
451
  bot_manage: 'Botu yönet',
@@ -678,6 +682,8 @@
678
682
  mem_saved: 'Memory saved — including SOUL.md',
679
683
  sk_h2: 'Skills',
680
684
  sk_sub: 'SKILL.md index — full text is read by the model',
685
+ sk_auto: 'Auto skills: the agent installs learned procedures without draft approval and updates existing skills when it finds an easier or better way',
686
+ 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.',
681
687
  sk_rules_h2: 'Persistent Rules',
682
688
  sk_rules_sub: 'Instructions the agent follows in every chat — also addable via /rule on WA',
683
689
  sk_no_rule: 'No rules yet.',
@@ -1014,6 +1020,8 @@
1014
1020
  bot_msg_user: 'USER',
1015
1021
  bot_bind_title: 'Which bot should answer this number?',
1016
1022
  bot_sel_empty: '— no bot —',
1023
+ bot_code_title: "This bot's unique 5-digit code — the inter-bot DM address",
1024
+ bot_vis: 'Invitable to the main chat (chat visibility)',
1017
1025
  bot_switch_hint: 'Click: work as this bot · ⚙: manage',
1018
1026
  bot_switched: 'active — chats run with this bot\u2019s identity',
1019
1027
  bot_manage: 'Manage bot',
@@ -101,6 +101,10 @@
101
101
  <button id="stopBtn" title="Durdur" hidden data-i18n-title="tipStop">■</button>
102
102
  <button id="sendBtn" title="Gönder" data-i18n-title="tipSend">&#x27A4;&#xFE0E;</button>
103
103
  </div>
104
+ <div id="guestBotDD" class="dd" style="align-self:center">
105
+ <button id="guestBotBtn" class="btn ghost" style="margin-top:6px;padding:4px 12px;font-size:12px" title="Konuşmaya başka bot davet et">🤖 Bot davet et</button>
106
+ <div id="guestBotMenu" class="dd-menu" hidden style="left:50%;transform:translateX(-50%)"></div>
107
+ </div>
104
108
  </div>
105
109
 
106
110
  <div id="ideRow">
@@ -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 || []);
@@ -1255,6 +1258,16 @@ async function renderFalloutPane() {
1255
1258
  async function renderSkillsPane() {
1256
1259
  const pane = $('#tab-skills');
1257
1260
  pane.innerHTML = '<h2>' + _t('sk_h2') + '</h2><div class="sub">' + _t('sk_sub') + '</div>';
1261
+ /* OTOMATİK SKİLL SİSTEMİ: öğrenilen prosedürler otomatik kurulur/güncellenir */
1262
+ const auto = await beast.skillsGetAuto().catch(() => true);
1263
+ pane.insertAdjacentHTML(
1264
+ 'afterbegin',
1265
+ `<div class="fo-toggles" style="margin-bottom:12px"><label class="lock-row"><input type="checkbox" id="autoSkillsOn" ${auto ? 'checked' : ''}/><span>${_t('sk_auto')}</span></label></div>`
1266
+ );
1267
+ pane.querySelector('#autoSkillsOn').addEventListener('change', async (e) => {
1268
+ await beast.skillsSetAuto(e.target.checked);
1269
+ toast((e.target.checked ? 'Otomatik skill: AÇIK' : 'Otomatik skill: KAPALI'));
1270
+ });
1258
1271
  const list = await beast.listSkills();
1259
1272
  for (const s of list) {
1260
1273
  const row = document.createElement('div');
@@ -1800,14 +1813,15 @@ async function renderUpdatePane(autoCheck) {
1800
1813
  if (!st) return;
1801
1814
  clearInterval(updatePaneTimer);
1802
1815
 
1803
- /* 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. */
1804
1818
  const isDev = !st.packaged && !st.npm;
1805
1819
  let actions;
1806
1820
  if (st.npm) {
1807
1821
  actions = `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1808
- <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>
1809
1823
  </div>
1810
- <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>`;
1811
1825
  } else if (isDev) {
1812
1826
  actions = `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1813
1827
  <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
@@ -1816,9 +1830,8 @@ async function renderUpdatePane(autoCheck) {
1816
1830
  } else {
1817
1831
  actions = `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1818
1832
  <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
1819
- <button id="upInstall" class="btn ghost" ${st.downloaded ? '' : 'disabled style="opacity:.45;cursor:default"'}>${_t('up_install_now')}</button>
1820
1833
  </div>
1821
- <div class="sub" style="margin-top:8px">${_t('up_note')}</div>`;
1834
+ <div class="sub" style="margin-top:8px">${_t('up_npm_only')}</div>`;
1822
1835
  }
1823
1836
 
1824
1837
  pane.innerHTML =
@@ -1854,6 +1867,7 @@ async function renderUpdatePane(autoCheck) {
1854
1867
  if (r.ok && r.npm) toast(_t('up_npm_started'));
1855
1868
  else if (!r.ok && r.error) toast(r.error);
1856
1869
  });
1870
+ /* #upInstall butonu kaldırıldı — tek dağıtım npm (buton geri gelirse çalışsın diye koruma duruyor) */
1857
1871
 
1858
1872
  /* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
1859
1873
  updatePaneTimer = setInterval(() => {
@@ -2447,6 +2461,39 @@ let botsCache = [];
2447
2461
  let botPageId = null; // null = genel bakış (Tüm Botlar)
2448
2462
  let botPageStats = [];
2449
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; davetli botlar
2467
+ session'a kaydedilir, ajan bot_dm aracıyla onlara danışıp cevapları aktarır */
2468
+ function renderGuestBotMenu() {
2469
+ const wrap = $('#guestBotDD');
2470
+ const menu = $('#guestBotMenu');
2471
+ if (!wrap || !menu) return;
2472
+ const isAdmin = activeBotId === 'beast';
2473
+ wrap.style.display = isAdmin && activeId ? '' : 'none';
2474
+ if (!isAdmin) return;
2475
+ const candidates = botsCache.filter((b) => !b.admin && b.vis !== false);
2476
+ menu.innerHTML = '';
2477
+ if (!candidates.length) {
2478
+ menu.innerHTML = '<div class="dd-item" style="pointer-events:none">Davet edilebilir bot yok</div>';
2479
+ return;
2480
+ }
2481
+ for (const b of candidates) {
2482
+ const invited = activeGuests.some((g) => g.id === b.id);
2483
+ const item = document.createElement('div');
2484
+ item.className = 'dd-item';
2485
+ item.innerHTML = `${b.icon} ${escapeHtml(b.name)} <span style="float:right;color:var(--muted)">${invited ? '✓ davetli' : '+ davet et'}</span>`;
2486
+ item.addEventListener('click', async () => {
2487
+ const next = invited ? activeGuests.filter((g) => g.id !== b.id) : [...activeGuests, { id: b.id, code: b.code || '', name: b.name }];
2488
+ activeGuests = next;
2489
+ await beast.sessionSetGuests(activeId, next).catch(() => {});
2490
+ renderGuestBotMenu();
2491
+ toast(invited ? `${b.name} davetten çıkarıldı` : `${b.name} sohbete davet edildi — ajan gerektiğinde ona danışacak`);
2492
+ menu.hidden = true;
2493
+ });
2494
+ menu.appendChild(item);
2495
+ }
2496
+ }
2450
2497
 
2451
2498
  async function refreshBots() {
2452
2499
  try {
@@ -2473,7 +2520,8 @@ function renderBotCards() {
2473
2520
  `<span class="bot-ico">${b.icon || '🤖'}</span>` +
2474
2521
  `<span class="bot-nm">${escapeHtml(b.name)}</span>` +
2475
2522
  `<span class="bot-gear" title="${_t('bot_manage')}">⚙</span>` +
2476
- `<span class="bot-tag">${b.admin ? 'ADMIN' : 'BOT'}</span>`;
2523
+ `<span class="bot-tag">${b.admin ? 'ADMIN' : 'BOT'}</span>` +
2524
+ (b.code ? `<span class="bot-code" title="${_t('bot_code_title')}">${escapeHtml(b.code)}</span>` : '');
2477
2525
  /* tıkla → UI o bota geçer; ⚙ → yönetim sayfası */
2478
2526
  row.addEventListener('click', () => switchBot(b.id));
2479
2527
  row.querySelector('.bot-gear').addEventListener('click', (e) => {
@@ -2566,6 +2614,9 @@ function renderBotPage() {
2566
2614
  head.querySelector('#botHeadIcon').textContent = b.icon || '🤖';
2567
2615
  head.querySelector('#botHeadName').textContent = b.name;
2568
2616
  $('#botHeadTag').textContent = b.admin ? 'ADMIN' : 'BOT';
2617
+ /* DM Log sekmesi yalnız admin botta görünür — botlar arası tüm trafiği o izler */
2618
+ const dmTab = document.querySelector('.btab[data-btab="dmlog"]');
2619
+ if (dmTab) dmTab.hidden = !b.admin;
2569
2620
  const active = document.querySelector('.btab.active');
2570
2621
  const tab = active ? active.dataset.btab : 'settings';
2571
2622
  if (tab === 'settings') renderBotSettings(pane, b);
@@ -2574,9 +2625,43 @@ function renderBotPage() {
2574
2625
  else if (tab === 'watcher') renderBotWatcher(pane, b);
2575
2626
  else if (tab === 'stats') renderBotStats(pane, b);
2576
2627
  else if (tab === 'notes') renderBotNotes(pane, b);
2628
+ else if (tab === 'dmlog') renderBotDmLog(pane, b);
2577
2629
  renderBotChats(b);
2578
2630
  }
2579
2631
 
2632
+ /* --- DM Log sekmesi (yalnız admin): botlar arası TÜM özel mesaj trafiği.
2633
+ Botlar birbirini göremez; admin her çiftin konuşmasını burada okur. --- */
2634
+ async function renderBotDmLog(pane, b) {
2635
+ 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>`;
2636
+ const list = await beast.botsDmList().catch(() => []);
2637
+ if (!list.length) {
2638
+ pane.insertAdjacentHTML('beforeend', '<p class="sub">Henüz botlar arası DM yok — admin botda bot_dm aracını kullanın.</p>');
2639
+ return;
2640
+ }
2641
+ for (const d of list) {
2642
+ const row = document.createElement('div');
2643
+ row.className = 'skill-row';
2644
+ row.innerHTML =
2645
+ `<div class="skill-name">${escapeHtml(d.aName)} (${escapeHtml(d.aCode)}) ⇄ ${escapeHtml(d.bName)} (${escapeHtml(d.bCode)})</div>` +
2646
+ `<div class="skill-desc">${d.count} mesaj · son: ${new Date(d.updatedAt).toLocaleString('tr-TR')}</div>` +
2647
+ `<button class="btn ghost" style="margin-top:6px;padding:3px 12px;font-size:12px">Dökümü aç/kapat</button>`;
2648
+ row.querySelector('button').addEventListener('click', async () => {
2649
+ const old = row.nextElementSibling;
2650
+ if (old && old.dataset && old.dataset.dmread === d.id) { old.remove(); return; }
2651
+ const r = await beast.botsDmRead(d.id).catch(() => ({ ok: false, error: 'ipc' }));
2652
+ const pre = document.createElement('pre');
2653
+ pre.dataset.dmread = d.id;
2654
+ pre.className = 'notes-body';
2655
+ 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';
2656
+ pre.textContent = r.ok
2657
+ ? r.messages.map((m) => `[${m.role === 'user' ? '→ hedef bot' : m.role === 'assistant' ? '← hedef bot' : m.role}] ${m.content}`).join('\n\n')
2658
+ : (r.error || 'okunamadı');
2659
+ row.after(pre);
2660
+ });
2661
+ pane.appendChild(row);
2662
+ }
2663
+ }
2664
+
2580
2665
  /* --- Notlar sekmesi: oturum notları — konuşma kodu, başlık, tarih ve özet metni.
2581
2666
  Admin bot (beast) tüm oturumları görür; müşteri botu yalnız kendi oturumlarını. --- */
2582
2667
  async function renderBotNotes(pane, b) {
@@ -2719,6 +2804,7 @@ function renderBotSettings(pane, b) {
2719
2804
  <div><label class="mem-label">${_t('bot_name')}</label><input id="bName" class="inp" value="${escapeHtml(b.name)}" maxlength="40"/></div>
2720
2805
  <div><label class="mem-label">${_t('bot_icon')}</label><div class="icon-pick" id="bIconPick">${iconBtns}</div></div>
2721
2806
  </div>
2807
+ <div class="sub" style="margin:2px 0 6px">BOT KODU: <b style="letter-spacing:2px">${escapeHtml(b.code || '—')}</b> — botlar arası DM adresi</div>
2722
2808
  <label class="mem-label">${_t('bot_prompt')}</label>
2723
2809
  <textarea id="bPrompt" class="mem-area" rows="5" placeholder="${_t('bot_prompt_ph')}">${escapeHtml(b.prompt || '')}</textarea>
2724
2810
  <div class="sub" style="margin-top:4px">${_t('bot_tpl_hint')}</div>
@@ -2738,6 +2824,9 @@ function renderBotSettings(pane, b) {
2738
2824
  <div class="bot-checks" style="margin-bottom:6px">
2739
2825
  <label><input type="checkbox" id="bExtBrowser" ${b.extBrowser ? 'checked' : ''}/> ${_t('bot_ext_browser')}</label>
2740
2826
  </div>
2827
+ <div class="bot-checks" style="margin-bottom:6px">
2828
+ <label><input type="checkbox" id="bVis" ${b.vis !== false ? 'checked' : ''}/> ${_t('bot_vis')}</label>
2829
+ </div>
2741
2830
  <div class="form-grid">
2742
2831
  <div>
2743
2832
  <label class="mem-label" style="margin-top:0">${_t('bot_def_browser')}</label>
@@ -2798,6 +2887,7 @@ function renderBotSettings(pane, b) {
2798
2887
  prompt: $('#bPrompt').value,
2799
2888
  seeBots: [...f.querySelectorAll('#bSee input:checked')].map((c) => c.dataset.see),
2800
2889
  extBrowser: $('#bExtBrowser').checked,
2890
+ vis: f.querySelector('#bVis') ? f.querySelector('#bVis').checked : true,
2801
2891
  browserDefault: $('#bBrDef').value,
2802
2892
  extCommand: $('#bBrCmd').value.trim(),
2803
2893
  numbers: (b.numbers || []).map((n) => n.num),
@@ -4386,6 +4476,18 @@ async function init() {
4386
4476
  if ($('#botClose')) $('#botClose').addEventListener('click', () => botOverlaySetOpen(false));
4387
4477
  if ($('#botOverviewBack')) $('#botOverviewBack').addEventListener('click', () => openBotPage(null));
4388
4478
  if ($('#botChip')) $('#botChip').addEventListener('click', () => openBotPage(activeBotId));
4479
+
4480
+ /* BOT DAVET MENÜSÜ: butona tıkla → aç/kapa; dışarı tık → kapa */
4481
+ if ($('#guestBotBtn') && $('#guestBotMenu')) {
4482
+ $('#guestBotBtn').addEventListener('click', (e) => {
4483
+ e.stopPropagation();
4484
+ renderGuestBotMenu();
4485
+ $('#guestBotMenu').hidden = !$('#guestBotMenu').hidden;
4486
+ });
4487
+ document.addEventListener('click', (e) => {
4488
+ if (!$('#guestBotMenu').hidden && !$('#guestBotMenu').contains(e.target)) $('#guestBotMenu').hidden = true;
4489
+ });
4490
+ }
4389
4491
  /* kalıcı aktif bot: restart sonrası aynı botun UI'ı açılır */
4390
4492
  beast.botsActiveGet().then((r) => { activeBotId = (r && r.id) || 'beast'; updateBotChip(); renderBotCards(); }).catch(() => {});
4391
4493
  document.querySelectorAll('.btab').forEach((b) =>
@@ -1714,6 +1714,7 @@ body.browser-open #toast { left: calc((100vw - var(--bw, 480px)) / 2); }
1714
1714
  #botList { overflow-y: auto; }
1715
1715
  .bot-card {
1716
1716
  display: flex;
1717
+ flex-wrap: wrap;
1717
1718
  align-items: center;
1718
1719
  gap: 8px;
1719
1720
  padding: 6px 8px;
@@ -1725,6 +1726,17 @@ body.browser-open #toast { left: calc((100vw - var(--bw, 480px)) / 2); }
1725
1726
  .bot-card:hover { background: var(--panel2); color: var(--text); }
1726
1727
  .bot-card.active { background: var(--panel2); color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
1727
1728
  .bot-card .bot-ico { font-size: 16px; line-height: 1; flex-shrink: 0; }
1729
+ /* 5 haneli benzersiz bot kodu — satırda ortalanmış */
1730
+ .bot-card .bot-code {
1731
+ width: 100%;
1732
+ text-align: center;
1733
+ font-family: ui-monospace, Consolas, monospace;
1734
+ font-size: 10.5px;
1735
+ font-weight: 700;
1736
+ letter-spacing: 3px;
1737
+ color: var(--accent);
1738
+ opacity: 0.85;
1739
+ }
1728
1740
  .bot-card .bot-gear {
1729
1741
  visibility: hidden;
1730
1742
  color: var(--muted);