beast-agent 2.12.0 → 2.14.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/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: {
@@ -558,7 +573,9 @@ async function chatStreamAuto(sel, body, opts = {}) {
558
573
  let last = first;
559
574
  for (let i = 0; i < 2; i++) {
560
575
  const msgs2 = body.messages.slice();
561
- msgs2.push({ role: 'assistant', content: full });
576
+ const asst = { role: 'assistant', content: full };
577
+ if (first.reasoning) asst.reasoning_content = first.reasoning; /* thinking geri-besleme */
578
+ msgs2.push(asst);
562
579
  msgs2.push({ role: 'user', content: CONTINUE_PROMPT });
563
580
  let cont;
564
581
  try {
@@ -624,6 +641,7 @@ module.exports = {
624
641
  capsFor,
625
642
  resetCaps,
626
643
  learnCaps,
644
+ setHeadroomRoutes,
627
645
  PARAM_LABELS,
628
646
  isNetworkError,
629
647
  wantsCacheKey,
package/src/main.js CHANGED
@@ -26,7 +26,10 @@ const watchers = require('./agent/watchers');
26
26
  const usageMod = require('./agent/usage');
27
27
  const bus = require('./agent/bus');
28
28
  const computeruse = require('./agent/computeruse');
29
+ const android = require('./agent/android');
29
30
  const log = require('./agent/logger');
31
+ const headroom = require('./agent/headroom');
32
+ const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
30
33
 
31
34
  /* #3 otomatik updater: sessiz — indirir, kapanışta kurar, kullanıcıya soru sormaz.
32
35
  Paketlenmemiş (npm start) modda devre dışı; Update sekmesi ve /update komutu kontrol eder. */
@@ -3107,7 +3110,18 @@ function reloadBackend() {
3107
3110
  /* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
3108
3111
  preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
3109
3112
  if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
3113
+ /* klasör değiştiyse eski sunucu bayat: KAPAT, en yeniye geç */
3114
+ try {
3115
+ if (bcLastServerUrl && bcLastServerRoot && path.relative(bcLastServerRoot, ideRoot()) !== '') {
3116
+ bcKillServer(bcLastServerUrl);
3117
+ }
3118
+ } catch {}
3110
3119
  bcLastServerUrl = String(ev.url);
3120
+ try { bcLastServerRoot = ideRoot(); } catch { bcLastServerRoot = ''; }
3121
+ }
3122
+ /* Expo Go QR: ajanın dev sunucusu exp:// adresi yazdıysa QR üretip panele bas */
3123
+ if (ev.type === 'bc-expurl') {
3124
+ bcExpQrMake(String(ev.url || '')).catch(() => {});
3111
3125
  }
3112
3126
  /* WA canlı iş takibi: oturum bir WhatsApp sohbetine bağlıysa araç
3113
3127
  hareketlerini (terminal, web, dosya…) kısa satırla bildir.
@@ -3288,6 +3302,30 @@ function reloadBackend() {
3288
3302
  /* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
3289
3303
  süreç koşucusu (sandbox:run IPC ile aynı makine). */
3290
3304
  engine.sbRunHook = sbRunStartManaged;
3305
+
3306
+ /* HEADROOM (opsiyonel token sıkıştırma proxy'si): engine zincirinden provider
3307
+ listesi alınır; durum değişince renderer'a 'headroom-status' düşer */
3308
+ headroom.setProviderGetter(() => {
3309
+ const seen = new Map();
3310
+ for (const c of (engine && engine.cfg && engine.cfg.chain) || []) {
3311
+ if (!c || !c.providerId || !c.url || seen.has(c.providerId)) continue;
3312
+ seen.set(c.providerId, {
3313
+ id: String(c.providerId),
3314
+ name: String(c.providerName || c.providerId),
3315
+ baseUrl: String(c.url).replace(/\/chat\/completions$/, ''),
3316
+ });
3317
+ }
3318
+ return [...seen.values()];
3319
+ });
3320
+ headroom.setStatusSink((st) => {
3321
+ try {
3322
+ if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'headroom-status', status: st });
3323
+ } catch {}
3324
+ });
3325
+ /* ayarlardan açık kaldıysa: uygulama açılışında arka planda başlat */
3326
+ if (settings.headroom && settings.headroom.enabled) {
3327
+ setTimeout(() => { headroom.setEnabled(true).catch(() => {}); }, 4000);
3328
+ }
3291
3329
  return engine.publicState();
3292
3330
  }
3293
3331
 
@@ -3516,6 +3554,7 @@ app.whenReady().then(() => {
3516
3554
  flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
3517
3555
  try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
3518
3556
  try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
3557
+ try { headroom.stopAll(); } catch {} // headroom proxy süreçlerini kapat
3519
3558
  });
3520
3559
 
3521
3560
  if (process.argv.includes('--smoke')) {
@@ -6248,12 +6287,73 @@ ipcMain.handle('install:status', async () => {
6248
6287
  /* 8) Edge TTS (bulut) */
6249
6288
  rows.push({ id: 'edge', name: 'Edge TTS — seslendirme', state: 'cloud', detail: 'bulut — kurulum gerekmez' });
6250
6289
 
6290
+ /* 9) Headroom CLI — arka plan paket kurulumu burada görünür;
6291
+ aç/kapat Token Sıkıştırma sekmesinde */
6292
+ {
6293
+ const st = headroom.probe();
6294
+ let state = 'optional';
6295
+ if (st.installed) state = 'ok';
6296
+ else if (st.installing) state = 'loading';
6297
+ if (st.failed) state = 'failed';
6298
+ rows.push({
6299
+ id: 'headroom',
6300
+ name: 'Headroom CLI — token sıkıştırma motoru',
6301
+ state,
6302
+ detail: st.installing
6303
+ ? 'arka planda kuruluyor — uv tool install headroom-ai[proxy]'
6304
+ : st.failed
6305
+ ? (st.error || 'kurulum başarısız')
6306
+ : st.installed
6307
+ ? 'kurulu — aç/kapat: Token Sıkıştırma sekmesi'
6308
+ : 'kurulu değil — Token Sıkıştırma sekmesinden açınca otomatik kurulur',
6309
+ ...(st.installing ? pctFields('headroom') : {}),
6310
+ });
6311
+ }
6312
+
6313
+ /* 10) Android Emülatörü — opsiyonel; varsa Beast Code mobil uygulamaları emülatörde açar */
6314
+ {
6315
+ const st = android.probe();
6316
+ let state = 'optional';
6317
+ if (st.sdk) state = 'ok';
6318
+ else if (st.installing) state = 'loading';
6319
+ if (st.failed) state = 'failed';
6320
+ rows.push({
6321
+ id: 'android',
6322
+ name: 'Android Emülatörü — mobil uygulamalar emülatörde çalışır',
6323
+ state,
6324
+ canInstall: true,
6325
+ detail: st.installing
6326
+ ? (st.stage || 'arka planda kuruluyor — JDK + SDK + sistem imajı (~1.5GB, internete göre 5-20 dk)')
6327
+ : st.failed
6328
+ ? (st.error || 'kurulum başarısız')
6329
+ : st.sdk
6330
+ ? 'hazır — SDK: ' + st.sdkPath
6331
+ : 'kurulu değil — KUR ile arka planda kurulur (istekge bağlı)',
6332
+ ...(st.installing ? pctFields('android') : {}),
6333
+ });
6334
+ }
6335
+
6251
6336
  return rows;
6252
6337
  });
6253
6338
 
6254
6339
  /* cua-driver kurulumunu şimdi başlat (Kurulum sekmesi otomatiği + elle tetikleme) */
6255
6340
  ipcMain.handle('cua:install', () => require('./agent/computeruse').autoInstall());
6256
6341
 
6342
+ /* HEADROOM (opsiyonel token sıkıştırma): aç/kapa + durum */
6343
+ ipcMain.handle('headroom:toggle', async (_e, on) => {
6344
+ settings.headroom = { ...(settings.headroom || {}), enabled: !!on };
6345
+ saveSettings();
6346
+ const r = await headroom.setEnabled(!!on).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
6347
+ return r;
6348
+ });
6349
+ ipcMain.handle('headroom:status', () => headroom.probe());
6350
+ ipcMain.handle('headroom:stats', () => headroom.stats());
6351
+
6352
+ /* ANDROID EMÜLATÖR (opsiyonel): durum + arka plan kurulum + AVD başlatma */
6353
+ ipcMain.handle('android:status', async () => ({ ok: true, ...(await android.probe()), avds: await android.listAvds() }));
6354
+ ipcMain.handle('android:install', () => android.autoInstall());
6355
+ ipcMain.handle('android:startAvd', async (_e, name) => android.startAvd(name));
6356
+
6257
6357
  /* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
6258
6358
  ipcMain.handle('embed:prefetch', () => {
6259
6359
  try {
@@ -8063,6 +8163,14 @@ ipcMain.handle('ide:setroot', async () => {
8063
8163
  properties: ['openDirectory'],
8064
8164
  });
8065
8165
  if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
8166
+ /* klasör değişti: eski klasörün dev server'ı bayat — kill + unut */
8167
+ try {
8168
+ if (bcLastServerUrl) {
8169
+ bcKillServer(bcLastServerUrl);
8170
+ bcLastServerUrl = '';
8171
+ bcLastServerRoot = '';
8172
+ }
8173
+ } catch {}
8066
8174
  settings.ideRoot = r.filePaths[0];
8067
8175
  saveSettings();
8068
8176
  ideWatchStart(); // yeni kökte izleme yeniden kurulur
@@ -8686,10 +8794,60 @@ ipcMain.handle('sandbox:openurl', async (_e, url) => {
8686
8794
  ve "clean URL" yolları file://'da çalışmaz. Her preview http://127.0.0.1 üzerinden
8687
8795
  servis edilir; ajan kendi dev sunucusunu başlattıysa o adres önceliklidir. */
8688
8796
  let bcLastServerUrl = ''; /* ajanın başlattığı dev server adresi (bc-preview'dan yakalanır) */
8797
+ let bcLastServerRoot = ''; /* adresin yakalandığı workspace kökü — klasör değişince bayat sayılır */
8798
+
8799
+ /* dev server'ı port'tan bulup süreç AĞACINI kapat (best-effort).
8800
+ Ajan arka planda başlattığı sunucular izlenmez — port üzerinden bulunur. */
8801
+ function bcKillServer(url) {
8802
+ try {
8803
+ const m = /:(\d{2,5})(?:\/|$)/.exec(String(url || ''));
8804
+ if (!m) return false;
8805
+ const port = m[1];
8806
+ const { execSync } = require('child_process');
8807
+ const out = String(execSync(
8808
+ 'netstat -ano | findstr ":' + port + '" | findstr LISTENING',
8809
+ { timeout: 6000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }
8810
+ ));
8811
+ const pids = new Set();
8812
+ for (const line of out.split(/\r?\n/)) {
8813
+ const p = /\s(\d+)\s*$/.exec(String(line).trim());
8814
+ if (p) pids.add(p[1]);
8815
+ }
8816
+ let killed = 0;
8817
+ for (const pid of pids) {
8818
+ if (Number(pid) === process.pid) continue; /* kendimize dokunma */
8819
+ try {
8820
+ spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true });
8821
+ killed++;
8822
+ } catch {}
8823
+ }
8824
+ try { log.info('bc', 'bayat dev server kapatıldı (port ' + port + ', ' + killed + ' süreç)'); } catch {}
8825
+ return killed > 0;
8826
+ } catch {
8827
+ return false; /* port boş / netstat hata — yok say */
8828
+ }
8829
+ }
8689
8830
  const bcStatic = { server: null, root: '', base: '' };
8690
8831
  /* MOBİL PROJE önizleme: npm run web (expo start --web) süreci + canlı adres */
8691
8832
  const bcMobile = { proc: null, url: '', root: '' };
8692
8833
 
8834
+ /* ---------- EXPO GO QR (yalnız Beast Code) ----------
8835
+ Ajan expo start çıktısında exp://<LAN-IP>:<port> yazınca QR'e çevrilir ve
8836
+ renderer'a { type:'bc-exp-qr', qr, url } düşer — telefonda Expo Go okutulur. */
8837
+ let bcLastExpUrl = '';
8838
+ let bcLastExpQr = '';
8839
+
8840
+ function bcExpQrMake(url) {
8841
+ if (!url || url === bcLastExpUrl) return Promise.resolve();
8842
+ bcLastExpUrl = url;
8843
+ return QRCode.toDataURL(url, { width: 240, margin: 1, errorCorrectionLevel: 'M' }).then((qr) => {
8844
+ bcLastExpQr = qr;
8845
+ if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'bc-exp-qr', qr, url });
8846
+ });
8847
+ }
8848
+
8849
+ ipcMain.handle('ide:expqr', () => ({ ok: true, qr: bcLastExpQr, url: bcLastExpUrl }));
8850
+
8693
8851
  /* workspace bir MOBİL UYGULAMA projesi mi? (expo / react-native / app.json) */
8694
8852
  function ideMobileProject(root) {
8695
8853
  try {
@@ -8714,6 +8872,49 @@ function ideMobileProject(root) {
8714
8872
  }
8715
8873
  }
8716
8874
 
8875
+ /* Beast Code PROJE TİPİ algılama: preview butonu ajana hangi talimatı vereceğini
8876
+ bununla seçer. kind: expo | react-native | web | static | unknown */
8877
+ function ideProjectInfo(root) {
8878
+ const out = { kind: 'unknown', name: '', cmd: '' };
8879
+ try {
8880
+ const pkgPath = path.join(root, 'package.json');
8881
+ let pkg = null;
8882
+ try { if (fs.existsSync(pkgPath)) pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch {}
8883
+ if (!pkg) {
8884
+ out.kind = fs.existsSync(path.join(root, 'index.html')) ? 'static' : 'unknown';
8885
+ return out;
8886
+ }
8887
+ out.name = String(pkg.name || '');
8888
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
8889
+ const scripts = pkg.scripts || {};
8890
+ const hasAppJson = fs.existsSync(path.join(root, 'app.json')) || fs.existsSync(path.join(root, 'app.config.js'));
8891
+ if (deps.expo || (hasAppJson && (deps['react-native'] || deps['react-dom']))) {
8892
+ out.kind = 'expo';
8893
+ out.cmd = scripts.web ? 'npm run web' : deps.expo ? 'npx expo start --web' : String(scripts.start || '');
8894
+ } else if (deps['react-native']) {
8895
+ out.kind = 'react-native';
8896
+ out.cmd = String(scripts.start || scripts.ios || '') || 'npx react-native start';
8897
+ } else if (deps.next) {
8898
+ out.kind = 'web';
8899
+ out.cmd = String(scripts.dev || scripts.start || '') || 'npx next dev';
8900
+ } else if (deps.vite || deps['react-scripts'] || deps['@angular/core'] || deps['vue']) {
8901
+ out.kind = 'web';
8902
+ out.cmd = String(scripts.dev || scripts.start || scripts.serve || '');
8903
+ } else if (scripts.dev) {
8904
+ out.kind = 'web';
8905
+ out.cmd = 'npm run dev';
8906
+ } else if (scripts.start) {
8907
+ out.kind = 'web';
8908
+ out.cmd = 'npm start';
8909
+ } else {
8910
+ out.kind = fs.existsSync(path.join(root, 'index.html')) ? 'static' : 'unknown';
8911
+ }
8912
+ } catch {}
8913
+ return out;
8914
+ }
8915
+
8916
+ ipcMain.handle('ide:projinfo', () => ({ ok: true, ...ideProjectInfo(ideRoot()) }));
8917
+
8717
8918
  function killMobilePreview() {
8718
8919
  const p = bcMobile.proc;
8719
8920
  if (p) {
@@ -8902,6 +9103,13 @@ ipcMain.handle('ide:preview', async () => {
8902
9103
  if (!entry) return { ok: false, error: 'workspace kökünde index.html yok — önce agent\'a siteyi yazdır' };
8903
9104
  /* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın */
8904
9105
  setBrowserOpen(true, true);
9106
+ /* KLASÖR UYUŞMAZLIĞI: kayıtlı dev server ESKİ klasörün artıklarıysa
9107
+ kill et ve unut — yeni klasör kendi sunucusuyla başlasın */
9108
+ if (bcLastServerUrl && bcLastServerRoot && path.relative(bcLastServerRoot, root) !== '') {
9109
+ bcKillServer(bcLastServerUrl);
9110
+ bcLastServerUrl = '';
9111
+ bcLastServerRoot = '';
9112
+ }
8905
9113
  let url = '';
8906
9114
  if (bcLastServerUrl) {
8907
9115
  url = bcLastServerUrl; /* ajanın kendi dev sunucusu öncelikli */
@@ -8964,6 +9172,7 @@ ipcMain.handle('custom:set', (_e, list) => {
8964
9172
  settings.customProviders = Array.isArray(list) ? list : [];
8965
9173
  saveSettings();
8966
9174
  engine.setCustomProviders(settings.customProviders);
9175
+ headroom.syncProviders(); /* headroom açıksa proxy setini yeni listeye uydur */
8967
9176
  return engine.publicState();
8968
9177
  });
8969
9178
 
package/src/preload.js CHANGED
@@ -222,6 +222,14 @@ contextBridge.exposeInMainWorld('beast', {
222
222
  idePreview: () => ipcRenderer.invoke('ide:preview'),
223
223
  idePreviewFile: (rel) => ipcRenderer.invoke('ide:previewFile', rel),
224
224
  idePreviewUrl: (url) => ipcRenderer.invoke('ide:previewUrl', url),
225
+ ideProjInfo: () => ipcRenderer.invoke('ide:projinfo'),
226
+ ideExpQr: () => ipcRenderer.invoke('ide:expqr'),
227
+ headroomToggle: (on) => ipcRenderer.invoke('headroom:toggle', !!on),
228
+ headroomStatus: () => ipcRenderer.invoke('headroom:status'),
229
+ headroomStats: () => ipcRenderer.invoke('headroom:stats'),
230
+ androidStatus: () => ipcRenderer.invoke('android:status'),
231
+ androidInstall: () => ipcRenderer.invoke('android:install'),
232
+ androidStartAvd: (name) => ipcRenderer.invoke('android:startAvd', name),
225
233
  agentsClearAll: () => ipcRenderer.invoke('agents:clearAll'),
226
234
  ideDelete: (rel) => ipcRenderer.invoke('ide:delete', rel),
227
235
  onWaEvent: (cb) => ipcRenderer.on('wa:event', (_e, ev) => cb(ev)),
@@ -64,6 +64,7 @@
64
64
  set_title: 'AYARLAR',
65
65
  tab_lang: 'Dil',
66
66
  lang_sub: 'Arayüz dili — uygulama dili ve sesli komut dili buradan değişir',
67
+ tab_headroom: 'Token Sıkıştırma',
67
68
  tab_provider: 'Provider',
68
69
  tab_fallout: 'Fallout',
69
70
  tab_memory: 'Memory',
@@ -163,7 +164,7 @@
163
164
  em_mem_clear: 'Hafızayı Temizle',
164
165
  em_mem_cleared: 'Empati hafızası temizlendi',
165
166
  tab_cron: 'Cron',
166
- tab_usage: 'Maliyet · Limit',
167
+ tab_usage: 'Maliyet',
167
168
  tab_logs: 'Log',
168
169
  tab_dash: 'Dashboard',
169
170
  tab_limits: 'Limit',
@@ -539,6 +540,8 @@
539
540
  bot_plugin_sub: 'Aktif plugin\u2019leri seç (değişiklik loglanır).',
540
541
  bot_watcher_sub: 'Bu botun oturumlarına bağlı izleyiciler.',
541
542
  bot_no_watchers: 'Bu bota ait izleyici yok.',
543
+ w_pane: 'İZLEYİCİLER',
544
+ w_empty: 'Kayıtlı izleyici yok.',
542
545
  bot_stats_sub: 'Bu botun kullanım özeti.',
543
546
  bot_stat_numbers: 'Numara',
544
547
  bot_stat_sessions: 'Sohbet',
@@ -696,6 +699,7 @@
696
699
  set_title: 'SETTINGS',
697
700
  tab_lang: 'Language',
698
701
  lang_sub: 'Interface language — app UI and voice command language',
702
+ tab_headroom: 'Token Compression',
699
703
  tab_provider: 'Provider',
700
704
  tab_fallout: 'Fallout',
701
705
  tab_memory: 'Memory',
@@ -795,7 +799,7 @@
795
799
  em_mem_clear: 'Clear Memory',
796
800
  em_mem_cleared: 'Empathy memory cleared',
797
801
  tab_cron: 'Cron',
798
- tab_usage: 'Cost · Limit',
802
+ tab_usage: 'Cost',
799
803
  tab_logs: 'Logs',
800
804
  tab_dash: 'Dashboard',
801
805
  tab_limits: 'Limit',
@@ -1033,6 +1037,7 @@
1033
1037
  cr_ran: 'Started',
1034
1038
  cr_toggle_pause: 'Pause',
1035
1039
  w_empty: 'No saved watchers — add from the form above.',
1040
+ w_pane: 'WATCHERS',
1036
1041
  wa_vazgec: 'Cancel',
1037
1042
  wa_kaydet: 'save',
1038
1043
  wa_updated: 'Updated',
@@ -1063,7 +1068,6 @@
1063
1068
  cr_modal_empty: 'Kayıtlı cron görev yok — Ayarlar → Cron sekmesinden ekle.',
1064
1069
  cr_ran: 'Çalıştırıldı',
1065
1070
  cr_toggle_pause: 'Duraklat',
1066
- w_empty: 'Kayıtlı izleyici yok — yukarıdaki formla ekle.',
1067
1071
  wa_vazgec: 'Vazgeç',
1068
1072
  wa_kaydet: 'kaydet',
1069
1073
  wa_updated: 'Güncellendi',
@@ -13,6 +13,14 @@
13
13
  <div id="brand"><span class="brand-beast">BEAST</span><span class="brand-sub">Agent</span></div>
14
14
  <button id="newChat" title="Yeni sohbet" data-i18n="newChat">+ Yeni Sohbet</button>
15
15
  <div id="sessList"></div>
16
+ <div id="watchPane">
17
+ <div id="watchPaneHead">
18
+ <span id="watchPaneTitle" data-i18n="w_pane">İZLEYİCİLER</span>
19
+ <span class="file-spacer"></span>
20
+ <button id="watchPaneOpen" title="İzleyicileri yönet">+</button>
21
+ </div>
22
+ <div id="watchPaneList"></div>
23
+ </div>
16
24
  <div id="botBar">
17
25
  <div id="botBarHead">
18
26
  <span id="botBarTitle" data-i18n="bot_head">BOTLAR</span>
@@ -164,11 +172,21 @@
164
172
  <button id="codeShow" title="Editörü göster" data-i18n-title="tipCodeShow" hidden>&#x25A3;&#xFE0E;</button>
165
173
  <button id="bcNew" title="Yeni oturum" data-i18n-title="bc_new">*</button>
166
174
  <button id="bcClear" title="Çıktıyı temizle" data-i18n-title="bc_clear">Temizle</button>
175
+ <button id="bcQrBtn" title="Expo Go QR" hidden>&#x25A3;&#xFE0E;</button>
167
176
  </div>
168
177
  <div id="bcOut"></div>
169
178
  <div id="bcTodoWrap" hidden></div>
170
179
  <div id="bcStatus" hidden></div>
171
180
  <div id="bcChips" hidden></div>
181
+ <div id="bcQrCard" hidden>
182
+ <div class="bq-head">
183
+ <span>EXPO GO QR</span>
184
+ <button id="bcQrClose" title="Kapat">&#x00D7;</button>
185
+ </div>
186
+ <img id="bcQrImg" alt="Expo QR" draggable="false" />
187
+ <div id="bcQrUrl" title=""></div>
188
+ <div class="bq-hint">Telefonda <b>Expo Go</b> ile okut — tünel modunda her ağdan çalışır</div>
189
+ </div>
172
190
  <div id="bcInputRow">
173
191
  <button id="bcAttach" title="Dosya / resim ekle" data-i18n-title="tipAttach">+</button>
174
192
  <button id="bcMic" title="Sesli komut — konuş, bırakınca yazıya çevirsin" data-i18n-title="tipMic"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><path d="M12 19v3"/></svg></button>
@@ -297,6 +315,7 @@
297
315
  <button class="tab" data-tab="empati" data-i18n="tab_empati">Empati Loop</button>
298
316
  <button class="tab" data-tab="cron" data-i18n="tab_cron">Cron</button>
299
317
  <button class="tab" data-tab="usage" data-i18n="tab_usage">Maliyet · Limit</button>
318
+ <button class="tab" data-tab="headroom" data-i18n="tab_headroom">Token Sıkıştırma</button>
300
319
  <button class="tab" data-tab="logs" data-i18n="tab_logs">Log</button>
301
320
  <button class="tab" data-tab="dash" data-i18n="tab_dash">Dashboard</button>
302
321
  <button class="tab" data-tab="sec" data-i18n="tab_security">Güvenlik</button>
@@ -322,6 +341,7 @@
322
341
  <div id="tab-events" class="pane" hidden></div>
323
342
  <div id="tab-empati" class="pane" hidden></div>
324
343
  <div id="tab-usage" class="pane" hidden></div>
344
+ <div id="tab-headroom" class="pane" hidden></div>
325
345
  <div id="tab-logs" class="pane" hidden></div>
326
346
  <div id="tab-dash" class="pane" hidden></div>
327
347
  <div id="tab-sec" class="pane" hidden></div>