beast-agent 2.15.0 → 2.17.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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/agent/engine.js +505 -156
  3. package/src/agent/opencode/agents.js +161 -0
  4. package/src/agent/opencode/askperm.js +135 -0
  5. package/src/agent/opencode/index.js +51 -0
  6. package/src/agent/opencode/permission.js +108 -0
  7. package/src/agent/opencode/prompts/anthropic.txt +105 -0
  8. package/src/agent/opencode/prompts/beast.txt +147 -0
  9. package/src/agent/opencode/prompts/build-switch.txt +5 -0
  10. package/src/agent/opencode/prompts/codex.txt +79 -0
  11. package/src/agent/opencode/prompts/default.txt +95 -0
  12. package/src/agent/opencode/prompts/edit.txt +10 -0
  13. package/src/agent/opencode/prompts/explore.txt +18 -0
  14. package/src/agent/opencode/prompts/gemini.txt +155 -0
  15. package/src/agent/opencode/prompts/glob.txt +6 -0
  16. package/src/agent/opencode/prompts/gpt.txt +107 -0
  17. package/src/agent/opencode/prompts/grep.txt +8 -0
  18. package/src/agent/opencode/prompts/kimi.txt +95 -0
  19. package/src/agent/opencode/prompts/max-steps.txt +16 -0
  20. package/src/agent/opencode/prompts/meta.txt +65 -0
  21. package/src/agent/opencode/prompts/plan.txt +26 -0
  22. package/src/agent/opencode/prompts/read.txt +14 -0
  23. package/src/agent/opencode/prompts/skill.txt +5 -0
  24. package/src/agent/opencode/prompts/task.txt +19 -0
  25. package/src/agent/opencode/prompts/todowrite.txt +44 -0
  26. package/src/agent/opencode/prompts/trinity.txt +97 -0
  27. package/src/agent/opencode/prompts/webfetch.txt +13 -0
  28. package/src/agent/opencode/prompts/websearch.txt +14 -0
  29. package/src/agent/opencode/prompts/write.txt +8 -0
  30. package/src/agent/opencode/systemprompt.js +157 -0
  31. package/src/agent/opencode/toolmap.js +316 -0
  32. package/src/agent/supermemory.js +132 -0
  33. package/src/main.js +135 -41
  34. package/src/preload.js +5 -0
  35. package/src/renderer/renderer.js +59 -15
package/src/main.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session, net: electronNet, clipboard } = require('electron');
4
4
  const path = require('path');
@@ -426,6 +426,11 @@ function parseSettingsText(text) {
426
426
  const parsed = JSON.parse(text);
427
427
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('bozuk yapı');
428
428
  if (!parsed.roleModels) parsed.roleModels = {};
429
+ /* Supermemory (lokal) DEFAULT AÇIK — sunucu ayaktayken daha akıllı bellek;
430
+ ayakta değilken klasik hafıza sorunsuz çalışır (engine fallback) */
431
+ if (!parsed.supermemory || typeof parsed.supermemory !== 'object') {
432
+ parsed.supermemory = { enabled: true, baseUrl: 'http://localhost:6767', apiKey: '', containerTag: 'beast' };
433
+ }
429
434
  return parsed;
430
435
  }
431
436
 
@@ -1335,6 +1340,18 @@ function resolveApproval(id, ok, always) {
1335
1340
  }
1336
1341
 
1337
1342
  function resolveFirstApproval(ok, always) {
1343
+ /* opencode permission (BC): önce opencode izin istekleri yanıtlanır —
1344
+ cevap 'once' | 'always' | 'reject' üçlüsüdür (permission/reply portu) */
1345
+ if (engine && typeof engine.replyPermission === 'function') {
1346
+ try {
1347
+ const pend = engine._perm && engine._perm.list ? engine._perm.list() : [];
1348
+ if (pend.length) {
1349
+ const action = ok ? (always ? 'always' : 'once') : 'reject';
1350
+ const r = engine.replyPermission(pend[0].id, action);
1351
+ if (r && r.ok) return { ok: true, tool: pend[0].permission };
1352
+ }
1353
+ } catch {}
1354
+ }
1338
1355
  const first = pendingApprovals.keys().next();
1339
1356
  if (first.done) return { ok: false, error: 'bekleyen onay yok' };
1340
1357
  return resolveApproval(first.value, ok, always);
@@ -1677,17 +1694,17 @@ async function tryWaSlash(jid, rawText, senderNum, payload0) {
1677
1694
  `Tekrar kodlamak için: /beastcode`;
1678
1695
  }
1679
1696
  } else if (waBcMode.has(jid) && (cmd === 'plan' || cmd === 'build' || cmd === 'auto')) {
1680
- /* BeastCode modunda çalışma modu: /plan · /build · /auto */
1697
+ /* opencode ajan değişimi (WA): /plan → plan ajanı (salt-okur),
1698
+ /build · /auto → build ajanı (varsayılan; auto opencode'da yoktur) */
1681
1699
  const sid = waChats.get(jid);
1682
1700
  const s = sid ? engine.cache.get(sid) : null;
1683
1701
  if (s && s.bcCode) {
1684
- s.bcMode = cmd;
1685
- engine.cache.set(sid, s);
1686
- out = cmd === 'plan'
1687
- ? '*🔍 PLAN MODU* — dosyaları inceler, kod YAZMAZ; adım adım uygulama planı verir.'
1702
+ const agent = engine.setBcAgent(s.id, cmd === 'plan' ? 'plan' : 'build');
1703
+ out = agent === 'plan'
1704
+ ? '*🔍 PLAN AJANI* (opencode plan) — salt-okur: dosyaları okur/inceler, kod YAZMAZ; adım adım uygulama planı verir. Uygulamak için /build yaz.'
1688
1705
  : cmd === 'build'
1689
- ? '*🛠 BUILD MODU* — son planı uygular: dosyalar, komutlar, doğrulama.'
1690
- : '*⚡ AUTO MOD* — kısa plan + uygulama + doğrulama.';
1706
+ ? '*🛠 BUILD AJANI* (opencode build) — bağlamı inceler, todowrite ile planlar, uygular, doğrular.'
1707
+ : '*⚡ BUILD AJANI* (opencode build) — kısa plan + hemen uygulama + doğrulama.';
1691
1708
  } else {
1692
1709
  out = 'Bu komut yalnız BeastCode modunda çalışır — önce /beastcode yaz.';
1693
1710
  }
@@ -2441,14 +2458,12 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
2441
2458
  }
2442
2459
  engine.setSessionModel(sid, botCfg && !botCfg.admin ? (botCfg.model || null) : null);
2443
2460
  /* BEASTCODE MODU: sohbet uzaktan kodlama modundaysa oturumu Beast Code
2444
- olarak tazele — restart sonrası bcCode/workspace bayrakları dosyadan
2461
+ olarak tazele — restart sonrası bcCode/bcAgent/workspace bayrakları dosyadan
2445
2462
  yüklenmediği için masaüstü bcFlush disipliniyle her mesajda bindirilir */
2446
2463
  if (waBcMode.has(jid)) {
2447
2464
  const bs = engine.cache.get(sid);
2448
2465
  if (bs) {
2449
- bs.bcCode = true;
2450
- bs.workspace = waBcWorkspace();
2451
- engine.cache.set(sid, bs);
2466
+ bcBindOc(bs, waBcWorkspace());
2452
2467
  }
2453
2468
  }
2454
2469
  waLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}${waBcMode.has(jid) ? ' beastcode=1' : ''}`);
@@ -3065,6 +3080,10 @@ function reloadBackend() {
3065
3080
  nightReflectAt: settings.nightReflectAt || null,
3066
3081
  approvals: settings.security && settings.security.approvals ? approvalsBridge : null,
3067
3082
  alwaysAllowTools: (settings.security && settings.security.alwaysAllow) || [],
3083
+ /* Supermemory (lokal) bellek katmanı — default açıktır (parseSettingsText);
3084
+ sunucu ayakta değilse engine klasik hafızaya düşer. Kapamak:
3085
+ settings.json → supermemory.enabled = false */
3086
+ supermemory: settings.supermemory || { enabled: true, baseUrl: 'http://localhost:6767', apiKey: '', containerTag: 'beast' },
3068
3087
  crashFile: FALLOUT_CRASH_FILE,
3069
3088
  notifyOwnerFail: settings.notifyOwnerFail !== false,
3070
3089
  fileSend: deliverFile,
@@ -3100,6 +3119,21 @@ function reloadBackend() {
3100
3119
  },
3101
3120
  emit: (ev) => {
3102
3121
  if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
3122
+ /* opencode permission (BC): ask kanalı UI'a zaten yukarıda düştü — WA
3123
+ sahibine de tek satır bildir gitsin (/approve · /approve always · /deny) */
3124
+ if (ev.type === 'permission.asked' && ev.request && wa && wa.connected) {
3125
+ try {
3126
+ const own = waOwnerNum();
3127
+ if (own) {
3128
+ const pats = Array.isArray(ev.request.patterns) ? ev.request.patterns.join(', ') : '*';
3129
+ sendWaSafe(
3130
+ own + '@s.whatsapp.net',
3131
+ '\u26A0\uFE0F *\u0130zin bekleniyor* \u2014 `' + String(ev.request.permission) + '`\n' +
3132
+ String(pats).slice(0, 160) + '\n\nOnayla: `/approve`\nBu desen i\u00e7in bir daha sorma: `/approve always`\nReddet: `/deny`'
3133
+ ).catch(() => {});
3134
+ }
3135
+ } catch {}
3136
+ }
3103
3137
  try { empatiRememberFromEvent(ev); } catch {} /* empati hafızası: sohbet → kayıt + ilgi öğrenme */
3104
3138
  empatiFlushInjectsOnDone(ev); /* tur ortasında kuyruğa düşen proaktif bildirimleri iş bitince geçmişe bas */
3105
3139
  flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
@@ -6005,7 +6039,54 @@ ipcMain.handle('sec:set', (_e, cfg) => {
6005
6039
  log.info('sec', `güvenlik: onay kapısı ${settings.security.approvals ? 'AÇIK' : 'KAPALI (her şey serbest)'}`);
6006
6040
  return { approvals: settings.security.approvals, alwaysAllow: settings.security.alwaysAllow };
6007
6041
  });
6008
- ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
6042
+
6043
+ /* Supermemory (lokal) ayarları: engine'e canlı yansıt + sağlık önbelleğini tazele */
6044
+ ipcMain.handle('supermemory:set', (_e, cfg) => {
6045
+ try {
6046
+ const base = String((cfg && cfg.baseUrl) || '').trim() || 'http://localhost:6767';
6047
+ settings.supermemory = {
6048
+ enabled: !!(cfg && cfg.enabled),
6049
+ baseUrl: base.replace(/\/+$/, ''),
6050
+ apiKey: String((cfg && cfg.apiKey) || '').trim(),
6051
+ containerTag: String((cfg && cfg.containerTag) || 'beast').trim() || 'beast',
6052
+ };
6053
+ saveSettings();
6054
+ if (engine) {
6055
+ engine.supermemory = settings.supermemory;
6056
+ engine._smUp = null;
6057
+ engine._smCheckedAt = 0;
6058
+ engine._smWarned = false;
6059
+ }
6060
+ return { ok: true, supermemory: settings.supermemory };
6061
+ } catch (e) {
6062
+ return { ok: false, error: String((e && e.message) || e) };
6063
+ }
6064
+ });
6065
+ ipcMain.handle('supermemory:get', () => {
6066
+ const sm = settings.supermemory || {};
6067
+ return {
6068
+ enabled: sm.enabled !== false,
6069
+ baseUrl: sm.baseUrl || 'http://localhost:6767',
6070
+ apiKey: sm.apiKey || '',
6071
+ containerTag: sm.containerTag || 'beast',
6072
+ up: engine ? !!engine._smUp : null,
6073
+ };
6074
+ });
6075
+ ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
6076
+ /* opencode permission reply (BC): UI kartı üçlü cevap verir —
6077
+ 'once' | 'always' | 'reject' (+ opsiyonel geri bildirim) */
6078
+ ipcMain.handle('permission:reply', (_e, payload) => {
6079
+ try {
6080
+ if (!engine || typeof engine.replyPermission !== 'function') return { ok: false, error: 'ajan hazır değil' };
6081
+ return engine.replyPermission(
6082
+ String((payload && payload.requestId) || ''),
6083
+ String((payload && payload.action) || 'once'),
6084
+ String((payload && payload.message) || '')
6085
+ );
6086
+ } catch (e) {
6087
+ return { ok: false, error: String((e && e.message) || e) };
6088
+ }
6089
+ });
6009
6090
 
6010
6091
  /* ---------------- #Update IPC ---------------- */
6011
6092
  ipcMain.handle('update:status', async () => {
@@ -7419,6 +7500,23 @@ ipcMain.handle('terminal:stop', () => {
7419
7500
  panele orada akıtılır. */
7420
7501
  const bcSessions = new Map(); /* klasör yolu → sessionId */
7421
7502
 
7503
+ /* opencode bayrak bindirme (bcCode disiplini): bcCode/bcAgent runtime
7504
+ bayrakları restart sonrası jsonl'den gelmez — her mesajda yeniden bindirilir.
7505
+ Ajan: 'plan' | 'build' | 'sandbox' (bcMode:'plan' oturumları geriye uyumlu
7506
+ plan'a düşer; Sandbox oturumları sandbox ajanına sabitlenir) */
7507
+ function bcBindOc(s, folder, agent) {
7508
+ s.workspace = folder;
7509
+ s.bcCode = true;
7510
+ const known = s.bcAgent === 'plan' || s.bcAgent === 'build' || s.bcAgent === 'sandbox';
7511
+ if (agent) {
7512
+ s.bcAgent = agent;
7513
+ } else if (!known) {
7514
+ s.bcAgent = String(s.bcMode || '').toLowerCase() === 'plan' ? 'plan' : 'build';
7515
+ }
7516
+ engine.cache.set(s.id, s);
7517
+ return s;
7518
+ }
7519
+
7422
7520
  function bcGetSession(folder) {
7423
7521
  let sid = bcSessions.get(folder);
7424
7522
  if (sid) {
@@ -7431,7 +7529,8 @@ function bcGetSession(folder) {
7431
7529
  const s = engine._load(engine.createSession().id);
7432
7530
  s.messages = s.messages || [];
7433
7531
  s.bgTitle = 'Beast Code'; /* _view.isBg → sohbet geçmişi listesinde gizli */
7434
- s.bcCode = true; /* engine: her işte todo planı çıkar (BEAST CODE MODU bloğu) */
7532
+ s.bcCode = true; /* opencode ajan sistemi: build/plan ajanları engine'de koşar */
7533
+ s.bcAgent = 'build'; /* opencode varsayılan ajanı (agent.ts:141) */
7435
7534
  try {
7436
7535
  fs.appendFileSync(
7437
7536
  engine._file(s.id),
@@ -7500,9 +7599,7 @@ function bcFlush(folder) {
7500
7599
  clearTimeout(q.timer);
7501
7600
  return;
7502
7601
  }
7503
- s.workspace = folder;
7504
- s.bcCode = true;
7505
- engine.cache.set(s.id, s);
7602
+ bcBindOc(s, folder);
7506
7603
  const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
7507
7604
  if (engine.send(s.id, payload, { userAction: true })) {
7508
7605
  bcQueue.delete(folder);
@@ -7543,25 +7640,21 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
7543
7640
 
7544
7641
  if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
7545
7642
  /* oturumu HEMEN aç: cevap daima gerçek sessionId taşır — renderer paneli bu id ile
7546
- eşler; boşta/kuyrukta olsa bile id yoksa panel olayları eşleyemez ve ölü kalır */
7547
- const s = bcGetSession(ws);
7548
- s.workspace = ws; /* soldaki klasörde çalış */
7549
- s.bcCode = true; /* todo disiplini + iş sonu hızlı kapanış (engine) */
7550
- engine.cache.set(s.id, s);
7643
+ eşler; boşta/kuyrukta olsa bile id yoksa panel olayları eşleyemez ve ölü kalır */
7644
+ const s = bcBindOc(bcGetSession(ws), ws);
7551
7645
  const busy = engine.isBusy(s.id);
7552
7646
  if (modeM) {
7553
- /* mod değişimi anında uygulanır — meşgulken de (yalnız bayrak; engine çakışmaz) */
7554
- s.bcMode = modeM[1].toLowerCase();
7647
+ /* opencode ajan değişimi (agent.ts: plan/build primary ajanlar) — anında
7648
+ uygulanır, meşgulken de (yalnız ajan bayrağı; koşan tur çakışmaz) */
7649
+ const agent = engine.setBcAgent(s.id, modeM[1].toLowerCase() === 'plan' ? 'plan' : 'build');
7555
7650
  if (win && !win.isDestroyed()) {
7556
- const body = modeM[1].toLowerCase() === 'plan'
7557
- ? 'PLAN MODU — dosyaları okuyup inceler, KOD YAZMAZ; adım adım uygulama planı verir.'
7558
- : modeM[1].toLowerCase() === 'build'
7559
- ? 'BUILD MODU — son planı UYGULAR: dosyaları düzenler, komutları çalıştırır, doğrular.'
7560
- : 'OTOMATİK MOD — önce kısa plan, sonra uygulama + doğrulama (OpenCode disiplini).';
7561
- win.webContents.send('agent:event', { sessionId: s.id, type: 'bc-mode', mode: s.bcMode, body });
7651
+ const body = agent === 'plan'
7652
+ ? 'PLAN AJANI (opencode plan) — salt-okur: dosyaları okur/inceler, KOD YAZMAZ; adım adım uygulama planı verir. Uygulamak için /build.'
7653
+ : 'BUILD AJANI (opencode build) — bağlamı inceler, todowrite ile planlar, uygular, doğrular.';
7654
+ win.webContents.send('agent:event', { sessionId: s.id, type: 'bc-agent', agent, body });
7562
7655
  }
7563
- return { ok: true, sessionId: s.id, mode: s.bcMode };
7564
- }
7656
+ return { ok: true, sessionId: s.id, agent, mode: agent };
7657
+ }
7565
7658
  if (busy) {
7566
7659
  /* OPENCODE STEER: koşan tur varken mesaj kuyrukta BEKLEMEZ — engine.send
7567
7660
  konuşmaya ekler, koşan tur sonraki istekte görür (ana chat ile aynı) */
@@ -7655,8 +7748,7 @@ ipcMain.handle('bc:open', async (_e, payload) => {
7655
7748
  if (s.bgTitle && s.bgTitle !== 'Beast Code') return { ok: false, error: 'bu oturum Beast Code oturumu değil' };
7656
7749
  /* mevcut klasöre bağlan: sonraki mesajlar BU oturumda sürer */
7657
7750
  const ws = ideRoot();
7658
- s.workspace = ws;
7659
- s.bcCode = true;
7751
+ bcBindOc(s, ws);
7660
7752
  bcMarkWs(s, ws);
7661
7753
  engine.cache.set(s.id, s);
7662
7754
  bcSessions.set(ws, s.id);
@@ -7671,7 +7763,8 @@ ipcMain.handle('bc:open', async (_e, payload) => {
7671
7763
  ok: true,
7672
7764
  sessionId: s.id,
7673
7765
  busy: !!engine.isBusy(s.id),
7674
- mode: s.bcMode || '',
7766
+ agent: s.bcAgent || 'build',
7767
+ mode: s.bcAgent || 'build',
7675
7768
  messages: msgs.slice(-200),
7676
7769
  };
7677
7770
  });
@@ -8773,8 +8866,9 @@ function sandboxGetSession(folder) {
8773
8866
  const s = engine._load(engine.createSession().id);
8774
8867
  s.messages = s.messages || [];
8775
8868
  s.bgTitle = 'Beast Sandbox'; /* _view.isBg → sohbet geçmişinde gizli */
8776
- s.bcCode = true; /* todo disiplini + hızlı iş kapanışı (engine) */
8777
- s.sbSandbox = true; /* engine: SANDBOX sistem bloğu — ajan açık kaynak repoda olduğunu bilir */
8869
+ s.bcCode = true; /* opencode ajan sistemi: sandbox ajanı engine'de koşar */
8870
+ s.sbSandbox = true; /* engine: 'sandbox' ajanı çözümlemesi */
8871
+ s.bcAgent = 'sandbox'; /* opencode custom-agent: repo disiplini + panel_run */
8778
8872
  try {
8779
8873
  fs.appendFileSync(
8780
8874
  engine._file(s.id),
@@ -8817,10 +8911,10 @@ function sbFlush(folder) {
8817
8911
  clearTimeout(q.timer);
8818
8912
  return;
8819
8913
  }
8820
- s.workspace = folder;
8821
- s.bcCode = true;
8822
- s.sbSandbox = true;
8823
- engine.cache.set(s.id, s);
8914
+ /* opencode mantığı: Sandbox da ajan sistemiyle koşar — 'sandbox' ajanı
8915
+ (repo disiplini + panel_run izni, opencode/agents.js'te tanımlı) */
8916
+ s.sbSandbox = true; /* engine: sandbox ajanı çözümlemesi + ÇALIŞTIR paneli köprüsü */
8917
+ bcBindOc(s, folder, 'sandbox');
8824
8918
  const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
8825
8919
  engine.send(s.id, payload, { userAction: true });
8826
8920
  sbQueue.delete(folder);
package/src/preload.js CHANGED
@@ -87,6 +87,11 @@ contextBridge.exposeInMainWorld('beast', {
87
87
  bcOpen: (id) => ipcRenderer.invoke('bc:open', { id }),
88
88
  bcDelete: (id) => ipcRenderer.invoke('bc:delete', { id }),
89
89
  bcDeleteAll: () => ipcRenderer.invoke('bc:deleteAll'),
90
+ /* opencode permission (BC): UI kartı cevabı — 'once' | 'always' | 'reject' */
91
+ permissionReply: (requestId, action, message) => ipcRenderer.invoke('permission:reply', { requestId, action, message }),
92
+ /* Supermemory (lokal) bellek katmanı ayarları */
93
+ supermemoryGet: () => ipcRenderer.invoke('supermemory:get'),
94
+ supermemorySet: (cfg) => ipcRenderer.invoke('supermemory:set', cfg),
90
95
  studioSend: (msg, attachments) => ipcRenderer.invoke('studio:send', { msg, attachments }),
91
96
  studioStop: () => ipcRenderer.invoke('studio:stop'),
92
97
  studioNew: () => ipcRenderer.invoke('studio:new'),
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  /* Beast renderer — vanilla JS, event-driven via window.beast IPC bridge. */
4
4
 
@@ -762,7 +762,7 @@ function closeChatToolGroup() {
762
762
  function argSummary(name, args) {
763
763
  try {
764
764
  if (name === 'run_command') return String(args.command || '');
765
- if (name === 'read_file' || name === 'write_file' || name === 'edit_file') return String(args.path || '');
765
+ if (name === 'read_file' || name === 'write_file' || name === 'edit_file' || name === 'read' || name === 'write' || name === 'edit') return String(args.path || args.filePath || '');
766
766
  if (name === 'grep') return String(args.pattern || '');
767
767
  if (name === 'glob') return String(args.pattern || '');
768
768
  if (name === 'list_dir') return String(args.path || '.');
@@ -3126,8 +3126,39 @@ async function renderUpdatePane(autoCheck) {
3126
3126
  if (autoCheck) beast.updateCheck().catch(() => {});
3127
3127
  }
3128
3128
 
3129
- /* Sohbete onay kartı düşürür — Onayla / Her zaman / Reddet */function showApprovalCard(ev) {
3130
- streamEl = null;
3129
+ /* opencode permission kartı (BC): izin isteği — Bir kez / Her zaman / Reddet.
3130
+ ev: {id, permission, patterns, metadata, always, tool} */
3131
+ function showPermissionCard(req, inBc) {
3132
+ const toolName = (req.tool && req.tool.name) || req.permission || 'izin';
3133
+ let preview = '';
3134
+ try {
3135
+ preview = JSON.stringify(req.metadata || {}).slice(0, 200);
3136
+ } catch {}
3137
+ const card = document.createElement('div');
3138
+ card.className = 'tool-card';
3139
+ card.innerHTML =
3140
+ `<div class="tool-head"><span class="tool-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>` +
3141
+ `<span class="tool-name">${escapeHtml(String(req.permission || 'izin'))}</span>` +
3142
+ `<span class="tool-arg">${escapeHtml(String(Array.isArray(req.patterns) ? req.patterns.join(', ') : '*').slice(0, 160))}</span></div>` +
3143
+ `<div class="tool-body open" style="display:flex;gap:8px;align-items:center">` +
3144
+ `<button class="btn ap-ok">${_t('sec_ok')}</button>` +
3145
+ `<button class="btn ghost ap-always">${_t('sec_always')}</button>` +
3146
+ `<button class="btn ghost ap-no">${_t('sec_no')}</button>` +
3147
+ `</div>`;
3148
+ const done = (txt) => {
3149
+ card.querySelector('.tool-body').innerHTML = `<span class="sub">${escapeHtml(txt)}</span>`;
3150
+ };
3151
+ card.querySelector('.ap-ok').addEventListener('click', async () => { await beast.permissionReply(req.id, 'once'); done(_t('sec_ok_done')); });
3152
+ card.querySelector('.ap-always').addEventListener('click', async () => { await beast.permissionReply(req.id, 'always'); done(_t('sec_always_done')); });
3153
+ card.querySelector('.ap-no').addEventListener('click', async () => { await beast.permissionReply(req.id, 'reject'); done(_t('sec_no_done')); });
3154
+ if (inBc && typeof bcLine === 'function') {
3155
+ bcLine('t-dim', '⏳ izin bekleniyor: ' + toolName + (preview ? ' — ' + preview : ''));
3156
+ }
3157
+ els.msgs.appendChild(card);
3158
+ scrollDown(true);
3159
+ }
3160
+
3161
+ /* Sohbete onay kartı düşürür — Onayla / Her zaman / Reddet */function showApprovalCard(ev) { streamEl = null;
3131
3162
  const card = document.createElement('div');
3132
3163
  card.className = 'tool-card';
3133
3164
  card.innerHTML =
@@ -5061,6 +5092,15 @@ function onEvent(ev) {
5061
5092
  return;
5062
5093
  }
5063
5094
  if (ev.type === 'install-progress') { updateInstallPct(ev); return; }
5095
+ /* opencode permission (BC): izin kartı — Bir kez / Her zaman / Reddet.
5096
+ BC oturumunda kart IDE panel akışına düşer; diğerlerinde sohbete */
5097
+ if (ev.type === 'permission.asked') {
5098
+ const req = ev.request || {};
5099
+ const isBc = bcSessionId && ev.sessionId === bcSessionId;
5100
+ if (!isBc && ev.sessionId && ev.sessionId !== activeId) return;
5101
+ showPermissionCard(req, isBc);
5102
+ return;
5103
+ }
5064
5104
  /* Headroom durum değişimi → açık olan sekmeyi tazele (Token Sıkıştırma / Kurulum) */
5065
5105
  if (ev.type === 'headroom-status') {
5066
5106
  try {
@@ -5187,7 +5227,7 @@ function onEvent(ev) {
5187
5227
  setStatus(ev.name + '…');
5188
5228
  termAgentEvent(ev);
5189
5229
  /* ajan dosya yazarsa/editlerse açık IDE sekmesini izlemeye al */
5190
- if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
5230
+ if ((ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
5191
5231
  break;
5192
5232
  case 'tool-end':
5193
5233
  finishToolCard(ev.callId, ev.ok, ev.result, ev.diff);
@@ -9388,7 +9428,7 @@ function bcIngest(ev) {
9388
9428
  bcSetBusy(true); /* kuyruktan tetiklenen işte panel kendini kilitler */
9389
9429
  bcToolBoxStart(ev.callId, ev.name, ev.args);
9390
9430
  /* ajan dosya yazarsa/editlerse açık IDE sekmesini izlemeye al */
9391
- if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
9431
+ if ((ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
9392
9432
  break;
9393
9433
  case 'tool-end':
9394
9434
  bcToolBoxEnd(ev.callId, ev.ok, ev.result, ev.diff);
@@ -9398,7 +9438,7 @@ function bcIngest(ev) {
9398
9438
  codeReloadIfOpen(wp);
9399
9439
  }
9400
9440
  /* dosya/klasör üreten araçlardan sonra soldaki ağaç otomatik tazelensin */
9401
- if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command' || ev.name === 'python_run') {
9441
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit' || ev.name === 'run_command' || ev.name === 'python_run') {
9402
9442
  ideRefreshTree();
9403
9443
  }
9404
9444
  break;
@@ -9411,13 +9451,17 @@ function bcIngest(ev) {
9411
9451
  bcQrShow(ev.qr, ev.url);
9412
9452
  break;
9413
9453
  case 'bc-mode':
9414
- /* OpenCode disiplini çalışma modu: başlıkta rozet + panelde bilgi satırı */
9415
- if (els.bcTitle) {
9416
- els.bcTitle.textContent =
9417
- ev.mode === 'plan' ? 'BEAST CODE · PLAN' :
9418
- ev.mode === 'build' ? 'BEAST CODE · BUILD' : 'BEAST CODE';
9454
+ case 'bc-agent':
9455
+ /* opencode ajan değişimi: başlıkta rozet + panelde bilgi satırı */
9456
+ {
9457
+ const agentName = ev.agent || ev.mode || '';
9458
+ if (els.bcTitle) {
9459
+ els.bcTitle.textContent =
9460
+ agentName === 'plan' ? 'BEAST CODE · PLAN' :
9461
+ agentName === 'build' ? 'BEAST CODE · BUILD' : 'BEAST CODE';
9462
+ }
9463
+ bcLine('t-dim', '[' + (ev.body || agentName || 'ajan değişti') + ']');
9419
9464
  }
9420
- bcLine('t-dim', '[' + (ev.body || ev.mode || 'mod değişti') + ']');
9421
9465
  break;
9422
9466
  case 'done':
9423
9467
  bcFlushStream();
@@ -9863,7 +9907,7 @@ function stIngest(ev) {
9863
9907
  break;
9864
9908
  case 'tool-end':
9865
9909
  stToolBoxEnd(ev.callId, ev.ok, ev.result, ev.diff);
9866
- if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command' || ev.name === 'python_run') {
9910
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit' || ev.name === 'run_command' || ev.name === 'python_run') {
9867
9911
  ideRefreshTree(); /* ffmpeg çıktısı anında ağaca düşer */
9868
9912
  }
9869
9913
  break;
@@ -10094,7 +10138,7 @@ function sbIngest(ev) {
10094
10138
  const ok = ev.ok !== false;
10095
10139
  sbLine(ok ? 't-dim' : 't-err', (ok ? ' \u2713 ' : ' \u2717 ') + String(ev.result || '').replace(/\s+/g, ' ').slice(0, 160));
10096
10140
  /* ajan dosya yazarsa/editlerse soldaki repo ağacı canlı tazelenir */
10097
- if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command') ideRefreshTree();
10141
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit' || ev.name === 'run_command') ideRefreshTree();
10098
10142
  break;
10099
10143
  }
10100
10144
  case 'status':