beast-agent 0.29.0 → 1.0.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.
Files changed (47) hide show
  1. package/package.json +6 -3
  2. package/src/agent/engine.js +554 -33
  3. package/src/agent/llm.js +52 -3
  4. package/src/agent/obscura.js +26 -10
  5. package/src/agent/skills.js +87 -29
  6. package/src/agent/tools.js +519 -7
  7. package/src/main.js +48 -7
  8. package/src/preload.js +2 -0
  9. package/src/renderer/i18n.js +11 -7
  10. package/src/renderer/index.html +5 -2
  11. package/src/renderer/renderer.js +246 -8
  12. package/src/renderer/style.css +60 -4
  13. package/.env.example +0 -8
  14. package/config.example.yaml +0 -39
  15. package/eng.traineddata +0 -0
  16. package/scripts/fix-electron.js +0 -126
  17. package/scripts/release.js +0 -121
  18. package/scripts/swap-electron.js +0 -40
  19. package/store/skills.json +0 -5
  20. package/tests/approval.test.js +0 -56
  21. package/tests/bg-jobs.test.js +0 -379
  22. package/tests/bots-name.test.js +0 -42
  23. package/tests/bus.test.js +0 -46
  24. package/tests/computeruse-context.test.js +0 -69
  25. package/tests/cron.test.js +0 -95
  26. package/tests/engine.test.js +0 -203
  27. package/tests/eval.test.js +0 -91
  28. package/tests/fallout.test.js +0 -188
  29. package/tests/llm.test.js +0 -205
  30. package/tests/memory.test.js +0 -33
  31. package/tests/memoryloop.test.js +0 -26
  32. package/tests/notegen.test.js +0 -63
  33. package/tests/obscura.test.js +0 -124
  34. package/tests/owner.test.js +0 -32
  35. package/tests/python.test.js +0 -76
  36. package/tests/research.test.js +0 -116
  37. package/tests/scenarios.json +0 -81
  38. package/tests/sessioncode.test.js +0 -50
  39. package/tests/setup.js +0 -9
  40. package/tests/tokens.test.js +0 -42
  41. package/tests/tools.test.js +0 -84
  42. package/tests/usage.test.js +0 -44
  43. package/tests/v11.test.js +0 -76
  44. package/tests/watchers.test.js +0 -206
  45. package/tests/watext.test.js +0 -53
  46. package/tests/whatsapp.test.js +0 -103
  47. package/tests/wherewasi.test.js +0 -40
package/src/preload.js CHANGED
@@ -76,6 +76,8 @@ contextBridge.exposeInMainWorld('beast', {
76
76
  obscuraSetEnabled: (v) => ipcRenderer.invoke('obscura:setEnabled', v),
77
77
  searchOrderGet: () => ipcRenderer.invoke('searchorder:get'),
78
78
  searchOrderSet: (chain) => ipcRenderer.invoke('searchorder:set', chain),
79
+ sessionsOrderGet: () => ipcRenderer.invoke('sessions:order:get'),
80
+ sessionsOrderSet: (order) => ipcRenderer.invoke('sessions:order:set', order),
79
81
  tinyfishGet: () => ipcRenderer.invoke('tinyfish:get'),
80
82
  tinyfishSet: (key) => ipcRenderer.invoke('tinyfish:set', key),
81
83
  tinyfishClear: () => ipcRenderer.invoke('tinyfish:clear'),
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  /* Hafif i18n (TR/EN):
4
4
  - data-i18n → textContent
@@ -21,6 +21,7 @@
21
21
  tipNet: 'İnternet durumu',
22
22
  tip_net_online: 'İnternet: bağlı',
23
23
  tip_net_offline: 'İnternet: yok — bağlantı dönünce işler otomatik sürer',
24
+ sess_drag: 'Sürükleyerek sırayı değiştir',
24
25
  tipWatch: 'İzleyiciler (Watchers)',
25
26
  tipCron: 'Cron Görevler',
26
27
  tip_light: 'Açık tema',
@@ -262,12 +263,13 @@
262
263
  us_where_off_toast: 'Açılış özeti kapandı',
263
264
  ws_calls: 'çağrı',
264
265
  ws_token: 'token',
266
+ td_hide: 'Görev listesini gizle (yeni güncellemede yine görünür)',
265
267
  ws_h2: 'Web Arama',
266
268
  ws_sub: 'Arama zinciri: 1) dahili tarayıcı (direk Google) 2) Obscura (stealth headless → DuckDuckGo) 3) TinyFish (anahtar varsa) 4) python çoklu-motor (ddgs / DDG+Bing+Mojeek) — otomatik. Sıra ve aç/kapa aşağıdan değiştirilir.',
267
269
  oc_h2: 'Obscura (Stealth Tarayıcı)',
268
- oc_sub: 'Rust tabanlı gizli headless tarayıcı (anti-detect + V8, Chromium\u2019suz). Kurulu değilse ilk açılışta otomatik indirilir (%APPDATA%\\beast\\obscura); DuckDuckGo aramasını bot korumasını aşarak yapar.',
270
+ oc_sub: 'Rust tabanlı gizli headless tarayıcı (anti-detect + V8, Chromium\u2019suz). Paket içinde hazır gelir; ilk açılışta %APPDATA%\\beast\\obscura\'ya açılır; DuckDuckGo aramasını bot korumasını aşarak yapar.',
269
271
  oc_status_ok: 'Obscura kurulu ve hazır.',
270
- oc_status_missing: 'Obscura kurulu değil — ilk açılışta otomatik indirilir (≈74 MB).',
272
+ oc_status_missing: 'Obscura paket içinde hazır — ilk açılışta otomatik kurulur.',
271
273
  oc_install: 'Kur / Güncelle',
272
274
  oc_install_running: 'Kuruluyor…',
273
275
  oc_installing: 'İndiriliyor… (≈74 MB, bağlantıya göre birkaç dakika)',
@@ -323,7 +325,7 @@
323
325
  us_where_off_toast: 'Açılış özeti kapandı',
324
326
  ws_h2: 'Web Arama',
325
327
  oc_status_ok: 'Obscura kurulu ve hazır.',
326
- oc_status_missing: 'Obscura kurulu değil — ilk açılışta otomatik indirilir.',
328
+ oc_status_missing: 'Obscura paket içinde hazır — ilk açılışta otomatik kurulur.',
327
329
  ws_save: 'Kaydet',
328
330
  ws_clear: 'Anahtarı Sil',
329
331
  ws_key_note: 'Anahtar maskeli tutulur; yenisini yazıp kaydettiğinde eskisinin yerine geçer. Boş kaydet: mevcut anahtar korunur.',
@@ -575,6 +577,7 @@
575
577
  tipNet: 'Internet status',
576
578
  tip_net_online: 'Internet: connected',
577
579
  tip_net_offline: 'Internet: offline — work resumes automatically once the connection returns',
580
+ sess_drag: 'Drag to reorder',
578
581
  tipWatch: 'Watchers',
579
582
  tipCron: 'Cron Jobs',
580
583
  tip_light: 'Light theme',
@@ -815,12 +818,13 @@
815
818
  us_where_off_toast: 'Startup summary off',
816
819
  ws_calls: 'calls',
817
820
  ws_token: 'tokens',
821
+ td_hide: 'Hide todo list (reappears on next update)',
818
822
  ws_h2: 'Web Search',
819
823
  ws_sub: 'Search chain: 1) built-in browser (direct Google) 2) Obscura (stealth headless → DuckDuckGo) 3) TinyFish (if a key is set) 4) python multi-engine (ddgs / DDG+Bing+Mojeek) — automatic. Order and on/off are editable below.',
820
824
  oc_h2: 'Obscura (Stealth Browser)',
821
- oc_sub: 'Rust-based stealth headless browser (anti-detect + V8, no Chromium). If not installed it is auto-downloaded at first launch (%APPDATA%\\beast\\obscura); it searches DuckDuckGo past bot protection.',
825
+ oc_sub: 'Rust-based stealth headless browser (anti-detect + V8, no Chromium). It ships inside the package and is extracted to %APPDATA%\\beast\\obscura at first launch; it searches DuckDuckGo past bot protection.',
822
826
  oc_status_ok: 'Obscura is installed and ready.',
823
- oc_status_missing: 'Obscura is not installed — it is auto-downloaded at first launch (≈74 MB).',
827
+ oc_status_missing: 'Obscura ships inside the package — it is extracted automatically at first launch.',
824
828
  oc_install: 'Install / Update',
825
829
  oc_install_running: 'Installing…',
826
830
  oc_installing: 'Downloading… (≈74 MB, may take a few minutes)',
@@ -938,7 +942,7 @@
938
942
  us_where_off_toast: 'Startup summary off',
939
943
  ws_h2: 'Web Search',
940
944
  oc_status_ok: 'Obscura is installed and ready.',
941
- oc_status_missing: 'Obscura is not installed — it is auto-downloaded at first launch.',
945
+ oc_status_missing: 'Obscura ships inside the package — it is extracted automatically at first launch.',
942
946
  ws_save: 'Save',
943
947
  ws_clear: 'Delete Key',
944
948
  ws_key_note: 'Key is kept masked; saving a new one replaces the old. Save empty: existing key kept.',
@@ -41,6 +41,7 @@
41
41
  <button id="storeBtn" title="Skills Store" data-i18n-title="tipStore"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16l-1.3 12.1a2 2 0 0 1-2 1.9H7.3a2 2 0 0 1-2-1.9L4 7z"/><path d="M8 10V6a4 4 0 0 1 8 0v4"/></svg></button>
42
42
  <button id="gitBtn" title="GitHub Trending" data-i18n-title="tipGithub"><svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></button>
43
43
  <button id="ideBtn" title="IDE Modu" data-i18n-title="tipIde"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6l-5 6 5 6"/><path d="M16 6l5 6-5 6"/><path d="M13.5 4l-3 16"/></svg></button>
44
+ <span id="netDot" class="net-on" title="İnternet: bağlı" data-i18n-title="tipNet"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8.82a15 15 0 0 1 20 0"/><path d="M5 12.85a10 10 0 0 1 14 0"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/><line class="net-slash" x1="4" y1="4" x2="20" y2="20"/></svg></span>
44
45
  </div>
45
46
  </aside>
46
47
 
@@ -77,7 +78,6 @@
77
78
  <button id="termCBtn" class="dd-btn dd-icon-btn" title="CMD terminali" data-i18n-title="tipTermC">&#x276F;&#xFE0E;</button>
78
79
  <button id="browserBtn" class="dd-btn dd-icon-btn" title="Dahili tarayıcı" data-i18n-title="tipBrowser">&#x29C9;</button>
79
80
  <button id="eyeBtn" class="dd-btn dd-icon-btn" title="Ajan tarayıcısı görünür/gizli — göz açıkken aramalar panelde izlenir, kapalıyken gizli çalışır" data-i18n-title="tipEye"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/><line class="eye-slash" x1="4" y1="4" x2="20" y2="20"/></svg></button>
80
- <span id="netDot" class="dd-btn dd-icon-btn net-on" title="İnternet: bağlı" data-i18n-title="tipNet"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.55a11 11 0 0 1 14.08 0"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/><line class="net-slash" x1="4" y1="4" x2="20" y2="20"/></svg></span>
81
81
  <div class="drag-spacer"></div>
82
82
  </header>
83
83
 
@@ -112,7 +112,10 @@
112
112
  <code id="codePath" title="">—</code>
113
113
  <button id="codeSave" title="Kaydet (Ctrl+S)">Kaydet</button>
114
114
  </div>
115
- <textarea id="codeTa" spellcheck="false"></textarea>
115
+ <div id="codeBody">
116
+ <div id="codeGutter" aria-hidden="true"></div>
117
+ <textarea id="codeTa" spellcheck="false" wrap="off"></textarea>
118
+ </div>
116
119
  </section>
117
120
 
118
121
  <div id="ideSplit" title="Sürükleyerek boyutlandır"></div>
@@ -78,6 +78,7 @@ const els = {
78
78
  bcTodoWrap: $('#bcTodoWrap'),
79
79
  codeTabs: $('#codeTabs'),
80
80
  codeTa: $('#codeTa'),
81
+ codeGutter: $('#codeGutter'),
81
82
  codePath: $('#codePath'),
82
83
  codeSave: $('#codeSave'),
83
84
  ideRow: $('#ideRow'),
@@ -280,8 +281,14 @@ function renderTodos(items) {
280
281
  })
281
282
  .join('');
282
283
  els.todoPanel.innerHTML =
283
- `<div class="td-head"><span>GÖREVLER</span><span class="td-count">${done}/${list.length}</span></div>` + rows;
284
+ `<div class="td-head"><span>GÖREVLER</span>` +
285
+ `<span class="td-right"><span class="td-count">${done}/${list.length}</span>` +
286
+ `<button class="td-x" title="${_t('td_hide')}">×</button></span></div>` + rows;
284
287
  els.todoPanel.hidden = false;
288
+ /* X yalnız BU listeyi gizler — kalıcı kapanma yok: bir sonraki görev
289
+ güncellemesi (yeni görev eklendi / görev yapıldı) paneli yeniden gösterir */
290
+ const x = els.todoPanel.querySelector('.td-x');
291
+ if (x) x.addEventListener('click', () => { els.todoPanel.hidden = true; });
285
292
  }
286
293
  function setBusy(b) {
287
294
  busy = b;
@@ -482,7 +489,41 @@ function finishToolCard(callId, ok, result) {
482
489
 
483
490
  /* ---------------- sessions sidebar ---------------- */
484
491
 
492
+ /* sol panel sohbet listesi: kullanıcı ELLE sıralayabilir (sürükle-bırak).
493
+ sessionOrder: session id'lerinin tercih sırası — settings.json'da kalıcı.
494
+ Listede olmayan oturumlar engine sırasıyla (son mesaja göre) arkada gelir. */
495
+ let sessionOrder = [];
496
+ let sessionOrderLoaded = null;
497
+ function loadSessionOrder() {
498
+ if (!sessionOrderLoaded) {
499
+ sessionOrderLoaded = beast
500
+ .sessionsOrderGet()
501
+ .then((r) => { sessionOrder = (r && Array.isArray(r.order) ? r.order : []).map(String); })
502
+ .catch(() => {});
503
+ }
504
+ return sessionOrderLoaded;
505
+ }
506
+
507
+ function reorderSessions(dragId, targetId) {
508
+ const idx = (id) => sessionOrder.indexOf(id);
509
+ const dp = idx(dragId);
510
+ const tp = idx(targetId);
511
+ sessionOrder = sessionOrder.filter((x) => x !== dragId);
512
+ const ti = sessionOrder.indexOf(targetId);
513
+ if (ti === -1) {
514
+ /* hedef hiç elle sıralanmamış: listeden görünür konumunu korumak mümkün değil — sona ekle */
515
+ sessionOrder.push(targetId, dragId);
516
+ } else if (dp !== -1 && dp < tp) {
517
+ sessionOrder.splice(ti + 1, 0, dragId); /* aşağı sürüklendi → hedefin ALTINA */
518
+ } else {
519
+ sessionOrder.splice(ti, 0, dragId); /* yukarı sürüklendi → hedefin ÜSTÜNE */
520
+ }
521
+ beast.sessionsOrderSet(sessionOrder).catch(() => {});
522
+ refreshSessions();
523
+ }
524
+
485
525
  async function renderSessions(list) {
526
+ await loadSessionOrder();
486
527
  let waSet = new Set();
487
528
  let tgSet = new Set();
488
529
  try {
@@ -492,13 +533,19 @@ async function renderSessions(list) {
492
533
  tgSet = new Set(await beast.tgListSessions());
493
534
  } catch {}
494
535
  els.sessList.innerHTML = '';
536
+ /* elle sıra: sessionOrder'daki id'ler önde (o sırayla), diğerleri engine sırasıyla arkada */
537
+ const rank = new Map(sessionOrder.map((id, i) => [id, i]));
538
+ const withRank = list.map((s, i) => ({ s, r: rank.has(s.id) ? rank.get(s.id) : 1e6 + i }));
539
+ withRank.sort((a, b) => a.r - b.r);
495
540
  /* aktif botun oturumları — botlar arası geçişte liste de o bota göre değişir.
496
541
  KATI KURAL: her oturum yalnız BAĞLI OLDUĞU botun listesinde görünür;
497
542
  bot kaydı olmayan (eski) oturumlar Beast (varsayılan sahip) altında görünür. */
498
- for (const s of list) {
543
+ for (const { s } of withRank) {
499
544
  if ((s.botId || 'beast') !== activeBotId) continue;
500
545
  const row = document.createElement('div');
501
546
  row.className = 'sess' + (s.id === activeId ? ' active' : '');
547
+ row.title = _t('sess_drag');
548
+ row.draggable = true;
502
549
  row.innerHTML =
503
550
  (waSet.has(s.id) ? '<span class="sess-wa" title="WhatsApp">W</span>' : '') +
504
551
  (tgSet.has(s.id) ? '<span class="sess-tg" title="Telegram">T</span>' : '') +
@@ -516,12 +563,40 @@ async function renderSessions(list) {
516
563
  const created = await beast.createSession();
517
564
  await openSession(created.id);
518
565
  }
566
+ sessionOrder = sessionOrder.filter((x) => x !== s.id);
519
567
  refreshSessions();
520
568
  });
569
+ /* sürükle-bırak: yukarı/aşağı yer değiştirme */
570
+ row.addEventListener('dragstart', (e) => {
571
+ dragSid = s.id;
572
+ row.classList.add('dragging');
573
+ try { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', s.id); } catch {}
574
+ });
575
+ row.addEventListener('dragend', () => {
576
+ dragSid = null;
577
+ row.classList.remove('dragging');
578
+ els.sessList.querySelectorAll('.drag-over').forEach((x) => x.classList.remove('drag-over'));
579
+ });
580
+ row.addEventListener('dragover', (e) => {
581
+ if (!dragSid || dragSid === s.id) return;
582
+ e.preventDefault();
583
+ try { e.dataTransfer.dropEffect = 'move'; } catch {}
584
+ row.classList.add('drag-over');
585
+ });
586
+ row.addEventListener('dragleave', () => row.classList.remove('drag-over'));
587
+ row.addEventListener('drop', (e) => {
588
+ e.preventDefault();
589
+ row.classList.remove('drag-over');
590
+ if (!dragSid || dragSid === s.id) return;
591
+ reorderSessions(dragSid, s.id);
592
+ dragSid = null;
593
+ });
521
594
  els.sessList.appendChild(row);
522
595
  }
523
596
  }
524
597
 
598
+ let dragSid = null;
599
+
525
600
  async function refreshSessions() {
526
601
  await renderSessions(await beast.listSessions());
527
602
  renderBotCards(); // bot kartlarındaki numara/sayı etiketleri de tazelensin
@@ -816,7 +891,7 @@ async function renderWebSearchPane() {
816
891
  '<h2>' + _t('oc_h2') + '</h2>' +
817
892
  '<div class="sub">' + _t('oc_sub') + '</div>' +
818
893
  '<div id="ocStatus" class="sub" style="text-align:left;margin-top:8px"></div>' +
819
- '<label class="mem-label" style="display:flex;align-items:center;gap:8px;cursor:pointer">' +
894
+ '<label class="mem-label" style="display:flex;align-items:center;justify-content:center;gap:8px;cursor:pointer">' +
820
895
  '<input id="ocEnabled" type="checkbox" style="width:auto" /> <span>' + _t('oc_enabled') + '</span></label>' +
821
896
  '<div id="ocProg" class="oc-prog" hidden>' +
822
897
  '<div class="oc-prog-bar"><div class="oc-prog-fill" style="width:0%"></div></div>' +
@@ -3831,11 +3906,18 @@ function onEvent(ev) {
3831
3906
  addToolCard(ev.callId, ev.name, ev.args);
3832
3907
  setStatus(ev.name + '…');
3833
3908
  termAgentEvent(ev);
3909
+ /* ajan dosya yazarsa açık IDE sekmesini izlemeye al */
3910
+ if (ev.name === 'write_file' && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
3834
3911
  break;
3835
3912
  case 'tool-end':
3836
3913
  finishToolCard(ev.callId, ev.ok, ev.result);
3837
3914
  setStatus('düşünüyor…');
3838
3915
  termAgentEvent(ev);
3916
+ if (codeWriteWatch.has(ev.callId)) {
3917
+ const wp = codeWriteWatch.get(ev.callId);
3918
+ codeWriteWatch.delete(ev.callId);
3919
+ codeReloadIfOpen(wp);
3920
+ }
3839
3921
  break;
3840
3922
  case 'todos':
3841
3923
  renderTodos(ev.todos);
@@ -5618,6 +5700,7 @@ async function setIdeMode(on) {
5618
5700
  ideSplitRestore();
5619
5701
  await loadIdeTree();
5620
5702
  bcBanner();
5703
+ codeGutterRender(); /* dosya açık olmasa bile rakamlar görünür */
5621
5704
  }
5622
5705
  }
5623
5706
 
@@ -5749,9 +5832,103 @@ async function renderFileTree() {
5749
5832
  Dosya tıklaması MODAL değil, codePane içinde SEKME açar.
5750
5833
  Yanında Beast Code chat'i, onun sağındaki preview tarayıcı durur. */
5751
5834
 
5752
- const codeTabs = []; /* { rel, content, dirty } */
5835
+ const codeTabs = []; /* { rel, content, dirty, base } — base: temiz (disk) içerik, diff bununla */
5753
5836
  let codeActive = -1;
5754
5837
 
5838
+ /* ---------- editör gutter: satır numaraları + değişiklik işaretleri ---------- */
5839
+ /* base ↔ mevcut içerik diff'i: eklenen/değişen satırlar YEŞİL, silinenler
5840
+ kısmen/kırmızı çizgiyle (VS Code gutter stili). LCS; satır adedi aşılırsa
5841
+ bölge-bütününü değişmiş sayar (büyük dosyalarda takılmaz). */
5842
+ function codeDiffLines(baseText, curText) {
5843
+ const added = new Set();
5844
+ const dels = new Map();
5845
+ if (typeof baseText !== 'string') return { added, dels };
5846
+ const a = baseText.split('\n');
5847
+ const b = String(curText || '').split('\n');
5848
+ let p = 0;
5849
+ while (p < a.length && p < b.length && a[p] === b[p]) p++;
5850
+ let ea = a.length - 1;
5851
+ let eb = b.length - 1;
5852
+ while (ea >= p && eb >= p && a[ea] === b[eb]) { ea--; eb--; }
5853
+ const midA = a.slice(p, ea + 1);
5854
+ const midB = b.slice(p, eb + 1);
5855
+ const n = midA.length;
5856
+ const m = midB.length;
5857
+ if (!n && !m) return { added, dels };
5858
+ const pushDel = (at, count) => {
5859
+ if (!count) return;
5860
+ const line = Math.min(Math.max(1, at), b.length || 1);
5861
+ dels.set(line, (dels.get(line) || 0) + count);
5862
+ };
5863
+ if (n * m > 400000 || n > 1500 || m > 1500) {
5864
+ for (let k = 0; k < m; k++) added.add(p + k + 1);
5865
+ pushDel(p + m + 1, n);
5866
+ return { added, dels };
5867
+ }
5868
+ const w = m + 1;
5869
+ const dp = new Int32Array((n + 1) * w);
5870
+ for (let i = n - 1; i >= 0; i--) {
5871
+ for (let j = m - 1; j >= 0; j--) {
5872
+ dp[i * w + j] =
5873
+ midA[i] === midB[j]
5874
+ ? dp[(i + 1) * w + j + 1] + 1
5875
+ : Math.max(dp[(i + 1) * w + j], dp[i * w + j + 1]);
5876
+ }
5877
+ }
5878
+ let i = 0;
5879
+ let j = 0;
5880
+ let pendingDel = 0;
5881
+ while (i < n && j < m) {
5882
+ if (midA[i] === midB[j]) {
5883
+ pushDel(p + j + 1, pendingDel);
5884
+ pendingDel = 0;
5885
+ i++; j++;
5886
+ } else if (dp[(i + 1) * w + j] >= dp[i * w + j + 1]) {
5887
+ pendingDel++; i++;
5888
+ } else {
5889
+ pushDel(p + j + 1, pendingDel);
5890
+ pendingDel = 0;
5891
+ added.add(p + j + 1);
5892
+ j++;
5893
+ }
5894
+ }
5895
+ while (i < n) { pendingDel++; i++; }
5896
+ while (j < m) {
5897
+ pushDel(p + j + 1, pendingDel);
5898
+ pendingDel = 0;
5899
+ added.add(p + j + 1);
5900
+ j++;
5901
+ }
5902
+ pushDel(p + m + 1, pendingDel);
5903
+ return { added, dels };
5904
+ }
5905
+
5906
+ function codeGutterRender() {
5907
+ const g = els.codeGutter;
5908
+ const ta = els.codeTa;
5909
+ if (!g || !ta) return;
5910
+ const hasFile = codeActive >= 0 && codeTabs[codeActive];
5911
+ const t = hasFile ? codeTabs[codeActive] : null;
5912
+ const diff = hasFile ? codeDiffLines(t.base, ta.value) : null;
5913
+ /* dosya açık olmasa bile rakamlar HEP görünür (boş editörde 50 satır iskeleti) */
5914
+ const count = hasFile
5915
+ ? ta.value.split('\n').length
5916
+ : 50;
5917
+ let html = '';
5918
+ for (let i = 1; i <= count; i++) {
5919
+ const isAdd = diff && diff.added.has(i);
5920
+ const delN = diff && diff.dels.get(i);
5921
+ html +=
5922
+ `<div class="cl${isAdd ? ' add' : ''}${delN ? ' del' : ''}${hasFile ? '' : ' idle'}"` +
5923
+ (delN ? ` title="${delN} satır silindi"` : '') +
5924
+ `>${i}</div>`;
5925
+ }
5926
+ g.innerHTML = html;
5927
+ g.scrollTop = ta.scrollTop;
5928
+ }
5929
+
5930
+ let codeGutterTimer = null;
5931
+
5755
5932
  function codeRenderTabs() {
5756
5933
  const bar = els.codeTabs;
5757
5934
  if (!bar) return;
@@ -5786,6 +5963,7 @@ function codeActivate(i) {
5786
5963
  els.codeTa.value = '';
5787
5964
  els.codePath.textContent = '\u2014';
5788
5965
  codeRenderTabs();
5966
+ codeGutterRender();
5789
5967
  return;
5790
5968
  }
5791
5969
  codeActive = i;
@@ -5794,6 +5972,7 @@ function codeActivate(i) {
5794
5972
  els.codePath.textContent = t.rel;
5795
5973
  els.codePath.title = (ideState.path || '') + '\\' + t.rel.replace(/\//g, '\\');
5796
5974
  codeRenderTabs();
5975
+ codeGutterRender();
5797
5976
  }
5798
5977
 
5799
5978
  async function codeOpen(rel) {
@@ -5807,7 +5986,7 @@ async function codeOpen(rel) {
5807
5986
  toast((r && r.error) || 'okunamad\u0131');
5808
5987
  return;
5809
5988
  }
5810
- codeTabs.push({ rel, content: r.content || '', dirty: false });
5989
+ codeTabs.push({ rel, content: r.content || '', dirty: false, base: r.content || '' });
5811
5990
  codeActivate(codeTabs.length - 1);
5812
5991
  els.codeTa.focus();
5813
5992
  }
@@ -5839,13 +6018,44 @@ async function codeSave() {
5839
6018
  const r = await beast.ideWrite(t.rel, t.content).catch(() => null);
5840
6019
  if (r && r.ok) {
5841
6020
  t.dirty = false;
6021
+ t.base = t.content; /* kaydedildi → diff sıfırlanır (VS Code davranışı) */
5842
6022
  codeRenderTabs();
6023
+ codeGutterRender();
5843
6024
  toast(_t('ide_saved'));
5844
6025
  } else {
5845
6026
  toast((r && r.error) || 'kaydedilemedi');
5846
6027
  }
5847
6028
  }
5848
6029
 
6030
+ /* ajan dosyayı YAZINCA açık sekmeyi tazele: eski içerik base olur →
6031
+ ajanın değiştirdiği satırlar gutter'da yeşil/kırmızı görünür */
6032
+ const codeWriteWatch = new Map(); /* callId → path */
6033
+ async function codeReloadIfOpen(p) {
6034
+ const abs = String(p || '').replace(/\//g, '\\');
6035
+ const root = String(ideState.path || '').replace(/\//g, '\\').replace(/\\+$/, '');
6036
+ let rel = null;
6037
+ if (root && abs.toLowerCase().startsWith(root.toLowerCase() + '\\')) {
6038
+ rel = abs.slice(root.length + 1).replace(/\\/g, '/');
6039
+ } else {
6040
+ const cand = abs.toLowerCase();
6041
+ const hit = codeTabs.find((t) => cand.endsWith('\\' + t.rel.replace(/\//g, '\\').toLowerCase()) || cand === t.rel.replace(/\//g, '\\').toLowerCase());
6042
+ if (!hit) return;
6043
+ rel = hit.rel;
6044
+ }
6045
+ const i = codeTabs.findIndex((t) => t.rel === rel);
6046
+ if (i < 0) return;
6047
+ const t = codeTabs[i];
6048
+ if (t.dirty) return; /* kullanıcının kaydedilmemiş düzenlemesini EZME */
6049
+ const r = await beast.ideRead(t.rel).catch(() => null);
6050
+ if (!r || !r.ok) return;
6051
+ t.base = t.content; /* eski disk içeriği = diff tabanı */
6052
+ t.content = r.content || ''; /* ajanın yazdığı yeni içerik */
6053
+ if (i === codeActive) {
6054
+ els.codeTa.value = t.content;
6055
+ }
6056
+ codeGutterRender();
6057
+ }
6058
+
5849
6059
  $('#ideBtn').addEventListener('click', () => setIdeMode(!ideModeOn()));
5850
6060
  $('#filePick').addEventListener('click', async () => {
5851
6061
  const r = await beast.ideSetRoot().catch(() => null);
@@ -5872,6 +6082,12 @@ if (els.codeTa) {
5872
6082
  codeTabs[codeActive].dirty = true;
5873
6083
  const el = els.codeTabs.children[codeActive];
5874
6084
  if (el) el.classList.add('dirty');
6085
+ /* gutter diff'i hafif gecikmeli — her tuşta LCS koşmasın */
6086
+ clearTimeout(codeGutterTimer);
6087
+ codeGutterTimer = setTimeout(codeGutterRender, 120);
6088
+ });
6089
+ els.codeTa.addEventListener('scroll', () => {
6090
+ if (els.codeGutter) els.codeGutter.scrollTop = els.codeTa.scrollTop;
5875
6091
  });
5876
6092
  els.codeTa.addEventListener('keydown', (e) => {
5877
6093
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
@@ -6230,9 +6446,25 @@ function bcIngest(ev) {
6230
6446
  break;
6231
6447
  case 'tool-start':
6232
6448
  bcToolBoxStart(ev.callId, ev.name, ev.args);
6449
+ /* ajan dosya yazarsa açık IDE sekmesini izlemeye al */
6450
+ if (ev.name === 'write_file' && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
6233
6451
  break;
6234
6452
  case 'tool-end':
6235
6453
  bcToolBoxEnd(ev.callId, ev.ok, ev.result);
6454
+ if (codeWriteWatch.has(ev.callId)) {
6455
+ const wp = codeWriteWatch.get(ev.callId);
6456
+ codeWriteWatch.delete(ev.callId);
6457
+ codeReloadIfOpen(wp);
6458
+ }
6459
+ break;
6460
+ case 'bc-mode':
6461
+ /* OpenCode disiplini çalışma modu: başlıkta rozet + panelde bilgi satırı */
6462
+ if (els.bcTitle) {
6463
+ els.bcTitle.textContent =
6464
+ ev.mode === 'plan' ? 'BEAST CODE · PLAN' :
6465
+ ev.mode === 'build' ? 'BEAST CODE · BUILD' : 'BEAST CODE';
6466
+ }
6467
+ bcLine('t-dim', '[' + (ev.body || ev.mode || 'mod değişti') + ']');
6236
6468
  break;
6237
6469
  case 'done':
6238
6470
  bcFlushStream();
@@ -6251,13 +6483,13 @@ function bcIngest(ev) {
6251
6483
  break;
6252
6484
  case 'status':
6253
6485
  /* 'idle' = engine oturumu GERÇEKTEN bıraktı (finally bloğu, her yolda gelir) —
6254
- done kaçsa bile panel kilitli kalmaz, ■ otomatik kalkar, ring durur */
6486
+ done kaçsa bile panel kilitli kalmaz, ⏳ otomatik kalkar, ring durur */
6255
6487
  if (ev.status === 'idle') {
6256
6488
  bcCloseToolGroup();
6257
6489
  bcSetBusy(false);
6258
6490
  bcRefreshPreview();
6259
6491
  }
6260
- break;
6492
+ break;
6261
6493
  }
6262
6494
  }
6263
6495
 
@@ -6271,7 +6503,12 @@ function bcRunCurrent() {
6271
6503
  /* sürüyor/serbest kararını main'deki gerçek engine.isBusy verir —
6272
6504
  panel bayat kilitli kaldıysa ilk mesajla kendini düzeltir */
6273
6505
  beast.beastcodeSend(msg).then((r) => {
6274
- if (r && r.ok) {
6506
+ if (r && r.ok && r.mode) {
6507
+ /* /plan · /build · /auto: mod değişimi — tur AÇILMAZ, kilit anında açılır
6508
+ (bilgi satırı bc-mode olayıyla düşer, başlık rozeti güncellenir) */
6509
+ bcSessionId = r.sessionId;
6510
+ bcSetBusy(false);
6511
+ } else if (r && r.ok) {
6275
6512
  bcSessionId = r.sessionId;
6276
6513
  } else if (r && r.busy) {
6277
6514
  /* iş gerçekten sürüyor — kilit açılmaz, bilgi satırı düşer */
@@ -6306,6 +6543,7 @@ if (els.bcNew) els.bcNew.addEventListener('click', async () => {
6306
6543
  bcCloseToolGroup();
6307
6544
  bcHideTodos();
6308
6545
  bcFlushStream();
6546
+ if (els.bcTitle) els.bcTitle.textContent = 'BEAST CODE';
6309
6547
  bcLine('t-sys', 'yeni oturum — sonraki mesaj taze başlar');
6310
6548
  }
6311
6549
  });
@@ -119,6 +119,9 @@ body {
119
119
  }
120
120
  .sess:hover { background: var(--panel2); color: var(--text); }
121
121
  .sess.active { background: var(--panel2); color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
122
+ /* sürükle-bırak sıralama görselleri */
123
+ .sess.dragging { opacity: 0.35; }
124
+ .sess.drag-over { box-shadow: inset 0 2px 0 var(--accent); }
122
125
  .sess-wa {
123
126
  width: 15px;
124
127
  height: 15px;
@@ -288,11 +291,15 @@ body.browser-open #topbar { width: auto; }
288
291
  /* göz ikonu: gizli modda çapraz çizgi + soluk, görünür modda normal */
289
292
  #eyeBtn .eye-slash { display: none; }
290
293
  #eyeBtn:not(.on) .eye-slash { display: block; }
291
- #eyeBtn:not(.on) { opacity: 0.55; }
292
294
  #eyeBtn.on { background: var(--accent-dim); color: var(--accent); }
293
295
 
294
- /* internet göstergesi (topbar sağ uç): bağlı = YEŞİL wifi, kopuk = KIRMIZI */
295
- #netDot { cursor: default; }
296
+ /* internet göstergesi (sol alt sideFoot satırında, ortalanmış): bağlı = YEŞİL wifi, kopuk = KIRMIZI */
297
+ #netDot {
298
+ display: inline-flex;
299
+ align-items: center;
300
+ padding: 3px 7px;
301
+ cursor: default;
302
+ }
296
303
  #netDot.net-on { color: #22c55e; }
297
304
  #netDot.net-off { color: #ef4444; }
298
305
  #netDot.net-off .net-slash { display: block; }
@@ -934,6 +941,18 @@ body.term-open #settingsOverlay { right: var(--tw, 520px); }
934
941
  margin-bottom: 5px;
935
942
  }
936
943
  #todoPanel .td-count { letter-spacing: 0; font-weight: 700; color: var(--text); }
944
+ #todoPanel .td-right { display: flex; align-items: center; gap: 8px; }
945
+ /* sağ köşe kapatma (×): yalnız bu listeyi gizler — yeni güncellemede yine görünür */
946
+ #todoPanel .td-x {
947
+ border: none;
948
+ background: none;
949
+ color: var(--muted);
950
+ font-size: 14px;
951
+ line-height: 1;
952
+ padding: 0 3px;
953
+ cursor: pointer;
954
+ }
955
+ #todoPanel .td-x:hover { color: var(--err); }
937
956
  #todoPanel .td-row {
938
957
  display: flex;
939
958
  align-items: baseline;
@@ -2424,6 +2443,41 @@ body.ide-mode #codePane { display: flex; }
2424
2443
  line-height: 1.4;
2425
2444
  }
2426
2445
  #codeSave:hover { color: var(--accent); border-color: var(--accent); background: var(--panel2); }
2446
+ #codeBody { flex: 1; display: flex; min-height: 0; }
2447
+ #codeGutter {
2448
+ flex: none;
2449
+ width: 56px;
2450
+ overflow: hidden;
2451
+ user-select: none;
2452
+ font-family: Consolas, "Cascadia Mono", monospace;
2453
+ font-size: 12.5px;
2454
+ line-height: 20px;
2455
+ padding: 12px 0;
2456
+ background: var(--panel);
2457
+ border-right: 1px solid var(--border);
2458
+ text-align: right;
2459
+ color: var(--muted);
2460
+ }
2461
+ #codeGutter .cl { height: 20px; padding-right: 8px; position: relative; }
2462
+ /* VS Code tarzı değişiklik işaretleri: değişen satır YEŞİL, silinen kırmızı çizgi */
2463
+ #codeGutter .cl.add {
2464
+ background: rgba(34, 197, 94, 0.16);
2465
+ box-shadow: inset 2px 0 0 #22c55e;
2466
+ color: #16a34a;
2467
+ font-weight: 700;
2468
+ }
2469
+ #codeGutter .cl.del { box-shadow: inset 2px 0 0 #ef4444; }
2470
+ #codeGutter .cl.del::after {
2471
+ content: '';
2472
+ position: absolute;
2473
+ left: 0;
2474
+ right: 0;
2475
+ bottom: 0;
2476
+ height: 2px;
2477
+ background: #ef4444;
2478
+ }
2479
+ /* dosya açık değilken boş editör iskelet rakamları daha sönük */
2480
+ #codeGutter .cl.idle { opacity: 0.45; }
2427
2481
  #codeTa {
2428
2482
  flex: 1;
2429
2483
  resize: none;
@@ -2434,7 +2488,9 @@ body.ide-mode #codePane { display: flex; }
2434
2488
  padding: 12px 14px;
2435
2489
  font-family: Consolas, "Cascadia Mono", monospace;
2436
2490
  font-size: 12.5px;
2437
- line-height: 1.55;
2491
+ line-height: 20px;
2492
+ white-space: pre;
2493
+ overflow: auto;
2438
2494
  }
2439
2495
  /* dikey scrollbar hep görünür, tutulabilir ve imleç değişsin */
2440
2496
  #codeTa::-webkit-scrollbar {
package/.env.example DELETED
@@ -1,8 +0,0 @@
1
- # Beast Agent API anahtarları örneği.
2
- # Kullanım: bu dosyayı %APPDATA%\beast\.env olarak kopyala ve anahtarlarını gir.
3
- # Bu dosya config.yaml'daki key_env alanlarıyla eşleşir;
4
- # key_env yoksa <PROVIDER_ID>_API_KEY denenir (örn. zhipu -> ZHIPU_API_KEY).
5
-
6
- OPENCODE_API_KEY=
7
- ZHIPU_API_KEY=
8
- OPENAI_API_KEY=
@@ -1,39 +0,0 @@
1
- # Beast Agent sağlayıcı yapılandırması örneği.
2
- # Kullanım: bu dosyayı %APPDATA%\beast\config.yaml olarak kopyala ve düzenle.
3
- # (Taşınabilir kullanım için BEAST_DATA ortam değişkeni ile başka klasöre alabilirsin.)
4
-
5
- # ÖNERİLEN: OpenCode Zen — ücretsiz modeller, kredi kartı gerekmez (opencode.ai/auth)
6
- providers:
7
- opencode:
8
- name: OpenCode Zen
9
- base_url: https://opencode.ai/zen/v1
10
- key_env: OPENCODE_API_KEY
11
- models:
12
- glm-5.2: {}
13
- kimi-k2.7-code: {}
14
- deepseek-v4-flash-free: {}
15
- big-pickle: {}
16
- minimax-m3: {}
17
- # OpenCode Go aboneliğin varsa base_url: https://opencode.ai/zen/go/v1 kullan.
18
-
19
- zhipu:
20
- name: Zhipu AI
21
- base_url: https://api.z.ai/api/paas/v4
22
- key_env: ZHIPU_API_KEY
23
- models:
24
- glm-4.6: {}
25
- glm-4.5-air: {}
26
-
27
- openai:
28
- name: OpenAI
29
- base_url: https://api.openai.com/v1
30
- key_env: OPENAI_API_KEY
31
- models:
32
- gpt-4o-mini: {}
33
- gpt-4o: {}
34
-
35
- model:
36
- provider: opencode # varsayılan provider id (yoksa ilk provider kullanılır)
37
- default: glm-5.2 # varsayılan model
38
- # base_url: ... # istersen aktif provider'ın URL'ini override et
39
- # key_env: ... # istersen anahtarı farklı env'den oku