beast-agent 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/main.js CHANGED
@@ -662,6 +662,7 @@ function waSlashHelp() {
662
662
  '• */notes* – bu oturumun notlarını göster',
663
663
  '• */notify* on|off – hata mail bildirimini aç/kapa',
664
664
  '• */think* <0-5> – düşünme seviyesi (0 kapalı · 1 low · 2 medium · 3 high · 4 xhigh · 5 max)',
665
+ '• */agent* [isim] – özel ajan bağla/listele (%APPDATA%\beast\agents)',
665
666
  '• */clear* – bu oturumun geçmişini temizle',
666
667
  '• */screenshot* – masaüstü ekran görüntüsünü gönder',
667
668
  '• */rule* <metin> – kalıcı kural ekle (*/rules*: liste)',
@@ -3925,6 +3926,32 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
3925
3926
  desktopEcho(sessionId, t, r && r.error ? r.error : r ? r.text : thinkStatusText());
3926
3927
  return true;
3927
3928
  }
3929
+ if (t === '/agent' || t.startsWith('/agent ')) {
3930
+ /* opencode agent port: özel ajan tanımları (%APPDATA%\beast\agents\*.md) */
3931
+ const arg = t.slice(6).trim();
3932
+ if (!arg) {
3933
+ const list = engine.listAgents();
3934
+ desktopEcho(
3935
+ sessionId,
3936
+ t,
3937
+ list.length
3938
+ ? '**Özel ajanlar:**\n' + list.map((d) => `- **${d.name}**${d.model ? ' · ' + d.model : ''}${d.steps ? ' · ' + d.steps + ' tur' : ''}${d.tools ? ' · ' + d.tools.length + ' araç' : ''}`).join('\n') + '\n\nBağlamak için: **/agent <isim>** · ayırmak için: **/agent off**'
3939
+ : 'Özel ajan yok — `%APPDATA%\\beast\\agents\\` klasörüne `<isim>.md` tanımı koy (örnek dosya orada).'
3940
+ );
3941
+ return true;
3942
+ }
3943
+ const r = engine.setSessionAgent(sessionId, arg);
3944
+ desktopEcho(
3945
+ sessionId,
3946
+ t,
3947
+ r.ok
3948
+ ? r.agent
3949
+ ? `**Ajan bağlandı: ${r.agent}**${r.model ? ' · model: ' + r.model : ''}${r.steps ? ' · ' + r.steps + ' tur' : ''}${r.tools ? ' · araçlar: ' + r.tools.join(', ') : ''}`
3950
+ : '**Ajan bağlantısı kaldırıldı** — oturum normal akışa döndü.'
3951
+ : r.error
3952
+ );
3953
+ return true;
3954
+ }
3928
3955
  if (t === '/clear') {
3929
3956
  /* #25 artık GERÇEK silme: oturum dosyasındaki mesajlar + notlar silinir,
3930
3957
  kod/meta/todolar korunur. Ekran da temizlenir ('clear' olayı). */
@@ -4008,6 +4035,7 @@ function desktopSlashHelp() {
4008
4035
  '**/change [n]** – modelleri listele · n. modele geç',
4009
4036
  '**/model [isim]** – aktif modeli göster / değiştir',
4010
4037
  '**/think 0-5** – düşünme seviyesi (0 kapalı · 5 max)',
4038
+ '**/agent [isim]** – özel ajan bağla / listele (%APPDATA%\\beast\\agents\\*.md)',
4011
4039
  '**/clear** – oturum geçmişini gerçekten sil (kod korunur)',
4012
4040
  '**/notes** – bu oturumun notlarını göster',
4013
4041
  '**/rule <metin>** – kalıcı kural ekle · **/rules** – listele',
@@ -4458,9 +4486,22 @@ ipcMain.handle('model:set', (_e, sel) => {
4458
4486
  saveSettings();
4459
4487
  engine.setModelOverride(sel);
4460
4488
  }
4489
+ /* IDE/Beast Code dahil tüm paneller anlık haberdar olsun — picker +
4490
+ durum etiketleri kapatıp açmadan güncellenir */
4491
+ try { win && !win.isDestroyed() && win.webContents.send('agent:event', { type: 'modelChanged' }); } catch {}
4461
4492
  return engine.publicState();
4462
4493
  });
4463
4494
 
4495
+ /* Paralel ajan geçmişini TOPLUCA sil (rail başlığındaki çöp ikonu) */
4496
+ ipcMain.handle('agents:clearAll', () => {
4497
+ try {
4498
+ const removed = engine.clearAllBgJobs();
4499
+ return { ok: true, removed };
4500
+ } catch (e) {
4501
+ return { ok: false, error: String((e && e.message) || e) };
4502
+ }
4503
+ });
4504
+
4464
4505
  ipcMain.handle('model:role', (_e, map) => {
4465
4506
  const roleModels = {};
4466
4507
  const allowed = {};
package/src/preload.js CHANGED
@@ -180,6 +180,7 @@ contextBridge.exposeInMainWorld('beast', {
180
180
  idePreview: () => ipcRenderer.invoke('ide:preview'),
181
181
  idePreviewFile: (rel) => ipcRenderer.invoke('ide:previewFile', rel),
182
182
  idePreviewUrl: (url) => ipcRenderer.invoke('ide:previewUrl', url),
183
+ agentsClearAll: () => ipcRenderer.invoke('agents:clearAll'),
183
184
  ideDelete: (rel) => ipcRenderer.invoke('ide:delete', rel),
184
185
  onWaEvent: (cb) => ipcRenderer.on('wa:event', (_e, ev) => cb(ev)),
185
186
  onEvent: (cb) => ipcRenderer.on('agent:event', (_e, ev) => cb(ev)),
@@ -130,6 +130,7 @@
130
130
  </div>
131
131
  <div id="bcOut"></div>
132
132
  <div id="bcTodoWrap" hidden></div>
133
+ <div id="bcStatus" hidden></div>
133
134
  <div id="bcInputRow">
134
135
  <span id="bcPrompt">code&gt;</span>
135
136
  <input id="bcInput" spellcheck="false" autocomplete="off" placeholder="Beast Code mesajı yaz — Enter ile gönder…" data-i18n-ph="bc_ph" />
@@ -140,7 +141,12 @@
140
141
  </main>
141
142
 
142
143
  <aside id="rail">
143
- <div id="railHead" data-i18n="labelParallel">Paralel Ajanlar</div>
144
+ <div id="railHead">
145
+ <span id="railTitle" data-i18n="labelParallel">Paralel Ajanlar</span>
146
+ <button id="railClear" title="Tüm paralel ajan geçmişini sil" aria-label="Tüm paralel ajan geçmişini sil">
147
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>
148
+ </button>
149
+ </div>
144
150
  <div id="railList"></div>
145
151
  <div id="sideFoot2">
146
152
  <span class="foot-label2" data-i18n="labelParallel">Paralel Ajanlar</span>
@@ -42,6 +42,7 @@ const els = {
42
42
  eyeBtn: $('#eyeBtn'),
43
43
  netDot: $('#netDot'),
44
44
  railBtn: $('#railBtn'),
45
+ railClear: $('#railClear'),
45
46
  watchBtn: $('#watchBtn'),
46
47
  cronBtn: $('#cronBtn'),
47
48
  watchOverlay: $('#watchOverlay'),
@@ -76,6 +77,7 @@ const els = {
76
77
  bcClear: $('#bcClear'),
77
78
  bcNew: $('#bcNew'),
78
79
  bcTodoWrap: $('#bcTodoWrap'),
80
+ bcStatus: $('#bcStatus'),
79
81
  codeTabs: $('#codeTabs'),
80
82
  codeTa: $('#codeTa'),
81
83
  codeGutter: $('#codeGutter'),
@@ -411,6 +413,176 @@ function addStopNote(reason) {
411
413
  scrollDown(true);
412
414
  }
413
415
 
416
+ /* ---------------- opencode diff görünümü (BİREBİR port) ----------------
417
+ edit_file/write_file sonuçları chat'te ve Beast Code panelinde opencode'un
418
+ permission/diff ekranı gibi gösterilir: SOLDa kırmızı (eski/silinen),
419
+ SAĞDA yeşil (yeni/eklenen) — dar alanda birleşik (unified) görünüme düşer.
420
+ Motor: LCS tabanlı satır diff'i (opencode npm "diff" paketinin diffLines
421
+ karşılığı) + del/add blok eşleştirmesi (split görünüm hizalaması). */
422
+ function diffOps(aText, bText) {
423
+ const a = aText ? String(aText).split('\n') : [];
424
+ const b = bText ? String(bText).split('\n') : [];
425
+ let p = 0;
426
+ while (p < a.length && p < b.length && a[p] === b[p]) p++;
427
+ let ea = a.length - 1;
428
+ let eb = b.length - 1;
429
+ while (ea >= p && eb >= p && a[ea] === b[eb]) { ea--; eb--; }
430
+ const ops = [];
431
+ for (let i = 0; i < p; i++) ops.push({ t: 'ctx', a: a[i], b: b[i] });
432
+ const midA = a.slice(p, ea + 1);
433
+ const midB = b.slice(p, eb + 1);
434
+ const n = midA.length;
435
+ const m = midB.length;
436
+ if (n * m > 400000 || n > 1500 || m > 1500) {
437
+ for (const x of midA) ops.push({ t: 'del', a: x });
438
+ for (const y of midB) ops.push({ t: 'add', b: y });
439
+ } else {
440
+ const w = m + 1;
441
+ const dp = new Int32Array((n + 1) * w);
442
+ for (let i = n - 1; i >= 0; i--) {
443
+ for (let j = m - 1; j >= 0; j--) {
444
+ dp[i * w + j] =
445
+ midA[i] === midB[j]
446
+ ? dp[(i + 1) * w + j + 1] + 1
447
+ : Math.max(dp[(i + 1) * w + j], dp[i * w + j + 1]);
448
+ }
449
+ }
450
+ let i = 0;
451
+ let j = 0;
452
+ while (i < n && j < m) {
453
+ if (midA[i] === midB[j]) { ops.push({ t: 'ctx', a: midA[i], b: midB[j] }); i++; j++; }
454
+ else if (dp[(i + 1) * w + j] >= dp[i * w + j + 1]) { ops.push({ t: 'del', a: midA[i] }); i++; }
455
+ else { ops.push({ t: 'add', b: midB[j] }); j++; }
456
+ }
457
+ while (i < n) ops.push({ t: 'del', a: midA[i++] });
458
+ while (j < m) ops.push({ t: 'add', b: midB[j++] });
459
+ }
460
+ for (let k = ea + 1; k < a.length; k++) ops.push({ t: 'ctx', a: a[k], b: b[k] });
461
+ return ops;
462
+ }
463
+
464
+ /* del/add bloklarını yan yana hizalar: split görünümde sol satır ile sağ satır
465
+ aynı yükseklikte durur; eksik taraf boş "space" satırla doldurulur */
466
+ function diffSplitRows(ops, startLine) {
467
+ const rows = [];
468
+ let oldNo = Math.max(1, startLine || 1);
469
+ let newNo = oldNo;
470
+ let i = 0;
471
+ while (i < ops.length) {
472
+ const op = ops[i];
473
+ if (op.t === 'ctx') {
474
+ rows.push({ l: { no: oldNo++, text: op.a, cls: 'ctx' }, r: { no: newNo++, text: op.b, cls: 'ctx' } });
475
+ i++;
476
+ continue;
477
+ }
478
+ const dels = [];
479
+ const adds = [];
480
+ while (i < ops.length && ops[i].t !== 'ctx') {
481
+ if (ops[i].t === 'del') dels.push(ops[i].a);
482
+ else adds.push(ops[i].b);
483
+ i++;
484
+ }
485
+ const max = Math.max(dels.length, adds.length);
486
+ for (let k = 0; k < max; k++) {
487
+ rows.push({
488
+ l: k < dels.length ? { no: oldNo++, text: dels[k], cls: 'del' } : { no: '', text: '', cls: 'space' },
489
+ r: k < adds.length ? { no: newNo++, text: adds[k], cls: 'add' } : { no: '', text: '', cls: 'space' },
490
+ });
491
+ }
492
+ }
493
+ return rows;
494
+ }
495
+
496
+ const DIFF_RENDER_MAX_ROWS = 400; /* dev değişikliklerde DOM şişmesin */
497
+
498
+ function diffLineEl(no, text, cls, sign) {
499
+ const line = document.createElement('div');
500
+ line.className = 'diff-line ' + cls;
501
+ const num = document.createElement('span');
502
+ num.className = 'diff-num';
503
+ num.textContent = String(no);
504
+ const code = document.createElement('span');
505
+ code.className = 'diff-code';
506
+ code.textContent = (sign || '') + text;
507
+ line.appendChild(num);
508
+ line.appendChild(code);
509
+ return line;
510
+ }
511
+
512
+ /* opencode <diff view="split|unified"> karşılığı: genişlik >= 620px ise
513
+ yan-yana (solda kırmızı eski / sağda yeşil yeni), dar ise birleşik */
514
+ function buildDiffEl(diff, availWidth) {
515
+ const wrap = document.createElement('div');
516
+ wrap.className = 'diffbox';
517
+ const head = document.createElement('div');
518
+ head.className = 'diff-head';
519
+ const fname = document.createElement('span');
520
+ fname.className = 'diff-file';
521
+ fname.textContent = String(diff.path || '');
522
+ const badge = document.createElement('span');
523
+ badge.className = 'diff-badge';
524
+ const a = document.createElement('span');
525
+ a.className = 'diff-badge-add';
526
+ a.textContent = '+' + (diff.additions || 0);
527
+ const d = document.createElement('span');
528
+ d.className = 'diff-badge-del';
529
+ d.textContent = '−' + (diff.deletions || 0);
530
+ badge.appendChild(a);
531
+ badge.appendChild(d);
532
+ head.appendChild(fname);
533
+ head.appendChild(badge);
534
+ wrap.appendChild(head);
535
+
536
+ const ops = diffOps(diff.before, diff.after);
537
+ const split = (availWidth || 0) >= 620;
538
+ const view = document.createElement('div');
539
+ view.className = 'diff ' + (split ? 'diff-split' : 'diff-uni');
540
+ if (split) {
541
+ const rows = diffSplitRows(ops, diff.startLine);
542
+ const left = document.createElement('div');
543
+ left.className = 'diff-side old';
544
+ const right = document.createElement('div');
545
+ right.className = 'diff-side new';
546
+ const capped = rows.length > DIFF_RENDER_MAX_ROWS;
547
+ for (const row of rows.slice(0, DIFF_RENDER_MAX_ROWS)) {
548
+ left.appendChild(diffLineEl(row.l.no, row.l.text, row.l.cls, ''));
549
+ right.appendChild(diffLineEl(row.r.no, row.r.text, row.r.cls, ''));
550
+ }
551
+ view.appendChild(left);
552
+ view.appendChild(right);
553
+ if (capped) {
554
+ const note = document.createElement('div');
555
+ note.className = 'diff-more';
556
+ note.textContent = `… ${rows.length - DIFF_RENDER_MAX_ROWS} satır daha (dosyada gör)`;
557
+ view.appendChild(note);
558
+ }
559
+ } else {
560
+ let oldNo = Math.max(1, diff.startLine || 1);
561
+ let newNo = oldNo;
562
+ const capped = ops.length > DIFF_RENDER_MAX_ROWS;
563
+ for (const op of ops.slice(0, DIFF_RENDER_MAX_ROWS)) {
564
+ if (op.t === 'ctx') {
565
+ view.appendChild(diffLineEl(newNo, op.b, 'ctx', ' '));
566
+ oldNo++; newNo++;
567
+ } else if (op.t === 'del') {
568
+ view.appendChild(diffLineEl(oldNo, op.a, 'del', '-'));
569
+ oldNo++;
570
+ } else {
571
+ view.appendChild(diffLineEl(newNo, op.b, 'add', '+'));
572
+ newNo++;
573
+ }
574
+ }
575
+ if (capped) {
576
+ const note = document.createElement('div');
577
+ note.className = 'diff-more';
578
+ note.textContent = `… ${ops.length - DIFF_RENDER_MAX_ROWS} satır daha (dosyada gör)`;
579
+ view.appendChild(note);
580
+ }
581
+ }
582
+ wrap.appendChild(view);
583
+ return wrap;
584
+ }
585
+
414
586
  /* ---------------- tool cards ---------------- */
415
587
  /* Ard arda gelen araç kartları TEK kutuda toplanır (.tool-box): çalışırken
416
588
  lacivert outline döner, gövde max 420px scroll'lu. Grup; tool_calls İÇEREN
@@ -425,7 +597,9 @@ function closeChatToolGroup() {
425
597
  function argSummary(name, args) {
426
598
  try {
427
599
  if (name === 'run_command') return String(args.command || '');
428
- if (name === 'read_file' || name === 'write_file') return String(args.path || '');
600
+ if (name === 'read_file' || name === 'write_file' || name === 'edit_file') return String(args.path || '');
601
+ if (name === 'grep') return String(args.pattern || '');
602
+ if (name === 'glob') return String(args.pattern || '');
429
603
  if (name === 'list_dir') return String(args.path || '.');
430
604
  if (name === 'memory_write' || name === 'user_write') return String(args.text || '').slice(0, 80);
431
605
  return JSON.stringify(args).slice(0, 100);
@@ -468,7 +642,10 @@ function addToolCard(callId, name, args) {
468
642
  return card;
469
643
  }
470
644
 
471
- function finishToolCard(callId, ok, result) {
645
+ /* opencode tool-end portu: edit/write sonucu ham JSON yerine kırmızı/yeşil
646
+ DIFF olarak çizilir (opencode'un permission/diff ekranı); diğer araçlar
647
+ eskisi gibi düz metin gösterir */
648
+ function finishToolCard(callId, ok, result, diff) {
472
649
  const sel = callId
473
650
  ? `.tool-card[data-call-id="${CSS.escape(callId)}"]`
474
651
  : null;
@@ -479,8 +656,29 @@ function finishToolCard(callId, ok, result) {
479
656
  st.classList.add(ok ? 'ok' : 'err');
480
657
  st.textContent = ok ? 'tamam' : 'hata';
481
658
  const body = card.querySelector('.tool-body');
482
- body.textContent = String(result || '(çıktı yok)').slice(0, 4000);
483
- if (String(result || '').length <= 300) body.classList.add('open');
659
+ if (diff && diff.path) {
660
+ /* başlığa +/- rozetleri (opencode filediff additions/deletions) */
661
+ const head = card.querySelector('.tool-head');
662
+ if (head && !head.querySelector('.diff-badge')) {
663
+ const badge = document.createElement('span');
664
+ badge.className = 'diff-badge';
665
+ const a = document.createElement('span');
666
+ a.className = 'diff-badge-add';
667
+ a.textContent = '+' + (diff.additions || 0);
668
+ const d = document.createElement('span');
669
+ d.className = 'diff-badge-del';
670
+ d.textContent = '−' + (diff.deletions || 0);
671
+ badge.appendChild(a);
672
+ badge.appendChild(d);
673
+ head.insertBefore(badge, st);
674
+ }
675
+ body.textContent = '';
676
+ body.classList.add('open', 'diff-body');
677
+ body.appendChild(buildDiffEl(diff, body.clientWidth || 800));
678
+ } else {
679
+ body.textContent = String(result || '(çıktı yok)').slice(0, 4000);
680
+ if (String(result || '').length <= 300) body.classList.add('open');
681
+ }
484
682
  /* ring burada söndürülmez — iş bitene kadar (closeChatToolGroup) döner */
485
683
  if (chatToolGroup && chatToolGroup.body.contains(card)) {
486
684
  chatToolGroup.body.scrollTop = chatToolGroup.body.scrollHeight;
@@ -621,7 +819,7 @@ async function openSession(id) {
621
819
  addToolCard(tc.id, tc.function.name, args);
622
820
  const out = s.messages[i + 1];
623
821
  if (out && out.role === 'tool' && out.tool_call_id === tc.id) {
624
- finishToolCard(tc.id, true, out.content);
822
+ finishToolCard(tc.id, true, out.content, out.diffView);
625
823
  i++;
626
824
  } else {
627
825
  finishToolCard(tc.id, false, '(sonuç yok)');
@@ -3906,11 +4104,11 @@ function onEvent(ev) {
3906
4104
  addToolCard(ev.callId, ev.name, ev.args);
3907
4105
  setStatus(ev.name + '…');
3908
4106
  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);
4107
+ /* ajan dosya yazarsa/editlerse açık IDE sekmesini izlemeye al */
4108
+ if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
3911
4109
  break;
3912
4110
  case 'tool-end':
3913
- finishToolCard(ev.callId, ev.ok, ev.result);
4111
+ finishToolCard(ev.callId, ev.ok, ev.result, ev.diff);
3914
4112
  setStatus('düşünüyor…');
3915
4113
  termAgentEvent(ev);
3916
4114
  if (codeWriteWatch.has(ev.callId)) {
@@ -4843,9 +5041,29 @@ async function init() {
4843
5041
  });
4844
5042
  }
4845
5043
  if (els.railBtn) els.railBtn.addEventListener('click', () => {
5044
+ const willOpen = document.body.classList.contains('rail-hidden');
5045
+ /* IDE modunda rail istenirse açık tarayıcı yerini bırakır — ikisi aynı
5046
+ sağ dock'u paylaşır; tarayıcı kapanma olayı rail'i tekrar KAPATMASIN
5047
+ diye railPrefBeforeBrowser=true ile bırakılır */
5048
+ if (willOpen && ideModeOn() && document.body.classList.contains('browser-open')) {
5049
+ railPrefBeforeBrowser = true;
5050
+ els.bbClose.click();
5051
+ }
4846
5052
  toggleRail(!document.body.classList.contains('rail-hidden'));
4847
5053
  railPrefBeforeBrowser = !document.body.classList.contains('rail-hidden');
4848
5054
  });
5055
+ if (els.railClear) {
5056
+ els.railClear.addEventListener('click', async () => {
5057
+ if (!confirm('TÜM paralel ajan geçmişi silinsin mi?\n(Çalışan ajanlar da iptal edilir — geri alınamaz)')) return;
5058
+ try {
5059
+ const r = await beast.agentsClearAll();
5060
+ if (r && r.ok) toast('Paralel ajan geçmişi silindi' + (r.removed ? ' — ' + r.removed + ' iş' : ''));
5061
+ else toast('Silinemedi: ' + ((r && r.error) || '?'));
5062
+ } catch (e) {
5063
+ toast('Silinemedi: ' + String((e && e.message) || e));
5064
+ }
5065
+ });
5066
+ }
4849
5067
  els.bbBack.addEventListener('click', () => beast.browserCtrl('back'));
4850
5068
  els.bbFwd.addEventListener('click', () => beast.browserCtrl('forward'));
4851
5069
  els.bbReload.addEventListener('click', () => beast.browserCtrl('reload'));
@@ -6285,6 +6503,20 @@ function bcStreamDelta(delta) {
6285
6503
  /* İş bitti → preview açık ve IDE modundaysa workspace sayfasını otomatik yenile */
6286
6504
  let bcPrevAt = 0;
6287
6505
 
6506
+ /* Beast Code durum balonu: düşünüyor… / araç adları / durum satırları —
6507
+ chat inputun üstündeki balonun BC panelindeki karşılığı */
6508
+ function bcStatusShow(text) {
6509
+ if (!els.bcStatus) return;
6510
+ const t = String(text || '').trim();
6511
+ if (!t) return;
6512
+ els.bcStatus.textContent = t;
6513
+ els.bcStatus.hidden = false;
6514
+ }
6515
+
6516
+ function bcStatusHide() {
6517
+ if (els.bcStatus) els.bcStatus.hidden = true;
6518
+ }
6519
+
6288
6520
  function bcRefreshPreview() {
6289
6521
  const now = Date.now();
6290
6522
  if (now - bcPrevAt < 1500) return; /* done + idle çift tetiklemesin */
@@ -6369,7 +6601,7 @@ function bcToolBoxStart(callId, name, args) {
6369
6601
  bcBodyScroll(body);
6370
6602
  }
6371
6603
 
6372
- function bcToolBoxEnd(callId, ok, result) {
6604
+ function bcToolBoxEnd(callId, ok, result, diff) {
6373
6605
  bcFlushStream();
6374
6606
  const t = (callId && bcTools.get(callId)) || null;
6375
6607
  if (callId) bcTools.delete(callId);
@@ -6380,7 +6612,27 @@ function bcToolBoxEnd(callId, ok, result) {
6380
6612
  sec.classList.add('done');
6381
6613
  if (!ok) sec.classList.add('failed');
6382
6614
  const pre = sec.querySelector('.bc-toolout');
6383
- if (out) {
6615
+ if (diff && diff.path) {
6616
+ /* opencode diff görünümü: başlığa +/- rozeti, gövdeye kırmızı/yeşil diff */
6617
+ const headEl = sec.querySelector('.bc-toolhead');
6618
+ if (headEl && !headEl.querySelector('.diff-badge')) {
6619
+ const badge = document.createElement('span');
6620
+ badge.className = 'diff-badge';
6621
+ const a = document.createElement('span');
6622
+ a.className = 'diff-badge-add';
6623
+ a.textContent = '+' + (diff.additions || 0);
6624
+ const d = document.createElement('span');
6625
+ d.className = 'diff-badge-del';
6626
+ d.textContent = '−' + (diff.deletions || 0);
6627
+ badge.appendChild(a);
6628
+ badge.appendChild(d);
6629
+ headEl.appendChild(badge);
6630
+ }
6631
+ pre.hidden = false;
6632
+ pre.textContent = '';
6633
+ pre.classList.add('bc-diffview');
6634
+ pre.appendChild(buildDiffEl(diff, pre.clientWidth || 480));
6635
+ } else if (out) {
6384
6636
  pre.hidden = false;
6385
6637
  pre.textContent = out.length > 4000 ? out.slice(0, 4000) + '\n… (kesildi)' : out;
6386
6638
  } else {
@@ -6484,7 +6736,7 @@ function bcIngest(ev) {
6484
6736
  if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
6485
6737
  break;
6486
6738
  case 'tool-end':
6487
- bcToolBoxEnd(ev.callId, ev.ok, ev.result);
6739
+ bcToolBoxEnd(ev.callId, ev.ok, ev.result, ev.diff);
6488
6740
  if (codeWriteWatch.has(ev.callId)) {
6489
6741
  const wp = codeWriteWatch.get(ev.callId);
6490
6742
  codeWriteWatch.delete(ev.callId);
@@ -6512,6 +6764,7 @@ function bcIngest(ev) {
6512
6764
  bcFlushStream();
6513
6765
  bcCloseToolGroup();
6514
6766
  bcSetBusy(false);
6767
+ bcStatusHide();
6515
6768
  /* iptal/durdurma SEBEBİ panelde de görünür */
6516
6769
  bcLine(ev.aborted ? 't-err' : 't-dim', ev.aborted ? '[durduruldu — sebep: ' + (ev.reason || 'sebep belirtilmedi') + ']' : '(tamamlandı)');
6517
6770
  ideRefreshTree(); /* son güvenlik tazelemesi — izlenmeyen yoldan üretilen dosyalar da düşsün */
@@ -6522,6 +6775,7 @@ function bcIngest(ev) {
6522
6775
  bcFlushStream();
6523
6776
  bcCloseToolGroup();
6524
6777
  bcSetBusy(false);
6778
+ bcStatusHide();
6525
6779
  bcLine('t-err', '[hata] ' + (ev.error || ''));
6526
6780
  break;
6527
6781
  case 'status':
@@ -6530,7 +6784,11 @@ function bcIngest(ev) {
6530
6784
  if (ev.status === 'idle') {
6531
6785
  bcCloseToolGroup();
6532
6786
  bcSetBusy(false);
6787
+ bcStatusHide();
6533
6788
  bcRefreshPreview();
6789
+ } else {
6790
+ /* düşünüyor… / araç adı / bağlam durumu — input üstünde canlı balon */
6791
+ bcStatusShow(ev.status === 'thinking' ? 'düşünüyor…' : String(ev.status || ''));
6534
6792
  }
6535
6793
  break;
6536
6794
  }