beast-agent 2.15.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/agent/engine.js +505 -156
  3. package/src/agent/opencode/agents.js +161 -0
  4. package/src/agent/opencode/askperm.js +135 -0
  5. package/src/agent/opencode/index.js +51 -0
  6. package/src/agent/opencode/permission.js +108 -0
  7. package/src/agent/opencode/prompts/anthropic.txt +105 -0
  8. package/src/agent/opencode/prompts/beast.txt +147 -0
  9. package/src/agent/opencode/prompts/build-switch.txt +5 -0
  10. package/src/agent/opencode/prompts/codex.txt +79 -0
  11. package/src/agent/opencode/prompts/default.txt +95 -0
  12. package/src/agent/opencode/prompts/edit.txt +10 -0
  13. package/src/agent/opencode/prompts/explore.txt +18 -0
  14. package/src/agent/opencode/prompts/gemini.txt +155 -0
  15. package/src/agent/opencode/prompts/glob.txt +6 -0
  16. package/src/agent/opencode/prompts/gpt.txt +107 -0
  17. package/src/agent/opencode/prompts/grep.txt +8 -0
  18. package/src/agent/opencode/prompts/kimi.txt +95 -0
  19. package/src/agent/opencode/prompts/max-steps.txt +16 -0
  20. package/src/agent/opencode/prompts/meta.txt +65 -0
  21. package/src/agent/opencode/prompts/plan.txt +26 -0
  22. package/src/agent/opencode/prompts/read.txt +14 -0
  23. package/src/agent/opencode/prompts/skill.txt +5 -0
  24. package/src/agent/opencode/prompts/task.txt +19 -0
  25. package/src/agent/opencode/prompts/todowrite.txt +44 -0
  26. package/src/agent/opencode/prompts/trinity.txt +97 -0
  27. package/src/agent/opencode/prompts/webfetch.txt +13 -0
  28. package/src/agent/opencode/prompts/websearch.txt +14 -0
  29. package/src/agent/opencode/prompts/write.txt +8 -0
  30. package/src/agent/opencode/systemprompt.js +157 -0
  31. package/src/agent/opencode/toolmap.js +316 -0
  32. package/src/agent/supermemory.js +132 -0
  33. package/src/main.js +135 -41
  34. package/src/preload.js +5 -0
  35. package/src/renderer/renderer.js +59 -15
@@ -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
@@ -17,6 +17,7 @@ const research = require('./research');
17
17
  const agentdefs = require('./agentdefs');
18
18
  const memory = require('./memory');
19
19
  const mem0 = require('./mem0');
20
+ const supermemory = require('./supermemory');
20
21
  const nightref = require('./nightref');
21
22
  const skills = require('./skills');
22
23
  const mcp = require('./mcp');
@@ -24,6 +25,10 @@ const apps = require('./apps');
24
25
  const pdfwrite = require('./pdfwrite');
25
26
  const { estTokens, estMsgTokens } = require('./tokens');
26
27
  const log = require('./logger');
28
+ /* opencode mantığı (birebir port): ajanlar, izinler, tool seti, promptlar —
29
+ Beast Code (bcCode) oturumları bu çekirdek üzerinden koşar */
30
+ const opencode = require('./opencode');
31
+ const { AskService } = opencode;
27
32
 
28
33
  const MAX_TURNS = 40;
29
34
  const SUB_MAX_TURNS = 8;
@@ -137,15 +142,9 @@ const PERM_TOOL_SETS = {
137
142
  };
138
143
  const PERM_LEVELS = ['all', 'web', 'read', 'chat'];
139
144
 
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
- ]);
145
+ /* opencode plan ajanı salt-okurluğu artık PERMISSION kurallarından türer
146
+ (opencode/agents.js: edit {"*":"deny"} → Permission.disabled seti düşürür) —
147
+ elle bakılan araç beyaz listesi kalktı (tamamen opencode mantığı) */
149
148
 
150
149
  /* İzin değerini normalize eder: 'all' → ['all'], 'web' → ['web'],
151
150
  'web,read' / ['web','read'] → ['web','read'] (sıra PERM_LEVELS'e göre dizilir).
@@ -277,6 +276,14 @@ class Engine {
277
276
  /* mem0-native hafıza katmanı: semantik arama + LLM konsolidasyonu (bot başına izole store).
278
277
  Ayarlardan kapatılabilir (mem0:false) — kapalıysa klasik keyword hafızası çalışır. */
279
278
  this.mem0Enabled = opts.mem0 !== false;
279
+ /* Supermemory (lokal) bellek katmanı — DEFAULT AÇIK: sunucu ayaktaysa
280
+ (npx supermemory local → localhost:6767) hibrit arama + kullanıcı profili
281
+ öncelikli gelir; ayakta değilse klasik hafıza sorunsuz devralır.
282
+ Sağlık önbelleği: probe 60 sn'de bir tazelenir (spam probe yok). */
283
+ this.supermemory = opts.supermemory || null; // { enabled, baseUrl, apiKey, containerTag }
284
+ this._smUp = null; // null = henüz bilinmiyor
285
+ this._smCheckedAt = 0;
286
+ this._smWarned = false; // erişilemez iletisi bir kez basılır
280
287
  /* ANA KOD KİLİDİ: agent kendi kaynak koduna dokunamaz (yazma/silme yok, okuma serbest) */
281
288
  this.protectedDirs = (opts.protectedDirs || [])
282
289
  .map((d) => String(d).replace(/[\\/]+$/, '').toLowerCase().replace(/\//g, '\\'))
@@ -313,6 +320,15 @@ class Engine {
313
320
  this.notifyOwnerFail = opts.notifyOwnerFail !== false; // #25 hata mail bildirimi (runtime /notify)
314
321
  this.reminders = opts.reminders || null; // hatırlatıcı kancası (main enjekte eder)
315
322
  this.watchers = opts.watchers || null; // arka plan izleyici köprüsü (main enjekte eder)
323
+ /* opencode permission Service (birebir port): ask/reply — once|always|reject.
324
+ BC oturumlarında riskli araç onayı bu akışla sorulur; UI 'permission.asked'
325
+ eventi dinler, 'permission:reply' ile yanıtlar. approved listesi oturum
326
+ boyunca "always" desenlerini kural olarak taşır (opencode birebir). */
327
+ this._perm = new AskService((ev) => this.emit(ev));
328
+ this.bcAskApprovals = opts.approvals ? true : false; // onay ayarı → opencode user konfigi gibi "ask" üretir
329
+ /* opencode reminders.ts: bu oturumda daha önce plan ajanı koştu mu?
330
+ plan→build geçişinde build-switch.txt synthetic part'ı buna bağlanır */
331
+ this._bcWasPlan = new Map(); // sessionId -> bool
316
332
  /* onay kapısı: riskli araç öncesi dış onay beklenir ({request} async) */
317
333
  this.approvals = opts.approvals || null;
318
334
  /* "bir daha sorma" onaylanan araçlar — bu setteki riskli araçlar sorulmaz */
@@ -942,11 +958,10 @@ class Engine {
942
958
  return lv.effort || null;
943
959
  }
944
960
 
945
- /* Oturum bazlı düşünme çabası: Beast Code hız modunda en fazla 'low' */
961
+ /* opencode birebir: düşünme çabası ajan tanımına değil kullanıcı ayarına
962
+ bağlıdır — eski "BC'de low'a sabitle" hız kısıtı kalktı (opencode'da yok) */
946
963
  _thinkEffortFor(session) {
947
- const e = this._thinkEffort();
948
- if (!session || !session.bcCode) return e;
949
- return e ? 'low' : null;
964
+ return this._thinkEffort();
950
965
  }
951
966
 
952
967
  _thinkLabel() {
@@ -1525,56 +1540,219 @@ class Engine {
1525
1540
  return out;
1526
1541
  }
1527
1542
 
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.';
1543
+ /* ---------- opencode mantığı: Beast Code ajan sistemi ----------
1544
+ opencode-dev/packages/opencode/src birebir portu — BC oturumları artık
1545
+ "mod" değil AJAN koşar (agent/agent.ts):
1546
+ build — varsayılan (edit serbest, question/plan_enter allow)
1547
+ plan — salt-okur: edit * deny (yalnız .opencode/plans/*.md yazılabilir)
1548
+ System birleşimi (llm/request.ts:58-66 + prompt.ts:1257-1269):
1549
+ [ ajanın promptu VARSA o, yoksa model-bazlı ana prompt (beast.txt,
1550
+ gpt.txt, anthropic.txt, gemini.txt, kimi.txt, codex.txt, default.txt),
1551
+ environment bloğu, proje talimatları (AGENTS.md), skills kataloğu ] */
1552
+
1553
+ /* BC ajanını çözer (opencode agents.get):
1554
+ - Sandbox oturumları (sbSandbox) → 'sandbox' ajanı (repo disiplini + panel_run)
1555
+ - session.bcAgent ('plan'|'build') — eski bcMode:'plan' oturumları geriye
1556
+ uyumlu plan ajanına düşer
1557
+ Studio/Finance/bgJob kendi ajan dünyalarında koşar — opencode BC sistemine
1558
+ katılmaz (buildStudioSystem/buildFinanceSystem/buildBgSystem öncelikli). */
1559
+ _bcAgentInfo(session) {
1560
+ if (!session || !session.bcCode) return null;
1561
+ if (session.studio || session.finance || session.bgJob) return null;
1562
+ const name = session.sbSandbox
1563
+ ? 'sandbox'
1564
+ : session.bcAgent === 'plan' || session.bcAgent === 'build' || session.bcAgent === 'sandbox'
1565
+ ? session.bcAgent
1566
+ : String(session.bcMode || '').toLowerCase() === 'plan'
1567
+ ? 'plan'
1568
+ : 'build';
1569
+ const ws = (session && session.workspace) || this.workspace;
1570
+ const user = this.bcAskApprovals ? { bash: 'ask', edit: { '*': 'ask' } } : {};
1571
+ const info = opencode.agents.get(name, { worktree: ws, promptExplore: opencode.PROMPT_EXPLORE, user });
1572
+ if (info) return info;
1573
+ const fallbackName = session.sbSandbox ? 'sandbox' : 'build';
1574
+ return opencode.agents.get(fallbackName, { worktree: ws, promptExplore: opencode.PROMPT_EXPLORE, user });
1575
+ }
1576
+
1577
+ /* opencode opencode ajan değişimi: /plan /build — oturumun ajanı döner,
1578
+ plan→build geçişi reminders (build-switch.txt) için işaretlenir */
1579
+ setBcAgent(sessionId, name) {
1580
+ const s = this.cache.get(String(sessionId || ''));
1581
+ if (!s || !s.bcCode) return null;
1582
+ const prev = s.bcAgent || 'build';
1583
+ const next = name === 'plan' ? 'plan' : 'build';
1584
+ if (prev === 'plan' && next === 'build') this._bcWasPlan.set(String(s.id), true);
1585
+ s.bcAgent = next;
1586
+ this.cache.set(s.id, s);
1587
+ this.emit({ type: 'bc-agent', sessionId: s.id, agent: next, prev });
1588
+ return next;
1589
+ }
1590
+
1591
+ /* opencode Permission.reply (UI/WA köprüsü): 'once' | 'always' | 'reject' */
1592
+ replyPermission(requestId, action, message) {
1593
+ try {
1594
+ return this._perm.reply(requestId, action === 'always' ? 'always' : action === 'reject' ? 'reject' : 'once', message);
1595
+ } catch (e) {
1596
+ return { ok: false, error: String((e && e.message) || e) };
1597
+ }
1598
+ }
1599
+
1600
+ /* ---------- Supermemory (lokal) bellek köprüsü ---------- */
1601
+
1602
+ _smConfig() {
1603
+ const sm = this.supermemory;
1604
+ if (!sm || sm.enabled === false || !sm.baseUrl) return null;
1605
+ return sm;
1606
+ }
1607
+
1608
+ /* Ayaktayken true — 60 sn cooldown'lu probe (aynı tura 10 kez probe vurmaz) */
1609
+ async _smReady() {
1610
+ const sm = this._smConfig();
1611
+ if (!sm) return false;
1612
+ const now = Date.now();
1613
+ if (this._smUp === true && now - this._smCheckedAt < supermemory.HEALTH_COOLDOWN_MS) return true;
1614
+ if (this._smUp === false && now - this._smCheckedAt < supermemory.HEALTH_COOLDOWN_MS) return false;
1615
+ this._smUp = await supermemory.probe(sm.baseUrl, sm.apiKey);
1616
+ this._smCheckedAt = now;
1617
+ if (!this._smUp && !this._smWarned) {
1618
+ this._smWarned = true;
1619
+ emitSafe(this, null, {
1620
+ type: 'status',
1621
+ status: '🧠 supermemory erişilemedi — klasik hafıza kullanılıyor (açmak için: npx supermemory local)',
1622
+ });
1623
+ }
1624
+ return this._smUp;
1625
+ }
1626
+
1627
+ /* Container tag: bot oturumları kendi izole havuzuna yazar (mem0 deseni) */
1628
+ _smContainer(sessionId) {
1629
+ const sm = this._smConfig();
1630
+ const bctx = this._sessionBotCtx(sessionId ? this.cache.get(String(sessionId)) : null);
1631
+ return (bctx && bctx.id ? 'bot:' + bctx.id : '') || (sm && sm.containerTag) || 'beast';
1632
+ }
1633
+
1634
+ /* YAZ: yerel hafıza HER ZAMEN yazılır (risksiz yedek); supermemory ASENKRON
1635
+ push edilir — turu bloklamaz, hata yutulur (mem0 deseninin aynısı) */
1636
+ _smPush(sessionId, text) {
1637
+ const sm = this._smConfig();
1638
+ if (!sm) return;
1639
+ supermemory
1640
+ .add(sm.baseUrl, sm.apiKey, { content: text, containerTag: this._smContainer(sessionId), metadata: { type: 'conversation' } })
1641
+ .catch(() => {});
1642
+ }
1643
+
1644
+ /* ARA: supermemory hibrit arama + profil → yerel sonuçlarla BİRLEŞTİR.
1645
+ Sunucu yoksa/hata yerse eski akış aynen çalışır (fallback). */
1646
+ async _smSearch(sessionId, q, limit) {
1647
+ const sm = this._smConfig();
1648
+ if (!sm || !(await this._smReady())) return null;
1649
+ const container = this._smContainer(sessionId);
1650
+ try {
1651
+ const [hits, prof] = await Promise.all([
1652
+ supermemory.search(sm.baseUrl, sm.apiKey, { q, containerTag: container, limit, mode: 'hybrid' }),
1653
+ supermemory.profile(sm.baseUrl, sm.apiKey, { containerTag: container }).catch(() => null),
1654
+ ]);
1655
+ const out = {
1656
+ engine: 'supermemory',
1657
+ results: hits.map((h) => ({ text: h.text, score: h.similarity, source: 'supermemory' })),
1658
+ };
1659
+ if (prof && (prof.static.length || prof.dynamic.length)) out.profile = prof;
1660
+ return out;
1661
+ } catch {
1662
+ this._smUp = false;
1663
+ this._smCheckedAt = Date.now();
1664
+ return null;
1665
+ }
1666
+ }
1667
+
1668
+ /* opencode request.ts prepare — system birleşimi (birebir sıra) */
1669
+ buildOcSystem(session, agentInfo) {
1670
+ const sid = String(session && session.id);
1671
+ const sel = this.sessionModel.get(sid) || this.modelFor(null) || this.sel || { model: '', providerId: '' };
1672
+ const ws = (session && session.workspace) || this.workspace;
1673
+ const agentPrompt = (agentInfo && agentInfo.prompt) || '';
1674
+ const main = agentPrompt ? [agentPrompt] : opencode.systemprompt.provider(sel.model);
1547
1675
  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
- );
1676
+ const skillsBlock = (() => {
1677
+ try {
1678
+ return opencode.systemprompt.skills((agentInfo && agentInfo.permission) || [], () => skills.scan());
1679
+ } catch {
1680
+ return undefined;
1681
+ }
1682
+ })();
1683
+ /* opencode'da birleşim .filter(x => x).join("\n") — ajan promptu varsa
1684
+ model-bazlı dosya YERİNE geçer */
1685
+ return [
1686
+ ...main,
1687
+ ...opencode.systemprompt.environment({
1688
+ modelId: sel.model,
1689
+ providerId: sel.providerId || '?',
1690
+ directory: ws,
1691
+ worktree: this.workspace,
1692
+ }),
1693
+ ...(proj ? [proj] : []),
1694
+ ...(skillsBlock ? [skillsBlock] : []),
1695
+ ]
1696
+ .filter((x) => x)
1697
+ .join('\n');
1698
+ }
1699
+
1700
+ /* opencode araç izinleri: pattern'ler araç çağrısından türer.
1701
+ bash'ta always: [komut] — opencode tree-sitter deseni basitleştirilmiş. */
1702
+ _bcPermissionFor(name, args, ws) {
1703
+ const a = args || {};
1704
+ const abs = (p) => {
1705
+ try {
1706
+ const raw = String(p || '').trim();
1707
+ if (!raw) return '*';
1708
+ return path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(ws || this.workspace, raw);
1709
+ } catch {
1710
+ return '*';
1711
+ }
1712
+ };
1713
+ switch (name) {
1714
+ case 'edit_file':
1715
+ case 'write_file':
1716
+ return { permission: 'edit', patterns: [abs(a.path)], always: [] };
1717
+ case 'run_command':
1718
+ return { permission: 'bash', patterns: [String(a.command || '*')], always: [String(a.command || '*')] };
1719
+ case 'read_file':
1720
+ return { permission: 'read', patterns: [abs(a.path)], always: [] };
1721
+ case 'glob':
1722
+ return { permission: 'glob', patterns: [String(a.pattern || '*')], always: [] };
1723
+ case 'grep':
1724
+ return { permission: 'grep', patterns: [String(a.pattern || '*')], always: [] };
1725
+ case 'list_dir':
1726
+ return { permission: 'list', patterns: [abs(a.path)], always: [] };
1727
+ case 'webfetch':
1728
+ return { permission: 'webfetch', patterns: [String(a.url || '*')], always: [] };
1729
+ case 'web_search':
1730
+ return { permission: 'websearch', patterns: [String(a.query || '*')], always: [] };
1731
+ case 'todo_write':
1732
+ return { permission: 'todowrite', patterns: ['*'], always: ['*'] };
1733
+ case 'delegate_task':
1734
+ return { permission: 'task', patterns: [String(a.subagent_type || '*')], always: [] };
1735
+ default:
1736
+ return { permission: name, patterns: ['*'], always: [] };
1737
+ }
1576
1738
  }
1577
1739
 
1740
+ /* opencode external-directory: workspace dışı dosya erişimi ayrı izin ister
1741
+ (tool/external-directory.ts) — read/edit/write kapsar */
1742
+ _bcExternalCheck(name, args, ws) {
1743
+ if (name !== 'read_file' && name !== 'edit_file' && name !== 'write_file') return null;
1744
+ const raw = String((args && args.path) || '').trim();
1745
+ if (!raw) return null;
1746
+ try {
1747
+ const abs = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(ws || this.workspace, raw);
1748
+ const base = path.resolve(ws || this.workspace);
1749
+ if (path.normalize(abs).toLowerCase().startsWith(path.normalize(base).toLowerCase())) return null;
1750
+ return { permission: 'external_directory', patterns: [abs], always: [] };
1751
+ } catch {
1752
+ return null;
1753
+ }
1754
+ }
1755
+
1578
1756
  /* Beast Studio (video paneli): video YAPMA ve DÜZENLEME ajanı. Workspace'teki
1579
1757
  her şey video proje malzemesi olarak görülür (ham görüntü, ses, görsel,
1580
1758
  altyazı, çıktı). Medya işi ffprobe + ffmpeg ile yürütür; ikisi de app ile
@@ -3211,36 +3389,27 @@ class Engine {
3211
3389
  session.messages = keep;
3212
3390
  }
3213
3391
 
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'));
3392
+ async _chatTurn(session, signal, onDelta, toolsList = TOOLS, bcOpts = null) {
3393
+ /* opencode mantığı (BC): araç seti opencode builtin registry'si — read/edit/
3394
+ write/bash/glob/grep/list/webfetch/websearch/todowrite/task/skill.
3395
+ MCP/apps/panel_run BC'ye GİRMEZ (opencode disiplini: hızlı ilk token). */
3396
+ const bcAgent = session && session.bcCode ? this._bcAgentInfo(session) : null;
3397
+ if (bcAgent) {
3398
+ toolsList = opencode.toolmap.definitions();
3399
+ } else {
3400
+ /* MCP: %APPDATA%\beast\mcp.json'daki etkin serverların araçlarını şema listesine ekle
3401
+ (bağlı değilse lazy bağlanır; kapalıysa liste değişmez) */
3402
+ toolsList = await mcp.mergeTools(toolsList);
3403
+ /* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
3404
+ toolsList = apps.mergeTools(toolsList);
3405
+ /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3406
+ modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3407
+ if (!(session && session.sbSandbox)) {
3408
+ toolsList = toolsList.filter((t) => !(t && t.function && t.function.name === 'panel_run'));
3409
+ }
3225
3410
  }
3226
3411
  /* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
3227
3412
  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
3413
  const promptText = this._lastUserText(session);
3245
3414
  let system = session.bgJob
3246
3415
  ? this.buildBgSystem(session)
@@ -3248,18 +3417,21 @@ class Engine {
3248
3417
  ? this.buildStudioSystem(session)
3249
3418
  : session.finance
3250
3419
  ? this.buildFinanceSystem(session)
3251
- : session.bcCode
3252
- ? this.buildBcSystem(session)
3253
- : await this.buildSystem(promptText, session);
3420
+ : bcAgent
3421
+ ? this.buildOcSystem(session, bcAgent)
3422
+ : await this.buildSystem(promptText, session);
3254
3423
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
3255
3424
  // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
3425
+ // BC'de opencode adları Beast adına çevrilerek denetlenir (read → read_file).
3256
3426
  const perms = this.sessionPermFor(session.id);
3257
3427
  let allowedSet = null;
3258
3428
  if (!perms.includes('all')) {
3259
3429
  allowedSet = new Set();
3260
3430
  for (const p of perms) for (const t of PERM_TOOL_SETS[p] || []) allowedSet.add(t);
3261
3431
  }
3262
- let activeTools = allowedSet ? toolsList.filter((t) => allowedSet.has(t.function.name)) : toolsList;
3432
+ let activeTools = allowedSet
3433
+ ? toolsList.filter((t) => allowedSet.has(opencode.toolmap.TO_BUILTIN[t.function.name] || t.function.name))
3434
+ : toolsList;
3263
3435
  /* Beast Finance: mt5_* araçları YALNIZ finance oturumlarında görünür */
3264
3436
  if (FINANCE_TOOL_SET.size && !(session && session.finance)) {
3265
3437
  activeTools = activeTools.filter((t) => !FINANCE_TOOL_SET.has(t.function.name));
@@ -3276,9 +3448,16 @@ class Engine {
3276
3448
  /* CEO: uygulayıcı araçlar kapalı — her şey paralel ajana devredilir */
3277
3449
  activeTools = activeTools.filter((t) => !CEO_EXEC_TOOLS.has(t.function.name));
3278
3450
  }
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));
3451
+ if (bcAgent) {
3452
+ /* opencode permission.visibleTools (birebir): ruleset'te pattern:'*'+deny
3453
+ olan araçlar setten düşer — plan ajanında edit/write GERÇEKTEN gizlenir,
3454
+ salt-okurluk prompta kalmaz. opencode'da edit/write/apply_patch aynı
3455
+ "edit" iznine iner. */
3456
+ const hidden = opencode.Permission.disabled(
3457
+ activeTools.map((t) => t.function.name),
3458
+ bcAgent.permission
3459
+ );
3460
+ if (hidden.size) activeTools = activeTools.filter((t) => !hidden.has(t.function.name));
3282
3461
  }
3283
3462
  if (adef && adef.tools) {
3284
3463
  /* özel ajan araç beyaz listesi — tanımda olmayan araç görünmez */
@@ -3316,7 +3495,25 @@ class Engine {
3316
3495
  /* opencode port: model bağlamına göre dinamik bütçe + compaction özeti */
3317
3496
  const selEarly = this.sessionModel.get(String(session.id)) || this.modelFor(null) || this.sel;
3318
3497
  const dynBudget = this._historyBudget(selEarly);
3319
- let payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3498
+ /* opencode SessionReminders.apply (reminders.ts birebir): plan ajanındaysa
3499
+ plan.txt salt-okur hatırlatması, plan→build geçişinde build-switch.txt
3500
+ son USER mesajına synthetic eklenir. session.messages ASLA değişmez —
3501
+ her tur taze kopyaya uygulanır (opencode her turda DB'den yeniden yükler). */
3502
+ let msgsForPayload = session.messages;
3503
+ if (bcAgent) {
3504
+ msgsForPayload = opencode.systemprompt.applyReminders(
3505
+ session.messages,
3506
+ bcAgent,
3507
+ !!this._bcWasPlan.get(String(session.id))
3508
+ );
3509
+ }
3510
+ let payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3511
+ if (bcAgent && bcOpts && bcOpts.isLastStep) {
3512
+ /* opencode prompt.ts:1178-1181 — isLastStep: MAX_STEPS_PROMPT assistant
3513
+ mesajı olarak akışa eklenir; araçlar kilitlenir (yalnız metin kapanışı) */
3514
+ if (Array.isArray(payload.messages)) payload.messages.push({ role: 'assistant', content: opencode.MAX_STEPS_PROMPT });
3515
+ activeTools = [];
3516
+ }
3320
3517
  // Vision model sadece mesajda görsel varsa devreye girer; terminal ise prompt'un
3321
3518
  // shell/command işi olduğunda. Aksi halde ana model kullanılır.
3322
3519
  let role = this._lastUserHasImage(session) ? 'vision' : null;
@@ -3365,7 +3562,7 @@ class Engine {
3365
3562
  type: 'status',
3366
3563
  status: `🧠 düşük uyumlu sağlayıcı: geçmişteki düşünme içerikleri temizlendi (${dropped} mesaj) — tur yeniden deneniyor`,
3367
3564
  });
3368
- payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3565
+ payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3369
3566
  res = await this._streamWithFallbacks(
3370
3567
  session,
3371
3568
  payload,
@@ -3399,7 +3596,7 @@ class Engine {
3399
3596
  type: 'status',
3400
3597
  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
3598
  });
3402
- payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3599
+ payload = this._buildPayload(system, msgsForPayload, session.notes, dynBudget, session.summary);
3403
3600
  res = await this._streamWithFallbacks(
3404
3601
  session,
3405
3602
  payload,
@@ -3477,9 +3674,17 @@ class Engine {
3477
3674
  doom-loop koruması + kullanıcı ■ kırar. bg ajanlarda kapanış
3478
3675
  disiplini için tavan kalır. */
3479
3676
  const runDef = this._agentDefFor(session, !!session.bgJob);
3677
+ /* opencode mantığı (BC): ajan çözümleme — build/plan ajanı ruleset'iyle
3678
+ beraber tur boyunca kullanılır (permission ask + disabled filtreleri) */
3679
+ const bcAgent = session.bcCode ? this._bcAgentInfo(session) : null;
3680
+ /* opencode prompt.ts:1178 (birebir): maxSteps = agent.steps ?? Infinity —
3681
+ build/plan ajanlarında steps tanımsızdır → sınırsız; bağlam dolunca
3682
+ compaction özetler, sonsuz döngüyü doom-loop koruması + ■ kırar. */
3480
3683
  const maxTurns = session.bgJob
3481
3684
  ? Math.max(2, Math.min(60, (runDef && runDef.steps) || MAX_TURNS))
3482
- : Infinity;
3685
+ : bcAgent
3686
+ ? (bcAgent.steps ?? Infinity)
3687
+ : Infinity;
3483
3688
  const recentSigs = []; // doom-loop dedektörü: son araç imzaları
3484
3689
  /* Beast Code canlı önizleme: run boyunca üretilen eserleri topla —
3485
3690
  iş bitince dahili tarayıcıda CANLI açılır (site/dev server/HTML) */
@@ -3528,9 +3733,12 @@ class Engine {
3528
3733
  /* opencode runLoop portu: istek kurulmadan ÖNCEKİ mesaj sayısı —
3529
3734
  koşan tur SIRASINDA gelen (steer) user mesajlarını saptar */
3530
3735
  const preLen = session.messages.length;
3736
+ /* opencode prompt.ts:1178-1181: isLastStep = step >= maxSteps — son
3737
+ turda MAX_STEPS_PROMPT enjekte edilir ve araçlar kilitlenir */
3738
+ const isLastStep = !!bcAgent && maxTurns !== Infinity && turn >= maxTurns - 1;
3531
3739
  const res = await this._chatTurn(session, ctrl.signal, (delta) =>
3532
3740
  emit({ type: 'token', delta })
3533
- );
3741
+ , bcAgent ? opencode.toolmap.definitions() : undefined, bcAgent ? { isLastStep } : null);
3534
3742
  /* opencode port (processor.ts:477-482): step-finish usage'ı compaction
3535
3743
  tetiğine besle — gerçek toplam biliniyorsa tahmine gerek kalmaz */
3536
3744
  const uTot =
@@ -3615,14 +3823,16 @@ class Engine {
3615
3823
 
3616
3824
  // Paralel yürütme — bağımsız çağrılar beklemesin
3617
3825
  session.toolsSinceReflect = (session.toolsSinceReflect || 0) + res.toolCalls.length;
3618
- if (!this._knownToolNames) this._knownToolNames = new Set(TOOLS.map((t) => t.function.name));
3826
+ if (!this._knownToolNames) this._knownToolNames = new Set([...TOOLS.map((t) => t.function.name), ...opencode.toolmap.names()]);
3619
3827
  await Promise.all(
3620
3828
  res.toolCalls.map(async (tc) => {
3621
3829
  let name = tc.function && tc.function.name;
3622
3830
  /* 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 */
3831
+ yanlış yazdıysa büyük/küçük harf normalizasyonuyle onar — tur kaybı yok.
3832
+ BC'de havuz opencode builtin seti (read/edit/write/bash...) */
3624
3833
  if (name && !this._knownToolNames.has(name)) {
3625
- const fixed = TOOLS.find((t) => t.function.name.toLowerCase() === String(name).trim().toLowerCase());
3834
+ const pool = bcAgent ? opencode.toolmap.definitions() : TOOLS;
3835
+ const fixed = pool.find((t) => t.function.name.toLowerCase() === String(name).trim().toLowerCase());
3626
3836
  if (fixed) {
3627
3837
  emitSafe(this, sid, {
3628
3838
  type: 'status',
@@ -3638,33 +3848,59 @@ class Engine {
3638
3848
  emit({ type: 'tool-start', callId: tc.id, name, args });
3639
3849
  emit({ type: 'status', status: name });
3640
3850
  /* 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 */
3851
+ aynı argüman 3 kez koştuysa 4.'yi çalıştırma — döngüye para/hız
3852
+ akıtma; BC'de opencode disiplini: doom_loop İZİN ister (defaults:
3853
+ doom_loop ask) — kullanıcı onaylarsa koşmaya devam eder */
3643
3854
  const sig = name + '\u0000' + String((tc.function && tc.function.arguments) || '');
3644
3855
  const dups = recentSigs.reduce((a, s) => a + (s === sig ? 1 : 0), 0);
3645
3856
  recentSigs.push(sig);
3646
3857
  let out;
3647
3858
  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` });
3859
+ if (bcAgent) {
3860
+ try {
3861
+ await this._perm.ask({
3862
+ sessionId: sid,
3863
+ ruleset: bcAgent.permission,
3864
+ permission: 'doom_loop',
3865
+ patterns: [name],
3866
+ always: [name],
3867
+ metadata: { callCount: DOOM_LOOP_THRESHOLD, arguments: (tc.function && tc.function.arguments) || '' },
3868
+ tool: { name, args },
3869
+ });
3870
+ out = await this._execTool(name, args, ctrl.signal, sid);
3871
+ } catch (permErr) {
3872
+ out = JSON.stringify({
3873
+ error:
3874
+ `doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez koştu. ` +
3875
+ AskService.errorText(permErr) +
3876
+ ' Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
3877
+ });
3878
+ }
3879
+ } else {
3880
+ out = JSON.stringify({
3881
+ error:
3882
+ `doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez çalıştı ve hep aynı sonucu verdi. ` +
3883
+ 'Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
3884
+ });
3885
+ emitSafe(this, sid, { type: 'status', status: `⛔ doom-loop: ${name} tekrarı engellendi` });
3886
+ }
3654
3887
  } else {
3655
3888
  out = await this._execTool(name, args, ctrl.signal, sid);
3656
3889
  }
3657
- /* Beast Code eser takibi: yazılan HTML + başlatılan dev server */
3890
+ /* Beast Code eser takibi: yazılan HTML + başlatılan dev server.
3891
+ BC'de araçlar opencode adlarıyla gelir: write/edit (filePath),
3892
+ bash (command) — opencode şemasına göre yakalanır */
3658
3893
  if (bcArtifacts) {
3659
3894
  try {
3895
+ const filePathOc = args && (args.filePath ?? args.path);
3660
3896
  if (
3661
- (name === 'write_file' || name === 'edit_file') &&
3662
- args && args.path && /\.html?$/i.test(String(args.path))
3897
+ (name === 'write' || name === 'edit' || name === 'write_file' || name === 'edit_file') &&
3898
+ filePathOc && /\.html?$/i.test(String(filePathOc))
3663
3899
  ) {
3664
- bcArtifacts.html.push(String(args.path));
3900
+ bcArtifacts.html.push(String(filePathOc));
3665
3901
  }
3666
- if (name === 'run_command') {
3667
- const cmdStr = String((args && args.command) || '');
3902
+ if (name === 'bash' || name === 'run_command') {
3903
+ const cmdStr = String((args && (args.command ?? args.cmd)) || '');
3668
3904
  const hay = cmdStr + '\n' + String(out || '').slice(0, 6000);
3669
3905
  const m = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?[^\s'"<>]*/i.exec(hay);
3670
3906
  if (m) {
@@ -3680,7 +3916,7 @@ class Engine {
3680
3916
  if (port) bcArtifacts.serverUrl = 'http://localhost:' + port;
3681
3917
  }
3682
3918
  /* Expo dev server: exp://<LAN-IP>:<port> çıktıdan yakalanır —
3683
- telefon/Expo Go için QR renderer'a canlı iletilir */
3919
+ telefon/Expo Go için QR renderer'a canlı iletilir */
3684
3920
  const xm = /exp:\/\/[^\s'"<>]+/i.exec(hay);
3685
3921
  if (xm) {
3686
3922
  const eurl = xm[0].replace(/[)\].,;:'"]+$/, '');
@@ -3691,7 +3927,7 @@ class Engine {
3691
3927
  }
3692
3928
  }
3693
3929
  } catch {}
3694
- }
3930
+ }
3695
3931
  // Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
3696
3932
  let injectedImage = null;
3697
3933
  /* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
@@ -3709,7 +3945,7 @@ class Engine {
3709
3945
  }
3710
3946
  if (injectedImage || diffView) out = JSON.stringify(obj);
3711
3947
  } 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) };
3948
+ 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
3949
  if (diffView) toolMsg.diffView = diffView; /* oturum dosyasına da düşer — geçmiş açılınca diff yeniden çizilir */
3714
3950
  session.messages.push(toolMsg);
3715
3951
  try {
@@ -4172,31 +4408,70 @@ const skills = require('./skills');
4172
4408
 
4173
4409
  /* ---------- alt-agent ---------- */
4174
4410
 
4175
- async _subagent(task, context, parentSignal, sessionId) {
4411
+ async _subagent(task, context, parentSignal, sessionId, subagentType = '') {
4176
4412
  const ctrl = new AbortController();
4177
4413
  const onAbort = () => ctrl.abort();
4178
4414
  if (parentSignal) {
4179
4415
  if (parentSignal.aborted) throw new Error('iptal edildi');
4180
4416
  parentSignal.addEventListener('abort', onAbort, { once: true });
4181
4417
  }
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
4418
+ /* opencode task aracı (tool/task.ts) subagent_tipi ZORUNLU kılar: BC'den
4419
+ gelen task çağrılarında 'general' | 'explore' ajanları opencode mantığıyla
4420
+ koşar (agent tanımı + permission + opencode builtin seti). Eski
4421
+ delegate_task (BC dışı) davranışı aynen korunur. */
4422
+ const ocType = subagentType === 'explore' || subagentType === 'general' ? subagentType : '';
4188
4423
  const role = 'subagent';
4189
4424
  const sel = this.modelFor(role) || this.sel;
4190
4425
  if (this.roleModels[role]) {
4191
4426
  emitSafe(this, sessionId, { type: 'status', status: `rol: ${role} → ${sel.providerName} · ${sel.model}` });
4192
4427
  }
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;
4428
+ let subTools;
4429
+ let system;
4430
+ let ocMode = false;
4431
+ if (ocType) {
4432
+ ocMode = true;
4433
+ const ws = this._sessionWorkspace(sessionId);
4434
+ const agentInfo =
4435
+ opencode.agents.get(ocType, { worktree: ws, promptExplore: opencode.PROMPT_EXPLORE, user: {} }) || null;
4436
+ const ruleset = agentInfo ? agentInfo.permission : [];
4437
+ /* opencode child session: subagent ANA döngüdeki gibi ajan tanımıyla
4438
+ koşar — system = [ajan promptu yoksa model-bazlı prompt, env] */
4439
+ const main = agentInfo && agentInfo.prompt ? [agentInfo.prompt] : opencode.systemprompt.provider(sel.model);
4440
+ system = [
4441
+ ...main,
4442
+ ...opencode.systemprompt.environment({
4443
+ modelId: sel.model,
4444
+ providerId: sel.providerId || '?',
4445
+ directory: ws,
4446
+ worktree: ws,
4447
+ }),
4448
+ ]
4449
+ .filter((x) => x)
4450
+ .join('\n');
4451
+ /* opencode SessionTools.resolve: ajanın permission'ında '*' deny olan
4452
+ araçlar setten düşer — explore: salt-okur (bash dahil okuma amaçlı allow),
4453
+ general: todowrite yok (opencode general tanımı birebir) */
4454
+ const defs = opencode.toolmap.definitions();
4455
+ const hidden = opencode.Permission.disabled(defs.map((t) => t.function.name), ruleset);
4456
+ subTools = defs
4457
+ .filter((t) => !hidden.has(t.function.name))
4458
+ .filter((t) => t.function.name !== 'task')
4459
+ .sort((a, b) => String(a.function.name).localeCompare(String(b.function.name)));
4460
+ } else {
4461
+ subTools = TOOLS.filter(
4462
+ (t) =>
4463
+ t.function.name !== 'delegate_task' &&
4464
+ t.function.name !== 'set_reminder' &&
4465
+ !t.function.name.startsWith('email_')
4466
+ ).sort((a, b) => String(a.function.name).localeCompare(String(b.function.name))); // önek-cache: sabit sıra
4467
+ system =
4468
+ `Sen odaklı bir alt-agentsın. Ana ajansın verdiği TEK görevi bitir ve sadece nihai sonucu döndür.\n` +
4469
+ `Windows + PowerShell, çalışma klasörü: ${this.workspace}\n` +
4470
+ `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` +
4471
+ `ARAŞTIRMA SINIRI: 3-5 kaynak yeter; 2-3 denemede bulamazsan kısmi sonucu getir, takılma.\n` +
4472
+ `Kısa çalış, gereksiz soru sorma, sonucu madde madde raporla.\n` +
4473
+ FORMAT_RULES;
4474
+ }
4200
4475
  const user = context ? `${task}\n\n# BAĞLAM\n${context}` : task;
4201
4476
  const msgs = [
4202
4477
  { role: 'system', content: system },
@@ -4236,8 +4511,15 @@ const skills = require('./skills');
4236
4511
  try {
4237
4512
  args = JSON.parse((tc.function && tc.function.arguments) || '{}');
4238
4513
  } 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 });
4514
+ let subName = tc.function && tc.function.name;
4515
+ if (ocMode) {
4516
+ /* opencode builtin dilinde çağırır → Beast aracına çevir */
4517
+ const mapped = opencode.toolmap.execMap(subName, args);
4518
+ subName = mapped.name;
4519
+ args = mapped.args;
4520
+ }
4521
+ const out = await this._subExecTool(subName, args, ctrl.signal);
4522
+ msgs.push({ role: 'tool', tool_call_id: tc.id, name: subName, content: out });
4241
4523
  }
4242
4524
  }
4243
4525
  const last = [...msgs].reverse().find((m) => m.role === 'assistant' && m.content);
@@ -4373,27 +4655,85 @@ const skills = require('./skills');
4373
4655
 
4374
4656
  async _execTool(name, args, signal, sessionId) {
4375
4657
  try {
4658
+ /* opencode mantığı (BC): model opencode builtin dilinde çağırır —
4659
+ read/edit/write/bash/todowrite/task... execMap Beast'in yerleşik
4660
+ aracına çevirir (implementasyon değişmez, dil opencode olur).
4661
+ Studio/Finance/bgJob kendi dünyasında koşar — bu akışa girmez. */
4662
+ const bcSess = this.cache.get(String(sessionId || '')) || null;
4663
+ const isBc = !!(bcSess && bcSess.bcCode && !bcSess.studio && !bcSess.finance && !bcSess.bgJob);
4664
+ if (isBc) {
4665
+ const mapped = opencode.toolmap.execMap(name, args);
4666
+ name = mapped.name;
4667
+ args = mapped.args;
4668
+ }
4376
4669
  /* ANA KOD KİLİDİ: korumalı bölgeye yazma/silme girişimi daha kapıdan geçmez */
4377
4670
  const blocked = this._guardTool(name, args);
4378
4671
  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;
4672
+ if (isBc) {
4673
+ /* opencode permission akışı (tool/external-directory.ts + her aracın
4674
+ ctx.ask'ı): önce workspace DIŞI erişim izni, sonra araç izni.
4675
+ deny anında hata döner (asla sorulmaz), allow sessiz geçer,
4676
+ ask UI'ya 'permission.asked' eventi ile sorulur */
4677
+ const ws = this._sessionWorkspace(sessionId);
4678
+ const bcAgent = this._bcAgentInfo(bcSess);
4679
+ const ruleset = bcAgent ? bcAgent.permission : [];
4388
4680
  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.',
4681
+ const ext = this._bcExternalCheck(name, args, ws);
4682
+ if (ext) {
4683
+ await this._perm.ask({ sessionId, ruleset, ...ext, metadata: { tool: name, args: args || {} } });
4684
+ }
4685
+ const perm = this._bcPermissionFor(name, args, ws);
4686
+ await this._perm.ask({
4687
+ sessionId,
4688
+ ruleset,
4689
+ permission: perm.permission,
4690
+ patterns: perm.patterns,
4691
+ always: perm.always,
4692
+ metadata: { args: args || {} },
4693
+ tool: { name, args: args || {} },
4396
4694
  });
4695
+ } catch (permErr) {
4696
+ return JSON.stringify({ ok: false, error: AskService.errorText(permErr) });
4697
+ }
4698
+ /* opencode skill aracı: katalogdaki SKILL.md gövdesini modele açar */
4699
+ if (name === 'skill') {
4700
+ const wanted = String((args && args.name) || '').trim();
4701
+ let list = [];
4702
+ try { list = skills.scan(); } catch {}
4703
+ const hit = list.find((s) => s && s.name === wanted) || null;
4704
+ if (!hit) {
4705
+ return JSON.stringify({
4706
+ ok: false,
4707
+ error:
4708
+ 'skill bulunamadı: ' + (wanted || '(boş)') +
4709
+ (list.length ? ' — mevcut: ' + list.map((s) => s.name).join(', ') : ''),
4710
+ });
4711
+ }
4712
+ let body = '';
4713
+ try { body = fs.readFileSync(hit.path, 'utf8'); } catch {}
4714
+ return JSON.stringify({ ok: true, name: hit.name, path: hit.path, content: body.slice(0, 24000) });
4715
+ }
4716
+ } else {
4717
+ /* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
4718
+ "always" onaylı araçlar doğrudan geçer. (BC dışı oturumlar — opencode
4719
+ izin sistemi yalnız Beast Code oturumlarında koşar) */
4720
+ if (
4721
+ (Engine.RISKY_TOOLS.has(name) || String(name).startsWith('mcp__')) &&
4722
+ !this.alwaysAllowTools.has(name) &&
4723
+ this.approvals && typeof this.approvals.request === 'function'
4724
+ ) {
4725
+ emitSafe(this, sessionId, { type: 'status', status: `onay bekleniyor: ${name}` });
4726
+ let ok = false;
4727
+ try {
4728
+ ok = !!(await this.approvals.request({ sessionId, tool: name, args: args || {} }));
4729
+ } catch {}
4730
+ if (!ok) {
4731
+ return JSON.stringify({
4732
+ ok: false,
4733
+ error:
4734
+ 'kullanıcı bu işlemi ONAYLAMADI (iptal edildi veya zaman aşımı). İşlemi yeniden deneme; kullanıcıya sormadan alternatif öner.',
4735
+ });
4736
+ }
4397
4737
  }
4398
4738
  }
4399
4739
  /* GERİ ALMA GÜNLÜĞÜ: dosya yazımından ÖNCE eski içerik kayda geçer */
@@ -4419,6 +4759,9 @@ const skills = require('./skills');
4419
4759
  return JSON.stringify(await mcp.call(name, args, signal));
4420
4760
  }
4421
4761
  if (name === 'memory_write') {
4762
+ /* Supermemory (lokal) DEFAULT: her yazım asenkron push edilir —
4763
+ sunucu ayaktaysa graf motoru özet/çelişki/unutma işlerini yürütür */
4764
+ this._smPush(sessionId, args.text);
4422
4765
  /* bot oturumu → botun KENDİ hafıza store'una yaz (global Beast hafızasına değil).
4423
4766
  mem0 açıkken: hash+semantic dedup'lı store'a gider, MEMORY.md aynası güncellenir. */
4424
4767
  const bctx = this._sessionBotCtx(sessionId ? this.cache.get(String(sessionId)) : null);
@@ -4427,7 +4770,7 @@ const skills = require('./skills');
4427
4770
  const r = await mem0.add('bot:' + bctx.id, [args.text]).catch(() => null);
4428
4771
  if (r && r.ok) {
4429
4772
  mem0.syncMirror('bot:' + bctx.id);
4430
- return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event });
4773
+ return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event, supermemory: !!this._smConfig() });
4431
4774
  }
4432
4775
  }
4433
4776
  return JSON.stringify(this.botMemory.append(bctx.id, args.text));
@@ -4436,7 +4779,7 @@ const skills = require('./skills');
4436
4779
  const r = await mem0.add('main', [args.text]).catch(() => null);
4437
4780
  if (r && r.ok) {
4438
4781
  mem0.syncMirror('main');
4439
- return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event });
4782
+ return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event, supermemory: !!this._smConfig() });
4440
4783
  }
4441
4784
  }
4442
4785
  return JSON.stringify(memory.append(args.text));
@@ -4454,6 +4797,12 @@ const skills = require('./skills');
4454
4797
  const bctx = this._sessionBotCtx(sessionId ? this.cache.get(String(sessionId)) : null);
4455
4798
  const q = String(args.query || '');
4456
4799
  const limit = clamp(Number(args.limit) || 5, 1, 15);
4800
+ /* Supermemory DEFAULT öncelik: hibrit arama + kullanıcı profili.
4801
+ Erişilemediyse/hata yerse yerel yollar aynen çalışır (fallback). */
4802
+ const smRes = await this._smSearch(sessionId, q, limit);
4803
+ if (smRes && smRes.results.length) {
4804
+ return JSON.stringify({ ok: true, query: q, ...smRes });
4805
+ }
4457
4806
  if (bctx && this.botMemory) {
4458
4807
  const rows = this.mem0Enabled
4459
4808
  ? await mem0.search('bot:' + bctx.id, q, { limit }).catch(() => [])
@@ -4488,8 +4837,8 @@ const skills = require('./skills');
4488
4837
  if (name === 'delegate_task') {
4489
4838
  const task = String(args.task || '').trim();
4490
4839
  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);
4840
+ emitSafe(this, sessionId, { type: 'status', status: args.subagent_type === 'explore' ? 'task(explore)' : args.subagent_type ? 'task(general)' : 'delegate_task' });
4841
+ const result = await this._subagent(task, String(args.context || ''), signal, sessionId, String(args.subagent_type || ''));
4493
4842
  return JSON.stringify({ ok: true, task, result: String(result).slice(0, 12000) });
4494
4843
  }
4495
4844
  if (name === 'bot_dm') {