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 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.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 (['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;
@@ -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 };