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.
- package/bin/beast-agent.js +40 -27
- package/package.json +1 -1
- package/src/agent/bots.js +43 -1
- package/src/agent/engine.js +235 -10
- package/src/agent/skills.js +28 -0
- package/src/main.js +70 -147
- package/src/preload.js +5 -0
- package/src/renderer/i18n.js +8 -0
- package/src/renderer/index.html +5 -0
- package/src/renderer/renderer.js +108 -6
- package/src/renderer/style.css +12 -0
package/bin/beast-agent.js
CHANGED
|
@@ -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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if
|
|
84
|
-
|
|
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
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,
|
package/src/agent/engine.js
CHANGED
|
@@ -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:
|
|
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
|
-
'
|
|
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
|
-
'{"
|
|
2487
|
-
'
|
|
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.
|
|
2497
|
-
|
|
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: {
|
package/src/agent/skills.js
CHANGED
|
@@ -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
|
|
1018
|
+
/* /update — sürüm kontrol. TEK DAĞITIM npm: uygulama içi kurulum YOK. */
|
|
1019
1019
|
if (String(arg || '').toLowerCase() === 'now') {
|
|
1020
|
-
|
|
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
|
-
|
|
2311
|
-
|
|
2312
|
-
app.
|
|
2313
|
-
|
|
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
|
-
|
|
2318
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
4078
|
-
|
|
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
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
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(`🔄 *
|
|
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
|
-
|
|
4200
|
-
|
|
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)
|
|
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'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -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',
|
package/src/renderer/index.html
CHANGED
|
@@ -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">➤︎</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>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -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 →
|
|
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="
|
|
1822
|
+
<button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
|
|
1809
1823
|
</div>
|
|
1810
|
-
<div class="sub" style="margin-top:8px">${_t('
|
|
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('
|
|
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) =>
|
package/src/renderer/style.css
CHANGED
|
@@ -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);
|