beast-agent 2.15.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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/agent/engine.js +417 -154
  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/main.js +93 -41
  33. package/src/preload.js +2 -0
  34. package/src/renderer/renderer.js +59 -15
@@ -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
@@ -24,6 +24,10 @@ const apps = require('./apps');
24
24
  const pdfwrite = require('./pdfwrite');
25
25
  const { estTokens, estMsgTokens } = require('./tokens');
26
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;
27
31
 
28
32
  const MAX_TURNS = 40;
29
33
  const SUB_MAX_TURNS = 8;
@@ -137,15 +141,9 @@ const PERM_TOOL_SETS = {
137
141
  };
138
142
  const PERM_LEVELS = ['all', 'web', 'read', 'chat'];
139
143
 
140
- /* opencode plan agent portu: PLAN modu PROMPT düzeyinde değil GERÇEKten
141
- salt-okurdur — yazma/çalıştırma araçları setten düşer, sadece plan çıkar */
142
- const PLAN_ALLOW_TOOLS = new Set([
143
- 'read_file', 'list_dir', 'grep', 'glob',
144
- 'web_search', 'http_fetch', 'webfetch', 'deep_search',
145
- 'browser_open', 'browser_read', 'browser_snapshot',
146
- 'todo_write', 'memory_search', 'kb_search', 'ocr_read',
147
- 'git_diff_review', 'repo_map', 'repo_symbols', 'xlsx_read',
148
- ]);
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ığı) */
149
147
 
150
148
  /* İzin değerini normalize eder: 'all' → ['all'], 'web' → ['web'],
151
149
  'web,read' / ['web','read'] → ['web','read'] (sıra PERM_LEVELS'e göre dizilir).
@@ -313,6 +311,15 @@ class Engine {
313
311
  this.notifyOwnerFail = opts.notifyOwnerFail !== false; // #25 hata mail bildirimi (runtime /notify)
314
312
  this.reminders = opts.reminders || null; // hatırlatıcı kancası (main enjekte eder)
315
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
316
323
  /* onay kapısı: riskli araç öncesi dış onay beklenir ({request} async) */
317
324
  this.approvals = opts.approvals || null;
318
325
  /* "bir daha sorma" onaylanan araçlar — bu setteki riskli araçlar sorulmaz */
@@ -942,11 +949,10 @@ class Engine {
942
949
  return lv.effort || null;
943
950
  }
944
951
 
945
- /* 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) */
946
954
  _thinkEffortFor(session) {
947
- const e = this._thinkEffort();
948
- if (!session || !session.bcCode) return e;
949
- return e ? 'low' : null;
955
+ return this._thinkEffort();
950
956
  }
951
957
 
952
958
  _thinkLabel() {
@@ -1525,56 +1531,151 @@ class Engine {
1525
1531
  return out;
1526
1532
  }
1527
1533
 
1528
- /* Beast Code (IDE paneli): VS Code hızında çalışsın diye SECMET prompt.
1529
- OpenCode kodlama disiplini gömülü: bağlam → plan → küçük diff → doğrula.
1530
- GENEL AMAÇLI kodlama ajanı — web/mobil gibi tek stack'e zorlanmaz; stack'i
1531
- projenin kendi dosyaları belirler. Hafıza embedding araması, skills
1532
- taraması, kişilik/kural blokları YOK — prompt kısa kalır → ilk token
1533
- hızlı, tur maliyeti düşük. */
1534
- buildBcSystem(session) {
1535
- const nowD = new Date();
1536
- const localDate = nowD.toLocaleDateString('tr-TR');
1537
- /* önek-cache disiplini (opencode Context Epoch): dakika yerine saat —
1538
- system prompt her dakika değişse sağlayıcı önbelleği sürekli bozulur */
1539
- const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
1540
- const mode = String((session && session.bcMode) || 'auto').toLowerCase();
1541
- const modeBlock =
1542
- mode === 'plan'
1543
- ? 'Ç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.'
1544
- : mode === 'build'
1545
- ? 'Ç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.'
1546
- : 'Ç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);
1547
1598
  const proj = this._projectInstructions(session);
1548
- return (
1549
- 'Sen BEAST CODE\u2019sun — IDE panelinde çalışan, OpenCode disiplinli hızlı bir kodlama ajanı. VS Code gibi çevik ol.\n' +
1550
- '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' +
1551
- `Çalışma klasörü: ${(session && session.workspace) || this.workspace}\n` +
1552
- ((session && session.sbSandbox)
1553
- ? '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'
1554
- : '') +
1555
- `Yerel zaman: ${localDate} ${localTime}\n` +
1556
- `${modeBlock}\n` +
1557
- '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' +
1558
- (proj
1559
- ? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n'
1560
- : '') +
1561
- 'WORKFLOW (her işte bu sıra):\n' +
1562
- '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' +
1563
- '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' +
1564
- '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' +
1565
- '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' +
1566
- '5) RAPOR: 1-3 satır — ne değişti + doğrulama sonucu (ör. "npm test ✓ 154/154"). Uzun açıklama yok.\n' +
1567
- 'Ö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' +
1568
- 'HIZ KURALLARI:\n' +
1569
- '- 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' +
1570
- '- Bağımsız araç çağrılarını AYNI TURDA paralel ver (birden çok read_file tek turda).\n' +
1571
- '- Dosyayı kullanıcı söylememişse list_dir ile yapıyı görüp kendin karar ver.\n' +
1572
- '- 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' +
1573
- '- Soru sorma, sohbet etme; uygulayıp özetle. Kullanıcı /plan /build /auto ile modu değiştirir.\n' +
1574
- FORMAT_RULES
1575
- );
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');
1576
1621
  }
1577
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
+ }
1661
+ }
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
+
1578
1679
  /* Beast Studio (video paneli): video YAPMA ve DÜZENLEME ajanı. Workspace'teki
1579
1680
  her şey video proje malzemesi olarak görülür (ham görüntü, ses, görsel,
1580
1681
  altyazı, çıktı). Medya işi ffprobe + ffmpeg ile yürütür; ikisi de app ile
@@ -3211,36 +3312,27 @@ class Engine {
3211
3312
  session.messages = keep;
3212
3313
  }
3213
3314
 
3214
- async _chatTurn(session, signal, onDelta, toolsList = TOOLS) {
3215
- /* MCP: %APPDATA%\beast\mcp.json'daki etkin serverların araçlarını şema listesine ekle
3216
- (bağlı değilse lazy bağlanır; kapalıysa liste değişmez; Beast Code paneli hızlı
3217
- ilk-token sözü için MCP'siz kalır) */
3218
- if (!session || !session.bcCode) toolsList = await mcp.mergeTools(toolsList);
3219
- /* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
3220
- toolsList = apps.mergeTools(toolsList);
3221
- /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3222
- modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3223
- if (!(session && session.sbSandbox)) {
3224
- 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
+ }
3225
3333
  }
3226
3334
  /* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
3227
3335
  const adef = this._agentDefFor(session, !!session.bgJob);
3228
- /* Beast Code: todo_write açıklaması "3+ adım" kısıtı içerir ve model küçük
3229
- işlerde atlar — bcCode oturumunda HER işte İLK araç olacak şekilde değiştir */
3230
- if (session && session.bcCode) {
3231
- toolsList = toolsList.map((t) =>
3232
- t && t.function && t.function.name === 'todo_write'
3233
- ? {
3234
- ...t,
3235
- function: {
3236
- ...t.function,
3237
- description:
3238
- '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.',
3239
- },
3240
- }
3241
- : t
3242
- );
3243
- }
3244
3336
  const promptText = this._lastUserText(session);
3245
3337
  let system = session.bgJob
3246
3338
  ? this.buildBgSystem(session)
@@ -3248,18 +3340,21 @@ class Engine {
3248
3340
  ? this.buildStudioSystem(session)
3249
3341
  : session.finance
3250
3342
  ? this.buildFinanceSystem(session)
3251
- : session.bcCode
3252
- ? this.buildBcSystem(session)
3253
- : await this.buildSystem(promptText, session);
3343
+ : bcAgent
3344
+ ? this.buildOcSystem(session, bcAgent)
3345
+ : await this.buildSystem(promptText, session);
3254
3346
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
3255
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).
3256
3349
  const perms = this.sessionPermFor(session.id);
3257
3350
  let allowedSet = null;
3258
3351
  if (!perms.includes('all')) {
3259
3352
  allowedSet = new Set();
3260
3353
  for (const p of perms) for (const t of PERM_TOOL_SETS[p] || []) allowedSet.add(t);
3261
3354
  }
3262
- 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;
3263
3358
  /* Beast Finance: mt5_* araçları YALNIZ finance oturumlarında görünür */
3264
3359
  if (FINANCE_TOOL_SET.size && !(session && session.finance)) {
3265
3360
  activeTools = activeTools.filter((t) => !FINANCE_TOOL_SET.has(t.function.name));
@@ -3276,9 +3371,16 @@ class Engine {
3276
3371
  /* CEO: uygulayıcı araçlar kapalı — her şey paralel ajana devredilir */
3277
3372
  activeTools = activeTools.filter((t) => !CEO_EXEC_TOOLS.has(t.function.name));
3278
3373
  }
3279
- if (session.bcCode && session.bcMode === 'plan') {
3280
- /* opencode plan agent: salt-okur zorlaması — araç seti GERÇEKten daralır */
3281
- 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));
3282
3384
  }
3283
3385
  if (adef && adef.tools) {
3284
3386
  /* özel ajan araç beyaz listesi — tanımda olmayan araç görünmez */
@@ -3316,7 +3418,25 @@ class Engine {
3316
3418
  /* opencode port: model bağlamına göre dinamik bütçe + compaction özeti */
3317
3419
  const selEarly = this.sessionModel.get(String(session.id)) || this.modelFor(null) || this.sel;
3318
3420
  const dynBudget = this._historyBudget(selEarly);
3319
- 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
+ }
3320
3440
  // Vision model sadece mesajda görsel varsa devreye girer; terminal ise prompt'un
3321
3441
  // shell/command işi olduğunda. Aksi halde ana model kullanılır.
3322
3442
  let role = this._lastUserHasImage(session) ? 'vision' : null;
@@ -3365,7 +3485,7 @@ class Engine {
3365
3485
  type: 'status',
3366
3486
  status: `🧠 düşük uyumlu sağlayıcı: geçmişteki düşünme içerikleri temizlendi (${dropped} mesaj) — tur yeniden deneniyor`,
3367
3487
  });
3368
- payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3488
+ payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3369
3489
  res = await this._streamWithFallbacks(
3370
3490
  session,
3371
3491
  payload,
@@ -3399,7 +3519,7 @@ class Engine {
3399
3519
  type: 'status',
3400
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)`,
3401
3521
  });
3402
- payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3522
+ payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3403
3523
  res = await this._streamWithFallbacks(
3404
3524
  session,
3405
3525
  payload,
@@ -3477,9 +3597,17 @@ class Engine {
3477
3597
  doom-loop koruması + kullanıcı ■ kırar. bg ajanlarda kapanış
3478
3598
  disiplini için tavan kalır. */
3479
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. */
3480
3606
  const maxTurns = session.bgJob
3481
3607
  ? Math.max(2, Math.min(60, (runDef && runDef.steps) || MAX_TURNS))
3482
- : Infinity;
3608
+ : bcAgent
3609
+ ? (bcAgent.steps ?? Infinity)
3610
+ : Infinity;
3483
3611
  const recentSigs = []; // doom-loop dedektörü: son araç imzaları
3484
3612
  /* Beast Code canlı önizleme: run boyunca üretilen eserleri topla —
3485
3613
  iş bitince dahili tarayıcıda CANLI açılır (site/dev server/HTML) */
@@ -3528,9 +3656,12 @@ class Engine {
3528
3656
  /* opencode runLoop portu: istek kurulmadan ÖNCEKİ mesaj sayısı —
3529
3657
  koşan tur SIRASINDA gelen (steer) user mesajlarını saptar */
3530
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;
3531
3662
  const res = await this._chatTurn(session, ctrl.signal, (delta) =>
3532
3663
  emit({ type: 'token', delta })
3533
- );
3664
+ , bcAgent ? opencode.toolmap.definitions() : undefined, bcAgent ? { isLastStep } : null);
3534
3665
  /* opencode port (processor.ts:477-482): step-finish usage'ı compaction
3535
3666
  tetiğine besle — gerçek toplam biliniyorsa tahmine gerek kalmaz */
3536
3667
  const uTot =
@@ -3615,14 +3746,16 @@ class Engine {
3615
3746
 
3616
3747
  // Paralel yürütme — bağımsız çağrılar beklemesin
3617
3748
  session.toolsSinceReflect = (session.toolsSinceReflect || 0) + res.toolCalls.length;
3618
- 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()]);
3619
3750
  await Promise.all(
3620
3751
  res.toolCalls.map(async (tc) => {
3621
3752
  let name = tc.function && tc.function.name;
3622
3753
  /* opencode port (llm.ts:296-312 repairToolCall): model aracı adını
3623
- 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...) */
3624
3756
  if (name && !this._knownToolNames.has(name)) {
3625
- 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());
3626
3759
  if (fixed) {
3627
3760
  emitSafe(this, sid, {
3628
3761
  type: 'status',
@@ -3638,33 +3771,59 @@ class Engine {
3638
3771
  emit({ type: 'tool-start', callId: tc.id, name, args });
3639
3772
  emit({ type: 'status', status: name });
3640
3773
  /* opencode port (processor.ts:29 + 356-380): aynı araç + birebir
3641
- aynı argüman 3 kez koştuysa 4.'yü çalıştırma — döngüye para/hız
3642
- 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 */
3643
3777
  const sig = name + '\u0000' + String((tc.function && tc.function.arguments) || '');
3644
3778
  const dups = recentSigs.reduce((a, s) => a + (s === sig ? 1 : 0), 0);
3645
3779
  recentSigs.push(sig);
3646
3780
  let out;
3647
3781
  if (dups >= DOOM_LOOP_THRESHOLD) {
3648
- out = JSON.stringify({
3649
- error:
3650
- `doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez çalıştı ve hep aynı sonucu verdi. ` +
3651
- 'Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
3652
- });
3653
- 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
+ }
3654
3810
  } else {
3655
3811
  out = await this._execTool(name, args, ctrl.signal, sid);
3656
3812
  }
3657
- /* 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 */
3658
3816
  if (bcArtifacts) {
3659
3817
  try {
3818
+ const filePathOc = args && (args.filePath ?? args.path);
3660
3819
  if (
3661
- (name === 'write_file' || name === 'edit_file') &&
3662
- 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))
3663
3822
  ) {
3664
- bcArtifacts.html.push(String(args.path));
3823
+ bcArtifacts.html.push(String(filePathOc));
3665
3824
  }
3666
- if (name === 'run_command') {
3667
- const cmdStr = String((args && args.command) || '');
3825
+ if (name === 'bash' || name === 'run_command') {
3826
+ const cmdStr = String((args && (args.command ?? args.cmd)) || '');
3668
3827
  const hay = cmdStr + '\n' + String(out || '').slice(0, 6000);
3669
3828
  const m = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?[^\s'"<>]*/i.exec(hay);
3670
3829
  if (m) {
@@ -3680,7 +3839,7 @@ class Engine {
3680
3839
  if (port) bcArtifacts.serverUrl = 'http://localhost:' + port;
3681
3840
  }
3682
3841
  /* Expo dev server: exp://<LAN-IP>:<port> çıktıdan yakalanır —
3683
- telefon/Expo Go için QR renderer'a canlı iletilir */
3842
+ telefon/Expo Go için QR renderer'a canlı iletilir */
3684
3843
  const xm = /exp:\/\/[^\s'"<>]+/i.exec(hay);
3685
3844
  if (xm) {
3686
3845
  const eurl = xm[0].replace(/[)\].,;:'"]+$/, '');
@@ -3691,7 +3850,7 @@ class Engine {
3691
3850
  }
3692
3851
  }
3693
3852
  } catch {}
3694
- }
3853
+ }
3695
3854
  // Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
3696
3855
  let injectedImage = null;
3697
3856
  /* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
@@ -3709,7 +3868,7 @@ class Engine {
3709
3868
  }
3710
3869
  if (injectedImage || diffView) out = JSON.stringify(obj);
3711
3870
  } catch {}
3712
- 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) };
3713
3872
  if (diffView) toolMsg.diffView = diffView; /* oturum dosyasına da düşer — geçmiş açılınca diff yeniden çizilir */
3714
3873
  session.messages.push(toolMsg);
3715
3874
  try {
@@ -4172,31 +4331,70 @@ const skills = require('./skills');
4172
4331
 
4173
4332
  /* ---------- alt-agent ---------- */
4174
4333
 
4175
- async _subagent(task, context, parentSignal, sessionId) {
4334
+ async _subagent(task, context, parentSignal, sessionId, subagentType = '') {
4176
4335
  const ctrl = new AbortController();
4177
4336
  const onAbort = () => ctrl.abort();
4178
4337
  if (parentSignal) {
4179
4338
  if (parentSignal.aborted) throw new Error('iptal edildi');
4180
4339
  parentSignal.addEventListener('abort', onAbort, { once: true });
4181
4340
  }
4182
- const subTools = TOOLS.filter(
4183
- (t) =>
4184
- t.function.name !== 'delegate_task' &&
4185
- t.function.name !== 'set_reminder' &&
4186
- !t.function.name.startsWith('email_')
4187
- ).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 : '';
4188
4346
  const role = 'subagent';
4189
4347
  const sel = this.modelFor(role) || this.sel;
4190
4348
  if (this.roleModels[role]) {
4191
4349
  emitSafe(this, sessionId, { type: 'status', status: `rol: ${role} → ${sel.providerName} · ${sel.model}` });
4192
4350
  }
4193
- const system =
4194
- `Sen odaklı bir alt-agentsın. Ana ajansın verdiği TEK görevi bitir ve sadece nihai sonucu döndür.\n` +
4195
- `Windows + PowerShell, çalışma klasörü: ${this.workspace}\n` +
4196
- `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` +
4197
- `ARAŞTIRMA SINIRI: 3-5 kaynak yeter; 2-3 denemede bulamazsan kısmi sonucu getir, takılma.\n` +
4198
- `Kısa çalış, gereksiz soru sorma, sonucu madde madde raporla.\n` +
4199
- 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
+ }
4200
4398
  const user = context ? `${task}\n\n# BAĞLAM\n${context}` : task;
4201
4399
  const msgs = [
4202
4400
  { role: 'system', content: system },
@@ -4236,8 +4434,15 @@ const skills = require('./skills');
4236
4434
  try {
4237
4435
  args = JSON.parse((tc.function && tc.function.arguments) || '{}');
4238
4436
  } catch {}
4239
- const out = await this._subExecTool(tc.function.name, args, ctrl.signal);
4240
- 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 });
4241
4446
  }
4242
4447
  }
4243
4448
  const last = [...msgs].reverse().find((m) => m.role === 'assistant' && m.content);
@@ -4373,27 +4578,85 @@ const skills = require('./skills');
4373
4578
 
4374
4579
  async _execTool(name, args, signal, sessionId) {
4375
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
+ }
4376
4592
  /* ANA KOD KİLİDİ: korumalı bölgeye yazma/silme girişimi daha kapıdan geçmez */
4377
4593
  const blocked = this._guardTool(name, args);
4378
4594
  if (blocked) return blocked;
4379
- /* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
4380
- "always" onaylı araçlar doğrudan geçer. */
4381
- if (
4382
- (Engine.RISKY_TOOLS.has(name) || String(name).startsWith('mcp__')) &&
4383
- !this.alwaysAllowTools.has(name) &&
4384
- this.approvals && typeof this.approvals.request === 'function'
4385
- ) {
4386
- emitSafe(this, sessionId, { type: 'status', status: `onay bekleniyor: ${name}` });
4387
- 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 : [];
4388
4603
  try {
4389
- ok = !!(await this.approvals.request({ sessionId, tool: name, args: args || {} }));
4390
- } catch {}
4391
- if (!ok) {
4392
- return JSON.stringify({
4393
- ok: false,
4394
- error:
4395
- '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 || {} },
4396
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
+ }
4397
4660
  }
4398
4661
  }
4399
4662
  /* GERİ ALMA GÜNLÜĞÜ: dosya yazımından ÖNCE eski içerik kayda geçer */
@@ -4488,8 +4751,8 @@ const skills = require('./skills');
4488
4751
  if (name === 'delegate_task') {
4489
4752
  const task = String(args.task || '').trim();
4490
4753
  if (!task) return JSON.stringify({ ok: false, error: 'task gerekli' });
4491
- emitSafe(this, sessionId, { type: 'status', status: 'delegate_task' });
4492
- 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 || ''));
4493
4756
  return JSON.stringify({ ok: true, task, result: String(result).slice(0, 12000) });
4494
4757
  }
4495
4758
  if (name === 'bot_dm') {