beast-agent 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ Then start it:
37
37
  beast-agent
38
38
  ```
39
39
 
40
- That's it — the app window opens. On first launch Beast also creates a **desktop shortcut** and registers itself to **start with Windows** (lives in the tray). Later updates: close the app and run `beast-agent update`.
40
+ That's it — the app window opens. On first launch Beast also creates a **desktop shortcut** and registers itself to **start with Windows** (lives in the tray). Later updates: close the app and run `beast update` (or `beast-agent update`).
41
41
 
42
42
  ## ⚙️ Configuration
43
43
 
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- /* Beast Agent global npm başlatıcısı:
5
- `beast-agent` → uygulamayı detached başlatır, terminali hemen serbest bırakır
6
- `beast-agent update` → npm'den en son sürümü yükler (uygulama kapalıyken çalıştır) */
4
+ /* Beast Agent global npm başlatıcısı (`beast` kısa adı da aynı scripte bağlı):
5
+ `beast` / `beast-agent` → uygulamayı detached başlatır, terminali hemen serbest bırakır
6
+ `beast update` → npm'den en son sürümü yükler (uygulama kapalıyken çalıştır) */
7
7
 
8
8
  const { spawn, spawnSync } = require('child_process');
9
9
  const path = require('path');
@@ -62,10 +62,20 @@ if (process.argv[2] === 'update') {
62
62
  } else {
63
63
  try { spawnSync('pkill', ['-f', 'node_modules/beast-agent'], { stdio: 'ignore' }); } catch {}
64
64
  }
65
- const r = spawnSync('npm', ['install', '-g', 'beast-agent@latest'], { stdio: 'inherit', shell: isWin });
66
- if (r.status !== 0) {
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) {
67
77
  console.log('\n\u2717 g\u00FCncelleme ba\u015Far\u0131s\u0131z \u2014 elle: npm install -g beast-agent@latest');
68
- process.exit(r.status || 1);
78
+ process.exit(1);
69
79
  }
70
80
  console.log('\n\u2713 beast-agent g\u00FCncellendi \u2014 uygulama ba\u015Flat\u0131l\u0131yor\u2026');
71
81
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.21.0",
4
+ "version": "0.22.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -25,7 +25,8 @@
25
25
  ],
26
26
  "main": "src/main.js",
27
27
  "bin": {
28
- "beast-agent": "bin/beast-agent.js"
28
+ "beast-agent": "bin/beast-agent.js",
29
+ "beast": "bin/beast-agent.js"
29
30
  },
30
31
  "scripts": {
31
32
  "start": "electron .",
package/src/agent/bots.js CHANGED
@@ -239,9 +239,23 @@ function update(id, patch) {
239
239
  changes.push('prompt güncellendi');
240
240
  b.prompt = String(patch.prompt).slice(0, 4000);
241
241
  }
242
- if (['all', 'web', 'read', 'chat'].includes(patch.perm) && patch.perm !== b.perm) {
243
- changes.push(`perm: ${b.perm} ${patch.perm}`);
244
- b.perm = patch.perm;
242
+ if (patch.perm !== undefined) {
243
+ /* 'all' tek başına tüm araçları verir; web/read/chat çoklu seçilebilir
244
+ (['web','read'] gibi dizi ya da 'web,read' gibi string kabul edilir) */
245
+ const PERMS_ALL = ['all', 'web', 'read', 'chat'];
246
+ let nextPerm = null;
247
+ if (Array.isArray(patch.perm)) {
248
+ const picked = [...new Set(patch.perm.map((x) => String(x).trim()).filter((x) => PERMS_ALL.includes(x)))];
249
+ nextPerm = picked.includes('all') ? 'all' : (picked.length ? picked : 'chat');
250
+ } else if (typeof patch.perm === 'string') {
251
+ const arr = patch.perm.split(',').map((x) => x.trim()).filter((x) => PERMS_ALL.includes(x));
252
+ if (arr.length) nextPerm = arr.includes('all') ? 'all' : (arr.length === 1 ? arr[0] : arr);
253
+ }
254
+ if (nextPerm !== null && JSON.stringify(nextPerm) !== JSON.stringify(b.perm)) {
255
+ const fp = (p) => (Array.isArray(p) ? '[' + p.join('+') + ']' : String(p));
256
+ changes.push(`perm: ${fp(b.perm)} → ${fp(nextPerm)}`);
257
+ b.perm = nextPerm;
258
+ }
245
259
  }
246
260
  if (patch.skills && typeof patch.skills === 'object') {
247
261
  const merged = { ...DEFAULT_SKILLS, ...(b.skills || {}) };
@@ -83,6 +83,16 @@ const PERM_TOOL_SETS = {
83
83
  };
84
84
  const PERM_LEVELS = ['all', 'web', 'read', 'chat'];
85
85
 
86
+ /* İzin değerini normalize eder: 'all' → ['all'], 'web' → ['web'],
87
+ 'web,read' / ['web','read'] → ['web','read'] (sıra PERM_LEVELS'e göre dizilir).
88
+ Geçersiz değerler atılır; hiçbiri kalmazsa boş dizi döner. */
89
+ function normalizePerms(p) {
90
+ const arr = Array.isArray(p) ? p.map(String) : String(p == null ? '' : p).split(',');
91
+ const picked = arr.map((s) => s.trim()).filter((s) => PERM_LEVELS.includes(s));
92
+ if (picked.includes('all')) return ['all'];
93
+ return PERM_LEVELS.filter((k) => picked.includes(k));
94
+ }
95
+
86
96
  /* CEO modu: ana (konuşma) oturumunun KULLANAMAYACAĞI uygulayıcı araçlar.
87
97
  Bunların hepsi run_background ile paralel ajana devredilir — CEO sadece
88
98
  konuşur, planlar, emir verir ve takip eder. */
@@ -190,7 +200,7 @@ class Engine {
190
200
  this.sel = this._resolve(opts.modelOverride) || this.cfg.defaultSelection || null;
191
201
  this.roleModels = opts.roleModels || {}; // { vision?, terminal?, coding?, subagent? } // providerId::model string
192
202
  this.lockdown = !!opts.lockdown; // varsayılan kısıt (oturum bazlı override edilmezse)
193
- this.sessionPerm = new Map(); // sessionId -> 'web'|'read'|'chat' (kişi bazlı izin)
203
+ this.sessionPerm = new Map(); // sessionId -> ['web'] | ['web','read'] | ['chat'] (kişi/bot bazlı izin)
194
204
  this.sessionTools = new Map(); // sessionId -> Set(araç adları) — bot skill kısıtı
195
205
  this.resolveBot = opts.resolveBot || null; // botId -> bot bilgisi (main enjekte eder)
196
206
  /* bot oturumu hafıza köprüsü: botun kendi SOUL/USER/MEMORY dosyaları */
@@ -285,12 +295,13 @@ class Engine {
285
295
  this.lockdown = !!v;
286
296
  }
287
297
 
288
- /* Kişi bazlı granül izin — WhatsApp oturumları için. 'all' kaydı siler. */
298
+ /* Kişi/bot bazlı granül izin — WhatsApp oturumları için.
299
+ Tek seviye ('web') ya da çoklu (['web','read']) verilebilir; 'all' kaydı siler. */
289
300
  setSessionPerm(sessionId, perm) {
290
301
  const id = String(sessionId || '');
291
302
  if (!id) return;
292
- const p = PERM_LEVELS.includes(perm) ? perm : null;
293
- if (p && p !== 'all') this.sessionPerm.set(id, p);
303
+ const arr = normalizePerms(perm);
304
+ if (arr.length && !arr.includes('all')) this.sessionPerm.set(id, arr);
294
305
  else this.sessionPerm.delete(id);
295
306
  }
296
307
 
@@ -347,8 +358,8 @@ class Engine {
347
358
 
348
359
  sessionPermFor(sessionId) {
349
360
  const p = this.sessionPerm.get(String(sessionId || ''));
350
- if (p) return p;
351
- return this.lockdown ? 'chat' : 'all';
361
+ if (p && p.length) return p;
362
+ return this.lockdown ? ['chat'] : ['all'];
352
363
  }
353
364
 
354
365
  setRoleModels(map) {
@@ -929,7 +940,7 @@ class Engine {
929
940
  for (const v of this.listSessions()) {
930
941
  const s = this._load(v.id);
931
942
  if (s && s.notes && String(s.notes).trim()) {
932
- out.push({ id: s.id, code: s.code || '', title: v.title, updatedAt: v.updatedAt, count: v.count, notes: String(s.notes) });
943
+ out.push({ id: s.id, code: s.code || '', title: v.title, updatedAt: v.updatedAt, count: v.count, notes: String(s.notes), botId: s.botId || '' });
933
944
  }
934
945
  }
935
946
  return out;
@@ -2063,9 +2074,14 @@ class Engine {
2063
2074
  : session.bcCode
2064
2075
  ? this.buildBcSystem(session)
2065
2076
  : this.buildSystem(promptText, session);
2066
- // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır
2067
- const perm = this.sessionPermFor(session.id);
2068
- const allowedSet = PERM_TOOL_SETS[perm] || null;
2077
+ // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
2078
+ // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
2079
+ const perms = this.sessionPermFor(session.id);
2080
+ let allowedSet = null;
2081
+ if (!perms.includes('all')) {
2082
+ allowedSet = new Set();
2083
+ for (const p of perms) for (const t of PERM_TOOL_SETS[p] || []) allowedSet.add(t);
2084
+ }
2069
2085
  let activeTools = allowedSet ? toolsList.filter((t) => allowedSet.has(t.function.name)) : toolsList;
2070
2086
  /* bot skill kısıtı: bota verilen yetkiye göre araç seti daraltılır */
2071
2087
  const toolLimit = this.sessionTools.get(String(session.id));
@@ -2077,17 +2093,16 @@ class Engine {
2077
2093
  /* CEO: uygulayıcı araçlar kapalı — her şey paralel ajana devredilir */
2078
2094
  activeTools = activeTools.filter((t) => !CEO_EXEC_TOOLS.has(t.function.name));
2079
2095
  }
2080
- if (perm === 'chat') {
2096
+ if (perms.length === 1 && perms[0] === 'chat') {
2081
2097
  system +=
2082
2098
  '\n\n# KISITLI MOD\nTüm araçların (komut, dosya, web, tarayıcı, hafıza) kapalı. Sadece yazarak cevap ver. ' +
2083
2099
  'Bilgisayarla ilgili bir işlem istenirse bu modda yapamayacağını kibarca söyle.';
2084
- } else if (perm === 'web') {
2085
- system +=
2086
- '\n\n# SINIRLI YETKİ (web)\nSadece web ve tarayıcı araçlarına erişimin var; dosya okuma/yazma ve komut çalıştırma YOK. ' +
2087
- 'Böyle bir istek gelirse yetkin olmadığını söyle.';
2088
- } else if (perm === 'read') {
2100
+ } else if (!perms.includes('all')) {
2101
+ const bits = [];
2102
+ if (perms.includes('web')) bits.push('web ve tarayıcı araçlarına erişimin var');
2103
+ if (perms.includes('read')) bits.push('bilgisayarı SADECE OKUYABİLİRSİN (klasör listeleme, dosya okuma) + web/tarayıcı');
2089
2104
  system +=
2090
- '\n\n# SINIRLI YETKİ (salt-okunur)\nBilgisayarı SADECE OKUYABİLİRSİN (klasör listeleme, dosya okuma) + web/tarayıcı. ' +
2105
+ '\n\n# SINIRLI YETKİ\n' + bits.join('; ') + '. ' +
2091
2106
  'Dosya yazma, silme ve komut çalıştırma YOK; istenirse yapamayacağını söyle.';
2092
2107
  }
2093
2108
  /* bot kimliği: bota bağlı oturumlarda kişilik + izolasyon kuralları */
@@ -3484,3 +3499,6 @@ module.exports.sanitizeTodoItems = sanitizeTodoItems;
3484
3499
  module.exports.parseReflectionJson = parseReflectionJson;
3485
3500
  module.exports.CEO_EXEC_TOOLS = CEO_EXEC_TOOLS;
3486
3501
  module.exports.BG_HIDDEN_TOOLS = BG_HIDDEN_TOOLS;
3502
+ module.exports.PERM_TOOL_SETS = PERM_TOOL_SETS;
3503
+ module.exports.PERM_LEVELS = PERM_LEVELS;
3504
+ module.exports.normalizePerms = normalizePerms;
package/src/main.js CHANGED
@@ -193,6 +193,7 @@ const BUILTIN_PROVIDERS = [
193
193
  { id: 'gemini', name: 'Google Gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', hint: 'aistudio.google.com/apikey' },
194
194
  { id: 'zhipu', name: 'Zhipu AI', baseUrl: 'https://api.z.ai/api/paas/v4', hint: 'z.ai model konsolu' },
195
195
  { id: 'groq', name: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', hint: 'console.groq.com/keys' },
196
+ { id: 'nvidia', name: 'NVIDIA NIM', baseUrl: 'https://integrate.api.nvidia.com/v1', hint: 'build.nvidia.com — ücretsiz kredi veriyor, talep yüksek' },
196
197
  ];
197
198
  const SESSIONS_DIR = path.join(APP_DIR, 'sessions');
198
199
  const SETTINGS_FILE = path.join(APP_DIR, 'settings.json');
@@ -1333,13 +1334,37 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
1333
1334
  /* ---------------- BOT SİSTEMİ yardımcıları ----------------
1334
1335
  Bot eşleştirme, izolasyon, whitelist.json aynası ve bot istatistikleri. */
1335
1336
 
1336
- const PERM_RANK = { all: 3, web: 2, read: 1, chat: 0 }; // küçük = kısıtlı
1337
+ /* İzin değerini araç kümesine çevirir; null = tüm araçlar ('all').
1338
+ 'web' / ['web','read'] / 'web,read' biçimlerini kabul eder. */
1339
+ function permToToolSet(p) {
1340
+ const { PERM_TOOL_SETS } = require('./agent/engine');
1341
+ const set = new Set();
1342
+ for (const raw of Array.isArray(p) ? p : String(p == null ? 'all' : p).split(',')) {
1343
+ const k = String(raw).trim() || 'all';
1344
+ if (k === 'all' || !PERM_TOOL_SETS[k]) return null;
1345
+ for (const t of PERM_TOOL_SETS[k]) set.add(t);
1346
+ }
1347
+ return set;
1348
+ }
1337
1349
 
1350
+ /* Hangi izin daha kısıtlıysa o kazanır. Dizi (çoklu bot izni) destekler:
1351
+ araç kümesi diğerinin alt kümesiyse o geçer; ikisi de alt küme değilse
1352
+ daha küçük küme kazanır (eşitse kişi yetkisi). */
1338
1353
  function moreRestrictivePerm(a, b) {
1339
- const ra = PERM_RANK[a] ?? 3;
1340
- const rb = PERM_RANK[b] ?? 3;
1341
- const keys = Object.keys(PERM_RANK);
1342
- return keys.find((k) => PERM_RANK[k] === Math.min(ra, rb)) || 'all';
1354
+ const sa = permToToolSet(a);
1355
+ const sb = permToToolSet(b);
1356
+ if (sa === null && sb === null) return 'all';
1357
+ if (sa === null) return b; // b kısıtlı
1358
+ if (sb === null) return a; // a kısıtlı
1359
+ const aSubB = [...sa].every((t) => sb.has(t));
1360
+ const bSubA = [...sb].every((t) => sa.has(t));
1361
+ if (aSubB && !bSubA) return a;
1362
+ if (bSubA && !aSubB) return b;
1363
+ return sa.size <= sb.size ? a : b;
1364
+ }
1365
+
1366
+ function fmtPerm(p) {
1367
+ return Array.isArray(p) ? '[' + p.join('+') + ']' : String(p);
1343
1368
  }
1344
1369
 
1345
1370
  /* Bot skill checkbox'ları → oturumun görebileceği araç adları.
@@ -1623,7 +1648,7 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
1623
1648
  } else {
1624
1649
  engine.setSessionTools(sid, null);
1625
1650
  }
1626
- waLog(`perm=${perm} bot=${botId} sid=${sid}`);
1651
+ waLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}`);
1627
1652
 
1628
1653
  const participantName = payload.participant ? '+' + String(payload.participant).split('@')[0].split(':')[0] : '';
1629
1654
  /* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin */
@@ -3585,24 +3610,82 @@ ipcMain.handle('update:check', async () => {
3585
3610
  });
3586
3611
 
3587
3612
  /* npm kurulumunda KENDİ KENDİNİ GÜNCELLEME:
3588
- detached helper bırakır (uygulama çıkınca npm install + yeniden başlatma), sonra app.quit() */
3613
+ helper script %APPDATA%\beast'a yazılır (paket dizini değişse de yaşar) ve
3614
+ detached çalışır: 1) uygulama PID'i tamamen çıkana kadar bekler (en çok 30 sn,
3615
+ sonra zorla kapatır — EBUSY dosya kilidinin numarası), 2) npm install -g
3616
+ beast-agent@latest — kilide karşı 5 deneme, 3) tüm çıktı update.log'a yazılır,
3617
+ 4) beast-agent komut shim'i üzerinden yeniden başlatır (electron sürümü
3618
+ değişse de yol bozulmaz). Sonra app.quit(). */
3589
3619
  function npmSelfUpdate() {
3590
3620
  try {
3591
- const electronExe = process.execPath;
3592
- const appPath = app.getAppPath();
3621
+ fs.mkdirSync(APP_DIR, { recursive: true });
3622
+ const pid = String(process.pid);
3593
3623
  if (process.platform === 'win32') {
3594
- const ps =
3595
- 'Start-Sleep -Seconds 3;' +
3596
- 'npm install -g beast-agent@latest;' +
3597
- 'Start-Sleep -Seconds 1;' +
3598
- `Start-Process -FilePath '${electronExe}' -ArgumentList '\"${appPath}\"'`;
3599
- spawn('powershell.exe', ['-NoProfile', '-Command', ps], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
3624
+ const ps = [
3625
+ "param([int]$ProcId = 0)",
3626
+ "$ErrorActionPreference = 'Continue'",
3627
+ "$Log = Join-Path $env:APPDATA 'beast\\update.log'",
3628
+ "function L([string]$m) { try { Add-Content -LiteralPath $Log -Value (\"[\" + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + \"] \" + $m) } catch {} }",
3629
+ "L \"=== self-update basladi (app pid: $ProcId) ===\"",
3630
+ "if ($ProcId -gt 0) {",
3631
+ " $deadline = (Get-Date).AddSeconds(30)",
3632
+ " while ((Get-Date) -lt $deadline) {",
3633
+ " if (-not (Get-Process -Id $ProcId -ErrorAction SilentlyContinue)) { break }",
3634
+ " Start-Sleep -Milliseconds 500",
3635
+ " }",
3636
+ " if (Get-Process -Id $ProcId -ErrorAction SilentlyContinue) {",
3637
+ " L 'uygulama hala acik - zorla kapatiliyor'",
3638
+ " try { Stop-Process -Id $ProcId -Force } catch {}",
3639
+ " Start-Sleep -Seconds 2",
3640
+ " }",
3641
+ "}",
3642
+ "L 'uygulama kapandi, npm install basliyor'",
3643
+ "$npmCmd = (Get-Command 'npm.cmd' -ErrorAction SilentlyContinue).Source",
3644
+ "if (-not $npmCmd) { $npmCmd = Join-Path $env:APPDATA 'npm\\npm.cmd' }",
3645
+ "$ok = $false",
3646
+ "for ($i = 1; $i -le 5; $i++) {",
3647
+ " if ($ok) { break }",
3648
+ " L \"npm install -g beast-agent@latest (deneme $i)\"",
3649
+ " $out = & $npmCmd install -g beast-agent@latest 2>&1",
3650
+ " $code = $LASTEXITCODE",
3651
+ " foreach ($line in @($out)) { L \" npm: $line\" }",
3652
+ " if ($code -eq 0) { $ok = $true } else { Start-Sleep -Seconds 3 }",
3653
+ "}",
3654
+ "if (-not $ok) {",
3655
+ " L 'HATA: npm install 5 denemede basarisiz - uygulama yeniden baslatilmiyor'",
3656
+ " exit 1",
3657
+ "}",
3658
+ "L 'npm install tamam, yeniden baslatma'",
3659
+ "$shim = Get-Command 'beast-agent.cmd' -ErrorAction SilentlyContinue",
3660
+ "if ($shim) {",
3661
+ " L \"shim uzerinden: $($shim.Source)\"",
3662
+ " Start-Process -FilePath $shim.Source -WindowStyle Hidden",
3663
+ "} else {",
3664
+ " $prefix = (& $npmCmd prefix -g 2>$null)",
3665
+ " if (-not $prefix) { $prefix = Join-Path $env:APPDATA 'npm' }",
3666
+ " $exe = Join-Path $prefix 'node_modules\\electron\\dist\\electron.exe'",
3667
+ " if (-not (Test-Path $exe)) { $exe = Join-Path $prefix 'node_modules\\beast-agent\\node_modules\\electron\\dist\\electron.exe' }",
3668
+ " $appDir = Join-Path $prefix 'node_modules\\beast-agent'",
3669
+ " L \"shim bulunamadi - dogrudan: $exe\"",
3670
+ " Start-Process -FilePath $exe -ArgumentList \"`\"$appDir`\"\" -WindowStyle Hidden",
3671
+ "}",
3672
+ "L '=== self-update bitti ==='",
3673
+ ].join('\r\n');
3674
+ const psFile = path.join(APP_DIR, 'update-helper.ps1');
3675
+ fs.writeFileSync(psFile, ps, 'utf8');
3676
+ spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psFile, '-ProcId', pid],
3677
+ { detached: true, stdio: 'ignore', windowsHide: true }).unref();
3600
3678
  } else {
3601
- const sh = `sleep 3; npm install -g beast-agent@latest; sleep 1; '${electronExe}' '${appPath}' &`;
3679
+ const sh =
3680
+ `i=0; while [ $i -lt 60 ] && kill -0 ${pid} 2>/dev/null; do i=$((i+1)); sleep 0.5; done; ` +
3681
+ 'ok=0; for n in 1 2 3 4 5; do npm install -g beast-agent@latest && ok=1 && break; sleep 3; done; ' +
3682
+ 'if [ $ok -eq 1 ]; then nohup beast-agent >/dev/null 2>&1 & fi';
3602
3683
  spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' }).unref();
3603
3684
  }
3604
3685
  log.info('main', 'npm self-update: helper bırakıldı, uygulama kapatılıyor');
3605
- } catch {}
3686
+ } catch (e) {
3687
+ log.error('main', 'npm self-update hatası: ' + String((e && e.message) || e));
3688
+ }
3606
3689
  setTimeout(() => { try { app.quit(); } catch {} }, 400);
3607
3690
  }
3608
3691
 
@@ -73,7 +73,8 @@
73
73
  up_downloading: 'İndiriliyor…',
74
74
  up_downloaded: 'İndirildi — kurulum için hazır',
75
75
  up_npm_mode: 'npm kurulumu — güncellemeler otomatik kontrol edilir',
76
- up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast-agent update',
76
+ up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast update',
77
+ up_npm_started: 'Güncelleme başladı — uygulama kapanacak, en son sürüm kurulup kendiliğinden açılacak…',
77
78
  up_dev_note: 'Geliştirme modu — güncelleme buradan yapılmaz; dağıtım npm ve GitHub Releases üzerinden. Kaynak kod için: git pull',
78
79
  up_auto_check: 'Otomatik sürüm kontrolü (açılış + 6 saatte bir)',
79
80
  up_auto_dl: 'Yeni sürümü otomatik indir (kapanışta kurulur)',
@@ -374,6 +375,7 @@
374
375
  bot_tab_plugin: 'Plugin',
375
376
  bot_tab_watcher: 'Watcher',
376
377
  bot_tab_stats: 'İstatistik',
378
+ bot_tab_notes: 'Notlar',
377
379
  bot_name: 'Bot adı',
378
380
  bot_icon: 'İkon',
379
381
  bot_prompt: 'System prompt (kişilik/görev)',
@@ -396,6 +398,7 @@
396
398
  bot_see: 'Görebilir (diğer botlar)',
397
399
  bot_no_other: 'Başka bot yok.',
398
400
  bot_perm: 'Yetki seviyesi',
401
+ bot_perm_note: 'all tek başına tüm araçları açar (özel). Web / Okuma / Sohbet birden fazla seçilebilir — seçilenlerin araçları birleşir.',
399
402
  bot_skills: 'Skill erişimi',
400
403
  bot_browser: 'Tarayıcı ayarları',
401
404
  bot_ext_browser: 'Dış tarayıcı yetkisi (kullanıcı isterse)',
@@ -571,7 +574,8 @@
571
574
  up_downloading: 'Downloading…',
572
575
  up_downloaded: 'Downloaded — ready to install',
573
576
  up_npm_mode: 'npm install — updates are checked automatically',
574
- up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast-agent update',
577
+ up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast update',
578
+ up_npm_started: 'Update started — the app will close, install the latest version and relaunch…',
575
579
  up_dev_note: 'Development mode — updates are distributed via npm and GitHub Releases. For source code: git pull',
576
580
  up_auto_check: 'Automatic version check (on startup + every 6 hours)',
577
581
  up_auto_dl: 'Auto-download new versions (installed on quit)',
@@ -930,6 +934,7 @@
930
934
  bot_tab_plugin: 'Plugin',
931
935
  bot_tab_watcher: 'Watcher',
932
936
  bot_tab_stats: 'Stats',
937
+ bot_tab_notes: 'Notes',
933
938
  bot_name: 'Bot name',
934
939
  bot_icon: 'Icon',
935
940
  bot_prompt: 'System prompt (personality/task)',
@@ -952,6 +957,7 @@
952
957
  bot_see: 'Can see (other bots)',
953
958
  bot_no_other: 'No other bots.',
954
959
  bot_perm: 'Permission level',
960
+ bot_perm_note: 'all alone grants every tool (exclusive). Web / Read / Chat can be multi-selected — their tool sets are combined.',
955
961
  bot_skills: 'Skill access',
956
962
  bot_browser: 'Browser settings',
957
963
  bot_ext_browser: 'External browser permission (when user asks)',
@@ -240,6 +240,7 @@
240
240
  <button class="btab" data-btab="log" data-i18n="bot_tab_log">Log</button>
241
241
  <button class="btab" data-btab="watcher" data-i18n="bot_tab_watcher">Watcher</button>
242
242
  <button class="btab" data-btab="stats" data-i18n="bot_tab_stats">İstatistik</button>
243
+ <button class="btab" data-btab="notes" data-i18n="bot_tab_notes">Notlar</button>
243
244
  </nav>
244
245
  <div id="botPane"></div>
245
246
  </div>
@@ -1719,7 +1719,8 @@ async function renderUpdatePane(autoCheck) {
1719
1719
  const btnInstall = pane.querySelector('#upInstall');
1720
1720
  if (btnInstall) btnInstall.addEventListener('click', async () => {
1721
1721
  const r = await beast.updateInstall().catch(() => ({ ok: false, error: 'ipc' }));
1722
- if (!r.ok && r.error) toast(r.error);
1722
+ if (r.ok && r.npm) toast(_t('up_npm_started'));
1723
+ else if (!r.ok && r.error) toast(r.error);
1723
1724
  });
1724
1725
 
1725
1726
  /* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
@@ -2224,9 +2225,44 @@ function renderBotPage() {
2224
2225
  else if (tab === 'log') renderBotLog(pane, b);
2225
2226
  else if (tab === 'watcher') renderBotWatcher(pane, b);
2226
2227
  else if (tab === 'stats') renderBotStats(pane, b);
2228
+ else if (tab === 'notes') renderBotNotes(pane, b);
2227
2229
  renderBotChats(b);
2228
2230
  }
2229
2231
 
2232
+ /* --- Notlar sekmesi: oturum notları — konuşma kodu, başlık, tarih ve özet metni.
2233
+ Admin bot (beast) tüm oturumları görür; müşteri botu yalnız kendi oturumlarını. --- */
2234
+ async function renderBotNotes(pane, b) {
2235
+ pane.innerHTML = `<h2>${_t('notes_h2')}</h2><div class="sub">${_t('notes_sub')}</div>`;
2236
+ let all = [];
2237
+ try { all = await beast.listNotes(); } catch {}
2238
+ const list = (b.admin ? all : all.filter((n) => (n.botId || 'beast') === b.id))
2239
+ .slice()
2240
+ .sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
2241
+ if (!list.length) {
2242
+ pane.insertAdjacentHTML('beforeend', `<div class="mini-empty">${_t('notes_empty')}</div>`);
2243
+ return;
2244
+ }
2245
+ const f = document.createElement('div');
2246
+ f.className = 'bot-form';
2247
+ for (const n of list) {
2248
+ const when = n.updatedAt ? new Date(n.updatedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
2249
+ const card = document.createElement('div');
2250
+ card.className = 'notes-card';
2251
+ card.innerHTML =
2252
+ `<div class="notes-head"><div class="skill-name">${escapeHtml((b.icon || '') + ' ' + (n.code || '?'))} — ${escapeHtml(n.title || '')}</div>` +
2253
+ `<div class="notes-meta">${escapeHtml(when)} · ${n.count || 0} ${_t('bot_stat_msgs')}</div></div>` +
2254
+ `<div class="notes-body">${escapeHtml(String(n.notes || '').slice(0, 4000))}</div>` +
2255
+ `<div style="margin-top:8px"><button class="btn ghost notes-del">${_t('notes_del')}</button></div>`;
2256
+ card.querySelector('.notes-del').addEventListener('click', async () => {
2257
+ await beast.clearNotes(n.id).catch(() => {});
2258
+ toast(_t('deleted'));
2259
+ renderBotNotes(pane, b);
2260
+ });
2261
+ f.appendChild(card);
2262
+ }
2263
+ pane.appendChild(f);
2264
+ }
2265
+
2230
2266
  function renderBotOverview(pane) {
2231
2267
  pane.innerHTML = `<h2>${_t('bot_overview')}</h2><div class="sub">${_t('bot_overview_sub')}</div>`;
2232
2268
  const totals = { numbers: 0, sessions: 0, msgs: 0 };
@@ -2313,6 +2349,12 @@ function renderBotSettings(pane, b) {
2313
2349
  const skillChecks = BOT_SKILLS
2314
2350
  .map(([k, lbl]) => `<label><input type="checkbox" data-skill="${k}" ${(b.skills || {})[k] ? 'checked' : ''}/> ${lbl}</label>`)
2315
2351
  .join('');
2352
+ /* Yetki: 'all' tek başına tüm araçları verir (özel); web/read/chat çoklu seçilebilir */
2353
+ const PERM_OPTS = [['all', _t('wa_perm_all')], ['web', 'Web'], ['read', _t('wa_perm_read')], ['chat', _t('wa_perm_chat')]];
2354
+ const curPerms = Array.isArray(b.perm) ? b.perm : [b.perm || 'all'];
2355
+ const permChecks = PERM_OPTS
2356
+ .map(([k, lbl]) => `<label><input type="checkbox" data-perm="${k}" ${curPerms.includes(k) ? 'checked' : ''}/> ${lbl}</label>`)
2357
+ .join('');
2316
2358
  const numRows = (b.numbers || [])
2317
2359
  .map((n) => `<div class="bot-num-row" data-num="${n.num}"><span class="n">+${escapeHtml(n.num)}${n.name ? ' · ' + escapeHtml(n.name) : ''}</span><span class="x" title="${_t('bot_num_del')}">×</span></div>`)
2318
2360
  .join('');
@@ -2334,12 +2376,8 @@ function renderBotSettings(pane, b) {
2334
2376
  <div class="bot-checks" id="bSee">${seeChecks}</div>
2335
2377
  ${b.admin ? '' : `
2336
2378
  <label class="mem-label">${_t('bot_perm')}</label>
2337
- <select id="bPerm" class="inp">
2338
- <option value="all" ${b.perm === 'all' ? 'selected' : ''}>${_t('wa_perm_all')}</option>
2339
- <option value="web" ${b.perm === 'web' ? 'selected' : ''}>web</option>
2340
- <option value="read" ${b.perm === 'read' ? 'selected' : ''}>${_t('wa_perm_read')}</option>
2341
- <option value="chat" ${b.perm === 'chat' ? 'selected' : ''}>${_t('wa_perm_chat')}</option>
2342
- </select>
2379
+ <div class="bot-checks" id="bPerm">${permChecks}</div>
2380
+ <div class="sub" style="margin-top:4px">${_t('bot_perm_note')}</div>
2343
2381
  <label class="mem-label">${_t('bot_skills')}</label>
2344
2382
  <div class="bot-checks" id="bSkills">${skillChecks}</div>`}
2345
2383
  <label class="mem-label">${_t('bot_browser')}</label>
@@ -2387,6 +2425,18 @@ function renderBotSettings(pane, b) {
2387
2425
  if (bb) renderBotSettings(pane, bb);
2388
2426
  })
2389
2427
  );
2428
+ /* 'all' hariç diğer izinler çoklu seçilebilir; 'all' işaretlenince diğerleri kapanır */
2429
+ f.querySelectorAll('#bPerm input').forEach((c) =>
2430
+ c.addEventListener('change', () => {
2431
+ if (c.dataset.perm === 'all' && c.checked) {
2432
+ f.querySelectorAll('#bPerm input').forEach((x) => { if (x !== c) x.checked = false; });
2433
+ } else if (c.checked) {
2434
+ const all = f.querySelector('#bPerm input[data-perm="all"]');
2435
+ if (all) all.checked = false;
2436
+ }
2437
+ if (![...f.querySelectorAll('#bPerm input')].some((x) => x.checked)) c.checked = true; // en az biri seçili kalsın
2438
+ })
2439
+ );
2390
2440
  $('#bSave').addEventListener('click', async () => {
2391
2441
  const patch = {
2392
2442
  name: $('#bName').value.trim(),
@@ -2399,7 +2449,8 @@ function renderBotSettings(pane, b) {
2399
2449
  numbers: (b.numbers || []).map((n) => n.num),
2400
2450
  };
2401
2451
  if (!b.admin) {
2402
- patch.perm = $('#bPerm').value;
2452
+ const selPerms = [...f.querySelectorAll('#bPerm input:checked')].map((c) => c.dataset.perm);
2453
+ patch.perm = selPerms.includes('all') ? 'all' : selPerms;
2403
2454
  const sk = {};
2404
2455
  f.querySelectorAll('#bSkills input').forEach((c) => { sk[c.dataset.skill] = c.checked; });
2405
2456
  patch.skills = sk;