beast-agent 0.21.0 → 0.23.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 +1 -1
- package/bin/beast-agent.js +16 -6
- package/package.json +3 -2
- package/src/agent/bots.js +17 -3
- package/src/agent/engine.js +35 -17
- package/src/agent/telegram.js +155 -0
- package/src/main.js +554 -17
- package/src/preload.js +8 -0
- package/src/renderer/i18n.js +34 -2
- package/src/renderer/index.html +1 -0
- package/src/renderer/renderer.js +370 -14
- package/src/renderer/style.css +28 -0
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
|
|
package/bin/beast-agent.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
/* Beast Agent global npm
|
|
5
|
-
`beast-agent`
|
|
6
|
-
`beast
|
|
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
|
-
|
|
66
|
-
|
|
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(
|
|
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.
|
|
4
|
+
"version": "0.23.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 (
|
|
243
|
-
|
|
244
|
-
|
|
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 || {}) };
|
package/src/agent/engine.js
CHANGED
|
@@ -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.
|
|
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
|
|
293
|
-
if (
|
|
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
|
-
|
|
2068
|
-
const
|
|
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 (
|
|
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 (
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
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
|
|
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;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* TELEGRAM KÖPRÜSÜ (FEATURE 3)
|
|
4
|
+
WhatsApp köprüsünün Telegram hali — bağımlılık yok (saf Node https):
|
|
5
|
+
- Bot API long polling (getUpdates) ile gelen mesajlar onIncoming'e düşer
|
|
6
|
+
- send(chatId, text) cevap döner; 4096 karakter sınırında bölerek gönderir
|
|
7
|
+
- Aynı allow list mantığı: main tarafındaki tgFind() listesindeki kişilere cevap verir */
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
|
|
11
|
+
const API_BASE = 'https://api.telegram.org/bot';
|
|
12
|
+
const SEND_CHUNK = 3800; // Telegram mesaj sınırı 4096 — güvenli pay
|
|
13
|
+
|
|
14
|
+
class TelegramBridge {
|
|
15
|
+
constructor({ token, emit, onIncoming }) {
|
|
16
|
+
this.token = String(token || '').trim();
|
|
17
|
+
this.emit = emit || (() => {});
|
|
18
|
+
this.onIncoming = onIncoming || null;
|
|
19
|
+
this.connected = false;
|
|
20
|
+
this.stopping = false;
|
|
21
|
+
this.status = 'disconnected';
|
|
22
|
+
this.offset = 0;
|
|
23
|
+
this.me = null;
|
|
24
|
+
this._req = null; // aktif long-poll isteği (iptal için)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_setStatus(status, user) {
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.connected = status === 'connected';
|
|
30
|
+
this.emit({ type: 'status', status, user: user || null });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Bot API çağrısı — JSON POST, promise sarmalı */
|
|
34
|
+
api(method, body = {}, opts = {}) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const payload = JSON.stringify(body);
|
|
37
|
+
const req = https.request(
|
|
38
|
+
`${API_BASE}${this.token}/${method}`,
|
|
39
|
+
{
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
42
|
+
timeout: opts.timeout || 15000,
|
|
43
|
+
},
|
|
44
|
+
(res) => {
|
|
45
|
+
let data = '';
|
|
46
|
+
res.setEncoding('utf8');
|
|
47
|
+
res.on('data', (c) => { data += c; });
|
|
48
|
+
res.on('end', () => {
|
|
49
|
+
try {
|
|
50
|
+
const j = JSON.parse(data || '{}');
|
|
51
|
+
if (j.ok) resolve(j.result);
|
|
52
|
+
else reject(new Error(`telegram ${method}: ${j.description || 'hata ' + res.statusCode}`));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
reject(new Error(`telegram ${method}: bozuk yanıt`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
this._req = req;
|
|
62
|
+
req.write(payload);
|
|
63
|
+
req.end();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async start() {
|
|
68
|
+
if (!this.token) {
|
|
69
|
+
this._setStatus('error');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
this.stopping = false;
|
|
73
|
+
this.offset = 0;
|
|
74
|
+
this._setStatus('connecting');
|
|
75
|
+
try {
|
|
76
|
+
const me = await this.api('getMe', {});
|
|
77
|
+
this.me = me;
|
|
78
|
+
this._setStatus('connected', '@' + (me.username || me.first_name || 'bot'));
|
|
79
|
+
} catch (e) {
|
|
80
|
+
this._setStatus('error');
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
this._pollLoop().catch(() => {});
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* long polling döngüsü — bağlantı koparsa 3 sn bekleyip devam */
|
|
88
|
+
async _pollLoop() {
|
|
89
|
+
while (!this.stopping) {
|
|
90
|
+
try {
|
|
91
|
+
const updates = await this.api(
|
|
92
|
+
'getUpdates',
|
|
93
|
+
{ timeout: 25, offset: this.offset, allowed_updates: ['message'] },
|
|
94
|
+
{ timeout: 35000 }
|
|
95
|
+
);
|
|
96
|
+
if (this.stopping) break;
|
|
97
|
+
for (const u of Array.isArray(updates) ? updates : []) {
|
|
98
|
+
this.offset = Math.max(this.offset, (u.update_id || 0) + 1);
|
|
99
|
+
const msg = u.message;
|
|
100
|
+
if (!msg || !msg.text || !msg.from || msg.from.is_bot) continue;
|
|
101
|
+
const chatId = msg.chat && msg.chat.id;
|
|
102
|
+
if (chatId === undefined || chatId === null) continue;
|
|
103
|
+
const payload = {
|
|
104
|
+
text: String(msg.text).slice(0, 6000),
|
|
105
|
+
senderId: String(msg.from.id || ''),
|
|
106
|
+
username: String(msg.from.username || ''),
|
|
107
|
+
senderName: String(msg.from.first_name || msg.from.username || ''),
|
|
108
|
+
isGroup: !!(msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')),
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
if (this.onIncoming) this.onIncoming(String(chatId), payload);
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (this.stopping) break;
|
|
116
|
+
this.emit({ type: 'poll-error', error: String((e && e.message) || e) });
|
|
117
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async stop() {
|
|
123
|
+
this.stopping = true;
|
|
124
|
+
try { if (this._req) this._req.destroy(new Error('stop')); } catch {}
|
|
125
|
+
this._req = null;
|
|
126
|
+
this.connected = false;
|
|
127
|
+
this.status = 'disconnected';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
snapshot() {
|
|
131
|
+
return {
|
|
132
|
+
status: this.status,
|
|
133
|
+
user: this.me ? '@' + (this.me.username || this.me.first_name || 'bot') : null,
|
|
134
|
+
connected: this.connected,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Metin gönder — 4096 sınırı için parçalara böl */
|
|
139
|
+
async send(chatId, text) {
|
|
140
|
+
const t = String(text || '');
|
|
141
|
+
if (!t.trim()) return false;
|
|
142
|
+
const chunks = [];
|
|
143
|
+
for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
|
|
144
|
+
for (const part of chunks) {
|
|
145
|
+
await this.api('sendMessage', {
|
|
146
|
+
chat_id: chatId,
|
|
147
|
+
text: part,
|
|
148
|
+
disable_web_page_preview: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { TelegramBridge };
|