beast-agent 2.14.0 → 2.16.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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/agent/engine.js +499 -154
  3. package/src/agent/financetools.js +307 -0
  4. package/src/agent/opencode/agents.js +161 -0
  5. package/src/agent/opencode/askperm.js +135 -0
  6. package/src/agent/opencode/index.js +51 -0
  7. package/src/agent/opencode/permission.js +108 -0
  8. package/src/agent/opencode/prompts/anthropic.txt +105 -0
  9. package/src/agent/opencode/prompts/beast.txt +147 -0
  10. package/src/agent/opencode/prompts/build-switch.txt +5 -0
  11. package/src/agent/opencode/prompts/codex.txt +79 -0
  12. package/src/agent/opencode/prompts/default.txt +95 -0
  13. package/src/agent/opencode/prompts/edit.txt +10 -0
  14. package/src/agent/opencode/prompts/explore.txt +18 -0
  15. package/src/agent/opencode/prompts/gemini.txt +155 -0
  16. package/src/agent/opencode/prompts/glob.txt +6 -0
  17. package/src/agent/opencode/prompts/gpt.txt +107 -0
  18. package/src/agent/opencode/prompts/grep.txt +8 -0
  19. package/src/agent/opencode/prompts/kimi.txt +95 -0
  20. package/src/agent/opencode/prompts/max-steps.txt +16 -0
  21. package/src/agent/opencode/prompts/meta.txt +65 -0
  22. package/src/agent/opencode/prompts/plan.txt +26 -0
  23. package/src/agent/opencode/prompts/read.txt +14 -0
  24. package/src/agent/opencode/prompts/skill.txt +5 -0
  25. package/src/agent/opencode/prompts/task.txt +19 -0
  26. package/src/agent/opencode/prompts/todowrite.txt +44 -0
  27. package/src/agent/opencode/prompts/trinity.txt +97 -0
  28. package/src/agent/opencode/prompts/webfetch.txt +13 -0
  29. package/src/agent/opencode/prompts/websearch.txt +14 -0
  30. package/src/agent/opencode/prompts/write.txt +8 -0
  31. package/src/agent/opencode/systemprompt.js +157 -0
  32. package/src/agent/opencode/toolmap.js +316 -0
  33. package/src/agent/scripts/mt5_bridge.py +381 -0
  34. package/src/agent/tools.js +10 -3
  35. package/src/main.js +503 -69
  36. package/src/mt5bridge.js +215 -0
  37. package/src/preload.js +13 -3
  38. package/src/renderer/index.html +67 -0
  39. package/src/renderer/renderer.js +552 -53
  40. package/src/renderer/style.css +328 -1
  41. package/src/agent/android.js +0 -265
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  /* Beast engine: sessions, frugal context, streaming tool loop.
4
4
  v2: token-bazlı bağlam bütçesi (kullanım kalibrasyonlu), paralel tool
@@ -10,6 +10,9 @@ const llm = require('./llm');
10
10
  const { chatStream, chatStreamAuto, chatOnce } = llm;
11
11
  const { chatCompletionsUrl, beastDir } = require('./config');
12
12
  const tools = require('./tools');
13
+ let financetools = null;
14
+ try { financetools = require('./financetools'); } catch {}
15
+ const FINANCE_TOOL_SET = new Set(financetools ? financetools.NAMES : []);
13
16
  const research = require('./research');
14
17
  const agentdefs = require('./agentdefs');
15
18
  const memory = require('./memory');
@@ -21,6 +24,10 @@ const apps = require('./apps');
21
24
  const pdfwrite = require('./pdfwrite');
22
25
  const { estTokens, estMsgTokens } = require('./tokens');
23
26
  const log = require('./logger');
27
+ /* opencode mantığı (birebir port): ajanlar, izinler, tool seti, promptlar —
28
+ Beast Code (bcCode) oturumları bu çekirdek üzerinden koşar */
29
+ const opencode = require('./opencode');
30
+ const { AskService } = opencode;
24
31
 
25
32
  const MAX_TURNS = 40;
26
33
  const SUB_MAX_TURNS = 8;
@@ -134,15 +141,9 @@ const PERM_TOOL_SETS = {
134
141
  };
135
142
  const PERM_LEVELS = ['all', 'web', 'read', 'chat'];
136
143
 
137
- /* opencode plan agent portu: PLAN modu PROMPT düzeyinde değil GERÇEKten
138
- salt-okurdur — yazma/çalıştırma araçları setten düşer, sadece plan çıkar */
139
- const PLAN_ALLOW_TOOLS = new Set([
140
- 'read_file', 'list_dir', 'grep', 'glob',
141
- 'web_search', 'http_fetch', 'webfetch', 'deep_search',
142
- 'browser_open', 'browser_read', 'browser_snapshot',
143
- 'todo_write', 'memory_search', 'kb_search', 'ocr_read',
144
- 'git_diff_review', 'repo_map', 'repo_symbols', 'xlsx_read',
145
- ]);
144
+ /* opencode plan ajanı salt-okurluğu artık PERMISSION kurallarından türer
145
+ (opencode/agents.js: edit {"*":"deny"} → Permission.disabled seti düşürür) —
146
+ elle bakılan araç beyaz listesi kalktı (tamamen opencode mantığı) */
146
147
 
147
148
  /* İzin değerini normalize eder: 'all' → ['all'], 'web' → ['web'],
148
149
  'web,read' / ['web','read'] → ['web','read'] (sıra PERM_LEVELS'e göre dizilir).
@@ -310,6 +311,15 @@ class Engine {
310
311
  this.notifyOwnerFail = opts.notifyOwnerFail !== false; // #25 hata mail bildirimi (runtime /notify)
311
312
  this.reminders = opts.reminders || null; // hatırlatıcı kancası (main enjekte eder)
312
313
  this.watchers = opts.watchers || null; // arka plan izleyici köprüsü (main enjekte eder)
314
+ /* opencode permission Service (birebir port): ask/reply — once|always|reject.
315
+ BC oturumlarında riskli araç onayı bu akışla sorulur; UI 'permission.asked'
316
+ eventi dinler, 'permission:reply' ile yanıtlar. approved listesi oturum
317
+ boyunca "always" desenlerini kural olarak taşır (opencode birebir). */
318
+ this._perm = new AskService((ev) => this.emit(ev));
319
+ this.bcAskApprovals = opts.approvals ? true : false; // onay ayarı → opencode user konfigi gibi "ask" üretir
320
+ /* opencode reminders.ts: bu oturumda daha önce plan ajanı koştu mu?
321
+ plan→build geçişinde build-switch.txt synthetic part'ı buna bağlanır */
322
+ this._bcWasPlan = new Map(); // sessionId -> bool
313
323
  /* onay kapısı: riskli araç öncesi dış onay beklenir ({request} async) */
314
324
  this.approvals = opts.approvals || null;
315
325
  /* "bir daha sorma" onaylanan araçlar — bu setteki riskli araçlar sorulmaz */
@@ -939,11 +949,10 @@ class Engine {
939
949
  return lv.effort || null;
940
950
  }
941
951
 
942
- /* Oturum bazlı düşünme çabası: Beast Code hız modunda en fazla 'low' */
952
+ /* opencode birebir: düşünme çabası ajan tanımına değil kullanıcı ayarına
953
+ bağlıdır — eski "BC'de low'a sabitle" hız kısıtı kalktı (opencode'da yok) */
943
954
  _thinkEffortFor(session) {
944
- const e = this._thinkEffort();
945
- if (!session || !session.bcCode) return e;
946
- return e ? 'low' : null;
955
+ return this._thinkEffort();
947
956
  }
948
957
 
949
958
  _thinkLabel() {
@@ -1137,6 +1146,13 @@ class Engine {
1137
1146
  } else if (rec.t === 'bcws') {
1138
1147
  /* Beast Code oturumu: hangi çalışma klasörüne bağlı */
1139
1148
  session.bcWs = String(rec.ws || '');
1149
+ } else if (rec.t === 'fin') {
1150
+ /* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
1151
+ session.finance = true;
1152
+ session.financeTrader = !!rec.trader;
1153
+ } else if (rec.t === 'finoff') {
1154
+ session.finance = false;
1155
+ session.financeTrader = false;
1140
1156
  } else if (rec.t === 'msg') {
1141
1157
  delete rec.t;
1142
1158
  session.messages.push(rec);
@@ -1273,10 +1289,36 @@ class Engine {
1273
1289
  count: s.messages.length,
1274
1290
  isBg,
1275
1291
  isBotDm: !!s.isBotDm,
1292
+ finance: !!s.finance,
1276
1293
  bgStatus: isBg ? (job ? job.status : null) : null,
1277
1294
  };
1278
1295
  }
1279
1296
 
1297
+ /* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
1298
+ (jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
1299
+ markFinance(id, trader = false) {
1300
+ const s = this._load(String(id));
1301
+ s.finance = true;
1302
+ s.financeTrader = !!trader;
1303
+ try {
1304
+ fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'fin', trader: !!trader, at: nowIso() }) + '\n');
1305
+ } catch {}
1306
+ this.cache.set(String(s.id), s);
1307
+ return s;
1308
+ }
1309
+
1310
+ /* Finance etiketini kaldır — oturum normal sohbete döner */
1311
+ unmarkFinance(id) {
1312
+ const s = this._load(String(id));
1313
+ s.finance = false;
1314
+ s.financeTrader = false;
1315
+ try {
1316
+ fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
1317
+ } catch {}
1318
+ this.cache.set(String(s.id), s);
1319
+ return s;
1320
+ }
1321
+
1280
1322
  /* #17 paralel ajan oturumları sol sohbet listesine KARIŞMAZ;
1281
1323
  geçmişleri sağdaki Paralel Ajanlar panelinde izlenir */
1282
1324
  listSessions() {
@@ -1489,56 +1531,151 @@ class Engine {
1489
1531
  return out;
1490
1532
  }
1491
1533
 
1492
- /* Beast Code (IDE paneli): VS Code hızında çalışsın diye SECMET prompt.
1493
- OpenCode kodlama disiplini gömülü: bağlam → plan → küçük diff → doğrula.
1494
- GENEL AMAÇLI kodlama ajanı — web/mobil gibi tek stack'e zorlanmaz; stack'i
1495
- projenin kendi dosyaları belirler. Hafıza embedding araması, skills
1496
- taraması, kişilik/kural blokları YOK — prompt kısa kalır → ilk token
1497
- hızlı, tur maliyeti düşük. */
1498
- buildBcSystem(session) {
1499
- const nowD = new Date();
1500
- const localDate = nowD.toLocaleDateString('tr-TR');
1501
- /* önek-cache disiplini (opencode Context Epoch): dakika yerine saat —
1502
- system prompt her dakika değişse sağlayıcı önbelleği sürekli bozulur */
1503
- const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
1504
- const mode = String((session && session.bcMode) || 'auto').toLowerCase();
1505
- const modeBlock =
1506
- mode === 'plan'
1507
- ? 'ÇALIŞMA MODU: PLAN 🔍 — dosyaları oku/incele (read_file, list_dir), KOD YAZMA; mevcut yapıyı özetle ve adım adım UYGULAMA PLANI ver (hangi dosya, ne değişecek, nasıl doğrulanır). Kullanıcı /build deyince plan uygulanır.'
1508
- : mode === 'build'
1509
- ? 'ÇALIŞMA MODU: BUILD 🛠 — son planı SOHBETTEKİ bağlamdan al ve UYGULA: dosyaları düzenle, komutları çalıştır, doğrula. Yeni plan açma; en fazla 1 cümlelik yön gösterimi + uygulama.'
1510
- : 'ÇALIŞMA MODU: OTOMATİK ⚡ — önce 2-4 satırlık mini plan (todo_write), sonra hemen uygula + doğrula.';
1534
+ /* ---------- opencode mantığı: Beast Code ajan sistemi ----------
1535
+ opencode-dev/packages/opencode/src birebir portu — BC oturumları artık
1536
+ "mod" değil AJAN koşar (agent/agent.ts):
1537
+ build — varsayılan (edit serbest, question/plan_enter allow)
1538
+ plan — salt-okur: edit * deny (yalnız .opencode/plans/*.md yazılabilir)
1539
+ System birleşimi (llm/request.ts:58-66 + prompt.ts:1257-1269):
1540
+ [ ajanın promptu VARSA o, yoksa model-bazlı ana prompt (beast.txt,
1541
+ gpt.txt, anthropic.txt, gemini.txt, kimi.txt, codex.txt, default.txt),
1542
+ environment bloğu, proje talimatları (AGENTS.md), skills kataloğu ] */
1543
+
1544
+ /* BC ajanını çözer (opencode agents.get):
1545
+ - Sandbox oturumları (sbSandbox) → 'sandbox' ajanı (repo disiplini + panel_run)
1546
+ - session.bcAgent ('plan'|'build') — eski bcMode:'plan' oturumları geriye
1547
+ uyumlu plan ajanına düşer
1548
+ Studio/Finance/bgJob kendi ajan dünyalarında koşar — opencode BC sistemine
1549
+ katılmaz (buildStudioSystem/buildFinanceSystem/buildBgSystem öncelikli). */
1550
+ _bcAgentInfo(session) {
1551
+ if (!session || !session.bcCode) return null;
1552
+ if (session.studio || session.finance || session.bgJob) return null;
1553
+ const name = session.sbSandbox
1554
+ ? 'sandbox'
1555
+ : session.bcAgent === 'plan' || session.bcAgent === 'build' || session.bcAgent === 'sandbox'
1556
+ ? session.bcAgent
1557
+ : String(session.bcMode || '').toLowerCase() === 'plan'
1558
+ ? 'plan'
1559
+ : 'build';
1560
+ const ws = (session && session.workspace) || this.workspace;
1561
+ const user = this.bcAskApprovals ? { bash: 'ask', edit: { '*': 'ask' } } : {};
1562
+ const info = opencode.agents.get(name, { worktree: ws, promptExplore: opencode.PROMPT_EXPLORE, user });
1563
+ if (info) return info;
1564
+ const fallbackName = session.sbSandbox ? 'sandbox' : 'build';
1565
+ return opencode.agents.get(fallbackName, { worktree: ws, promptExplore: opencode.PROMPT_EXPLORE, user });
1566
+ }
1567
+
1568
+ /* opencode opencode ajan değişimi: /plan /build — oturumun ajanı döner,
1569
+ plan→build geçişi reminders (build-switch.txt) için işaretlenir */
1570
+ setBcAgent(sessionId, name) {
1571
+ const s = this.cache.get(String(sessionId || ''));
1572
+ if (!s || !s.bcCode) return null;
1573
+ const prev = s.bcAgent || 'build';
1574
+ const next = name === 'plan' ? 'plan' : 'build';
1575
+ if (prev === 'plan' && next === 'build') this._bcWasPlan.set(String(s.id), true);
1576
+ s.bcAgent = next;
1577
+ this.cache.set(s.id, s);
1578
+ this.emit({ type: 'bc-agent', sessionId: s.id, agent: next, prev });
1579
+ return next;
1580
+ }
1581
+
1582
+ /* opencode Permission.reply (UI/WA köprüsü): 'once' | 'always' | 'reject' */
1583
+ replyPermission(requestId, action, message) {
1584
+ try {
1585
+ return this._perm.reply(requestId, action === 'always' ? 'always' : action === 'reject' ? 'reject' : 'once', message);
1586
+ } catch (e) {
1587
+ return { ok: false, error: String((e && e.message) || e) };
1588
+ }
1589
+ }
1590
+
1591
+ /* opencode request.ts prepare — system birleşimi (birebir sıra) */
1592
+ buildOcSystem(session, agentInfo) {
1593
+ const sid = String(session && session.id);
1594
+ const sel = this.sessionModel.get(sid) || this.modelFor(null) || this.sel || { model: '', providerId: '' };
1595
+ const ws = (session && session.workspace) || this.workspace;
1596
+ const agentPrompt = (agentInfo && agentInfo.prompt) || '';
1597
+ const main = agentPrompt ? [agentPrompt] : opencode.systemprompt.provider(sel.model);
1511
1598
  const proj = this._projectInstructions(session);
1512
- return (
1513
- 'Sen BEAST CODE\u2019sun — IDE panelinde çalışan, OpenCode disiplinli hızlı bir kodlama ajanı. VS Code gibi çevik ol.\n' +
1514
- 'GENEL AMAÇLI KODLAMA AJANISIN — tek bir proje türüne sınırlı değilsin: web, backend/API, mobil, masaüstü, CLI, kütüphane, script, oyun, veri, DevOps, gömülü… İstenen neyse o. İstenmeyen şeyi (ör. site değilken site) kendiliğinden ÜRETME.\n' +
1515
- `Çalışma klasörü: ${(session && session.workspace) || this.workspace}\n` +
1516
- ((session && session.sbSandbox)
1517
- ? 'BEAST SANDBOX MODUNDASIN: GitHub\u2019dan indirilmiş AÇIK KAYNAK bir repo klasöründesin (Beast-Sandbox). Bu kodun sahibi değilsin — hazır bir dış proje; repo sahibinin konvansiyonlarına uy, gereksiz yeniden biçimlendirme yapma. İşe README + paket bildirim dosyasıyla başla (list_dir/glob); bağımlılıklar kurulu değilse önce kurulum komutunu çalıştır (npm install vb.). UZUN SÜRELİ sunucu/süreç (npm start, npm run dev, python app.py vb.) run_command ile ASLA başlatma — tur kilitlenir; kullanıcı çalıştırmak isterse panel_run aracıyla ÇALIŞTIR paneline bırakın (çıktı orada canlı akar, ■ ile durur) ve adresi kullanıcıya bildir. panel_run yalnız BİR süreç çalıştırır.\n'
1518
- : '') +
1519
- `Yerel zaman: ${localDate} ${localTime}\n` +
1520
- `${modeBlock}\n` +
1521
- 'STACK TESPİTİ: işe başlarken workspace\u2019i tanı — package.json, go.mod, Cargo.toml, requirements.txt/pyproject.toml, pom.xml/build.gradle, *.csproj, pubspec.yaml, Gemfile, composer.json vb. işaret dosyalarına bak (list_dir + glob). Dili, framework\u2019ü, konvansiyonları ve build/test/run komutlarını PROJE belirler; her işte aynı teknolojiye itme, mevcut stack\u2019e uy.\n' +
1522
- (proj
1523
- ? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n'
1524
- : '') +
1525
- 'WORKFLOW (her işte bu sıra):\n' +
1526
- '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' +
1527
- '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' +
1528
- '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' +
1529
- '4) DOĞRULA: edit sonrası projenin KENDİ komutlarıyla derle/test/lint çalıştır (run_command: npm test, go test ./..., cargo test, pytest, mvn test vb. — hangisi geçerliyse); 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' +
1530
- '5) RAPOR: 1-3 satır — ne değişti + doğrulama sonucu (ör. "npm test ✓ 154/154"). Uzun açıklama yok.\n' +
1531
- 'ÖNİZLEME: panel Preview\u2019da dahili statik sunucuyu KENDİSİ yönetir — statik HTML işinde sunucu başlatma (python -m http.server vs. GEREKMEZ); index.html\u2019i hazır bırak, kullanıcı Preview\u2019a basınca canlı açılır. Yalnız gerçek dev-server gerektiren projelerde (React/Vite/Next/Expo vb.) kendi sunucunu BLOKLAMADAN arka planda başlat ve çalışan adresi (http://localhost:PORT) raporda yaz. file:// protokolü ASLA kullanılmaz.\n' +
1532
- 'HIZ KURALLARI:\n' +
1533
- '- VAR OLAN dosyayı edit_file ile değiştir, YENİ dosyayı write_file ile yarat; basit dosya oluşturma/düzenleme için Python scripti yazma; python_run yalnız gerçek hesap/veri işleme gerekiyorsa.\n' +
1534
- '- Bağımsız araç çağrılarını AYNI TURDA paralel ver (birden çok read_file tek turda).\n' +
1535
- '- Dosyayı kullanıcı söylememişse list_dir ile yapıyı görüp kendin karar ver.\n' +
1536
- '- Run_command PowerShell ortamında çalışır (Windows): KALICI oturum — cd ve $env değişkenleri çağrılar arasında korunur; büyük çıktıda tam çıktı geçici dosyaya düşer, read_file ile oku. shell:"bash" verirsen git-bash ile bash sözdizimi koşar.\n' +
1537
- '- Soru sorma, sohbet etme; uygulayıp özetle. Kullanıcı /plan /build /auto ile modu değiştirir.\n' +
1538
- FORMAT_RULES
1539
- );
1599
+ const skillsBlock = (() => {
1600
+ try {
1601
+ return opencode.systemprompt.skills((agentInfo && agentInfo.permission) || [], () => skills.scan());
1602
+ } catch {
1603
+ return undefined;
1604
+ }
1605
+ })();
1606
+ /* opencode'da birleşim .filter(x => x).join("\n") — ajan promptu varsa
1607
+ model-bazlı dosya YERİNE geçer */
1608
+ return [
1609
+ ...main,
1610
+ ...opencode.systemprompt.environment({
1611
+ modelId: sel.model,
1612
+ providerId: sel.providerId || '?',
1613
+ directory: ws,
1614
+ worktree: this.workspace,
1615
+ }),
1616
+ ...(proj ? [proj] : []),
1617
+ ...(skillsBlock ? [skillsBlock] : []),
1618
+ ]
1619
+ .filter((x) => x)
1620
+ .join('\n');
1621
+ }
1622
+
1623
+ /* opencode araç izinleri: pattern'ler araç çağrısından türer.
1624
+ bash'ta always: [komut] — opencode tree-sitter deseni basitleştirilmiş. */
1625
+ _bcPermissionFor(name, args, ws) {
1626
+ const a = args || {};
1627
+ const abs = (p) => {
1628
+ try {
1629
+ const raw = String(p || '').trim();
1630
+ if (!raw) return '*';
1631
+ return path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(ws || this.workspace, raw);
1632
+ } catch {
1633
+ return '*';
1634
+ }
1635
+ };
1636
+ switch (name) {
1637
+ case 'edit_file':
1638
+ case 'write_file':
1639
+ return { permission: 'edit', patterns: [abs(a.path)], always: [] };
1640
+ case 'run_command':
1641
+ return { permission: 'bash', patterns: [String(a.command || '*')], always: [String(a.command || '*')] };
1642
+ case 'read_file':
1643
+ return { permission: 'read', patterns: [abs(a.path)], always: [] };
1644
+ case 'glob':
1645
+ return { permission: 'glob', patterns: [String(a.pattern || '*')], always: [] };
1646
+ case 'grep':
1647
+ return { permission: 'grep', patterns: [String(a.pattern || '*')], always: [] };
1648
+ case 'list_dir':
1649
+ return { permission: 'list', patterns: [abs(a.path)], always: [] };
1650
+ case 'webfetch':
1651
+ return { permission: 'webfetch', patterns: [String(a.url || '*')], always: [] };
1652
+ case 'web_search':
1653
+ return { permission: 'websearch', patterns: [String(a.query || '*')], always: [] };
1654
+ case 'todo_write':
1655
+ return { permission: 'todowrite', patterns: ['*'], always: ['*'] };
1656
+ case 'delegate_task':
1657
+ return { permission: 'task', patterns: [String(a.subagent_type || '*')], always: [] };
1658
+ default:
1659
+ return { permission: name, patterns: ['*'], always: [] };
1660
+ }
1540
1661
  }
1541
1662
 
1663
+ /* opencode external-directory: workspace dışı dosya erişimi ayrı izin ister
1664
+ (tool/external-directory.ts) — read/edit/write kapsar */
1665
+ _bcExternalCheck(name, args, ws) {
1666
+ if (name !== 'read_file' && name !== 'edit_file' && name !== 'write_file') return null;
1667
+ const raw = String((args && args.path) || '').trim();
1668
+ if (!raw) return null;
1669
+ try {
1670
+ const abs = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(ws || this.workspace, raw);
1671
+ const base = path.resolve(ws || this.workspace);
1672
+ if (path.normalize(abs).toLowerCase().startsWith(path.normalize(base).toLowerCase())) return null;
1673
+ return { permission: 'external_directory', patterns: [abs], always: [] };
1674
+ } catch {
1675
+ return null;
1676
+ }
1677
+ }
1678
+
1542
1679
  /* Beast Studio (video paneli): video YAPMA ve DÜZENLEME ajanı. Workspace'teki
1543
1680
  her şey video proje malzemesi olarak görülür (ham görüntü, ses, görsel,
1544
1681
  altyazı, çıktı). Medya işi ffprobe + ffmpeg ile yürütür; ikisi de app ile
@@ -1599,6 +1736,46 @@ class Engine {
1599
1736
  );
1600
1737
  }
1601
1738
 
1739
+ /* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
1740
+ Oturum alanları (main enjekte eder): financeTrader, financeAuto,
1741
+ financeSymbols, financeStrategy, financeLimits */
1742
+ buildFinanceSystem(session) {
1743
+ const nowD = new Date();
1744
+ const localDate = nowD.toLocaleDateString('tr-TR');
1745
+ const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
1746
+ const isTrader = !!(session && session.financeTrader);
1747
+ const auto = !!(session && session.financeAuto);
1748
+ const symbols = Array.isArray(session && session.financeSymbols) ? session.financeSymbols.join(', ') : String((session && session.financeSymbols) || '');
1749
+ const lim = (session && session.financeLimits) || {};
1750
+ const modeBlock = isTrader
1751
+ ? auto
1752
+ ? 'ROL: OTONOM TRADER ⚡ — tur tur çalışıyorsun. Her turda: veri çek → değerlendir → GEREKİRSE işlem aç/kapat/SL-TP güncelle → kısa rapor. Sistem seni turlar; sohbette konuşmazsın.'
1753
+ : 'ROL: GÖZLEMCİ ANALİST 🔍 — tur tur piyasa tararsın ama OTOMATİK İŞLEM KAPALI: mt5_trade/mt5_pending KULLANMA. Fırsatları net öneri olarak yaz (sembol, yön, giriş, SL, TP, sebep); sahibi onaylarsa açılır.'
1754
+ : 'ROL: TRADING CO-PİLOT 💬 — sahibinle sohbet ediyorsun. Veri isterse mt5_* araçlarıyla çek, analiz et; işlem istenirse limitler içinde uygula, istenmezse sadece öneri ver.';
1755
+ const proj = this._projectInstructions(session);
1756
+ return (
1757
+ 'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
1758
+ 'ORTAM: Beast Finance uygulamasındasın — solda sohbet geçmişi + izleyiciler, ortada bu sohbet, SAĞ panelde MT5 işlem panosu (hesap, pozisyonlar, piyasa, AKIŞ) var.\n' +
1759
+ 'KÖPRÜ: MT5 terminaline Python stdio köprüsüyle bağlısın; panel 3 saniyede bir hesap/pozisyon/piyasa verisini tazeler. mt5_* araç çağrıların ve işlem hareketlerin (açılan/kapanan pozisyonlar) SAĞ paneldeki AKIŞ akışına canlı düşer — panelin orayı izlediğini bil.\n' +
1760
+ 'GÖREV ALANI: piyasa verisi okuma (fiyat/hesap/pozisyon/geçmiş), teknik değerlendirme, pozisyon yönetimi (aç/kapat/SL/TP), risk disiplini ve kısa karar raporları. Kod yazma işi DEĞİLDİR.\n' +
1761
+ modeBlock + '\n' +
1762
+ 'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
1763
+ 'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
1764
+ (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
1765
+ (lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
1766
+ 'RİSK DİSİPLİNİ:\n' +
1767
+ '- SL\u2019siz pozisyon BIRAKMA: açtığın her işlemde mantıklı bir SL koy; SL\u2019siz kalan pozisyonu mt5_modify ile tamamla.\n' +
1768
+ '- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
1769
+ '- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
1770
+ '- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
1771
+ (session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
1772
+ 'RAPOR DİSİPLİNİ: kısa ve sayısal — sembol, yön, lot, giriş, SL/TP, sebep tek satırda. Uzun fal açma; tablo/liste kullanabilirsin.\n' +
1773
+ `Yerel zaman: ${localDate} ${localTime} — piyasa saatlerine dikkat et (borsa/metal seansları kapalıysa spread genişler; hafta sonu FX kapalıdır).\n` +
1774
+ (proj ? '# PROJE TALİMATLARI\n' + proj + '\n' : '') +
1775
+ FORMAT_RULES
1776
+ );
1777
+ }
1778
+
1602
1779
  async buildSystem(queryText, session) {
1603
1780
  /* BOT OTURUMU: non-admin bota bağlıysa global Beast hafızası HIÇ girmez —
1604
1781
  o botun kendi SOUL/USER/MEMORY dosyaları kullanılır (tam izolasyon) */
@@ -3135,53 +3312,53 @@ class Engine {
3135
3312
  session.messages = keep;
3136
3313
  }
3137
3314
 
3138
- async _chatTurn(session, signal, onDelta, toolsList = TOOLS) {
3139
- /* MCP: %APPDATA%\beast\mcp.json'daki etkin serverların araçlarını şema listesine ekle
3140
- (bağlı değilse lazy bağlanır; kapalıysa liste değişmez; Beast Code paneli hızlı
3141
- ilk-token sözü için MCP'siz kalır) */
3142
- if (!session || !session.bcCode) toolsList = await mcp.mergeTools(toolsList);
3143
- /* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
3144
- toolsList = apps.mergeTools(toolsList);
3145
- /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3146
- modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3147
- if (!(session && session.sbSandbox)) {
3148
- toolsList = toolsList.filter((t) => !(t && t.function && t.function.name === 'panel_run'));
3315
+ async _chatTurn(session, signal, onDelta, toolsList = TOOLS, bcOpts = null) {
3316
+ /* opencode mantığı (BC): araç seti opencode builtin registry'si — read/edit/
3317
+ write/bash/glob/grep/list/webfetch/websearch/todowrite/task/skill.
3318
+ MCP/apps/panel_run BC'ye GİRMEZ (opencode disiplini: hızlı ilk token). */
3319
+ const bcAgent = session && session.bcCode ? this._bcAgentInfo(session) : null;
3320
+ if (bcAgent) {
3321
+ toolsList = opencode.toolmap.definitions();
3322
+ } else {
3323
+ /* MCP: %APPDATA%\beast\mcp.json'daki etkin serverların araçlarını şema listesine ekle
3324
+ (bağlı değilse lazy bağlanır; kapalıysa liste değişmez) */
3325
+ toolsList = await mcp.mergeTools(toolsList);
3326
+ /* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
3327
+ toolsList = apps.mergeTools(toolsList);
3328
+ /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3329
+ modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3330
+ if (!(session && session.sbSandbox)) {
3331
+ toolsList = toolsList.filter((t) => !(t && t.function && t.function.name === 'panel_run'));
3332
+ }
3149
3333
  }
3150
3334
  /* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
3151
3335
  const adef = this._agentDefFor(session, !!session.bgJob);
3152
- /* Beast Code: todo_write açıklaması "3+ adım" kısıtı içerir ve model küçük
3153
- işlerde atlar — bcCode oturumunda HER işte İLK araç olacak şekilde değiştir */
3154
- if (session && session.bcCode) {
3155
- toolsList = toolsList.map((t) =>
3156
- t && t.function && t.function.name === 'todo_write'
3157
- ? {
3158
- ...t,
3159
- function: {
3160
- ...t.function,
3161
- description:
3162
- 'Replace the visible task checklist for this chat. Beast Code session: ALWAYS call this tool as your VERY FIRST action for EVERY job (even tiny 1-step jobs) BEFORE any text or other tool, then update statuses (status:"done") as you complete each step. Keep titles short, verb-first.',
3163
- },
3164
- }
3165
- : t
3166
- );
3167
- }
3168
3336
  const promptText = this._lastUserText(session);
3169
3337
  let system = session.bgJob
3170
3338
  ? this.buildBgSystem(session)
3171
3339
  : session.studio
3172
3340
  ? this.buildStudioSystem(session)
3173
- : session.bcCode
3174
- ? this.buildBcSystem(session)
3175
- : await this.buildSystem(promptText, session);
3341
+ : session.finance
3342
+ ? this.buildFinanceSystem(session)
3343
+ : bcAgent
3344
+ ? this.buildOcSystem(session, bcAgent)
3345
+ : await this.buildSystem(promptText, session);
3176
3346
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
3177
3347
  // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
3348
+ // BC'de opencode adları Beast adına çevrilerek denetlenir (read → read_file).
3178
3349
  const perms = this.sessionPermFor(session.id);
3179
3350
  let allowedSet = null;
3180
3351
  if (!perms.includes('all')) {
3181
3352
  allowedSet = new Set();
3182
3353
  for (const p of perms) for (const t of PERM_TOOL_SETS[p] || []) allowedSet.add(t);
3183
3354
  }
3184
- let activeTools = allowedSet ? toolsList.filter((t) => allowedSet.has(t.function.name)) : toolsList;
3355
+ let activeTools = allowedSet
3356
+ ? toolsList.filter((t) => allowedSet.has(opencode.toolmap.TO_BUILTIN[t.function.name] || t.function.name))
3357
+ : toolsList;
3358
+ /* Beast Finance: mt5_* araçları YALNIZ finance oturumlarında görünür */
3359
+ if (FINANCE_TOOL_SET.size && !(session && session.finance)) {
3360
+ activeTools = activeTools.filter((t) => !FINANCE_TOOL_SET.has(t.function.name));
3361
+ }
3185
3362
  /* bot skill kısıtı: bota verilen yetkiye göre araç seti daraltılır */
3186
3363
  const toolLimit = this.sessionTools.get(String(session.id));
3187
3364
  if (toolLimit) activeTools = activeTools.filter((t) => toolLimit.has(t.function.name));
@@ -3194,9 +3371,16 @@ class Engine {
3194
3371
  /* CEO: uygulayıcı araçlar kapalı — her şey paralel ajana devredilir */
3195
3372
  activeTools = activeTools.filter((t) => !CEO_EXEC_TOOLS.has(t.function.name));
3196
3373
  }
3197
- if (session.bcCode && session.bcMode === 'plan') {
3198
- /* opencode plan agent: salt-okur zorlaması — araç seti GERÇEKten daralır */
3199
- activeTools = activeTools.filter((t) => PLAN_ALLOW_TOOLS.has(t.function.name));
3374
+ if (bcAgent) {
3375
+ /* opencode permission.visibleTools (birebir): ruleset'te pattern:'*'+deny
3376
+ olan araçlar setten düşer — plan ajanında edit/write GERÇEKTEN gizlenir,
3377
+ salt-okurluk prompta kalmaz. opencode'da edit/write/apply_patch aynı
3378
+ "edit" iznine iner. */
3379
+ const hidden = opencode.Permission.disabled(
3380
+ activeTools.map((t) => t.function.name),
3381
+ bcAgent.permission
3382
+ );
3383
+ if (hidden.size) activeTools = activeTools.filter((t) => !hidden.has(t.function.name));
3200
3384
  }
3201
3385
  if (adef && adef.tools) {
3202
3386
  /* özel ajan araç beyaz listesi — tanımda olmayan araç görünmez */
@@ -3234,7 +3418,25 @@ class Engine {
3234
3418
  /* opencode port: model bağlamına göre dinamik bütçe + compaction özeti */
3235
3419
  const selEarly = this.sessionModel.get(String(session.id)) || this.modelFor(null) || this.sel;
3236
3420
  const dynBudget = this._historyBudget(selEarly);
3237
- let payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3421
+ /* opencode SessionReminders.apply (reminders.ts birebir): plan ajanındaysa
3422
+ plan.txt salt-okur hatırlatması, plan→build geçişinde build-switch.txt
3423
+ son USER mesajına synthetic eklenir. session.messages ASLA değişmez —
3424
+ her tur taze kopyaya uygulanır (opencode her turda DB'den yeniden yükler). */
3425
+ let msgsForPayload = session.messages;
3426
+ if (bcAgent) {
3427
+ msgsForPayload = opencode.systemprompt.applyReminders(
3428
+ session.messages,
3429
+ bcAgent,
3430
+ !!this._bcWasPlan.get(String(session.id))
3431
+ );
3432
+ }
3433
+ let payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3434
+ if (bcAgent && bcOpts && bcOpts.isLastStep) {
3435
+ /* opencode prompt.ts:1178-1181 — isLastStep: MAX_STEPS_PROMPT assistant
3436
+ mesajı olarak akışa eklenir; araçlar kilitlenir (yalnız metin kapanışı) */
3437
+ if (Array.isArray(payload.messages)) payload.messages.push({ role: 'assistant', content: opencode.MAX_STEPS_PROMPT });
3438
+ activeTools = [];
3439
+ }
3238
3440
  // Vision model sadece mesajda görsel varsa devreye girer; terminal ise prompt'un
3239
3441
  // shell/command işi olduğunda. Aksi halde ana model kullanılır.
3240
3442
  let role = this._lastUserHasImage(session) ? 'vision' : null;
@@ -3283,7 +3485,7 @@ class Engine {
3283
3485
  type: 'status',
3284
3486
  status: `🧠 düşük uyumlu sağlayıcı: geçmişteki düşünme içerikleri temizlendi (${dropped} mesaj) — tur yeniden deneniyor`,
3285
3487
  });
3286
- payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3488
+ payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3287
3489
  res = await this._streamWithFallbacks(
3288
3490
  session,
3289
3491
  payload,
@@ -3317,7 +3519,7 @@ class Engine {
3317
3519
  type: 'status',
3318
3520
  status: `\u{1F5BC} bu model görüntü girişini desteklemiyor — sohbetten ${removed} görsel kaldırıldı, mesaj görüntüsüz yanıtlanıyor (/change ile görsel destekli modele geçebilirsin)`,
3319
3521
  });
3320
- payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3522
+ payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3321
3523
  res = await this._streamWithFallbacks(
3322
3524
  session,
3323
3525
  payload,
@@ -3395,9 +3597,17 @@ class Engine {
3395
3597
  doom-loop koruması + kullanıcı ■ kırar. bg ajanlarda kapanış
3396
3598
  disiplini için tavan kalır. */
3397
3599
  const runDef = this._agentDefFor(session, !!session.bgJob);
3600
+ /* opencode mantığı (BC): ajan çözümleme — build/plan ajanı ruleset'iyle
3601
+ beraber tur boyunca kullanılır (permission ask + disabled filtreleri) */
3602
+ const bcAgent = session.bcCode ? this._bcAgentInfo(session) : null;
3603
+ /* opencode prompt.ts:1178 (birebir): maxSteps = agent.steps ?? Infinity —
3604
+ build/plan ajanlarında steps tanımsızdır → sınırsız; bağlam dolunca
3605
+ compaction özetler, sonsuz döngüyü doom-loop koruması + ■ kırar. */
3398
3606
  const maxTurns = session.bgJob
3399
3607
  ? Math.max(2, Math.min(60, (runDef && runDef.steps) || MAX_TURNS))
3400
- : Infinity;
3608
+ : bcAgent
3609
+ ? (bcAgent.steps ?? Infinity)
3610
+ : Infinity;
3401
3611
  const recentSigs = []; // doom-loop dedektörü: son araç imzaları
3402
3612
  /* Beast Code canlı önizleme: run boyunca üretilen eserleri topla —
3403
3613
  iş bitince dahili tarayıcıda CANLI açılır (site/dev server/HTML) */
@@ -3446,9 +3656,12 @@ class Engine {
3446
3656
  /* opencode runLoop portu: istek kurulmadan ÖNCEKİ mesaj sayısı —
3447
3657
  koşan tur SIRASINDA gelen (steer) user mesajlarını saptar */
3448
3658
  const preLen = session.messages.length;
3659
+ /* opencode prompt.ts:1178-1181: isLastStep = step >= maxSteps — son
3660
+ turda MAX_STEPS_PROMPT enjekte edilir ve araçlar kilitlenir */
3661
+ const isLastStep = !!bcAgent && maxTurns !== Infinity && turn >= maxTurns - 1;
3449
3662
  const res = await this._chatTurn(session, ctrl.signal, (delta) =>
3450
3663
  emit({ type: 'token', delta })
3451
- );
3664
+ , bcAgent ? opencode.toolmap.definitions() : undefined, bcAgent ? { isLastStep } : null);
3452
3665
  /* opencode port (processor.ts:477-482): step-finish usage'ı compaction
3453
3666
  tetiğine besle — gerçek toplam biliniyorsa tahmine gerek kalmaz */
3454
3667
  const uTot =
@@ -3533,14 +3746,16 @@ class Engine {
3533
3746
 
3534
3747
  // Paralel yürütme — bağımsız çağrılar beklemesin
3535
3748
  session.toolsSinceReflect = (session.toolsSinceReflect || 0) + res.toolCalls.length;
3536
- if (!this._knownToolNames) this._knownToolNames = new Set(TOOLS.map((t) => t.function.name));
3749
+ if (!this._knownToolNames) this._knownToolNames = new Set([...TOOLS.map((t) => t.function.name), ...opencode.toolmap.names()]);
3537
3750
  await Promise.all(
3538
3751
  res.toolCalls.map(async (tc) => {
3539
3752
  let name = tc.function && tc.function.name;
3540
3753
  /* opencode port (llm.ts:296-312 repairToolCall): model aracı adını
3541
- yanlış yazdıysa büyük/küçük harf normalizasyonuyle onar — tur kaybı yok */
3754
+ yanlış yazdıysa büyük/küçük harf normalizasyonuyle onar — tur kaybı yok.
3755
+ BC'de havuz opencode builtin seti (read/edit/write/bash...) */
3542
3756
  if (name && !this._knownToolNames.has(name)) {
3543
- const fixed = TOOLS.find((t) => t.function.name.toLowerCase() === String(name).trim().toLowerCase());
3757
+ const pool = bcAgent ? opencode.toolmap.definitions() : TOOLS;
3758
+ const fixed = pool.find((t) => t.function.name.toLowerCase() === String(name).trim().toLowerCase());
3544
3759
  if (fixed) {
3545
3760
  emitSafe(this, sid, {
3546
3761
  type: 'status',
@@ -3556,33 +3771,59 @@ class Engine {
3556
3771
  emit({ type: 'tool-start', callId: tc.id, name, args });
3557
3772
  emit({ type: 'status', status: name });
3558
3773
  /* opencode port (processor.ts:29 + 356-380): aynı araç + birebir
3559
- aynı argüman 3 kez koştuysa 4.'yü çalıştırma — döngüye para/hız
3560
- akıtma; modele hata bildirimiyle yol göster */
3774
+ aynı argüman 3 kez koştuysa 4.'yi çalıştırma — döngüye para/hız
3775
+ akıtma; BC'de opencode disiplini: doom_loop İZİN ister (defaults:
3776
+ doom_loop ask) — kullanıcı onaylarsa koşmaya devam eder */
3561
3777
  const sig = name + '\u0000' + String((tc.function && tc.function.arguments) || '');
3562
3778
  const dups = recentSigs.reduce((a, s) => a + (s === sig ? 1 : 0), 0);
3563
3779
  recentSigs.push(sig);
3564
3780
  let out;
3565
3781
  if (dups >= DOOM_LOOP_THRESHOLD) {
3566
- out = JSON.stringify({
3567
- error:
3568
- `doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez çalıştı ve hep aynı sonucu verdi. ` +
3569
- 'Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
3570
- });
3571
- emitSafe(this, sid, { type: 'status', status: `⛔ doom-loop: ${name} tekrarı engellendi` });
3782
+ if (bcAgent) {
3783
+ try {
3784
+ await this._perm.ask({
3785
+ sessionId: sid,
3786
+ ruleset: bcAgent.permission,
3787
+ permission: 'doom_loop',
3788
+ patterns: [name],
3789
+ always: [name],
3790
+ metadata: { callCount: DOOM_LOOP_THRESHOLD, arguments: (tc.function && tc.function.arguments) || '' },
3791
+ tool: { name, args },
3792
+ });
3793
+ out = await this._execTool(name, args, ctrl.signal, sid);
3794
+ } catch (permErr) {
3795
+ out = JSON.stringify({
3796
+ error:
3797
+ `doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez koştu. ` +
3798
+ AskService.errorText(permErr) +
3799
+ ' Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
3800
+ });
3801
+ }
3802
+ } else {
3803
+ out = JSON.stringify({
3804
+ error:
3805
+ `doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez çalıştı ve hep aynı sonucu verdi. ` +
3806
+ 'Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
3807
+ });
3808
+ emitSafe(this, sid, { type: 'status', status: `⛔ doom-loop: ${name} tekrarı engellendi` });
3809
+ }
3572
3810
  } else {
3573
3811
  out = await this._execTool(name, args, ctrl.signal, sid);
3574
3812
  }
3575
- /* Beast Code eser takibi: yazılan HTML + başlatılan dev server */
3813
+ /* Beast Code eser takibi: yazılan HTML + başlatılan dev server.
3814
+ BC'de araçlar opencode adlarıyla gelir: write/edit (filePath),
3815
+ bash (command) — opencode şemasına göre yakalanır */
3576
3816
  if (bcArtifacts) {
3577
3817
  try {
3818
+ const filePathOc = args && (args.filePath ?? args.path);
3578
3819
  if (
3579
- (name === 'write_file' || name === 'edit_file') &&
3580
- args && args.path && /\.html?$/i.test(String(args.path))
3820
+ (name === 'write' || name === 'edit' || name === 'write_file' || name === 'edit_file') &&
3821
+ filePathOc && /\.html?$/i.test(String(filePathOc))
3581
3822
  ) {
3582
- bcArtifacts.html.push(String(args.path));
3823
+ bcArtifacts.html.push(String(filePathOc));
3583
3824
  }
3584
- if (name === 'run_command') {
3585
- const cmdStr = String((args && args.command) || '');
3825
+ if (name === 'bash' || name === 'run_command') {
3826
+ const cmdStr = String((args && (args.command ?? args.cmd)) || '');
3586
3827
  const hay = cmdStr + '\n' + String(out || '').slice(0, 6000);
3587
3828
  const m = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?[^\s'"<>]*/i.exec(hay);
3588
3829
  if (m) {
@@ -3598,7 +3839,7 @@ class Engine {
3598
3839
  if (port) bcArtifacts.serverUrl = 'http://localhost:' + port;
3599
3840
  }
3600
3841
  /* Expo dev server: exp://<LAN-IP>:<port> çıktıdan yakalanır —
3601
- telefon/Expo Go için QR renderer'a canlı iletilir */
3842
+ telefon/Expo Go için QR renderer'a canlı iletilir */
3602
3843
  const xm = /exp:\/\/[^\s'"<>]+/i.exec(hay);
3603
3844
  if (xm) {
3604
3845
  const eurl = xm[0].replace(/[)\].,;:'"]+$/, '');
@@ -3609,7 +3850,7 @@ class Engine {
3609
3850
  }
3610
3851
  }
3611
3852
  } catch {}
3612
- }
3853
+ }
3613
3854
  // Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
3614
3855
  let injectedImage = null;
3615
3856
  /* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
@@ -3627,7 +3868,7 @@ class Engine {
3627
3868
  }
3628
3869
  if (injectedImage || diffView) out = JSON.stringify(obj);
3629
3870
  } catch {}
3630
- const toolMsg = { role: 'tool', tool_call_id: tc.id, name, content: out.slice(0, name === 'read_file' ? READ_OUT_KEEP : TOOL_OUT_KEEP * 6) };
3871
+ const toolMsg = { role: 'tool', tool_call_id: tc.id, name, content: out.slice(0, (name === 'read' || name === 'read_file') ? READ_OUT_KEEP : TOOL_OUT_KEEP * 6) };
3631
3872
  if (diffView) toolMsg.diffView = diffView; /* oturum dosyasına da düşer — geçmiş açılınca diff yeniden çizilir */
3632
3873
  session.messages.push(toolMsg);
3633
3874
  try {
@@ -4090,31 +4331,70 @@ const skills = require('./skills');
4090
4331
 
4091
4332
  /* ---------- alt-agent ---------- */
4092
4333
 
4093
- async _subagent(task, context, parentSignal, sessionId) {
4334
+ async _subagent(task, context, parentSignal, sessionId, subagentType = '') {
4094
4335
  const ctrl = new AbortController();
4095
4336
  const onAbort = () => ctrl.abort();
4096
4337
  if (parentSignal) {
4097
4338
  if (parentSignal.aborted) throw new Error('iptal edildi');
4098
4339
  parentSignal.addEventListener('abort', onAbort, { once: true });
4099
4340
  }
4100
- const subTools = TOOLS.filter(
4101
- (t) =>
4102
- t.function.name !== 'delegate_task' &&
4103
- t.function.name !== 'set_reminder' &&
4104
- !t.function.name.startsWith('email_')
4105
- ).sort((a, b) => String(a.function.name).localeCompare(String(b.function.name))); // önek-cache: sabit sıra
4341
+ /* opencode task aracı (tool/task.ts) subagent_tipi ZORUNLU kılar: BC'den
4342
+ gelen task çağrılarında 'general' | 'explore' ajanları opencode mantığıyla
4343
+ koşar (agent tanımı + permission + opencode builtin seti). Eski
4344
+ delegate_task (BC dışı) davranışı aynen korunur. */
4345
+ const ocType = subagentType === 'explore' || subagentType === 'general' ? subagentType : '';
4106
4346
  const role = 'subagent';
4107
4347
  const sel = this.modelFor(role) || this.sel;
4108
4348
  if (this.roleModels[role]) {
4109
4349
  emitSafe(this, sessionId, { type: 'status', status: `rol: ${role} → ${sel.providerName} · ${sel.model}` });
4110
4350
  }
4111
- const system =
4112
- `Sen odaklı bir alt-agentsın. Ana ajansın verdiği TEK görevi bitir ve sadece nihai sonucu döndür.\n` +
4113
- `Windows + PowerShell, çalışma klasörü: ${this.workspace}\n` +
4114
- `HIZ KURALI: döngü gerektiren işleri (çok sayfa okuma, çok URL çekme, tekrarlı hesap/parse) TEK python_run betiğinde topluca yap — her adım için ayrı araç turu harcama; 10 fetch = 1 betik = çok daha hızlı görev.\n` +
4115
- `ARAŞTIRMA SINIRI: 3-5 kaynak yeter; 2-3 denemede bulamazsan kısmi sonucu getir, takılma.\n` +
4116
- `Kısa çalış, gereksiz soru sorma, sonucu madde madde raporla.\n` +
4117
- FORMAT_RULES;
4351
+ let subTools;
4352
+ let system;
4353
+ let ocMode = false;
4354
+ if (ocType) {
4355
+ ocMode = true;
4356
+ const ws = this._sessionWorkspace(sessionId);
4357
+ const agentInfo =
4358
+ opencode.agents.get(ocType, { worktree: ws, promptExplore: opencode.PROMPT_EXPLORE, user: {} }) || null;
4359
+ const ruleset = agentInfo ? agentInfo.permission : [];
4360
+ /* opencode child session: subagent ANA döngüdeki gibi ajan tanımıyla
4361
+ koşar — system = [ajan promptu yoksa model-bazlı prompt, env] */
4362
+ const main = agentInfo && agentInfo.prompt ? [agentInfo.prompt] : opencode.systemprompt.provider(sel.model);
4363
+ system = [
4364
+ ...main,
4365
+ ...opencode.systemprompt.environment({
4366
+ modelId: sel.model,
4367
+ providerId: sel.providerId || '?',
4368
+ directory: ws,
4369
+ worktree: ws,
4370
+ }),
4371
+ ]
4372
+ .filter((x) => x)
4373
+ .join('\n');
4374
+ /* opencode SessionTools.resolve: ajanın permission'ında '*' deny olan
4375
+ araçlar setten düşer — explore: salt-okur (bash dahil okuma amaçlı allow),
4376
+ general: todowrite yok (opencode general tanımı birebir) */
4377
+ const defs = opencode.toolmap.definitions();
4378
+ const hidden = opencode.Permission.disabled(defs.map((t) => t.function.name), ruleset);
4379
+ subTools = defs
4380
+ .filter((t) => !hidden.has(t.function.name))
4381
+ .filter((t) => t.function.name !== 'task')
4382
+ .sort((a, b) => String(a.function.name).localeCompare(String(b.function.name)));
4383
+ } else {
4384
+ subTools = TOOLS.filter(
4385
+ (t) =>
4386
+ t.function.name !== 'delegate_task' &&
4387
+ t.function.name !== 'set_reminder' &&
4388
+ !t.function.name.startsWith('email_')
4389
+ ).sort((a, b) => String(a.function.name).localeCompare(String(b.function.name))); // önek-cache: sabit sıra
4390
+ system =
4391
+ `Sen odaklı bir alt-agentsın. Ana ajansın verdiği TEK görevi bitir ve sadece nihai sonucu döndür.\n` +
4392
+ `Windows + PowerShell, çalışma klasörü: ${this.workspace}\n` +
4393
+ `HIZ KURALI: döngü gerektiren işleri (çok sayfa okuma, çok URL çekme, tekrarlı hesap/parse) TEK python_run betiğinde topluca yap — her adım için ayrı araç turu harcama; 10 fetch = 1 betik = çok daha hızlı görev.\n` +
4394
+ `ARAŞTIRMA SINIRI: 3-5 kaynak yeter; 2-3 denemede bulamazsan kısmi sonucu getir, takılma.\n` +
4395
+ `Kısa çalış, gereksiz soru sorma, sonucu madde madde raporla.\n` +
4396
+ FORMAT_RULES;
4397
+ }
4118
4398
  const user = context ? `${task}\n\n# BAĞLAM\n${context}` : task;
4119
4399
  const msgs = [
4120
4400
  { role: 'system', content: system },
@@ -4154,8 +4434,15 @@ const skills = require('./skills');
4154
4434
  try {
4155
4435
  args = JSON.parse((tc.function && tc.function.arguments) || '{}');
4156
4436
  } catch {}
4157
- const out = await this._subExecTool(tc.function.name, args, ctrl.signal);
4158
- msgs.push({ role: 'tool', tool_call_id: tc.id, name: tc.function.name, content: out });
4437
+ let subName = tc.function && tc.function.name;
4438
+ if (ocMode) {
4439
+ /* opencode builtin dilinde çağırır → Beast aracına çevir */
4440
+ const mapped = opencode.toolmap.execMap(subName, args);
4441
+ subName = mapped.name;
4442
+ args = mapped.args;
4443
+ }
4444
+ const out = await this._subExecTool(subName, args, ctrl.signal);
4445
+ msgs.push({ role: 'tool', tool_call_id: tc.id, name: subName, content: out });
4159
4446
  }
4160
4447
  }
4161
4448
  const last = [...msgs].reverse().find((m) => m.role === 'assistant' && m.content);
@@ -4291,27 +4578,85 @@ const skills = require('./skills');
4291
4578
 
4292
4579
  async _execTool(name, args, signal, sessionId) {
4293
4580
  try {
4581
+ /* opencode mantığı (BC): model opencode builtin dilinde çağırır —
4582
+ read/edit/write/bash/todowrite/task... execMap Beast'in yerleşik
4583
+ aracına çevirir (implementasyon değişmez, dil opencode olur).
4584
+ Studio/Finance/bgJob kendi dünyasında koşar — bu akışa girmez. */
4585
+ const bcSess = this.cache.get(String(sessionId || '')) || null;
4586
+ const isBc = !!(bcSess && bcSess.bcCode && !bcSess.studio && !bcSess.finance && !bcSess.bgJob);
4587
+ if (isBc) {
4588
+ const mapped = opencode.toolmap.execMap(name, args);
4589
+ name = mapped.name;
4590
+ args = mapped.args;
4591
+ }
4294
4592
  /* ANA KOD KİLİDİ: korumalı bölgeye yazma/silme girişimi daha kapıdan geçmez */
4295
4593
  const blocked = this._guardTool(name, args);
4296
4594
  if (blocked) return blocked;
4297
- /* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
4298
- "always" onaylı araçlar doğrudan geçer. */
4299
- if (
4300
- (Engine.RISKY_TOOLS.has(name) || String(name).startsWith('mcp__')) &&
4301
- !this.alwaysAllowTools.has(name) &&
4302
- this.approvals && typeof this.approvals.request === 'function'
4303
- ) {
4304
- emitSafe(this, sessionId, { type: 'status', status: `onay bekleniyor: ${name}` });
4305
- let ok = false;
4595
+ if (isBc) {
4596
+ /* opencode permission akışı (tool/external-directory.ts + her aracın
4597
+ ctx.ask'ı): önce workspace DIŞI erişim izni, sonra araç izni.
4598
+ deny anında hata döner (asla sorulmaz), allow sessiz geçer,
4599
+ ask UI'ya 'permission.asked' eventi ile sorulur */
4600
+ const ws = this._sessionWorkspace(sessionId);
4601
+ const bcAgent = this._bcAgentInfo(bcSess);
4602
+ const ruleset = bcAgent ? bcAgent.permission : [];
4306
4603
  try {
4307
- ok = !!(await this.approvals.request({ sessionId, tool: name, args: args || {} }));
4308
- } catch {}
4309
- if (!ok) {
4310
- return JSON.stringify({
4311
- ok: false,
4312
- error:
4313
- 'kullanıcı bu işlemi ONAYLAMADI (iptal edildi veya zaman aşımı). İşlemi yeniden deneme; kullanıcıya sormadan alternatif öner.',
4604
+ const ext = this._bcExternalCheck(name, args, ws);
4605
+ if (ext) {
4606
+ await this._perm.ask({ sessionId, ruleset, ...ext, metadata: { tool: name, args: args || {} } });
4607
+ }
4608
+ const perm = this._bcPermissionFor(name, args, ws);
4609
+ await this._perm.ask({
4610
+ sessionId,
4611
+ ruleset,
4612
+ permission: perm.permission,
4613
+ patterns: perm.patterns,
4614
+ always: perm.always,
4615
+ metadata: { args: args || {} },
4616
+ tool: { name, args: args || {} },
4314
4617
  });
4618
+ } catch (permErr) {
4619
+ return JSON.stringify({ ok: false, error: AskService.errorText(permErr) });
4620
+ }
4621
+ /* opencode skill aracı: katalogdaki SKILL.md gövdesini modele açar */
4622
+ if (name === 'skill') {
4623
+ const wanted = String((args && args.name) || '').trim();
4624
+ let list = [];
4625
+ try { list = skills.scan(); } catch {}
4626
+ const hit = list.find((s) => s && s.name === wanted) || null;
4627
+ if (!hit) {
4628
+ return JSON.stringify({
4629
+ ok: false,
4630
+ error:
4631
+ 'skill bulunamadı: ' + (wanted || '(boş)') +
4632
+ (list.length ? ' — mevcut: ' + list.map((s) => s.name).join(', ') : ''),
4633
+ });
4634
+ }
4635
+ let body = '';
4636
+ try { body = fs.readFileSync(hit.path, 'utf8'); } catch {}
4637
+ return JSON.stringify({ ok: true, name: hit.name, path: hit.path, content: body.slice(0, 24000) });
4638
+ }
4639
+ } else {
4640
+ /* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
4641
+ "always" onaylı araçlar doğrudan geçer. (BC dışı oturumlar — opencode
4642
+ izin sistemi yalnız Beast Code oturumlarında koşar) */
4643
+ if (
4644
+ (Engine.RISKY_TOOLS.has(name) || String(name).startsWith('mcp__')) &&
4645
+ !this.alwaysAllowTools.has(name) &&
4646
+ this.approvals && typeof this.approvals.request === 'function'
4647
+ ) {
4648
+ emitSafe(this, sessionId, { type: 'status', status: `onay bekleniyor: ${name}` });
4649
+ let ok = false;
4650
+ try {
4651
+ ok = !!(await this.approvals.request({ sessionId, tool: name, args: args || {} }));
4652
+ } catch {}
4653
+ if (!ok) {
4654
+ return JSON.stringify({
4655
+ ok: false,
4656
+ error:
4657
+ 'kullanıcı bu işlemi ONAYLAMADI (iptal edildi veya zaman aşımı). İşlemi yeniden deneme; kullanıcıya sormadan alternatif öner.',
4658
+ });
4659
+ }
4315
4660
  }
4316
4661
  }
4317
4662
  /* GERİ ALMA GÜNLÜĞÜ: dosya yazımından ÖNCE eski içerik kayda geçer */
@@ -4406,8 +4751,8 @@ const skills = require('./skills');
4406
4751
  if (name === 'delegate_task') {
4407
4752
  const task = String(args.task || '').trim();
4408
4753
  if (!task) return JSON.stringify({ ok: false, error: 'task gerekli' });
4409
- emitSafe(this, sessionId, { type: 'status', status: 'delegate_task' });
4410
- const result = await this._subagent(task, String(args.context || ''), signal, sessionId);
4754
+ emitSafe(this, sessionId, { type: 'status', status: args.subagent_type === 'explore' ? 'task(explore)' : args.subagent_type ? 'task(general)' : 'delegate_task' });
4755
+ const result = await this._subagent(task, String(args.context || ''), signal, sessionId, String(args.subagent_type || ''));
4411
4756
  return JSON.stringify({ ok: true, task, result: String(result).slice(0, 12000) });
4412
4757
  }
4413
4758
  if (name === 'bot_dm') {