beast-agent 0.16.1 → 0.18.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.
@@ -8,13 +8,34 @@
8
8
  const { spawn, spawnSync } = require('child_process');
9
9
  const path = require('path');
10
10
 
11
- /* güncelleme modu: uygulama kapalıyken dosyalar kilitli olmaz */
11
+ /* güncelleme modu: çalışan Beast'i kapat (dosya kilidi EBUSY vermesin) → npm güncelle → yeniden başlat */
12
12
  if (process.argv[2] === 'update') {
13
- const r = spawnSync('npm', ['install', '-g', 'beast-agent@latest'], { stdio: 'inherit', shell: true });
14
- console.log(r.status === 0
15
- ? '\n✓ beast-agent güncellendi — "beast-agent" ile başlatabilirsin.'
16
- : '\n✗ güncelleme başarısız — elle: npm install -g beast-agent@latest');
17
- process.exit(r.status || 0);
13
+ const isWin = process.platform === 'win32';
14
+ console.log('\u27F3 beast-agent g\u00FCncelleniyor\u2026');
15
+ if (isWin) {
16
+ try {
17
+ spawnSync('powershell.exe', ['-NoProfile', '-Command',
18
+ "Get-Process electron -ErrorAction SilentlyContinue | Where-Object { $_.Path -like '*node_modules*beast-agent*' } | Stop-Process -Force"],
19
+ { stdio: 'ignore' });
20
+ console.log('\u2022 \u00E7al\u0131\u015Fan Beast kapat\u0131ld\u0131 (varsa)');
21
+ spawnSync('powershell.exe', ['-NoProfile', '-Command', 'Start-Sleep -Seconds 2'], { stdio: 'ignore' });
22
+ } catch {}
23
+ } else {
24
+ try { spawnSync('pkill', ['-f', 'node_modules/beast-agent'], { stdio: 'ignore' }); } catch {}
25
+ }
26
+ const r = spawnSync('npm', ['install', '-g', 'beast-agent@latest'], { stdio: 'inherit', shell: isWin });
27
+ if (r.status !== 0) {
28
+ console.log('\n\u2717 g\u00FCncelleme ba\u015Far\u0131s\u0131z \u2014 elle: npm install -g beast-agent@latest');
29
+ process.exit(r.status || 1);
30
+ }
31
+ console.log('\n\u2713 beast-agent g\u00FCncellendi \u2014 uygulama ba\u015Flat\u0131l\u0131yor\u2026');
32
+ try {
33
+ const electron = require('electron');
34
+ if (typeof electron === 'string') {
35
+ spawn(electron, [path.resolve(__dirname, '..')], { stdio: 'ignore', detached: true }).unref();
36
+ }
37
+ } catch {}
38
+ process.exit(0);
18
39
  }
19
40
 
20
41
  const electron = require('electron');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.16.1",
4
+ "version": "0.18.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/main.js CHANGED
@@ -30,6 +30,33 @@ function isNpmMode() {
30
30
  return !app.isPackaged && /node_modules[\\/]beast-agent/i.test(String(app.getAppPath()));
31
31
  }
32
32
 
33
+ /* npm registry'den en son sürüm (10 dk cache) — Update sekmesi + otomatik kontrol */
34
+ let npmLatestCache = { version: null, at: 0 };
35
+
36
+ function isNewerVersion(a, b) {
37
+ const pa = String(a).split('.').map(Number);
38
+ const pb = String(b).split('.').map(Number);
39
+ for (let i = 0; i < 3; i++) {
40
+ const d = (pa[i] || 0) - (pb[i] || 0);
41
+ if (d) return d > 0;
42
+ }
43
+ return false;
44
+ }
45
+
46
+ async function getNpmLatest(force) {
47
+ const now = Date.now();
48
+ if (!force && npmLatestCache.version && now - npmLatestCache.at < 10 * 60 * 1000) return npmLatestCache.version;
49
+ try {
50
+ const res = await fetch('https://registry.npmjs.org/beast-agent/latest', { signal: AbortSignal.timeout(15000) });
51
+ if (res.ok) {
52
+ const j = await res.json();
53
+ const v = String(j.version || '').trim();
54
+ if (v) npmLatestCache = { version: v, at: now };
55
+ }
56
+ } catch {}
57
+ return npmLatestCache.version;
58
+ }
59
+
33
60
  function emitUpdateEvent() {
34
61
  try {
35
62
  if (win && !win.isDestroyed()) {
@@ -99,6 +126,24 @@ function startAutoUpdater() {
99
126
  }
100
127
  } catch {}
101
128
  }
129
+
130
+ /* npm kurulumu için otomatik sürüm kontrolü (registry üzerinden, 6 saatte bir) */
131
+ function startNpmUpdateWatch() {
132
+ if (!isNpmMode() || settings.autoCheckUpdate === false) return;
133
+ const check = async () => {
134
+ try {
135
+ const v = await getNpmLatest();
136
+ if (v && isNewerVersion(v, app.getVersion()) && !updateState.available) {
137
+ updateState.available = true;
138
+ updateState.version = v;
139
+ emitUpdateEvent();
140
+ log.info('main', `npm: yeni sürüm var ${v} (mevcut ${app.getVersion()})`);
141
+ }
142
+ } catch {}
143
+ };
144
+ check();
145
+ setInterval(check, 6 * 60 * 60 * 1000);
146
+ }
102
147
  const { htmlToText, setExaKey, setTinyfishKey } = require('./agent/tools');
103
148
  const { waToolLine } = require('./agent/watext');
104
149
 
@@ -460,12 +505,17 @@ function decodeAudioToPcm16k(buf) {
460
505
  });
461
506
  }
462
507
 
463
- async function transcribeAudio(buf /* , mimetype */) {
508
+ async function transcribeAudio(buf, langOverride /* 'tr' | 'en' | 'auto' */) {
464
509
  try {
465
510
  const audio = await decodeAudioToPcm16k(buf);
466
511
  if (!audio || !audio.length) return null;
467
512
  const asr = await ensureStt();
468
- const out = await asr(audio, { language: 'turkish', task: 'transcribe', chunk_length_s: 30, stride_length_s: 5 });
513
+ /* dil: arayüz diline bağlı UI Türkçe ise Türkçe, İngilizce ise İngilizce algılar */
514
+ const lang = langOverride || settings.sttLang || 'tr';
515
+ const opts = { task: 'transcribe', chunk_length_s: 30, stride_length_s: 5 };
516
+ if (lang === 'en') opts.language = 'english';
517
+ else if (lang === 'tr') opts.language = 'turkish';
518
+ const out = await asr(audio, opts);
469
519
  return String((out && out.text) || '').trim() || null;
470
520
  } catch (e) {
471
521
  waLog('stt hata: ' + String((e && e.message) || e));
@@ -907,9 +957,11 @@ async function tryWaSlash(jid, rawText, senderNum) {
907
957
  ? `*${cmd === 'deny' ? 'Reddedildi' : 'Onaylandı'}:* ${r.tool}${always ? ' — bu araç için bir daha sorulmayacak' : ''}`
908
958
  : 'Bekleyen onay yok.';
909
959
  } else if (cmd === 'update') {
910
- /* /update — sürüm kontrol; /update now — indirileni kur */
960
+ /* /update — sürüm kontrol; /update now — indirileni kur (npm modunda kendini günceller) */
911
961
  if (String(arg || '').toLowerCase() === 'now') {
912
- if (updateState.downloaded && autoUpdater) {
962
+ if (isNpmMode()) {
963
+ npmUpdateNow(async (text) => { out = text; });
964
+ } else if (updateState.downloaded && autoUpdater) {
913
965
  out = `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`;
914
966
  setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
915
967
  } else {
@@ -1740,6 +1792,7 @@ app.whenReady().then(() => {
1740
1792
  maybeRunWhereWasI();
1741
1793
  falloutResume();
1742
1794
  startAutoUpdater(); // #3 sessiz güncelleme
1795
+ startNpmUpdateWatch(); // npm kurulumunda registry üzerinden otomatik sürüm kontrolü
1743
1796
 
1744
1797
  // #12 STT prefetch: whisper modelini arka planda hazırla (ilk sesli mesajda bekleme olmasın)
1745
1798
  if (settings.sttPrefetch !== false) {
@@ -2614,7 +2667,9 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
2614
2667
  const a = t.slice(7).trim().toLowerCase();
2615
2668
  updateReplies.sids.add(String(sessionId || ''));
2616
2669
  if (a === 'now') {
2617
- if (updateState.downloaded && autoUpdater) {
2670
+ if (isNpmMode()) {
2671
+ npmUpdateNow((text) => desktopEcho(sessionId, t, text));
2672
+ } else if (updateState.downloaded && autoUpdater) {
2618
2673
  desktopEcho(sessionId, t, `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`);
2619
2674
  setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
2620
2675
  } else {
@@ -3135,29 +3190,75 @@ ipcMain.handle('sec:set', (_e, cfg) => {
3135
3190
  ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
3136
3191
 
3137
3192
  /* ---------------- #Update IPC ---------------- */
3138
- ipcMain.handle('update:status', () => ({
3139
- current: app.getVersion(),
3140
- packaged: app.isPackaged,
3141
- npm: isNpmMode(),
3142
- ...updateState,
3143
- autoCheck: settings.autoCheckUpdate !== false,
3144
- autoDownload: settings.autoDownloadUpdate !== false,
3145
- }));
3146
-
3147
- ipcMain.handle('update:check', async (_e, viaCommand) => {
3148
- if (isNpmMode()) return { ok: false, npm: true, error: 'npm kurulumu — güncelleme: beast-agent update' };
3149
- if (!autoUpdater) return { ok: false, error: 'updater kullanılamıyor (taşınabilir/geliştirme modu)' };
3193
+ ipcMain.handle('update:status', async () => {
3194
+ const st = {
3195
+ current: app.getVersion(),
3196
+ packaged: app.isPackaged,
3197
+ npm: isNpmMode(),
3198
+ ...updateState,
3199
+ autoCheck: settings.autoCheckUpdate !== false,
3200
+ autoDownload: settings.autoDownloadUpdate !== false,
3201
+ };
3150
3202
  try {
3151
- const r = await autoUpdater.checkForUpdates();
3152
- const v = r && r.update && r.update.version;
3153
- return { ok: true, version: v, available: !!v && v !== app.getVersion() };
3154
- } catch (e) {
3155
- return { ok: false, error: String((e && e.message) || e) };
3203
+ /* npm registry'den güncel sürüm — updater ne yaparsa yapsın "En son sürüm" kartı dolu olsun */
3204
+ const v = await getNpmLatest();
3205
+ if (v) {
3206
+ st.npmLatest = v;
3207
+ if (!st.version) st.version = v;
3208
+ if (isNewerVersion(v, app.getVersion())) st.available = true;
3209
+ }
3210
+ } catch {}
3211
+ return st;
3212
+ });
3213
+
3214
+ ipcMain.handle('update:check', async () => {
3215
+ const current = app.getVersion();
3216
+ /* her modda npm registry'den taze kontrol (cache bypass) */
3217
+ const v = await getNpmLatest(true);
3218
+ const out = { ok: true, npm: isNpmMode(), version: v || undefined, available: v ? isNewerVersion(v, current) : false };
3219
+ if (v) {
3220
+ if (!updateState.version || isNewerVersion(v, updateState.version)) updateState.version = v;
3221
+ if (isNewerVersion(v, current)) updateState.available = true;
3156
3222
  }
3223
+ /* packaged + updater: GitHub Releases kontrolü de çalışsın */
3224
+ if (!isNpmMode() && autoUpdater) {
3225
+ try {
3226
+ const r = await autoUpdater.checkForUpdates();
3227
+ const uv = r && r.update && r.update.version;
3228
+ if (uv) { out.version = uv; out.available = uv !== current; }
3229
+ } catch (e) {
3230
+ if (!v) return { ok: false, error: String((e && e.message) || e) };
3231
+ }
3232
+ }
3233
+ emitUpdateEvent();
3234
+ return out;
3157
3235
  });
3158
3236
 
3237
+ /* npm kurulumunda KENDİ KENDİNİ GÜNCELLEME:
3238
+ detached helper bırakır (uygulama çıkınca npm install + yeniden başlatma), sonra app.quit() */
3239
+ function npmSelfUpdate() {
3240
+ try {
3241
+ const electronExe = process.execPath;
3242
+ const appPath = app.getAppPath();
3243
+ if (process.platform === 'win32') {
3244
+ const ps =
3245
+ 'Start-Sleep -Seconds 3;' +
3246
+ 'npm install -g beast-agent@latest;' +
3247
+ 'Start-Sleep -Seconds 1;' +
3248
+ `Start-Process -FilePath '${electronExe}' -ArgumentList '\"${appPath}\"'`;
3249
+ spawn('powershell.exe', ['-NoProfile', '-Command', ps], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
3250
+ } else {
3251
+ const sh = `sleep 3; npm install -g beast-agent@latest; sleep 1; '${electronExe}' '${appPath}' &`;
3252
+ spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' }).unref();
3253
+ }
3254
+ log.info('main', 'npm self-update: helper bırakıldı, uygulama kapatılıyor');
3255
+ } catch {}
3256
+ setTimeout(() => { try { app.quit(); } catch {} }, 400);
3257
+ }
3258
+
3159
3259
  ipcMain.handle('update:install', () => {
3160
- if (isNpmMode() || !autoUpdater) return { ok: false, error: 'updater kullanılamıyor' };
3260
+ if (isNpmMode()) { npmSelfUpdate(); return { ok: true, npm: true }; }
3261
+ if (!autoUpdater) return { ok: false, error: 'updater kullanılamıyor' };
3161
3262
  if (!updateState.downloaded) return { ok: false, error: 'indirilmiş sürüm yok — önce /update' };
3162
3263
  try { autoUpdater.quitAndInstall(); return { ok: true }; } catch (e) {
3163
3264
  return { ok: false, error: String((e && e.message) || e) };
@@ -3175,33 +3276,69 @@ ipcMain.handle('update:setAuto', (_e, cfg) => {
3175
3276
  });
3176
3277
 
3177
3278
  /* /update komutu (masaüstü + WA): hedefi kaydet, kontrol başlat */
3279
+ /* npm modunda güncelle-şimdi: sürüm kontrolü + numaralarıyla bildir + kendi kendini güncelle */
3280
+ function npmUpdateNow(reply /* fn(text) */) {
3281
+ const current = app.getVersion();
3282
+ getNpmLatest(true).then((v) => {
3283
+ if (v && isNewerVersion(v, current)) {
3284
+ updateState.available = true;
3285
+ updateState.version = v;
3286
+ reply(`🔄 *Güncelleniyor*\nMevcut: v${current}\nYeni: v${v}\nKapanıp yeniden açılıyorum…`);
3287
+ setTimeout(() => npmSelfUpdate(), 1500);
3288
+ } else {
3289
+ reply(`✅ *Güncelsin* — v${current} zaten en son sürüm.`);
3290
+ }
3291
+ }).catch(() => reply('Sürüm kontrol edilemedi — bağlantıyı kontrol et.'));
3292
+ }
3293
+
3178
3294
  async function runUpdateCommand(reply /* fn(text) */) {
3179
- if (isNpmMode()) {
3180
- reply('npm kurulumu — güncellemek için:\n1) Uygulamayı kapat\n2) Terminalde: `beast-agent update`\n3) Tekrar: `beast-agent`');
3181
- return;
3182
- }
3295
+ const current = app.getVersion();
3296
+ if (isNpmMode()) return npmUpdateNow(reply);
3183
3297
  if (!autoUpdater) {
3184
- reply('Updater bu modda kullanılamıyor (taşınabilir sürüm). Yeni exe: github.com/algokodcom/beast-agent/releases');
3298
+ reply('Updater bu modda kullanılamıyor. Yeni sürüm: github.com/algokodcom/beast-agent/releases');
3185
3299
  return;
3186
3300
  }
3187
- reply(`🔍 v${app.getVersion()}güncellemeler kontrol ediliyor…`);
3188
- try { await autoUpdater.checkForUpdates(); } catch (e) {
3189
- reply('Güncelleme kontrolü başarısız: ' + String((e && e.message) || e));
3301
+ /* önce npm registry'den sürüm bilgisi kullanıcıya numaraları söyle */
3302
+ const v = await getNpmLatest(true);
3303
+ if (v && isNewerVersion(v, current)) {
3304
+ updateState.available = true;
3305
+ updateState.version = v;
3306
+ emitUpdateEvent();
3307
+ reply(`🔄 *Yeni sürüm bulundu*\nMevcut: v${current}\nYeni: v${v}\nİndiriliyor… (kurulum için: /update now)`);
3308
+ try { await autoUpdater.checkForUpdates(); } catch (e) {
3309
+ reply('İndirme başlatılamadı: ' + String((e && e.message) || e));
3310
+ }
3311
+ } else if (v) {
3312
+ reply(`✅ *Güncelsin* — v${current} en son sürüm.`);
3313
+ } else {
3314
+ reply(`🔍 Kontrol ediliyor (mevcut sürüm v${current})…`);
3315
+ try { await autoUpdater.checkForUpdates(); } catch (e) {
3316
+ reply('Kontrol başarısız: ' + String((e && e.message) || e));
3317
+ }
3190
3318
  }
3191
3319
  }
3192
3320
 
3193
3321
  /* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
3194
- ipcMain.handle('stt:transcribe', async (_e, b64) => {
3322
+ /* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
3323
+ ipcMain.handle('stt:transcribe', async (_e, b64, lang) => {
3195
3324
  try {
3196
3325
  const buf = Buffer.from(String(b64 || '').split(',').pop() || '', 'base64');
3197
3326
  if (!buf.length) return { ok: false, error: 'boş ses kaydı' };
3198
- const text = await transcribeAudio(buf);
3327
+ const text = await transcribeAudio(buf, lang === 'en' || lang === 'auto' ? lang : undefined);
3199
3328
  return text ? { ok: true, text } : { ok: false, error: 'konuşma algılanamadı' };
3200
3329
  } catch (e) {
3201
3330
  return { ok: false, error: String((e && e.message) || e) };
3202
3331
  }
3203
3332
  });
3204
3333
 
3334
+ ipcMain.handle('stt:lang:get', () => settings.sttLang || 'tr');
3335
+ ipcMain.handle('stt:lang:set', (_e, lang) => {
3336
+ const v = ['auto', 'tr', 'en'].includes(String(lang)) ? String(lang) : 'tr';
3337
+ settings.sttLang = v;
3338
+ saveSettings();
3339
+ return v;
3340
+ });
3341
+
3205
3342
  ipcMain.handle('limits:get', () => {
3206
3343
  const l = settings.limits || {};
3207
3344
  return {
package/src/preload.js CHANGED
@@ -44,7 +44,9 @@ contextBridge.exposeInMainWorld('beast', {
44
44
  secGet: () => ipcRenderer.invoke('sec:get'),
45
45
  secSet: (cfg) => ipcRenderer.invoke('sec:set', cfg),
46
46
  approvalRespond: (id, ok, always) => ipcRenderer.invoke('approval:respond', { id, ok, always }),
47
- sttTranscribe: (b64) => ipcRenderer.invoke('stt:transcribe', b64),
47
+ sttTranscribe: (b64, lang) => ipcRenderer.invoke('stt:transcribe', b64, lang),
48
+ sttLangGet: () => ipcRenderer.invoke('stt:lang:get'),
49
+ sttLangSet: (lang) => ipcRenderer.invoke('stt:lang:set', lang),
48
50
  updateStatus: () => ipcRenderer.invoke('update:status'),
49
51
  updateCheck: () => ipcRenderer.invoke('update:check'),
50
52
  updateInstall: () => ipcRenderer.invoke('update:install'),
@@ -72,7 +72,8 @@
72
72
  up_uptodate: 'Güncel',
73
73
  up_downloading: 'İndiriliyor…',
74
74
  up_downloaded: 'İndirildi — kurulum için hazır',
75
- up_npm_mode: 'npm kurulumu — güncelleme: beast-agent update',
75
+ up_npm_mode: 'npm kurulumu — güncellemeler otomatik kontrol edilir',
76
+ up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast-agent update',
76
77
  up_auto_check: 'Otomatik sürüm kontrolü (açılış + 6 saatte bir)',
77
78
  up_auto_dl: 'Yeni sürümü otomatik indir (kapanışta kurulur)',
78
79
  up_check_now: 'Şimdi Kontrol Et',
@@ -422,7 +423,8 @@
422
423
  up_uptodate: 'Up to date',
423
424
  up_downloading: 'Downloading…',
424
425
  up_downloaded: 'Downloaded — ready to install',
425
- up_npm_mode: 'npm install — update via: beast-agent update',
426
+ up_npm_mode: 'npm install — updates are checked automatically',
427
+ up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast-agent update',
426
428
  up_auto_check: 'Automatic version check (on startup + every 6 hours)',
427
429
  up_auto_dl: 'Auto-download new versions (installed on quit)',
428
430
  up_check_now: 'Check Now',
@@ -93,6 +93,7 @@
93
93
  <div id="settingsDialog">
94
94
  <aside id="setTabs">
95
95
  <div class="set-tabs-title" data-i18n="set_title">AYARLAR</div>
96
+ <nav class="set-tabs-list">
96
97
  <button class="tab active" data-tab="provider" data-i18n="tab_provider">Provider</button>
97
98
  <button class="tab" data-tab="fallout" data-i18n="tab_fallout">Fallout</button>
98
99
  <button class="tab" data-tab="memory" data-i18n="tab_memory">Memory</button>
@@ -110,6 +111,7 @@
110
111
  <button class="tab" data-tab="limits" data-i18n="tab_limits">Limit</button>
111
112
  <button class="tab" data-tab="sec" data-i18n="tab_security">Güvenlik</button>
112
113
  <button class="tab" data-tab="update" data-i18n="tab_update">Güncelleme</button>
114
+ </nav>
113
115
  <div class="set-tabs-foot">
114
116
  <div id="beastCodeBox" class="beast-code" data-i18n-title="bc_label" title="Beast Kodu">
115
117
  <div class="bc-row">
@@ -1550,16 +1550,16 @@ let updatePaneTimer = null;
1550
1550
 
1551
1551
  function renderUpdateStateHtml(st) {
1552
1552
  let status;
1553
- if (st.npm) status = _t('up_npm_mode');
1554
- else if (st.error) status = '⚠ ' + st.error;
1553
+ if (st.error) status = '\u26A0\uFE0E ' + st.error;
1555
1554
  else if (st.downloaded) status = _t('up_downloaded') + ' (v' + (st.version || '?') + ')';
1556
1555
  else if (st.progress) status = _t('up_downloading') + ' %' + st.progress.percent;
1557
1556
  else if (st.checking) status = _t('up_checking');
1558
1557
  else if (st.available) status = _t('up_available') + ' (v' + (st.version || '?') + ')';
1558
+ else if (st.npm) status = _t('up_npm_mode');
1559
1559
  else if (st.available === false) status = _t('up_uptodate');
1560
1560
  else status = '—';
1561
1561
 
1562
- return `<div class="usage-stat" style="margin-top:10px"><div class="us-label">${_t('up_status')}</div><div class="us-value" style="font-size:14px">${escapeHtml(status)}</div></div>`;
1562
+ return `<div class="usage-stat" id="upStatusBox"><div class="us-label">${_t('up_status')}</div><div class="us-value" style="font-size:14px">${escapeHtml(status)}</div></div>`;
1563
1563
  }
1564
1564
 
1565
1565
  async function renderUpdatePane() {
@@ -1582,8 +1582,10 @@ async function renderUpdatePane() {
1582
1582
  <label class="lock-row"><input type="checkbox" id="upAutoDl" ${st.autoDownload ? 'checked' : ''}/><span>${_t('up_auto_dl')}</span></label>
1583
1583
  </div>` +
1584
1584
  (st.npm
1585
- ? `<div class="codeblock npm" style="margin-top:10px"><pre># uygulamayı kapat, sonra:
1586
- beast-agent update</pre></div>`
1585
+ ? `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1586
+ <button id="upInstall" class="btn ghost" ${st.available ? '' : 'disabled style="opacity:.45;cursor:default"'}>${_t('up_install_now')}</button>
1587
+ </div>
1588
+ <div class="sub" style="margin-top:8px">${_t('up_npm_note')}</div>`
1587
1589
  : `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1588
1590
  <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
1589
1591
  <button id="upInstall" class="btn ghost" ${st.downloaded ? '' : 'disabled style="opacity:.45;cursor:default"'}>${_t('up_install_now')}</button>
@@ -1599,7 +1601,9 @@ beast-agent update</pre></div>`
1599
1601
  if (btnCheck) btnCheck.addEventListener('click', async () => {
1600
1602
  btnCheck.disabled = true;
1601
1603
  const r = await beast.updateCheck().catch(() => ({ ok: false, error: 'ipc' }));
1604
+ btnCheck.disabled = false;
1602
1605
  if (!r.ok && r.error) toast(r.error);
1606
+ renderUpdatePane();
1603
1607
  });
1604
1608
  const btnInstall = pane.querySelector('#upInstall');
1605
1609
  if (btnInstall) btnInstall.addEventListener('click', async () => {
@@ -1607,16 +1611,13 @@ beast-agent update</pre></div>`
1607
1611
  if (!r.ok && r.error) toast(r.error);
1608
1612
  });
1609
1613
 
1610
- /* indirme ilerlemesi için sekme açıkken canlı tazele */
1614
+ /* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
1611
1615
  updatePaneTimer = setInterval(() => {
1612
1616
  if ($('#tab-update').hidden || els.settingsOverlay.hidden) { clearInterval(updatePaneTimer); return; }
1613
1617
  beast.updateStatus().then((s) => {
1614
1618
  if (!s) return;
1615
- const box = pane.querySelector('.us-value');
1616
- if (box) {
1617
- const wrap = pane.querySelector('.usage-stat');
1618
- if (wrap) wrap.outerHTML = renderUpdateStateHtml(s);
1619
- }
1619
+ const box = pane.querySelector('#upStatusBox');
1620
+ if (box) box.outerHTML = renderUpdateStateHtml(s);
1620
1621
  }).catch(() => {});
1621
1622
  }, 1000);
1622
1623
  }
@@ -2194,6 +2195,7 @@ function onEvent(ev) {
2194
2195
  }
2195
2196
  if (ev.type === 'update') {
2196
2197
  if (ev.downloaded) toast(_t('up_downloaded') + ' (v' + (ev.version || '?') + ') — /update now');
2198
+ else if (ev.available && ev.version && ev.version !== ev.current) toast(_t('up_available') + ' (v' + ev.version + ')');
2197
2199
  if (!els.settingsOverlay.hidden && setTab === 'update') renderUpdatePane();
2198
2200
  return;
2199
2201
  }
@@ -3051,6 +3053,10 @@ async function init() {
3051
3053
  });
3052
3054
  document.addEventListener('langchange', syncLangBtn);
3053
3055
  document.addEventListener('langchange', renderActiveSettingsTab);
3056
+ /* Whisper STT dili arayüz dilini izler (WA sesli notları da) */
3057
+ const syncSttLang = () => { try { beast.sttLangSet((window.I18N && window.I18N.lang) || 'tr'); } catch {} };
3058
+ syncSttLang();
3059
+ document.addEventListener('langchange', syncSttLang);
3054
3060
  }
3055
3061
 
3056
3062
  els.attachBtn.addEventListener('click', () => els.fileInput.click());
@@ -3091,7 +3097,9 @@ async function init() {
3091
3097
  if (!blob.size) return;
3092
3098
  toast(_t('mic_transcribing'));
3093
3099
  const b64 = await blobToDataUrl(blob);
3094
- const r = await beast.sttTranscribe(b64).catch(() => ({ ok: false, error: 'ipc' }));
3100
+ /* arayüz dili = Whisper dili: TR ise Türkçe, EN ise İngilizce algılar */
3101
+ const uiLang = (window.I18N && window.I18N.lang) || 'tr';
3102
+ const r = await beast.sttTranscribe(b64, uiLang).catch(() => ({ ok: false, error: 'ipc' }));
3095
3103
  if (r && r.ok && r.text) {
3096
3104
  els.input.value = (els.input.value ? els.input.value.replace(/\s+$/, '') + ' ' : '') + r.text;
3097
3105
  els.input.focus();
@@ -1096,11 +1096,33 @@ body.browser-open #toast { left: calc((100vw - var(--bw, 480px)) / 2); }
1096
1096
  flex-direction: column;
1097
1097
  padding: 16px 10px 12px;
1098
1098
  }
1099
- .set-tabs-title {
1100
- font-size: 11px;
1101
- letter-spacing: 2px;
1102
- color: var(--muted);
1103
- padding: 0 12px 10px;
1099
+ .set-tabs-title {
1100
+ font-size: 11px;
1101
+ letter-spacing: 2px;
1102
+ color: var(--muted);
1103
+ padding: 0 12px 10px;
1104
+ }
1105
+ /* sekme listesi: küçük ekranda taşarsa içeride kayar — alt kısım (Beast Kodu + Kapat) sabit kalır */
1106
+ .set-tabs-list {
1107
+ display: flex;
1108
+ flex-direction: column;
1109
+ align-items: stretch;
1110
+ flex: 1 1 auto;
1111
+ min-height: 0;
1112
+ overflow-y: auto;
1113
+ padding-right: 2px;
1114
+ margin: 0 -2px;
1115
+ scrollbar-width: thin;
1116
+ scrollbar-color: var(--border) transparent;
1117
+ }
1118
+ .set-tabs-list::-webkit-scrollbar { width: 8px; }
1119
+ .set-tabs-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }
1120
+ .set-tabs-list::-webkit-scrollbar-thumb:hover { background: var(--muted); }
1121
+ .set-tabs-list::-webkit-scrollbar-track { background: transparent; }
1122
+ @media (max-height: 860px) {
1123
+ #setTabs { padding: 10px 8px; }
1124
+ .set-tabs-title { padding: 0 12px 6px; }
1125
+ #setTabs .tab { padding: 6px 12px; font-size: 12.5px; margin-bottom: 1px; }
1104
1126
  }
1105
1127
  #setTabs .tab {
1106
1128
  text-align: left;