beast-agent 2.11.0 → 2.13.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.11.0",
4
+ "version": "2.13.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -1480,6 +1480,9 @@ class Engine {
1480
1480
  'Sen BEAST CODE\u2019sun — IDE panelinde çalışan, OpenCode disiplinli hızlı bir kodlama ajanı. VS Code gibi çevik ol.\n' +
1481
1481
  'GENEL AMAÇLI KODLAMA AJANISIN — tek bir proje türüne sınırlı değilsin: web, backend/API, mobil, masaüstü, CLI, kütüphane, script, oyun, veri, DevOps, gömülü… İstenen neyse o. İstenmeyen şeyi (ör. site değilken site) kendiliğinden ÜRETME.\n' +
1482
1482
  `Çalışma klasörü: ${(session && session.workspace) || this.workspace}\n` +
1483
+ ((session && session.sbSandbox)
1484
+ ? 'BEAST SANDBOX MODUNDASIN: GitHub\u2019dan indirilmiş AÇIK KAYNAK bir repo klasöründesin (Beast-Sandbox). Bu kodun sahibi değilsin — hazır bir dış proje; repo sahibinin konvansiyonlarına uy, gereksiz yeniden biçimlendirme yapma. İşe README + paket bildirim dosyasıyla başla (list_dir/glob); bağımlılıklar kurulu değilse önce kurulum komutunu çalıştır (npm install vb.). UZUN SÜRELİ sunucu/süreç (npm start, npm run dev, python app.py vb.) run_command ile ASLA başlatma — tur kilitlenir; kullanıcı çalıştırmak isterse panel_run aracıyla ÇALIŞTIR paneline bırakın (çıktı orada canlı akar, ■ ile durur) ve adresi kullanıcıya bildir. panel_run yalnız BİR süreç çalıştırır.\n'
1485
+ : '') +
1483
1486
  `Yerel zaman: ${localDate} ${localTime}\n` +
1484
1487
  `${modeBlock}\n` +
1485
1488
  'STACK TESPİTİ: işe başlarken workspace\u2019i tanı — package.json, go.mod, Cargo.toml, requirements.txt/pyproject.toml, pom.xml/build.gradle, *.csproj, pubspec.yaml, Gemfile, composer.json vb. işaret dosyalarına bak (list_dir + glob). Dili, framework\u2019ü, konvansiyonları ve build/test/run komutlarını PROJE belirler; her işte aynı teknolojiye itme, mevcut stack\u2019e uy.\n' +
@@ -3106,6 +3109,11 @@ class Engine {
3106
3109
  if (!session || !session.bcCode) toolsList = await mcp.mergeTools(toolsList);
3107
3110
  /* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
3108
3111
  toolsList = apps.mergeTools(toolsList);
3112
+ /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3113
+ modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3114
+ if (!(session && session.sbSandbox)) {
3115
+ toolsList = toolsList.filter((t) => !(t && t.function && t.function.name === 'panel_run'));
3116
+ }
3109
3117
  /* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
3110
3118
  const adef = this._agentDefFor(session, !!session.bgJob);
3111
3119
  /* Beast Code: todo_write açıklaması "3+ adım" kısıtı içerir ve model küçük
@@ -3521,6 +3529,16 @@ class Engine {
3521
3529
  else if (/\b(npm (run )?(dev|start)|npx serve|live-server)\b/i.test(cmdStr)) port = 3000;
3522
3530
  if (port) bcArtifacts.serverUrl = 'http://localhost:' + port;
3523
3531
  }
3532
+ /* Expo dev server: exp://<LAN-IP>:<port> çıktıdan yakalanır —
3533
+ telefon/Expo Go için QR renderer'a canlı iletilir */
3534
+ const xm = /exp:\/\/[^\s'"<>]+/i.exec(hay);
3535
+ if (xm) {
3536
+ const eurl = xm[0].replace(/[)\].,;:'"]+$/, '');
3537
+ if (bcArtifacts.expUrl !== eurl) {
3538
+ bcArtifacts.expUrl = eurl;
3539
+ emit({ type: 'bc-expurl', url: eurl });
3540
+ }
3541
+ }
3524
3542
  }
3525
3543
  } catch {}
3526
3544
  }
@@ -4229,6 +4247,22 @@ const skills = require('./skills');
4229
4247
  }
4230
4248
  /* GERİ ALMA GÜNLÜĞÜ: dosya yazımından ÖNCE eski içerik kayda geçer */
4231
4249
  this._journalBefore(sessionId, name, args);
4250
+ /* PANEL RUN: uzun süreli süreç ÇALIŞTIR paneline devredilir — tur
4251
+ BLOKLAMAZ (main'deki yönetilen süreç koşucusuna köprü). */
4252
+ if (name === 'panel_run') {
4253
+ const cmd = String((args && args.command) || '').trim();
4254
+ if (!cmd) return JSON.stringify({ ok: false, error: 'komut boş' });
4255
+ if (typeof this.sbRunHook !== 'function') {
4256
+ return JSON.stringify({ ok: false, error: 'panel koşucusu yok — bu araç yalnız Sandbox panelinde çalışır' });
4257
+ }
4258
+ let r;
4259
+ try {
4260
+ r = this.sbRunHook(this._sessionWorkspace(sessionId), cmd);
4261
+ } catch (e) {
4262
+ r = { ok: false, error: String((e && e.message) || e) };
4263
+ }
4264
+ return JSON.stringify(r);
4265
+ }
4232
4266
  /* MCP: dış server araçları (mcp__<server>__<tool>) — tools/call'a köprülenir */
4233
4267
  if (String(name).startsWith('mcp__')) {
4234
4268
  return JSON.stringify(await mcp.call(name, args, signal));
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ /* ---------- BEAST × HEADROOM (OPSİYONEL token sıkıştırma) ----------
4
+ Ayarlardan (Kurulum sekmesi) açılıp kapanır; kapalıyken hiçbir şey yapmaz.
5
+ Açıkken: LLM istekleri yerel headroom proxy'sinden geçer — araç çıktıları,
6
+ loglar ve JSON yükleri modele gitmeden sıkıştırılır (testte %39-71 tasarruf).
7
+ Kurulum: uv tool install "headroom-ai[proxy]" (uv yoksa hata bildirir).
8
+ Her provider için AYRI proxy portu (base URL farkları için); llm.js
9
+ providerId → port haritasıyla yönlendirir, kapatınca düz bağlanır. */
10
+
11
+ const { spawn } = require('child_process');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const { emitInstallProgress } = require('./progressbus');
16
+
17
+ const PORT_BASE = 18787;
18
+ const MAX_PROXIES = 8;
19
+
20
+ const state = {
21
+ enabled: false,
22
+ installing: false,
23
+ installFailed: false,
24
+ installError: '',
25
+ starting: false,
26
+ proxies: new Map(), // providerId -> { proc, port, baseUrl, ready }
27
+ providerGetter: null, // () => [{ id, name, baseUrl }]
28
+ statusSink: null, // (probe) => void — main renderer'a akıtır
29
+ };
30
+
31
+ function _emit() {
32
+ try { if (state.statusSink) state.statusSink(probe()); } catch {}
33
+ }
34
+
35
+ function headroomExe() {
36
+ const local = path.join(os.homedir(), '.local', 'bin', 'headroom.exe');
37
+ if (fs.existsSync(local)) return local;
38
+ try {
39
+ require('child_process').execSync('where headroom', { timeout: 5000, stdio: 'pipe' });
40
+ return 'headroom';
41
+ } catch {}
42
+ return '';
43
+ }
44
+
45
+ function uvExe() {
46
+ const local = path.join(os.homedir(), '.local', 'bin', 'uv.exe');
47
+ if (fs.existsSync(local)) return local;
48
+ try {
49
+ require('child_process').execSync('where uv', { timeout: 5000, stdio: 'pipe' });
50
+ return 'uv';
51
+ } catch {}
52
+ return '';
53
+ }
54
+
55
+ function probe() {
56
+ return {
57
+ installed: !!headroomExe(),
58
+ installing: state.installing,
59
+ failed: state.installFailed,
60
+ error: state.installError,
61
+ enabled: state.enabled,
62
+ starting: state.starting,
63
+ proxies: [...state.proxies.values()].map((p) => ({ port: p.port, baseUrl: p.baseUrl, ready: p.ready })),
64
+ };
65
+ }
66
+
67
+ /* ---------- KURULUM (arka plan) ----------
68
+ uv tool install headroom-ai[proxy] — kendi izole Python 3.13 ortamını kurar;
69
+ sistemde Python gerekmez. uv yoksa pip fallback, o da yoksa elle yönlendirir. */
70
+ function autoInstall() {
71
+ if (state.installing || headroomExe()) return;
72
+ const uv = uvExe();
73
+ if (!uv) {
74
+ state.installFailed = true;
75
+ state.installError = 'uv bulunamadı — kur: winget install astral-sh.uv';
76
+ _emit();
77
+ return;
78
+ }
79
+ state.installing = true;
80
+ state.installFailed = false;
81
+ state.installError = '';
82
+ _emit();
83
+ emitInstallProgress('headroom', { pct: 4 });
84
+ const proc = spawn(uv, ['tool', 'install', '--python', '3.13', 'headroom-ai[proxy]'], { windowsHide: true });
85
+ let buf = '';
86
+ let stage = 4;
87
+ const bump = () => {
88
+ stage = Math.min(92, stage + 7);
89
+ emitInstallProgress('headroom', { pct: stage });
90
+ };
91
+ const onData = (c) => {
92
+ const s = String(c);
93
+ if (/Download|Resolve|Installed|Prepared/i.test(s)) bump();
94
+ };
95
+ proc.stdout.on('data', onData);
96
+ proc.stderr.on('data', onData);
97
+ proc.on('close', (code) => {
98
+ state.installing = false;
99
+ if (code === 0 && headroomExe()) {
100
+ emitInstallProgress('headroom', { pct: 100 });
101
+ } else {
102
+ state.installFailed = true;
103
+ state.installError = 'kurulum başarısız — elle: uv tool install "headroom-ai[proxy]"';
104
+ }
105
+ _emit();
106
+ /* kurulum bitti ve açık kaldıysa proxy'leri hemen başlat */
107
+ if (state.enabled && headroomExe()) start().catch(() => {});
108
+ });
109
+ proc.on('error', () => {
110
+ state.installing = false;
111
+ state.installFailed = true;
112
+ state.installError = 'kurulum başlatılamadı (uv)';
113
+ _emit();
114
+ });
115
+ }
116
+
117
+ /* ---------- PROXY SÜREÇLERİ ----------
118
+ Provider başına bir proxy; cache modunu BOZMAMAK için mode=token,
119
+ Beast'in tanımadığı headroom_retrieve aracı enjekte EDİLMESİN diye
120
+ no-ccr (geri-getirilemez sıkıştırma, en yüksek tasarruf) ve rate limit
121
+ kapalı (Beast'in kendi istek patlamalarına takılmasın). Beacon kapalı. */
122
+ function nextPort() {
123
+ const used = new Set([...state.proxies.values()].map((p) => p.port));
124
+ for (let i = 0; i < MAX_PROXIES; i++) {
125
+ const port = PORT_BASE + i;
126
+ if (!used.has(port)) return port;
127
+ }
128
+ return 0;
129
+ }
130
+
131
+ function spawnFor(provider) {
132
+ return new Promise((resolve) => {
133
+ const exe = headroomExe();
134
+ if (!exe) return resolve({ ok: false, error: 'headroom kurulu değil' });
135
+ const port = nextPort();
136
+ if (!port) return resolve({ ok: false, error: 'port kalmadı' });
137
+ let proc;
138
+ try {
139
+ proc = spawn(exe, [
140
+ 'proxy', '--port', String(port), '--openai-api-url', provider.baseUrl,
141
+ '--mode', 'token', '--no-ccr', '--no-rate-limit',
142
+ ], {
143
+ windowsHide: true,
144
+ env: { ...process.env, HEADROOM_BEACON: 'off', DO_NOT_TRACK: '1' },
145
+ });
146
+ } catch (e) {
147
+ return resolve({ ok: false, error: String((e && e.message) || e) });
148
+ }
149
+ const rec = { proc, port, baseUrl: provider.baseUrl, ready: false };
150
+ state.proxies.set(provider.id, rec);
151
+ _emit();
152
+ proc.on('exit', () => {
153
+ if (state.proxies.get(provider.id) === rec) state.proxies.delete(provider.id);
154
+ _emit();
155
+ });
156
+ const t0 = Date.now();
157
+ const poll = setInterval(async () => {
158
+ let ready = false;
159
+ try {
160
+ const r = await fetch('http://127.0.0.1:' + port + '/health', { signal: AbortSignal.timeout(2000) });
161
+ ready = !!r.ok;
162
+ } catch {}
163
+ if (ready) {
164
+ clearInterval(poll);
165
+ rec.ready = true;
166
+ _emit();
167
+ return resolve({ ok: true, port });
168
+ }
169
+ if (Date.now() - t0 > 90000) {
170
+ clearInterval(poll);
171
+ try { rec.proc.kill(); } catch {}
172
+ state.proxies.delete(provider.id);
173
+ _emit();
174
+ resolve({ ok: false, error: 'proxy zaman aşımı (90sn)' });
175
+ }
176
+ }, 1200);
177
+ });
178
+ }
179
+
180
+ async function start() {
181
+ const exe = headroomExe();
182
+ if (!exe) {
183
+ autoInstall();
184
+ return { ok: false, error: 'kuruluyor — birkaç dakika sonra hazır olur' };
185
+ }
186
+ state.starting = true;
187
+ _emit();
188
+ try {
189
+ const providers = (state.providerGetter ? state.providerGetter() : []).slice(0, MAX_PROXIES);
190
+ const routes = {};
191
+ /* önce eskileri temizle: listeden çıkan provider'ın proxy'sini kapat */
192
+ const keep = new Set(providers.map((p) => p.id));
193
+ for (const [id, rec] of [...state.proxies.entries()]) {
194
+ if (keep.has(id)) continue;
195
+ try { rec.proc.kill(); } catch {}
196
+ try { spawn('taskkill', ['/pid', String(rec.proc.pid), '/T', '/F'], { windowsHide: true }); } catch {}
197
+ state.proxies.delete(id);
198
+ }
199
+ for (const p of providers) {
200
+ if (!p.baseUrl || state.proxies.has(p.id)) continue;
201
+ const r = await spawnFor(p);
202
+ if (r.ok) routes[p.id] = r.port;
203
+ }
204
+ try { require('./llm').setHeadroomRoutes(routes); } catch {}
205
+ state.enabled = true;
206
+ return { ok: true, routes };
207
+ } finally {
208
+ state.starting = false;
209
+ _emit();
210
+ }
211
+ }
212
+
213
+ async function setEnabled(on) {
214
+ if (on) {
215
+ state.enabled = true;
216
+ if (!headroomExe()) {
217
+ autoInstall();
218
+ return { ok: false, installing: true, error: 'headroom arka planda kuruluyor — birkaç dakika sonra hazır' };
219
+ }
220
+ return start();
221
+ }
222
+ state.enabled = false;
223
+ stopAll();
224
+ try { require('./llm').setHeadroomRoutes(null); } catch {}
225
+ return { ok: true };
226
+ }
227
+
228
+ /* provider listesi değişti (eklendi/silindi) → proxy setini senkronla */
229
+ function syncProviders() {
230
+ if (!state.enabled || !headroomExe()) return;
231
+ start().catch(() => {});
232
+ }
233
+
234
+ function stopAll() {
235
+ for (const rec of state.proxies.values()) {
236
+ try { rec.proc.kill(); } catch {}
237
+ try { spawn('taskkill', ['/pid', String(rec.proc.pid), '/T', '/F'], { windowsHide: true }); } catch {}
238
+ }
239
+ state.proxies.clear();
240
+ _emit();
241
+ }
242
+
243
+ function setProviderGetter(fn) { state.providerGetter = typeof fn === 'function' ? fn : null; }
244
+ function setStatusSink(fn) { state.statusSink = typeof fn === 'function' ? fn : null; }
245
+
246
+ /* proxy /stats özeti (sağlık duvarı yok — hata.yakalanır) */
247
+ async function stats() {
248
+ const rec = [...state.proxies.values()].find((p) => p.ready);
249
+ if (!rec) return { ok: false, running: false };
250
+ try {
251
+ const r = await fetch('http://127.0.0.1:' + rec.port + '/stats', { signal: AbortSignal.timeout(4000) });
252
+ const j = await r.json();
253
+ const s = (j && (j.tokens || j.summary)) || {};
254
+ return {
255
+ ok: true,
256
+ running: true,
257
+ port: rec.port,
258
+ input: Number(s.input) || 0,
259
+ output: Number(s.output) || 0,
260
+ saved: Number(s.saved) || 0,
261
+ proxyCompressionSaved: Number(s.proxy_compression_saved) || 0,
262
+ savingsPercent: Number(s.savings_percent) || 0,
263
+ allLayersSaved: Number(s.all_layers_saved) || 0,
264
+ };
265
+ } catch {
266
+ return { ok: false, running: true, port: rec.port };
267
+ }
268
+ }
269
+
270
+ module.exports = {
271
+ probe,
272
+ autoInstall,
273
+ setEnabled,
274
+ syncProviders,
275
+ stopAll,
276
+ setProviderGetter,
277
+ setStatusSink,
278
+ stats,
279
+ };
package/src/agent/llm.js CHANGED
@@ -347,6 +347,18 @@ function sessionHeaderId(body) {
347
347
  return _sessionFallback;
348
348
  }
349
349
 
350
+ /* Headroom proxy yönlendirme: providerId -> yerel proxy portu haritası.
351
+ main (agent/headroom.js) ayarlardan açıldığında kurar; null = düz bağlan. */
352
+ let _headroomRoutes = null;
353
+ function setHeadroomRoutes(map) {
354
+ _headroomRoutes = map && Object.keys(map).length ? map : null;
355
+ }
356
+ function headroomUrlFor(sel) {
357
+ if (!_headroomRoutes || !sel) return '';
358
+ const port = Number(_headroomRoutes[String(sel.providerId || '')] || 0);
359
+ return port ? 'http://127.0.0.1:' + port + '/v1/chat/completions' : '';
360
+ }
361
+
350
362
  async function openChat(sel, body, { stream, signal, drops } = {}) {
351
363
  const d = drops || {};
352
364
  const payload = {
@@ -368,7 +380,10 @@ async function openChat(sel, body, { stream, signal, drops } = {}) {
368
380
  if (body.cacheKey && wantsCacheKey(sel) && !d.noCacheKey) {
369
381
  payload.prompt_cache_key = String(body.cacheKey);
370
382
  }
371
- return fetch(sel.url, {
383
+ /* Headroom açıkken istek yerel sıkıştırma proxy'sinden geçer (aynı gövde+başlıklar);
384
+ yoksa sağlayıcıya düz bağlan */
385
+ const target = headroomUrlFor(sel) || sel.url;
386
+ return fetch(target, {
372
387
  method: 'POST',
373
388
  signal,
374
389
  headers: {
@@ -624,6 +639,7 @@ module.exports = {
624
639
  capsFor,
625
640
  resetCaps,
626
641
  learnCaps,
642
+ setHeadroomRoutes,
627
643
  PARAM_LABELS,
628
644
  isNetworkError,
629
645
  wantsCacheKey,
@@ -1488,6 +1488,21 @@ const definitions = [
1488
1488
  },
1489
1489
  },
1490
1490
  },
1491
+ {
1492
+ type: 'function',
1493
+ function: {
1494
+ name: 'panel_run',
1495
+ description:
1496
+ 'Starts a LONG-RUNNING process (dev server, npm start, npm run dev, python app.py, cargo run…) in the managed RUN PANEL instead of blocking this turn. Returns immediately after spawn; output streams live to the panel, the user can stop it with ■ there. Use this when the user asks to RUN/START the project or a server. NEVER use for short one-shot commands (build/test/lint) — use run_command for those. Only one managed process can run at a time.',
1497
+ parameters: {
1498
+ type: 'object',
1499
+ properties: {
1500
+ command: { type: 'string', description: 'Shell command to start, e.g. "npm start" or "npm run dev"' },
1501
+ },
1502
+ required: ['command'],
1503
+ },
1504
+ },
1505
+ },
1491
1506
  {
1492
1507
  type: 'function',
1493
1508
  function: {