beast-agent 1.2.3 → 1.3.1

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": "1.2.3",
4
+ "version": "1.3.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -16,10 +16,14 @@ const skills = require('./skills');
16
16
  const { estTokens, estMsgTokens } = require('./tokens');
17
17
  const log = require('./logger');
18
18
 
19
- const MAX_TURNS = 25;
19
+ const MAX_TURNS = 40;
20
20
  const SUB_MAX_TURNS = 8;
21
21
  const HISTORY_TOKEN_BUDGET = 18000;
22
22
  const TOOL_OUT_KEEP = 1200;
23
+ /* read_file sonuçları dosya HAFIZASI'dır: 7200 char'a kırpma modeli aynı
24
+ dosyayı offset offset tekrar okuyor → tur limiti doluyordu. Son okuma
25
+ oturum sonuna kadar tam tutulur (yaklaşık 48k char ≈ orta boy kaynak dosya). */
26
+ const READ_OUT_KEEP = 48000;
23
27
  const USER_MAX = 8000;
24
28
  /* observe() ile enjekte edilen bağlam mesajlarının öneki — ardışık bağlam
25
29
  mesajlarının tek mesajda birleşmesi bu işaretle anlaşılır */
@@ -1385,9 +1389,9 @@ class Engine {
1385
1389
  ? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n'
1386
1390
  : '') +
1387
1391
  'WORKFLOW (her işte bu sıra):\n' +
1388
- '1) BAĞLAM: değiştirmeden önce ilgili dosyaları OKU — read_file satır numaralı döner (N: içerik); büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter — içeriği sonraki turlarda da bağlamda KALIR, edit yaptıktan sonra dosyayı yeniden okuma (güncel durum = son okuma + kendi editlerin). İçerik araması için grep (regex), dosya adı için glob kullan; varsayım yapma, mevcut stili/konvansiyonu takip et.\n' +
1392
+ '1) BAĞLAM: değiştirmeden önce ilgili dosyaları OKU — read_file satır numaralı döner (N: içerik); büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter — okuduğun içerik oturum SONUNA KADAR bağlamda KALIR, edit yaptıktan sonra dosyayı yeniden okuma YASAK (güncel durum = son okuma + kendi editlerin). İçerik araması için grep (regex), dosya adı için glob kullan; varsayım yapma, mevcut stili/konvansiyonu takip et.\n' +
1389
1393
  '2) PLAN: 2+ adımlı işlerde İLK EYLEM todo_write olsun (2-6 madde); her adım bitince status:"done" ile GÜNCELLE — liste bitene kadar iş bitmiş sayılmaz.\n' +
1390
- '3) EDİT: VAR OLAN dosyada önce edit_file kullan (old_string/new_string ile yalnız ilgili bölümü değiştir; birden çok eşleşme varsa bağlam ekle ya da replace_all); write_file yalnız YENİ dosya ya da tam yeniden yazım için. İlgisiz yeniden biçimleme/kayıt boşluk değişikliği YAPMA. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA YASAK; sonraki editi önceki okuduğun içerik + kendi değişikliklerin üzerinden zincirle.\n' +
1394
+ '3) EDİT: VAR OLAN dosyada önce edit_file kullan (old_string/new_string ile yalnız ilgili bölümü değiştir; birden çok eşleşme varsa bağlam ekle ya da replace_all); write_file yalnız YENİ dosya ya da tam yeniden yazım için. Dosya işlemleri için ÖZEL ARAÇLARI kullan (edit_file/write_file/read_file/grep/glob); run_command terminal işlerindir (build, git, kurulum, paket) — dosya düzenlemeyi komut/scripte yedirme, edit_file ile yap. İlgisiz yeniden biçimleme/kayıp boşluk değişikliği YAPMA. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA YASAK; sonraki editi önceki okuduğun içerik + kendi değişikliklerin üzerinden zincirle.\n' +
1391
1395
  '4) DOĞRULA: edit sonrası mümkünse derle/test et/lint çalıştır (run_command); hata varsa DÜZELT ve TEKRAR dene (en fazla 2 doğrulama turu) — kırmızı bırakma. Doğrulama read_file ile DEĞİL run_command ile yapılır.\n' +
1392
1396
  '5) RAPOR: 1-3 satır — ne değişti + doğrulama sonucu (ör. "npm test ✓ 154/154"). Uzun açıklama yok.\n' +
1393
1397
  'CANLI ÖNİZLEME: web sitesi/app üretirsen son adımda ÇALIŞIR halde bırak (dev server ayakta ya da index.html yazılmış) — panel iş bitince ürettiğin şeyi dahili tarayıcıda otomatik canlı açar.\n' +
@@ -1478,13 +1482,13 @@ class Engine {
1478
1482
  '# ARAÇ KURALLARI\n' +
1479
1483
  [
1480
1484
  'Basit soruları araç kullanmadan doğrudan cevapla — hız önceliklidir.',
1481
- 'Kod/dosya işlerinde: içerik araması grep (regex), dosya adı araması glob, VAR OLAN dosyayı değiştirme edit_file (write_file yalnız yeni dosya/tam yeniden yazım). read_file satır numaralı döner; büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA; önceki okuduğun içerik + kendi değişikliklerin üzerinden devam et.',
1485
+ 'Kod/dosya işlerinde: içerik araması grep (regex), dosya adı araması glob, VAR OLAN dosyayı değiştirme edit_file (write_file yalnız yeni dosya/tam yeniden yazım). read_file satır numaralı döner; büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter — okunan içerik oturum sonuna kadar bağlamda kalır, dosyayı tekrar okuma. Dosya işlemlerinde özel araçları kullan (edit_file/write_file/read_file/grep/glob); run_command terminal işlerindir (build, git, kurulum, paket) — dosya düzenlemeyi komutla değil edit_file ile yap. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA; önceki okuduğun içerik + kendi değişikliklerin üzerinden devam et.',
1482
1486
  'Kullanıcı bir tarihte/saatte hatırlatılmasını isterse set_reminder kullan; when değerini ORTAMdaki bugüne göre hesapla (yerel saat). "Her sabah/gün/hafta" gibi tekrarlı isteklerde repeat alanını da ver (daily/weekly/monthly/weekdays veya cron).',
1483
1487
  'Kullanıcı kalıcı bir arka plan takibi isterse (fiyat eşiği, pil seviyesi, sayfa değişikliği) watcher_add ile izleyici kur; kurduktan sonra watcher_list ile doğrula ve kullanıcıya koşulu + kontrol sıklığını kısaca bildir.',
1484
1488
  'Anlık olay takipleri için (yeni mail, fiyat eşiği, dosya değişimi, webhook) event_subscribe kullan — cron/polling gerekmez; listeyi event_list ile göster, vazgeçirirse event_unsubscribe.',
1485
1489
  'Kullanıcı "artık hep böyle yap / bunu unutma" tarzı kalıcı talimat verirse kural olarak kaydet: sohbette /rule <metin> kullanmasını söyle VEYA kullanıcı isterse event_subscribe ile olaya bağlan (mail/fiyat/dosya/webhook).',
1486
1490
  'Kullanıcının mesajında 2+ ayrı iş/hedef varsa (örn "X yap ve sonra Y\u2019i kontrol et") KODLAMAYA/İŞE BAŞLAMADAN önce todo_write ile plan çıkar ve sırayla yürüt; her adımı tamamlarken güncelle. LİSTE DİSİPLİNİ: her adım bittiği AN status:"done" yap; son cevabını vermeden önce tüm maddeler done olmalı — yapılmayacaksa listeden düş. Listeyi yarım bırakma.',
1487
- 'HIZ KURALI: Bağımsız işleri AYNI turda birden çok tool_calls ile PARALEL ver. Küçük işleri tek tek çağırma — her ayrı araç turu 5-15 sn LLM gecikmesidir: 3+ küçük komutu TEK run_command\u2019te `;` ile zincirle (örn `git status; node -v; dir`), döngülü/çoklu işleri TEK python_run betiğinde topla, çok dosyalık değişikliği TEK script ile yap. Her küçük işlem için ayrı araç çağrısı açmak yavaşlığın 1 numaralı sebebidir.',
1491
+ 'HIZ KURALI: Bağımsız işleri AYNI turda birden çok tool_calls ile PARALEL ver. Küçük işleri tek tek çağırma — her ayrı araç turu 5-15 sn LLM gecikmesidir: 3+ küçük komutu TEK run_command\u2019te `;` ile zincirle (örn `git status; node -v; dir`), döngülü/çoklu işleri TEK python_run betiğinde topla, çok dosyalık değişikliği TEK script ile DEĞİL AYNI turda PARALEL edit_file çağrılarıyla yap (dosya düzenlemeyi komut/scripte yedirme). Her küçük işlem için ayrı araç çağrısı açmak yavaşlığın 1 numaralı sebebidir.',
1488
1492
  this.ceoMode
1489
1493
  ? 'Bağımsız alt-işleri run_background ile PARALEL ajana devret; işi KENDİN YÜRÜTME — emri ver, takip et, raporla.'
1490
1494
  : 'Bağımsız alt-işleri delegate_task ile devret; kendi başına halledebileceğin işleri devretme.',
@@ -1617,42 +1621,18 @@ class Engine {
1617
1621
  return m;
1618
1622
  });
1619
1623
  /* opencode file-state portu: read_file sonuçları bağlamda YAŞAR —
1620
- 1200 char'a kırpma modeli "unuttu" diye tekrar tekrar okumaya
1621
- zorluyordu. Her dosyanın yalnız EN SON okuması tam tutulur (diskte
1622
- zaten TOOL_OUT_KEEP*6 ile sınırlı); daha eski okumalar stub'a iner. */
1623
- const READ_KEEP = TOOL_OUT_KEEP * 6; /* depolama tavanıyla hizalı */
1624
- const readPathOf = (m) => {
1625
- const s = String(m.content || '');
1626
- try {
1627
- const j = JSON.parse(s);
1628
- if (j && j.path) return String(j.path);
1629
- } catch {}
1630
- const mm = /"path"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(s.slice(0, 600));
1631
- return mm ? mm[1] : null;
1632
- };
1633
- const lastRead = new Map(); /* dosya yolu → flat'teki en son okuma indeksi */
1634
- for (let k = 0; k < flat.length; k++) {
1635
- const m = flat[k];
1636
- if (m.role === 'tool' && m.name === 'read_file') {
1637
- const p = readPathOf(m);
1638
- if (p) lastRead.set(p, k);
1639
- }
1640
- }
1624
+ kırpma modeli "unuttu" diye tekrar tekrar okumaya zorluyordu.
1625
+ opencode'ta araç çıktıları tam tutulur; eski okumalar stub'a inmez
1626
+ bütçe dolunca en eski birimler doğal olarak pencere dışı kalır
1627
+ (opencode prune mantığıyla aynı). Okumalar READ_OUT_KEEP ile sınırlı. */
1628
+ const READ_KEEP = READ_OUT_KEEP; /* depolama tavanıyla hizalı */
1641
1629
  for (let k = 0; k < flat.length; k++) {
1642
1630
  const m = flat[k];
1643
1631
  if (m.role !== 'tool') continue;
1644
1632
  const len = String(m.content || '').length;
1645
1633
  let content;
1646
1634
  if (m.name === 'read_file') {
1647
- const p = readPathOf(m);
1648
- if (p && lastRead.get(p) !== k) {
1649
- content =
1650
- '[aynı dosyanın daha YENİ bir okuması bağlamda — bu ESKİ okuma bağlam tasarrufu için kırpıldı; en son okumayı esas al]';
1651
- } else if (len > READ_KEEP) {
1652
- content = String(m.content).slice(0, READ_KEEP) + '\n…[kırpıldı — devam için offset ile oku]';
1653
- } else {
1654
- content = null; /* TAM KORUNUR — kırpma yok */
1655
- }
1635
+ content = len > READ_KEEP ? String(m.content).slice(0, READ_KEEP) + '\n…[kırpıldı — devam için offset ile oku]' : null; /* TAM KORUNUR */
1656
1636
  } else {
1657
1637
  content = len > TOOL_OUT_KEEP * 2 ? String(m.content).slice(0, TOOL_OUT_KEEP) + '\n…[kırpıldı]' : null;
1658
1638
  }
@@ -1905,10 +1885,28 @@ class Engine {
1905
1885
  const msgs = session.messages;
1906
1886
  let total = 0, pruned = 0, turns = 0;
1907
1887
  const targets = [];
1888
+ /* her dosyanın EN SON read_file sonucu asla prune edilmez — dosya
1889
+ hafızası oturum sonuna kadar yaşar; eski okumalar serbest */
1890
+ const readPathOf = (m) => {
1891
+ const s = String(m.content || '');
1892
+ const mm = /"path"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(s.slice(0, 600));
1893
+ return mm ? mm[1] : null;
1894
+ };
1895
+ const lastReadId = new Map();
1896
+ for (const m of msgs) {
1897
+ if (m.role === 'tool' && m.name === 'read_file' && m.tool_call_id) {
1898
+ const p = readPathOf(m);
1899
+ if (p) lastReadId.set(p, m.tool_call_id);
1900
+ }
1901
+ }
1908
1902
  for (let i = msgs.length - 1; i >= 0; i--) {
1909
1903
  const m = msgs[i];
1910
1904
  if (m.role === 'user') turns++;
1911
1905
  if (turns < 2) continue; // en yeni tur dokunulmaz
1906
+ if (
1907
+ m.role === 'tool' && m.name === 'read_file' && m.tool_call_id &&
1908
+ readPathOf(m) && lastReadId.get(readPathOf(m)) === m.tool_call_id
1909
+ ) continue; // son dosya okuması korumalı
1912
1910
  if (m.role === 'tool' && typeof m.content === 'string' && m.content.length > 400) {
1913
1911
  const est = Math.ceil(estTokens(m.content) * this.tokRatio);
1914
1912
  total += est;
@@ -2697,6 +2695,7 @@ class Engine {
2697
2695
  (job.task ? `GÖREV: ${job.task}\n` : '') +
2698
2696
  `Ortam: Windows + PowerShell; çalışma klasörü: ${this.workspace}\n` +
2699
2697
  `GENEL AJAN DİSİPLİNİ: görevi A'dan Z'ye yürüt — bağlam topla, uygula, DOĞRULA; yarım bırakma; doğrulama sonucunu rapora yaz.\n` +
2698
+ `DOSYA KURALI: dosya işlemlerinde özel araçları kullan — VAR OLAN dosyayı edit_file ile düzenle, yeniyi write_file ile yaz, okuma/arama read_file/grep/glob; run_command terminal işlerindir (build, git, kurulum). Bir dosyayı BİR KEZ oku — içerik bağlamda kalır, tekrar okuma.\n` +
2700
2699
  (proj ? `PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT — daima uy):\n${proj}\n` : '') +
2701
2700
  `HIZ KURALLARI:\n` +
2702
2701
  `- Döngülü işleri (çok URL/dosya/sayfa, tekrarlı parse-hesap) TEK python_run betiğinde topluca bitir.\n` +
@@ -2711,7 +2710,7 @@ class Engine {
2711
2710
 
2712
2711
  /* uzun bg işlerinde geçmiş şişer → payload büyür, hız düşer. Son N mesaj kalsın
2713
2712
  (+ asıl görev mesajı); kopuk tool çifti sınırı hizalanır */
2714
- static BG_KEEP_MSGS = 24;
2713
+ static BG_KEEP_MSGS = 48;
2715
2714
  _bgTrim(session) {
2716
2715
  const max = Engine.BG_KEEP_MSGS;
2717
2716
  const msgs = session.messages;
@@ -2946,17 +2945,23 @@ class Engine {
2946
2945
  let nudged = false; // görev listesi disiplini: run başına en fazla 1 hatırlatma
2947
2946
  let wrapNudged = false; // tur limitine yaklaşınca zarif kapanış (bg ajanlar)
2948
2947
  let usageTotal = 0; // en yüksek görülen total_tokens — compaction tetiği
2949
- let compacted = false; // run başına en fazla 1 compaction
2950
- /* opencode agent.steps port: özel ajanın kendi tur limiti (2-60) */
2948
+ let lastCompactTurn = -99; // opencode: compaction run başına 1 DEĞİL her taşmada tekrar eder
2949
+ /* opencode agent.steps port: özel ajanın kendi tur limiti (2-60).
2950
+ ANA OTURUMDA SERT TUR LİMİTİ YOK (opencode birebir): bağlam dolunca
2951
+ compaction özetler ve işin ORTASINDA devam eder; sonsuz döngüyü
2952
+ doom-loop koruması + kullanıcı ■ kırar. bg ajanlarda kapanış
2953
+ disiplini için tavan kalır. */
2951
2954
  const runDef = this._agentDefFor(session, !!session.bgJob);
2952
- const maxTurns = Math.max(2, Math.min(60, (runDef && runDef.steps) || MAX_TURNS));
2955
+ const maxTurns = session.bgJob
2956
+ ? Math.max(2, Math.min(60, (runDef && runDef.steps) || MAX_TURNS))
2957
+ : Infinity;
2953
2958
  const recentSigs = []; // doom-loop dedektörü: son araç imzaları
2954
2959
  /* Beast Code canlı önizleme: run boyunca üretilen eserleri topla —
2955
2960
  iş bitince dahili tarayıcıda CANLI açılır (site/dev server/HTML) */
2956
2961
  const bcArtifacts = session.bcCode ? { html: [], serverUrl: null } : null;
2957
2962
  for (let turn = 0; turn < maxTurns; turn++) {
2958
- /* SÜRE SINIRI YOK ama sonsuz tur da yok: bg ajan son 3 tura gelirken
2959
- "raporu yaz ve bitir" uyarısı alır; maxTurns sert tavan olarak kalır. */
2963
+ /* SÜRE SINIRI YOK. Ana oturum sınırsız tur koşar (opencode birebir);
2964
+ yalnız bg ajan son 3 tura gelirken "raporu yaz ve bitir" uyarısı alır. */
2960
2965
  if (session.bgJob && turn === maxTurns - 3 && !wrapNudged) {
2961
2966
  wrapNudged = true;
2962
2967
  const wmsg = {
@@ -2970,26 +2975,16 @@ class Engine {
2970
2975
  } catch {}
2971
2976
  emit({ type: 'message', message: wmsg });
2972
2977
  }
2973
- /* opencode port (prompt.ts:1281): ana oturumda son turda zorunlu
2974
- nihai cevaptur limiti sessizce dolup boş 'done' dönmesin */
2975
- if (!session.bgJob && turn === maxTurns - 1 && !wrapNudged) {
2976
- wrapNudged = true;
2977
- const wmsg = {
2978
- role: 'user',
2979
- content:
2980
- '[SON TUR] Tur limitine ulaşıldı: YENİ araç çağırma — eldeki bilgilerle NİHAİ cevabını şimdi ver. Eksikler varsa açıkça belirt.',
2981
- };
2982
- session.messages.push(wmsg);
2983
- try {
2984
- this._append(session, wmsg);
2985
- } catch {}
2986
- emit({ type: 'message', message: wmsg });
2987
- }
2988
- /* opencode port (prompt.ts:1161 + overflow.ts): bağlam dolduysa
2989
- compaction checkpoint — kuyruk korunur, baş özetlenir */
2978
+ /* opencode port (prompt.ts:1161 + overflow.ts + processor.ts "compact"):
2979
+ bağlam dolduysa compaction kuyruk korunur, baş özetlenir ve işin
2980
+ ORTASINDA devam edilir. Run içinde TEKRAR EDELİR; taze compaction'dan
2981
+ hemen sonra hâlâ taşarsa opencode ContextOverflowError gibi güvenli dur. */
2990
2982
  const selNow = this.sessionModel.get(String(sid)) || this.modelFor(null) || this.sel;
2991
- if (!compacted && this._overContext(session, selNow, usageTotal)) {
2992
- compacted = true;
2983
+ if (this._overContext(session, selNow, usageTotal)) {
2984
+ if (turn - lastCompactTurn < 4) {
2985
+ throw new Error('ContextOverflow: compaction sonrası bağlam hâlâ model limitinin üstünde — oturum çok büyük');
2986
+ }
2987
+ lastCompactTurn = turn;
2993
2988
  await this._compactHistory(session, selNow, ctrl.signal);
2994
2989
  }
2995
2990
  this._bgTrim(session); // #21 bg geçmişini olabildiğince ince tut
@@ -3147,7 +3142,7 @@ class Engine {
3147
3142
  }
3148
3143
  if (injectedImage || diffView) out = JSON.stringify(obj);
3149
3144
  } catch {}
3150
- const toolMsg = { role: 'tool', tool_call_id: tc.id, name, content: out.slice(0, TOOL_OUT_KEEP * 6) };
3145
+ const toolMsg = { role: 'tool', tool_call_id: tc.id, name, content: out.slice(0, name === 'read_file' ? READ_OUT_KEEP : TOOL_OUT_KEEP * 6) };
3151
3146
  if (diffView) toolMsg.diffView = diffView; /* oturum dosyasına da düşer — geçmiş açılınca diff yeniden çizilir */
3152
3147
  session.messages.push(toolMsg);
3153
3148
  try {
@@ -3169,8 +3164,10 @@ class Engine {
3169
3164
  })
3170
3165
  );
3171
3166
  }
3167
+ /* yalnız bg ajanlar tavanla buraya düşebilir — ana oturum sınırsız koşar */
3172
3168
  emit({ type: 'done', usage: null });
3173
- this._bgFinish(sid, 'error', 'tur limiti doldu (maxTurns)'); } catch (e) {
3169
+ this._bgFinish(sid, 'error', 'bg görevi tur limitine ulaştı (maxTurns)');
3170
+ } catch (e) {
3174
3171
  const aborted = e && (e.name === 'AbortError' || ctrl.signal.aborted);
3175
3172
  if (aborted) {
3176
3173
  this._clearCrash(); // kullanıcı durdurdu — kurtarma yok
@@ -1327,7 +1327,7 @@ const definitions = [
1327
1327
  function: {
1328
1328
  name: 'run_command',
1329
1329
  description:
1330
- 'Run a PowerShell command on the user\'s Windows machine in the workspace directory and return combined stdout/stderr. Use for shell tasks, builds, git, installs, etc.',
1330
+ 'Executes a given PowerShell command on the user\'s Windows machine in the workspace directory and returns combined stdout/stderr. Use this tool for terminal operations like builds, git, npm installs, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead: read_file, write_file, edit_file, grep, glob.',
1331
1331
  parameters: {
1332
1332
  type: 'object',
1333
1333
  properties: {
@@ -1354,7 +1354,8 @@ const definitions = [
1354
1354
  '- Contents are returned with each line prefixed by its line number as `<line>: <content>`. For example, if a file has contents "foo\\n", you will receive "1: foo\\n".\n' +
1355
1355
  '- Any line longer than 2000 characters is truncated.\n' +
1356
1356
  '- Call this tool in parallel when you know there are multiple files you want to read.\n' +
1357
- '- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.',
1357
+ '- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.\n' +
1358
+ '- File contents you read STAY in your context for the WHOLE session. Do NOT re-read a file you already read: chain edits from your previous read plus your own edit_file/write_file results. Re-reading the same file (same or overlapping range) wastes turns and is forbidden unless the file actually changed on disk outside your own edits.',
1358
1359
  parameters: {
1359
1360
  type: 'object',
1360
1361
  properties: {
@@ -1402,7 +1403,8 @@ const definitions = [
1402
1403
  '- The edit will FAIL if `old_string` is not found in the file with an error "oldString not found in content".\n' +
1403
1404
  '- The edit will FAIL if `old_string` is found multiple times in the file with an error "Found multiple matches for oldString. Provide more surrounding lines in old_string to identify the correct match." Either provide a larger string with more surrounding context to make it unique or use `replace_all` to change every instance of `old_string`.\n' +
1404
1405
  '- Use `replace_all` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.\n' +
1405
- '- The result includes additions/deletions counts — the change is APPLIED to disk immediately; do NOT read the file again to verify.',
1406
+ '- The result includes additions/deletions counts — the change is APPLIED to disk immediately; do NOT read the file again to verify.\n' +
1407
+ '- Chain follow-up edits from what you already read plus your own previous edits — do NOT re-read the file between edits. Multiple edit_file calls to different files can be issued in PARALLEL in one turn.',
1406
1408
  parameters: {
1407
1409
  type: 'object',
1408
1410
  properties: {
package/src/main.js CHANGED
@@ -2439,6 +2439,7 @@ function reloadBackend() {
2439
2439
  emit: (ev) => {
2440
2440
  if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
2441
2441
  flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
2442
+ bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
2442
2443
  /* WA canlı iş takibi: oturum bir WhatsApp sohbetine bağlıysa araç
2443
2444
  hareketlerini (terminal, web, dosya…) kısa satırla bildir.
2444
2445
  Spam olmasın: sohbet başına en az 7 sn'de bir tek satır. */
@@ -5266,6 +5267,66 @@ function bcPanelEvent(sid, ev) {
5266
5267
  } catch {}
5267
5268
  }
5268
5269
 
5270
+ /* Beast Code mesaj kuyruğu: ajan çalışırken gelen mesajlar birikir, iş bitince
5271
+ TEK pakette (metinler \n ile birleşik) gönderilir. Boştayken de kısa pencere
5272
+ (debounce) vardır — ard arda hızlı mesajlar tek işte birleşir. */
5273
+ const BC_DEBOUNCE_MS = 900;
5274
+ const bcQueue = new Map(); /* klasör yolu → { timer, msgs[] } */
5275
+
5276
+ function bcQueuePush(ws, text) {
5277
+ let q = bcQueue.get(ws);
5278
+ if (!q) {
5279
+ q = { timer: null, msgs: [] };
5280
+ bcQueue.set(ws, q);
5281
+ }
5282
+ q.msgs.push({ text });
5283
+ return q;
5284
+ }
5285
+
5286
+ function bcFlush(folder) {
5287
+ const q = bcQueue.get(folder);
5288
+ if (!q || !q.msgs.length) return;
5289
+ const s = bcGetSession(folder); /* oturum yoksa oluşturur */
5290
+ if (engine.isBusy(s.id)) return; /* hâlâ çalışıyor — done/error eventini bekle */
5291
+ let merged = '';
5292
+ for (const m of q.msgs) if (m.text) merged += (merged ? '\n' : '') + m.text;
5293
+ if (!merged.trim()) {
5294
+ bcQueue.delete(folder);
5295
+ clearTimeout(q.timer);
5296
+ return;
5297
+ }
5298
+ s.workspace = folder;
5299
+ s.bcCode = true;
5300
+ engine.cache.set(s.id, s);
5301
+ if (engine.send(s.id, merged)) {
5302
+ bcQueue.delete(folder);
5303
+ clearTimeout(q.timer);
5304
+ } else {
5305
+ /* yarış: az önce meşgul oldu — kuyruk korunur, kısa süre sonra tekrar denenir */
5306
+ q.retries = (q.retries || 0) + 1;
5307
+ if (q.retries > 5) {
5308
+ bcQueue.delete(folder);
5309
+ clearTimeout(q.timer);
5310
+ try {
5311
+ if (win && !win.isDestroyed()) {
5312
+ win.webContents.send('agent:event', { sessionId: s.id, type: 'error', error: 'Beast Code mesajı gönderilemedi — panele tekrar yaz' });
5313
+ }
5314
+ } catch {}
5315
+ return;
5316
+ }
5317
+ setTimeout(() => { try { bcFlush(folder); } catch {} }, 800);
5318
+ }
5319
+ }
5320
+
5321
+ function bcFlushOnDone(ev) {
5322
+ if (!ev || (ev.type !== 'done' && ev.type !== 'error') || !ev.sessionId) return;
5323
+ for (const [folder, sid] of bcSessions) {
5324
+ if (String(sid) === String(ev.sessionId) && bcQueue.has(folder)) {
5325
+ setTimeout(() => { try { bcFlush(folder); } catch {} }, 150);
5326
+ }
5327
+ }
5328
+ }
5329
+
5269
5330
  ipcMain.handle('beastcode:send', async (_e, payload) => {
5270
5331
  const text = String((payload && payload.msg) || '').trim();
5271
5332
  if (!text) return { ok: false, error: 'boş mesaj' };
@@ -5274,13 +5335,16 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
5274
5335
  const modeM = /^\/(plan|build|auto)\b/i.exec(text);
5275
5336
 
5276
5337
  if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
5277
- if (engine.isBusy(bcSessions.get(ws))) return { ok: false, busy: true, error: 'önceki mesaj sürüyor ile durdurabilirsin' };
5338
+ /* oturumu HEMEN aç: cevap daima gerçek sessionId taşır renderer paneli bu id ile
5339
+ eşler; boşta/kuyrukta olsa bile id yoksa panel olayları eşleyemez ve ölü kalır */
5278
5340
  const s = bcGetSession(ws);
5279
5341
  s.workspace = ws; /* soldaki klasörde çalış */
5280
5342
  s.bcCode = true; /* todo disiplini + iş sonu hızlı kapanış (engine) */
5343
+ engine.cache.set(s.id, s);
5344
+ const busy = engine.isBusy(s.id);
5281
5345
  if (modeM) {
5346
+ /* mod değişimi anında uygulanır — meşgulken de (yalnız bayrak; engine çakışmaz) */
5282
5347
  s.bcMode = modeM[1].toLowerCase();
5283
- engine.cache.set(s.id, s);
5284
5348
  if (win && !win.isDestroyed()) {
5285
5349
  const body = modeM[1].toLowerCase() === 'plan'
5286
5350
  ? 'PLAN MODU — dosyaları okuyup inceler, KOD YAZMAZ; adım adım uygulama planı verir.'
@@ -5291,25 +5355,44 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
5291
5355
  }
5292
5356
  return { ok: true, sessionId: s.id, mode: s.bcMode };
5293
5357
  }
5294
- engine.cache.set(s.id, s);
5295
- const ok = engine.send(s.id, text);
5296
- if (!ok) return { ok: false, error: 'mesaj gönderilemedi' };
5297
- return { ok: true, sessionId: s.id };
5358
+ if (busy) {
5359
+ /* ajan çalışıyor → kuyruğa al; iş bitince (done/error) toplu gider */
5360
+ const q = bcQueuePush(ws, text);
5361
+ return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
5362
+ }
5363
+ /* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
5364
+ const q = bcQueuePush(ws, text);
5365
+ clearTimeout(q.timer);
5366
+ q.timer = setTimeout(() => { try { bcFlush(ws); } catch {} }, BC_DEBOUNCE_MS);
5367
+ return { ok: true, sessionId: s.id, pending: true };
5298
5368
  });
5299
5369
 
5300
5370
  ipcMain.handle('beastcode:stop', async () => {
5301
5371
  const ws = ideRoot();
5302
5372
  const sid = bcSessions.get(ws);
5303
- if (!sid) return { ok: false };
5373
+ /* durdurma: bekleyen kuyruğu da boşalt (kullanıcı vazgeçti) */
5374
+ const q = bcQueue.get(ws);
5375
+ if (q) {
5376
+ clearTimeout(q.timer);
5377
+ bcQueue.delete(ws);
5378
+ }
5379
+ const wasBusy = sid ? engine.isBusy(sid) : false;
5304
5380
  let r = false;
5305
- try { r = engine.interrupt(sid, 'kullanıcı Beast Code panelinden ■ ile durdurdu'); } catch {}
5306
- return { ok: !!r };
5381
+ if (wasBusy) {
5382
+ try { r = engine.interrupt(sid, 'kullanıcı Beast Code panelinden ■ ile durdurdu'); } catch {}
5383
+ }
5384
+ return { ok: true, wasBusy, interrupted: r };
5307
5385
  });
5308
5386
 
5309
5387
  ipcMain.handle('beastcode:new', async () => {
5310
5388
  const ws = ideRoot();
5311
5389
  const sid = bcSessions.get(ws);
5312
5390
  if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
5391
+ const q = bcQueue.get(ws);
5392
+ if (q) {
5393
+ clearTimeout(q.timer);
5394
+ bcQueue.delete(ws);
5395
+ }
5313
5396
  if (sid) {
5314
5397
  try { engine.deleteSession(sid); } catch {}
5315
5398
  bcSessions.delete(ws);
@@ -509,6 +509,8 @@
509
509
  tipStore: 'Skills Store',
510
510
  tipGithub: 'GitHub Trending — repoları favorile, chate at, ajana araştır',
511
511
  tipIde: 'IDE Modu — dosyalar + chat + preview',
512
+ tipCodeHide: 'Editörü kapat',
513
+ tipCodeShow: 'Editörü göster',
512
514
  ide_files: 'DOSYALAR',
513
515
  ide_pick: 'Klasör seç',
514
516
  ide_refresh: 'Yenile',
@@ -1122,6 +1124,8 @@
1122
1124
  tipStore: 'Skills Store',
1123
1125
  tipGithub: 'GitHub Trending — favorite repos, send to chat, ask the agent',
1124
1126
  tipIde: 'IDE Mode — files + chat + preview',
1127
+ tipCodeHide: 'Hide editor',
1128
+ tipCodeShow: 'Show editor',
1125
1129
  ide_files: 'FILES',
1126
1130
  ide_pick: 'Choose folder',
1127
1131
  ide_refresh: 'Refresh',
@@ -111,6 +111,7 @@
111
111
  <div id="codeTabs"></div>
112
112
  <code id="codePath" title="">—</code>
113
113
  <button id="codeSave" title="Kaydet (Ctrl+S)">Kaydet</button>
114
+ <button id="codeHide" title="Editörü kapat" data-i18n-title="tipCodeHide">&#x2715;&#xFE0E;</button>
114
115
  </div>
115
116
  <div id="codeBody">
116
117
  <div id="codeGutter" aria-hidden="true"></div>
@@ -125,6 +126,7 @@
125
126
  <span id="bcTitle">BEAST CODE</span>
126
127
  <span id="bcCwd" title=""></span>
127
128
  <span class="file-spacer"></span>
129
+ <button id="codeShow" title="Editörü göster" data-i18n-title="tipCodeShow" hidden>&#x25A3;&#xFE0E;</button>
128
130
  <button id="bcNew" title="Yeni oturum" data-i18n-title="bc_new">+</button>
129
131
  <button id="bcClear" title="Çıktıyı temizle" data-i18n-title="bc_clear">Temizle</button>
130
132
  </div>
@@ -83,6 +83,8 @@ const els = {
83
83
  codeGutter: $('#codeGutter'),
84
84
  codePath: $('#codePath'),
85
85
  codeSave: $('#codeSave'),
86
+ codeHide: $('#codeHide'),
87
+ codeShow: $('#codeShow'),
86
88
  ideRow: $('#ideRow'),
87
89
  ideSplit: $('#ideSplit'),
88
90
  bcPanel: $('#bcPanel'),
@@ -2471,7 +2473,7 @@ async function renderWaAllow() {
2471
2473
  `<select class="perm-select" title="${_t('bot_bind_title')}">` +
2472
2474
  `<option value="">${_t('bot_sel_empty')}</option>` +
2473
2475
  botChoices
2474
- .map((bb) => `<option value="${bb.id}" ${cur === bb.id ? 'selected' : ''}>${bb.icon} ${escapeHtml(bb.name)}</option>`)
2476
+ .map((bb) => `<option value="${bb.id}" ${cur === bb.id ? 'selected' : ''}>${escapeHtml(bb.name)}</option>`)
2475
2477
  .join('') +
2476
2478
  `</select>`;
2477
2479
  wrap.innerHTML = '';
@@ -2652,7 +2654,7 @@ async function renderWaAllow() {
2652
2654
  botSel.innerHTML =
2653
2655
  `<option value="">${_t('bot_sel_empty')}</option>` +
2654
2656
  botChoices
2655
- .map((bb) => `<option value="${bb.id}">${bb.icon} ${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
2657
+ .map((bb) => `<option value="${bb.id}">${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
2656
2658
  .join('');
2657
2659
  if (prev) botSel.value = prev; // render'lar arası seçim korunur
2658
2660
  }
@@ -2817,7 +2819,7 @@ async function renderTgAllow() {
2817
2819
  for (const bb of botChoices) {
2818
2820
  const o = document.createElement('option');
2819
2821
  o.value = bb.id;
2820
- o.textContent = `${bb.icon} ${bb.name}`;
2822
+ o.textContent = bb.name;
2821
2823
  if ((typeof entry === 'object' && entry.bot_id) === bb.id) o.selected = true;
2822
2824
  sel.appendChild(o);
2823
2825
  }
@@ -2886,7 +2888,7 @@ async function renderTgAllow() {
2886
2888
  botSel.innerHTML =
2887
2889
  `<option value="">${_t('bot_sel_empty')}</option>` +
2888
2890
  botChoices
2889
- .map((bb) => `<option value="${bb.id}">${bb.icon} ${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
2891
+ .map((bb) => `<option value="${bb.id}">${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
2890
2892
  .join('');
2891
2893
  if (prev) botSel.value = prev;
2892
2894
  }
@@ -3015,7 +3017,7 @@ async function renderDcAllow() {
3015
3017
  for (const bb of botChoices) {
3016
3018
  const o = document.createElement('option');
3017
3019
  o.value = bb.id;
3018
- o.textContent = `${bb.icon} ${bb.name}`;
3020
+ o.textContent = bb.name;
3019
3021
  if ((typeof entry === 'object' && entry.bot_id) === bb.id) o.selected = true;
3020
3022
  sel.appendChild(o);
3021
3023
  }
@@ -3082,7 +3084,7 @@ async function renderDcAllow() {
3082
3084
  botSel.innerHTML =
3083
3085
  `<option value="">${_t('bot_sel_empty')}</option>` +
3084
3086
  botChoices
3085
- .map((bb) => `<option value="${bb.id}">${bb.icon} ${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
3087
+ .map((bb) => `<option value="${bb.id}">${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
3086
3088
  .join('');
3087
3089
  if (prev) botSel.value = prev;
3088
3090
  }
@@ -3116,7 +3118,86 @@ async function renderDcAllow() {
3116
3118
  Sol panel altında bot kartları; her bot için sohbet geçmişi + sekmeli yönetim.
3117
3119
  İlk bot hep Beast (admin, silinemez). Max 5 bot. */
3118
3120
 
3119
- const BOT_ICONS = ['🦁', '🚀', '💎', '⭐', '🔥', '🧮', '📊', '🤖', '🦊', '🐼', '🎯', '🛠️'];
3121
+ /* Bot ikonları: tek renkli SVG (stroke: currentColor monokrom temaya uyar).
3122
+ Anahtar isimler data-ic/bot.icon olarak saklanır; eski botlardaki emoji
3123
+ LEGACY_BOT_ICONS ile otomatik yeni ikona eşlenir (görüntüde de, seçicide de). */
3124
+ const BOT_ICONS = ['lion', 'rocket', 'diamond', 'star', 'flame', 'calc', 'chart', 'robot', 'fox', 'panda', 'target', 'wrench'];
3125
+ const BOT_ICON_PATHS = {
3126
+ lion:
3127
+ '<circle cx="12" cy="12" r="7.2"/>' +
3128
+ '<path d="M12 4.8V2.6M12 21.4v-2.2M4.8 12H2.6M21.4 12h-2.2M6.8 6.8 5.2 5.2M18.8 5.2l-1.6 1.6M6.8 17.2l-1.6 1.6M18.8 18.8l-1.6-1.6"/>' +
3129
+ '<path d="M9.2 10.6h.01M14.8 10.6h.01M12 13.2l-1.2 1.4h2.4z"/>',
3130
+ rocket:
3131
+ '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/>' +
3132
+ '<path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/>' +
3133
+ '<path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/>' +
3134
+ '<path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
3135
+ diamond:
3136
+ '<path d="M6 3h12l4 6-10 13L2 9z"/>' +
3137
+ '<path d="M11 3 8 9l4 13 4-13-3-6"/>' +
3138
+ '<path d="M2 9h20"/>',
3139
+ star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>',
3140
+ flame:
3141
+ '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>',
3142
+ calc:
3143
+ '<rect width="16" height="20" x="4" y="2" rx="2"/>' +
3144
+ '<line x1="8" x2="16" y1="6" y2="6"/>' +
3145
+ '<path d="M16 14h.01M16 18h.01M12 10h.01M12 14h.01M12 18h.01M8 10h.01M8 14h.01M8 18h.01"/>',
3146
+ chart:
3147
+ '<path d="M3 3v18h18"/>' +
3148
+ '<path d="M18 17V9"/>' +
3149
+ '<path d="M13 17V5"/>' +
3150
+ '<path d="M8 17v-3"/>',
3151
+ robot:
3152
+ '<path d="M12 8V4H8"/>' +
3153
+ '<rect width="16" height="12" x="4" y="8" rx="2"/>' +
3154
+ '<path d="M2 14h2"/>' +
3155
+ '<path d="M20 14h2"/>' +
3156
+ '<path d="M15 13v2"/>' +
3157
+ '<path d="M9 13v2"/>',
3158
+ fox:
3159
+ '<path d="M5 4l4 3a6.5 6.5 0 0 1 6 0l4-3v7.5c0 4.6-3.1 8.5-7 8.5s-7-3.9-7-8.5z"/>' +
3160
+ '<path d="M9.4 12.8h.01"/>' +
3161
+ '<path d="M14.6 12.8h.01"/>' +
3162
+ '<path d="M12 15.6l-1.1 1.2h2.2z"/>',
3163
+ panda:
3164
+ '<circle cx="12" cy="13" r="7.4"/>' +
3165
+ '<circle cx="5.2" cy="6" r="2.1"/>' +
3166
+ '<circle cx="18.8" cy="6" r="2.1"/>' +
3167
+ '<ellipse cx="9.4" cy="12.6" rx="1.4" ry="1.8" transform="rotate(-14 9.4 12.6)"/>' +
3168
+ '<ellipse cx="14.6" cy="12.6" rx="1.4" ry="1.8" transform="rotate(14 14.6 12.6)"/>' +
3169
+ '<path d="M12 15.8v1.2"/>' +
3170
+ '<path d="M10.8 18.2c.7.6 1.7.6 2.4 0"/>',
3171
+ target:
3172
+ '<circle cx="12" cy="12" r="9"/>' +
3173
+ '<circle cx="12" cy="12" r="5"/>' +
3174
+ '<circle cx="12" cy="12" r="1.5"/>',
3175
+ wrench:
3176
+ '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
3177
+ };
3178
+ const LEGACY_BOT_ICONS = {
3179
+ '🦁': 'lion', '🚀': 'rocket', '💎': 'diamond', '⭐': 'star', '🔥': 'flame', '🧮': 'calc',
3180
+ '📊': 'chart', '🤖': 'robot', '🦊': 'fox', '🐼': 'panda', '🎯': 'target', '🛠️': 'wrench', '🛠': 'wrench',
3181
+ };
3182
+
3183
+ /* ikon anahtarını normalize et: eski emoji → yeni anahtar; bilinmiyorsa '' */
3184
+ function normIconKey(icon) {
3185
+ const k = String(icon || '').trim();
3186
+ return LEGACY_BOT_ICONS[k] || (BOT_ICON_PATHS[k] ? k : '');
3187
+ }
3188
+
3189
+ /* bot ikonu HTML'i: bilinen anahtar → monokrom SVG; eski/emoji → düz metin (geriye uyum) */
3190
+ function botIconSvg(icon, size = 15) {
3191
+ let key = normIconKey(icon);
3192
+ if (!key && !String(icon || '').trim()) key = 'robot';
3193
+ if (!key) return escapeHtml(String(icon));
3194
+ return (
3195
+ `<svg class="bot-svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" ` +
3196
+ 'stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
3197
+ BOT_ICON_PATHS[key] +
3198
+ '</svg>'
3199
+ );
3200
+ }
3120
3201
  const BOT_SKILLS = [
3121
3202
  ['email', 'E-posta'],
3122
3203
  ['browser', 'Dahili tarayıcı'],
@@ -3162,7 +3243,7 @@ function renderBotCards() {
3162
3243
  row.className = 'bot-card' + (b.id === activeBotId ? ' active' : '');
3163
3244
  row.title = _t('bot_switch_hint');
3164
3245
  row.innerHTML =
3165
- `<span class="bot-ico">${b.icon || '🤖'}</span>` +
3246
+ `<span class="bot-ico">${botIconSvg(b.icon)}</span>` +
3166
3247
  `<span class="bot-nm">${escapeHtml(b.name)}</span>` +
3167
3248
  `<span class="bot-gear" title="${_t('bot_manage')}">⚙</span>` +
3168
3249
  `<span class="bot-tag">${b.admin ? 'ADMIN' : 'BOT'}</span>` +
@@ -3181,8 +3262,8 @@ function renderBotCards() {
3181
3262
  function updateBotChip() {
3182
3263
  const chip = $('#botChip');
3183
3264
  if (!chip) return;
3184
- const b = botsCache.find((x) => x.id === activeBotId) || { icon: '🦁', name: 'Beast' };
3185
- chip.textContent = (b.icon || '🤖') + ' ' + b.name;
3265
+ const b = botsCache.find((x) => x.id === activeBotId) || { icon: 'lion', name: 'Beast' };
3266
+ chip.innerHTML = botIconSvg(b.icon, 14) + ' ' + escapeHtml(b.name);
3186
3267
  }
3187
3268
 
3188
3269
  /* BOTLAR ARASI GEÇİŞ: kart tıklaması SADECE aktif botu değiştirir —
@@ -3214,7 +3295,7 @@ async function switchBot(id) {
3214
3295
  } catch {}
3215
3296
  /* picker, aktif botun kendi modelini göstersin */
3216
3297
  applyState();
3217
- toast((b.icon || '🤖') + ' ' + b.name + (b.admin ? '' : ' — ' + _t('bot_switched')));
3298
+ toast(b.name + (b.admin ? '' : ' — ' + _t('bot_switched')));
3218
3299
  }
3219
3300
 
3220
3301
  function botOverlaySetOpen(open) {
@@ -3258,7 +3339,7 @@ function renderBotPage() {
3258
3339
  }
3259
3340
  const b = botsCache.find((x) => x.id === botPageId);
3260
3341
  if (!b) { botPageId = null; renderBotPage(); return; }
3261
- head.querySelector('#botHeadIcon').textContent = b.icon || '🤖';
3342
+ head.querySelector('#botHeadIcon').innerHTML = botIconSvg(b.icon, 22);
3262
3343
  head.querySelector('#botHeadName').textContent = b.name;
3263
3344
  $('#botHeadTag').textContent = b.admin ? 'ADMIN' : 'BOT';
3264
3345
  /* DM Log sekmesi yalnız admin botta görünür — botlar arası tüm trafiği o izler */
@@ -3329,7 +3410,7 @@ async function renderBotNotes(pane, b) {
3329
3410
  const card = document.createElement('div');
3330
3411
  card.className = 'notes-card';
3331
3412
  card.innerHTML =
3332
- `<div class="notes-head"><div class="skill-name">${escapeHtml((b.icon || '') + ' ' + (n.code || '?'))} — ${escapeHtml(n.title || '')}</div>` +
3413
+ `<div class="notes-head"><div class="skill-name">${botIconSvg(b.icon)} ${escapeHtml(n.code || '?')} — ${escapeHtml(n.title || '')}</div>` +
3333
3414
  `<div class="notes-meta">${escapeHtml(when)} · ${n.count || 0} ${_t('bot_stat_msgs')}</div></div>` +
3334
3415
  `<div class="notes-body">${escapeHtml(String(n.notes || '').slice(0, 4000))}</div>` +
3335
3416
  `<div style="margin-top:8px"><button class="btn ghost notes-del">${_t('notes_del')}</button></div>`;
@@ -3352,7 +3433,7 @@ function renderBotOverview(pane) {
3352
3433
  const row = document.createElement('div');
3353
3434
  row.className = 'bot-ov-row';
3354
3435
  row.innerHTML =
3355
- `<span class="ico">${b.icon || '🤖'}</span>` +
3436
+ `<span class="ico">${botIconSvg(b.icon, 17)}</span>` +
3356
3437
  `<span class="nm">${escapeHtml(b.name)}${b.admin ? ' <span class="bot-tag">ADMIN</span>' : ''}</span>` +
3357
3438
  `<span class="m">${_t('bot_stat_numbers')}: ${st.numbers || 0}</span>` +
3358
3439
  `<span class="m">${_t('bot_stat_sessions')}: ${st.sessions || 0}</span>` +
@@ -3427,10 +3508,13 @@ function renderBotSettings(pane, b) {
3427
3508
  pane.innerHTML = '';
3428
3509
  const f = document.createElement('div');
3429
3510
  f.className = 'bot-form';
3430
- const iconBtns = BOT_ICONS.map((ic) => `<button type="button" data-ic="${ic}" class="${ic === b.icon ? 'on' : ''}">${ic}</button>`).join('');
3511
+ const iconBtns = BOT_ICONS.map(
3512
+ (ic) =>
3513
+ `<button type="button" data-ic="${ic}" title="${ic}" class="${normIconKey(b.icon) === ic ? 'on' : ''}">${botIconSvg(ic, 16)}</button>`
3514
+ ).join('');
3431
3515
  const otherBots = botsCache.filter((x) => x.id !== b.id);
3432
3516
  const seeChecks = otherBots
3433
- .map((x) => `<label><input type="checkbox" data-see="${x.id}" ${((b.seeBots || []).includes(x.id)) ? 'checked' : ''}/> ${x.icon} ${escapeHtml(x.name)}</label>`)
3517
+ .map((x) => `<label><input type="checkbox" data-see="${x.id}" ${((b.seeBots || []).includes(x.id)) ? 'checked' : ''}/> ${botIconSvg(x.icon)} ${escapeHtml(x.name)}</label>`)
3434
3518
  .join('') || `<span class="sub">${_t('bot_no_other')}</span>`;
3435
3519
  const skillChecks = BOT_SKILLS
3436
3520
  .map(([k, lbl]) => `<label><input type="checkbox" data-skill="${k}" ${(b.skills || {})[k] ? 'checked' : ''}/> ${lbl}</label>`)
@@ -3661,7 +3745,10 @@ function renderBotAdd(pane) {
3661
3745
  pane.innerHTML = '';
3662
3746
  const f = document.createElement('div');
3663
3747
  f.className = 'bot-form';
3664
- const iconBtns = BOT_ICONS.map((ic, i) => `<button type="button" data-ic="${ic}" class="${i === 7 ? 'on' : ''}">${ic}</button>`).join('');
3748
+ const iconBtns = BOT_ICONS.map(
3749
+ (ic, i) =>
3750
+ `<button type="button" data-ic="${ic}" title="${ic}" class="${i === 7 ? 'on' : ''}">${botIconSvg(ic, 16)}</button>`
3751
+ ).join('');
3665
3752
  f.innerHTML = `
3666
3753
  <label class="mem-label">${_t('bot_name')}</label>
3667
3754
  <input id="bAddName" class="inp" placeholder="${_t('bot_name_ph')}" maxlength="40"/>
@@ -3696,7 +3783,7 @@ function renderBotAdd(pane) {
3696
3783
  if (!name) { toast(_t('bot_name_req')); $('#bAddName').focus(); return; }
3697
3784
  /* İLK HARF ZORUNLU: bot adı harf karakteriyle başlamalı */
3698
3785
  if (!/^\p{L}/u.test(name)) { toast(_t('bot_name_letter')); $('#bAddName').focus(); return; }
3699
- const icon = (f.querySelector('#bAddIconPick button.on') || {}).dataset?.ic || '🤖';
3786
+ const icon = (f.querySelector('#bAddIconPick button.on') || {}).dataset?.ic || 'robot';
3700
3787
  const r = await beast.botsAdd({ name, icon, prompt: $('#bAddPrompt').value });
3701
3788
  if (r.ok) {
3702
3789
  toast(_t('bot_created') + ' ' + r.bot.name);
@@ -4167,6 +4254,8 @@ function onEvent(ev) {
4167
4254
  break;
4168
4255
  }
4169
4256
  case 'status':
4257
+ /* kuyruktan tetiklenen yeni iş: kilitle kendiliğinden açılmışsa geri kapat */
4258
+ if (ev.status !== 'idle' && !busy) setBusy(true);
4170
4259
  if (!busy) break;
4171
4260
  setStatus(ev.status === 'idle' ? (busy ? 'düşünüyor…' : '') : ev.status === 'thinking' ? 'düşünüyor…' : ev.status + '…');
4172
4261
  break;
@@ -5922,12 +6011,29 @@ async function setIdeMode(on) {
5922
6011
  }
5923
6012
  if (on) {
5924
6013
  ideSplitRestore();
6014
+ setEditorHidden(localStorage.getItem('beast.editorHidden') === '1');
5925
6015
  await loadIdeTree();
5926
6016
  bcBanner();
5927
6017
  codeGutterRender(); /* dosya açık olmasa bile rakamlar görünür */
5928
6018
  }
5929
6019
  }
5930
6020
 
6021
+ /* ---------- editör panelini kapat/göster ----------
6022
+ Kapatınca kod bölmesi gider, Beast Code tam genişlik alır; tercih
6023
+ localStorage'da kalıcıdır. Dosya ağacından dosya açınca geri açılır. */
6024
+ function editorHiddenOn() {
6025
+ return document.body.classList.contains('editor-hidden');
6026
+ }
6027
+
6028
+ function setEditorHidden(h) {
6029
+ document.body.classList.toggle('editor-hidden', !!h);
6030
+ if (els.codeShow) els.codeShow.hidden = !h;
6031
+ try { localStorage.setItem('beast.editorHidden', h ? '1' : '0'); } catch {}
6032
+ }
6033
+
6034
+ if (els.codeHide) els.codeHide.addEventListener('click', () => setEditorHidden(true));
6035
+ if (els.codeShow) els.codeShow.addEventListener('click', () => setEditorHidden(false));
6036
+
5931
6037
  /* ---------- editor/chat ayırıcı (VS Code gibi sürükle-bırak) ----------
5932
6038
  Orantılı model: kaydedilen, Beast Code'un ideRow içindeki PAYI'dır (0-1).
5933
6039
  Tarayıcı açılınca/kapanınca/genişlik değişince row yeniden bölünür — editör
@@ -6215,6 +6321,7 @@ function codeActivate(i) {
6215
6321
  }
6216
6322
 
6217
6323
  async function codeOpen(rel) {
6324
+ if (editorHiddenOn()) setEditorHidden(false); /* dosya açılınca editör geri gelir */
6218
6325
  const idx = codeTabs.findIndex((t) => t.rel === rel);
6219
6326
  if (idx >= 0) {
6220
6327
  codeActivate(idx);
@@ -6737,6 +6844,7 @@ function bcIngest(ev) {
6737
6844
  bcRenderTodos(ev.todos);
6738
6845
  break;
6739
6846
  case 'tool-start':
6847
+ bcSetBusy(true); /* kuyruktan tetiklenen işte panel kendini kilitler */
6740
6848
  bcToolBoxStart(ev.callId, ev.name, ev.args);
6741
6849
  /* ajan dosya yazarsa/editlerse açık IDE sekmesini izlemeye al */
6742
6850
  if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
@@ -6793,7 +6901,9 @@ function bcIngest(ev) {
6793
6901
  bcStatusHide();
6794
6902
  bcRefreshPreview();
6795
6903
  } else {
6796
- /* düşünüyor… / araç adı / bağlam durumu — input üstünde canlı balon */
6904
+ /* düşünüyor… / araç adı / bağlam durumu — input üstünde canlı balon;
6905
+ kuyruktan tetiklenen yeni işte kilit geri kapanır */
6906
+ bcSetBusy(true);
6797
6907
  bcStatusShow(ev.status === 'thinking' ? 'düşünüyor…' : String(ev.status || ''));
6798
6908
  }
6799
6909
  break;
@@ -6827,10 +6937,17 @@ function bcRunCurrent() {
6827
6937
  if (r && r.ok && r.mode) {
6828
6938
  /* /plan · /build · /auto: mod değişimi — tur AÇILMAZ, kilit anında açılır
6829
6939
  (bilgi satırı bc-mode olayıyla düşer, başlık rozeti güncellenir) */
6830
- bcSessionId = r.sessionId;
6940
+ if (r.sessionId) bcSessionId = r.sessionId;
6831
6941
  bcSetBusy(false);
6942
+ } else if (r && r.ok && r.queued) {
6943
+ /* ajan çalışıyor → mesaj kuyrukta; iş bitince toplu gönderilir */
6944
+ if (r.sessionId) bcSessionId = r.sessionId;
6945
+ bcLine('t-dim', '⏳ kuyrukta (' + r.count + ') — iş bitince gönderilir');
6946
+ } else if (r && r.ok && r.pending) {
6947
+ /* boşta: kısa birleşme penceresi — aynı penceredeki mesajlar tek işte gider */
6948
+ if (r.sessionId) bcSessionId = r.sessionId;
6832
6949
  } else if (r && r.ok) {
6833
- bcSessionId = r.sessionId;
6950
+ if (r.sessionId) bcSessionId = r.sessionId;
6834
6951
  } else if (r && r.busy) {
6835
6952
  /* iş gerçekten sürüyor — kilit açılmaz, bilgi satırı düşer */
6836
6953
  bcLine('t-dim', (r && r.error) || 'mesaj sürüyor');
@@ -6851,7 +6968,15 @@ if (els.bcInput) {
6851
6968
  if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); bcRunCurrent(); }
6852
6969
  });
6853
6970
  }
6854
- if (els.bcStop) els.bcStop.addEventListener('click', () => beast.beastcodeStop().catch(() => {}));
6971
+ if (els.bcStop) els.bcStop.addEventListener('click', () => {
6972
+ beast.beastcodeStop().then((r) => {
6973
+ /* kuyrukta bekleyen mesajlar da silindi; engine boştaysa kilidi aç */
6974
+ if (r && r.ok && r.wasBusy === false) {
6975
+ bcSetBusy(false);
6976
+ bcStatusHide();
6977
+ }
6978
+ }).catch(() => {});
6979
+ });
6855
6980
  if (els.bcClear) els.bcClear.addEventListener('click', () => {
6856
6981
  els.bcOut.innerHTML = '';
6857
6982
  bcTools.clear();
@@ -1857,6 +1857,18 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
1857
1857
  }
1858
1858
  #botChip:hover { border-color: var(--accent); }
1859
1859
 
1860
+ /* bot ikonları: tek renkli SVG — currentColor ile temaya uyar */
1861
+ #botChip svg,
1862
+ .bot-ico svg,
1863
+ .ico svg,
1864
+ .skill-name svg,
1865
+ .icon-pick button svg {
1866
+ display: inline-block;
1867
+ vertical-align: -2.5px;
1868
+ }
1869
+ #botHeadIcon svg { vertical-align: -4px; }
1870
+ .icon-pick button svg { display: block; margin: 0 auto; }
1871
+
1860
1872
  #botBar {
1861
1873
  flex: none;
1862
1874
  max-height: 45%;
@@ -2492,6 +2504,26 @@ body.ide-mode #codePane { display: flex; }
2492
2504
  background: var(--border);
2493
2505
  opacity: 0.55;
2494
2506
  }
2507
+ /* editör kapatılabilir: kod bölmesi + ayırıcı gizlenir, Beast Code tam genişlik */
2508
+ body.ide-mode.editor-hidden #codePane,
2509
+ body.ide-mode.editor-hidden #ideSplit { display: none; }
2510
+ body.ide-mode.editor-hidden #ideRow #bcPanel { flex: 1 1 auto; min-width: 0; }
2511
+ #codeHide {
2512
+ flex: none;
2513
+ width: 24px;
2514
+ height: 24px;
2515
+ padding: 0;
2516
+ border: 1px solid var(--border);
2517
+ border-radius: 6px;
2518
+ background: transparent;
2519
+ color: var(--muted);
2520
+ cursor: pointer;
2521
+ font-size: 11px;
2522
+ line-height: 1;
2523
+ }
2524
+ #codeHide:hover { color: var(--text); border-color: var(--accent); background: var(--panel2); }
2525
+ #bcHead #codeShow { width: 26px; padding: 0; }
2526
+ #codeShow[hidden] { display: none; }
2495
2527
  #codeTabsBar {
2496
2528
  flex: none;
2497
2529
  display: flex;