beast-agent 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/agentdefs.js +122 -0
- package/src/agent/engine.js +176 -22
- package/src/agent/skills.js +1 -0
- package/src/agent/tools.js +527 -35
- package/src/main.js +41 -0
- package/src/preload.js +1 -0
- package/src/renderer/index.html +7 -1
- package/src/renderer/renderer.js +291 -13
- package/src/renderer/style.css +369 -254
package/package.json
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* ---------- opencode agent.ts port: özel ajan tanımları ----------
|
|
4
|
+
Kullanıcı ajanları: %APPDATA%\beast\agents\<isim>.md
|
|
5
|
+
Markdown frontmatter + prompt gövdesi:
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
model: providerId::model # opsiyonel — o ajanın modeli
|
|
9
|
+
tools: [run_command, web_search] # opsiyonel araç beyaz listesi
|
|
10
|
+
steps: 12 # opsiyonel tur limiti (2-60)
|
|
11
|
+
mode: all # chat | bg | all (nerede kullanılabilir)
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Buradan itibaren ajanın sistem prompt eki gelir.
|
|
15
|
+
|
|
16
|
+
Kullanım: sohbette /agent <isim> · paralel ajanda run_background(agent:"isim")
|
|
17
|
+
Ana akış bunları engine.setSessionAgent / runBackground(opts.agent) ile tüketir. */
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { parseYaml, beastDir } = require('./config');
|
|
22
|
+
|
|
23
|
+
function dir() {
|
|
24
|
+
return path.join(beastDir(), 'agents');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* İlk açılışta klasörü + örnek tanımı tohumla (kullanıcı hemen görür) */
|
|
28
|
+
function seedIfEmpty() {
|
|
29
|
+
try {
|
|
30
|
+
const d = dir();
|
|
31
|
+
if (fs.existsSync(d)) return;
|
|
32
|
+
fs.mkdirSync(d, { recursive: true });
|
|
33
|
+
fs.writeFileSync(
|
|
34
|
+
path.join(d, 'ornek-arastirmaci.md'),
|
|
35
|
+
[
|
|
36
|
+
'---',
|
|
37
|
+
'# Bu dosya bir ÖRNEK ajan tanımıdır — dilediğin gibi düzenle/kopyala.',
|
|
38
|
+
'# model: providerId::model → bu ajana özel model (boşsa aktif model)',
|
|
39
|
+
'# tools: [web_search, http_fetch, deep_search] → araç beyaz listesi',
|
|
40
|
+
'# steps: 10 → tur limiti (2-60)',
|
|
41
|
+
'# mode: all → chat | bg | all',
|
|
42
|
+
'mode: all',
|
|
43
|
+
'steps: 10',
|
|
44
|
+
'---',
|
|
45
|
+
'',
|
|
46
|
+
'Sen odaklı bir ARAŞTIRMACI ajansın. Görevi baştan sona yürüt:',
|
|
47
|
+
'1) Kaynakları paralel tara (3-5 yeterli), 2) çelişkileri işaretle,',
|
|
48
|
+
'3) bulguları madde madde + kaynak linkleriyle raporla.',
|
|
49
|
+
'2-3 denemede bulunamayan bilgiyi bırak — kısmi sonuçla gel, takılma.',
|
|
50
|
+
'',
|
|
51
|
+
].join('\n')
|
|
52
|
+
);
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _parseFile(file) {
|
|
57
|
+
const name = path.basename(file).replace(/\.md$/i, '');
|
|
58
|
+
let text = '';
|
|
59
|
+
try {
|
|
60
|
+
text = fs.readFileSync(file, 'utf8');
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const t = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
65
|
+
if (!t) return null;
|
|
66
|
+
let meta = {};
|
|
67
|
+
let body = t;
|
|
68
|
+
const m = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(t);
|
|
69
|
+
if (m) {
|
|
70
|
+
try {
|
|
71
|
+
meta = parseYaml(m[1]) || {};
|
|
72
|
+
} catch {
|
|
73
|
+
meta = {};
|
|
74
|
+
}
|
|
75
|
+
body = m[2] || '';
|
|
76
|
+
}
|
|
77
|
+
let tools = null;
|
|
78
|
+
if (Array.isArray(meta.tools)) tools = meta.tools.map(String).map((s) => s.trim()).filter(Boolean);
|
|
79
|
+
else if (meta.tools) tools = String(meta.tools).split(',').map((s) => s.trim()).filter(Boolean);
|
|
80
|
+
const steps = Math.round(Number(meta.steps) || 0);
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
model: String(meta.model || '').trim() || null,
|
|
84
|
+
tools: tools && tools.length ? tools : null,
|
|
85
|
+
steps: steps ? Math.max(2, Math.min(60, steps)) : null,
|
|
86
|
+
mode: ['chat', 'bg', 'all'].includes(String(meta.mode || '').toLowerCase())
|
|
87
|
+
? String(meta.mode).toLowerCase()
|
|
88
|
+
: 'all',
|
|
89
|
+
prompt: body.trim(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function list() {
|
|
94
|
+
let files = [];
|
|
95
|
+
try {
|
|
96
|
+
files = fs.readdirSync(dir()).filter((f) => f.toLowerCase().endsWith('.md'));
|
|
97
|
+
} catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const out = [];
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
for (const f of files) {
|
|
103
|
+
const def = _parseFile(path.join(dir(), f));
|
|
104
|
+
if (def && def.prompt && !seen.has(def.name.toLowerCase())) {
|
|
105
|
+
seen.add(def.name.toLowerCase());
|
|
106
|
+
out.push(def);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function get(name) {
|
|
113
|
+
const n = String(name || '').trim().toLowerCase();
|
|
114
|
+
if (!n) return null;
|
|
115
|
+
return (
|
|
116
|
+
list().find((d) => d.name.toLowerCase() === n) ||
|
|
117
|
+
list().find((d) => d.name.toLowerCase().startsWith(n)) ||
|
|
118
|
+
null
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { dir, list, get, seedIfEmpty };
|
package/src/agent/engine.js
CHANGED
|
@@ -10,6 +10,7 @@ const { chatStream, chatStreamAuto, chatOnce } = require('./llm');
|
|
|
10
10
|
const { chatCompletionsUrl } = require('./config');
|
|
11
11
|
const tools = require('./tools');
|
|
12
12
|
const research = require('./research');
|
|
13
|
+
const agentdefs = require('./agentdefs');
|
|
13
14
|
const memory = require('./memory');
|
|
14
15
|
const skills = require('./skills');
|
|
15
16
|
const { estTokens, estMsgTokens } = require('./tokens');
|
|
@@ -311,6 +312,59 @@ class Engine {
|
|
|
311
312
|
}, SUP_CHECK_MS);
|
|
312
313
|
if (this._supTimer.unref) this._supTimer.unref();
|
|
313
314
|
skills.seedIfEmpty();
|
|
315
|
+
agentdefs.seedIfEmpty();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* ---------- opencode agent.ts port: özel ajanlar ---------- */
|
|
319
|
+
|
|
320
|
+
/* Oturumu bir özel ajana bağla (%APPDATA%\beast\agents\<isim>.md) —
|
|
321
|
+
o oturumun prompt/model/araç/tur limiti ajan tanımından gelir */
|
|
322
|
+
setSessionAgent(sessionId, name) {
|
|
323
|
+
const sid = String(sessionId || '');
|
|
324
|
+
if (!sid) return { ok: false, error: 'oturum yok' };
|
|
325
|
+
const n = String(name || '').trim();
|
|
326
|
+
let s;
|
|
327
|
+
try { s = this._load(sid); } catch { return { ok: false, error: 'oturum açılamadı' }; }
|
|
328
|
+
if (!n || n === 'off' || n === 'kapalı') {
|
|
329
|
+
delete s.agentName;
|
|
330
|
+
this.cache.set(sid, s);
|
|
331
|
+
try {
|
|
332
|
+
fs.appendFileSync(this._file(sid), JSON.stringify({ t: 'agent', name: null, at: nowIso() }) + '\n');
|
|
333
|
+
} catch {}
|
|
334
|
+
return { ok: true, agent: null };
|
|
335
|
+
}
|
|
336
|
+
const def = agentdefs.get(n);
|
|
337
|
+
if (!def) {
|
|
338
|
+
const have = agentdefs.list().map((d) => d.name).join(', ') || '(tanım yok)';
|
|
339
|
+
return { ok: false, error: `ajan bulunamadı: ${n} — tanımlı: ${have}` };
|
|
340
|
+
}
|
|
341
|
+
s.agentName = def.name;
|
|
342
|
+
this.cache.set(sid, s);
|
|
343
|
+
try {
|
|
344
|
+
fs.appendFileSync(this._file(sid), JSON.stringify({ t: 'agent', name: def.name, at: nowIso() }) + '\n');
|
|
345
|
+
} catch {}
|
|
346
|
+
return { ok: true, agent: def.name, model: def.model, tools: def.tools, steps: def.steps };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* Oturumun (varsa) özel ajan tanımı — mode filtresiyle: chat oturumunda
|
|
350
|
+
mode:bg ajanı kullanılmaz, bg oturumunda mode:chat kullanılmaz */
|
|
351
|
+
_agentDefFor(session, forBg = false) {
|
|
352
|
+
const n = session && session.agentName;
|
|
353
|
+
if (!n) return null;
|
|
354
|
+
const def = agentdefs.get(n);
|
|
355
|
+
if (!def) return null;
|
|
356
|
+
if (def.mode !== 'all' && def.mode !== (forBg ? 'bg' : 'chat')) return null;
|
|
357
|
+
return def;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
listAgents() {
|
|
361
|
+
return agentdefs.list().map((d) => ({
|
|
362
|
+
name: d.name,
|
|
363
|
+
model: d.model,
|
|
364
|
+
tools: d.tools,
|
|
365
|
+
steps: d.steps,
|
|
366
|
+
mode: d.mode,
|
|
367
|
+
}));
|
|
314
368
|
}
|
|
315
369
|
|
|
316
370
|
/* Zinciri yeniden kur: base + custom, silinenleri ayıkla */
|
|
@@ -981,6 +1035,9 @@ class Engine {
|
|
|
981
1035
|
/* opencode compaction: konuşma özeti — birebir geçmişin yerine geçmez,
|
|
982
1036
|
payload'da system'den sonra sabit user mesajı olarak girer */
|
|
983
1037
|
if (rec.text) session.summary = String(rec.text);
|
|
1038
|
+
} else if (rec.t === 'agent') {
|
|
1039
|
+
/* opencode agent port: oturumun bağlı olduğu özel ajan */
|
|
1040
|
+
if (rec.name) session.agentName = String(rec.name);
|
|
984
1041
|
} else if (rec.t === 'botdm') {
|
|
985
1042
|
/* botlar arası DM oturumu — admin izleyebilir, sidebar'da görünmez */
|
|
986
1043
|
session.isBotDm = true;
|
|
@@ -1328,10 +1385,10 @@ class Engine {
|
|
|
1328
1385
|
? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n'
|
|
1329
1386
|
: '') +
|
|
1330
1387
|
'WORKFLOW (her işte bu sıra):\n' +
|
|
1331
|
-
'1) BAĞLAM: değiştirmeden önce ilgili dosyaları OKU — read_file satır numaralı döner (N: içerik)
|
|
1388
|
+
'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 — içeriği bağlamda zaten var. İçerik araması için grep (regex), dosya adı araması için glob kullan; varsayım yapma, mevcut stili/konvansiyonu takip et.\n' +
|
|
1332
1389
|
'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' +
|
|
1333
|
-
'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. İlgisiz yeniden biçimleme/kayıt boşluk değişikliği YAPMA.\n' +
|
|
1334
|
-
'4) DOĞRULA: edit sonrası mümkünse derle/test et/lint çalıştır (run_command); hata varsa DÜZELT ve TEKRAR dene (en fazla 2 doğrulama turu) — kırmızı bırakma.\n' +
|
|
1390
|
+
'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. İlgisiz yeniden biçimleme/kayıt 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' +
|
|
1391
|
+
'4) DOĞRULA: edit sonrası mümkünse derle/test et/lint çalıştır (run_command); 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' +
|
|
1335
1392
|
'5) RAPOR: 1-3 satır — ne değişti + doğrulama sonucu (ör. "npm test ✓ 154/154"). Uzun açıklama yok.\n' +
|
|
1336
1393
|
'CANLI ÖNİZLEME: web sitesi/app üretirsen son adımda ÇALIŞIR halde bırak (dev server ayakta ya da index.html yazılmış) — panel iş bitince ürettiğin şeyi dahili tarayıcıda otomatik canlı açar.\n' +
|
|
1337
1394
|
'HIZ KURALLARI:\n' +
|
|
@@ -1384,6 +1441,12 @@ class Engine {
|
|
|
1384
1441
|
'# SKILLS\nKullanmadan önce ilgili SKILL.md dosyasını read_file ile oku.\n' + sk.join('\n')
|
|
1385
1442
|
);
|
|
1386
1443
|
}
|
|
1444
|
+
/* opencode instruction port (ajan modu): workspace AGENTS/CLAUDE/CONTEXT
|
|
1445
|
+
talimatları — oturum başına bir kez okunur, epoch boyunca sabit */
|
|
1446
|
+
const projChat = this._projectInstructions(session);
|
|
1447
|
+
if (projChat) {
|
|
1448
|
+
parts.push('# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + projChat);
|
|
1449
|
+
}
|
|
1387
1450
|
/* Beast Code (bcCode) oturumları buildBcSystem kullanır — buraya düşmez */
|
|
1388
1451
|
const nowD = new Date();
|
|
1389
1452
|
const localDate = nowD.toLocaleDateString('tr-TR');
|
|
@@ -1415,7 +1478,7 @@ class Engine {
|
|
|
1415
1478
|
'# ARAÇ KURALLARI\n' +
|
|
1416
1479
|
[
|
|
1417
1480
|
'Basit soruları araç kullanmadan doğrudan cevapla — hız önceliklidir.',
|
|
1418
|
-
'Kod/dosya işlerinde: içerik araması grep (regex), dosya adı araması glob, VAR OLAN dosyayı değiştirme edit_file (write_file yalnız yeni dosya/tam yeniden yazım). read_file satır numaralı döner; offset/
|
|
1481
|
+
'Kod/dosya işlerinde: içerik araması grep (regex), dosya adı araması glob, VAR OLAN dosyayı değiştirme edit_file (write_file yalnız yeni dosya/tam yeniden yazım). read_file satır numaralı döner; büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA; önceki okuduğun içerik + kendi değişikliklerin üzerinden devam et.',
|
|
1419
1482
|
'Kullanıcı bir tarihte/saatte hatırlatılmasını isterse set_reminder kullan; when değerini ORTAMdaki bugüne göre hesapla (yerel saat). "Her sabah/gün/hafta" gibi tekrarlı isteklerde repeat alanını da ver (daily/weekly/monthly/weekdays veya cron).',
|
|
1420
1483
|
'Kullanıcı kalıcı bir arka plan takibi isterse (fiyat eşiği, pil seviyesi, sayfa değişikliği) watcher_add ile izleyici kur; kurduktan sonra watcher_list ile doğrula ve kullanıcıya koşulu + kontrol sıklığını kısaca bildir.',
|
|
1421
1484
|
'Anlık olay takipleri için (yeni mail, fiyat eşiği, dosya değişimi, webhook) event_subscribe kullan — cron/polling gerekmez; listeyi event_list ile göster, vazgeçirirse event_unsubscribe.',
|
|
@@ -1555,8 +1618,15 @@ class Engine {
|
|
|
1555
1618
|
});
|
|
1556
1619
|
for (let k = 0; k < flat.length; k++) {
|
|
1557
1620
|
const m = flat[k];
|
|
1558
|
-
if (m.role
|
|
1559
|
-
|
|
1621
|
+
if (m.role !== 'tool') continue;
|
|
1622
|
+
const tooLong = String(m.content || '').length > TOOL_OUT_KEEP * 2;
|
|
1623
|
+
/* diffView UI-only metadata'dır — sağlayıcıya GİTMEZ (bilinmeyen alan
|
|
1624
|
+
reddi + token israfı önlenir); kırpma ile aynı kopyada birleştirilir */
|
|
1625
|
+
if (tooLong || m.diffView) {
|
|
1626
|
+
const rest = { ...m };
|
|
1627
|
+
delete rest.diffView;
|
|
1628
|
+
if (tooLong) rest.content = String(m.content).slice(0, TOOL_OUT_KEEP) + '\n…[kırpıldı]';
|
|
1629
|
+
flat[k] = rest;
|
|
1560
1630
|
}
|
|
1561
1631
|
}
|
|
1562
1632
|
/* opencode compaction replay (message-v2.ts:521-572): özet, system'den
|
|
@@ -1816,7 +1886,10 @@ class Engine {
|
|
|
1816
1886
|
if (pruned <= PRUNE_MINIMUM) return 0;
|
|
1817
1887
|
const marker = '[eski araç çıktısı temizlendi — özet ve son çıktılar yeterli]';
|
|
1818
1888
|
const ids = new Map(targets.map((m) => [m.tool_call_id, marker]));
|
|
1819
|
-
for (const m of targets)
|
|
1889
|
+
for (const m of targets) {
|
|
1890
|
+
m.content = marker;
|
|
1891
|
+
delete m.diffView; /* prune edilen çıktının UI diff'i de düşer */
|
|
1892
|
+
}
|
|
1820
1893
|
try {
|
|
1821
1894
|
const file = this._file(session.id);
|
|
1822
1895
|
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
@@ -1826,6 +1899,7 @@ class Engine {
|
|
|
1826
1899
|
try { r = JSON.parse(lines[i]); } catch { continue; }
|
|
1827
1900
|
if (r.t === 'msg' && r.role === 'tool' && ids.has(r.tool_call_id)) {
|
|
1828
1901
|
r.content = marker;
|
|
1902
|
+
delete r.diffView;
|
|
1829
1903
|
lines[i] = JSON.stringify({ t: 'msg', ...r });
|
|
1830
1904
|
}
|
|
1831
1905
|
}
|
|
@@ -2006,6 +2080,27 @@ class Engine {
|
|
|
2006
2080
|
return true;
|
|
2007
2081
|
}
|
|
2008
2082
|
|
|
2083
|
+
/* Paralel ajan GEÇMİŞİNİ topluca sil: çalışanlar/ bekleyenler iptal edilir,
|
|
2084
|
+
tüm bg oturum dosyaları + kalıcı kayıt temizlenir. Döndürür: silinen iş sayısı */
|
|
2085
|
+
clearAllBgJobs() {
|
|
2086
|
+
let n = 0;
|
|
2087
|
+
for (const id of [...this._bgJobs.keys()]) {
|
|
2088
|
+
try {
|
|
2089
|
+
const j = this._bgJobs.get(id);
|
|
2090
|
+
if (j && (j.status === 'running' || j.status === 'queued')) {
|
|
2091
|
+
this._abortReasons = this._abortReasons || new Map();
|
|
2092
|
+
this._abortReasons.set(id, 'paralel ajan geçmişi topluca temizlendi');
|
|
2093
|
+
const c = this.ctrls.get(id);
|
|
2094
|
+
if (c) c.abort();
|
|
2095
|
+
}
|
|
2096
|
+
if (this.deleteSession(id)) n++;
|
|
2097
|
+
} catch {}
|
|
2098
|
+
}
|
|
2099
|
+
try { fs.unlinkSync(this._bgJobsFile); } catch {}
|
|
2100
|
+
this._bgEmit();
|
|
2101
|
+
return n;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2009
2104
|
/* #14 paralel ajanlar: ANA oturumu hiç bloklamayan arka plan çalıştırıcı.
|
|
2010
2105
|
Ayrı gizli oturum açar, işi orada koşturur; bitince özet bildirim
|
|
2011
2106
|
istenen sohbete düşer. Birden fazla iş aynı anda paralel koşabilir —
|
|
@@ -2019,6 +2114,11 @@ class Engine {
|
|
|
2019
2114
|
const bg = this._load(this.createSession().id);
|
|
2020
2115
|
bg.messages = bg.messages || [];
|
|
2021
2116
|
bg.bgJob = true; // arka plan oturumu: auto-memory/kalibrasyon kapalı
|
|
2117
|
+
/* opencode agent port: iş özel ajana bağlıysa prompt/model/araç/steps ondan */
|
|
2118
|
+
if (opts.agent) {
|
|
2119
|
+
const adef = agentdefs.get(String(opts.agent));
|
|
2120
|
+
if (adef && (adef.mode === 'bg' || adef.mode === 'all')) bg.agentName = adef.name;
|
|
2121
|
+
}
|
|
2022
2122
|
this.cache.set(bg.id, bg);
|
|
2023
2123
|
const ttl = String(title || t).replace(/\s+/g, ' ').trim().slice(0, 80) || 'arka plan görevi';
|
|
2024
2124
|
/* #17 cache'teki nesneye de işaret koy — listSessions temiz başlık göstersin */
|
|
@@ -2030,12 +2130,16 @@ class Engine {
|
|
|
2030
2130
|
this._file(bg.id),
|
|
2031
2131
|
JSON.stringify({ t: 'meta2', bgOf: parent, title: ttl, at: new Date().toISOString() }) + '\n'
|
|
2032
2132
|
);
|
|
2133
|
+
if (bg.agentName) {
|
|
2134
|
+
fs.appendFileSync(this._file(bg.id), JSON.stringify({ t: 'agent', name: bg.agentName, at: nowIso() }) + '\n');
|
|
2135
|
+
}
|
|
2033
2136
|
} catch {}
|
|
2034
2137
|
this._bgJobs.set(bg.id, {
|
|
2035
2138
|
id: bg.id,
|
|
2036
2139
|
code: bg.code,
|
|
2037
2140
|
title: ttl,
|
|
2038
2141
|
task: t.slice(0, 500),
|
|
2142
|
+
agent: bg.agentName || null,
|
|
2039
2143
|
parentId: parent,
|
|
2040
2144
|
groupId: opts.groupId || null,
|
|
2041
2145
|
status: 'running', // queued | running | done | error | aborted
|
|
@@ -2120,7 +2224,10 @@ class Engine {
|
|
|
2120
2224
|
for (const item of list) {
|
|
2121
2225
|
const task = String((item && item.task) || item || '').trim();
|
|
2122
2226
|
if (!task) continue;
|
|
2123
|
-
const r = this.runBackground(parent, task, (item && item.title) || `${groupTitle || 'grup'} #${++n}`, {
|
|
2227
|
+
const r = this.runBackground(parent, task, (item && item.title) || `${groupTitle || 'grup'} #${++n}`, {
|
|
2228
|
+
groupId,
|
|
2229
|
+
agent: (item && item.agent) || null,
|
|
2230
|
+
});
|
|
2124
2231
|
if (r.ok) ids.push(r.backgroundId);
|
|
2125
2232
|
}
|
|
2126
2233
|
if (!ids.length) {
|
|
@@ -2548,10 +2655,14 @@ class Engine {
|
|
|
2548
2655
|
→ her turda çok daha az prompt tokeni = hızlı ön-bellek + düşük maliyet */
|
|
2549
2656
|
buildBgSystem(session) {
|
|
2550
2657
|
const job = (session && this._bgJobs.get(String(session.id))) || {};
|
|
2658
|
+
/* opencode instruction port (bg ajanlar): workspace talimatları + genel ajan disiplini */
|
|
2659
|
+
const proj = this._projectInstructions(session);
|
|
2551
2660
|
return (
|
|
2552
2661
|
`Sen odaklı bir ARKA PLAN ajanısın. Tek görevini baştan sona bitir.\n` +
|
|
2553
2662
|
(job.task ? `GÖREV: ${job.task}\n` : '') +
|
|
2554
2663
|
`Ortam: Windows + PowerShell; çalışma klasörü: ${this.workspace}\n` +
|
|
2664
|
+
`GENEL AJAN DİSİPLİNİ: görevi A'dan Z'ye yürüt — bağlam topla, uygula, DOĞRULA; yarım bırakma; doğrulama sonucunu rapora yaz.\n` +
|
|
2665
|
+
(proj ? `PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT — daima uy):\n${proj}\n` : '') +
|
|
2555
2666
|
`HIZ KURALLARI:\n` +
|
|
2556
2667
|
`- Döngülü işleri (çok URL/dosya/sayfa, tekrarlı parse-hesap) TEK python_run betiğinde topluca bitir.\n` +
|
|
2557
2668
|
`- Web için web_search kullan (zincir dahili tarayıcıyla başlar — gerçek Chromium ile Google); tek aramada bulunamazsa veya çok kaynaklı derin araştırma gerekiyorsa deep_search kullan (çoklu sorgu paralel + gizli tarayıcıda tam sayfa okuma). Sayfa açma/göstermenin VARSAYILANI DAHİLİ tarayıcıdır: browser_open → browser_snapshot → browser_click/type/read. Kullanıcı açıkça dış tarayıcı (chrome/firefox/başka/normal/kendi tarayıcım) istediyse run_command ile \`start "" <url>\` çalıştır. Görseli göremiyorsan metni ocr_read ile oku (source:"browser").\n` +
|
|
@@ -2592,6 +2703,8 @@ class Engine {
|
|
|
2592
2703
|
}
|
|
2593
2704
|
|
|
2594
2705
|
async _chatTurn(session, signal, onDelta, toolsList = TOOLS) {
|
|
2706
|
+
/* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
|
|
2707
|
+
const adef = this._agentDefFor(session, !!session.bgJob);
|
|
2595
2708
|
/* Beast Code: todo_write açıklaması "3+ adım" kısıtı içerir ve model küçük
|
|
2596
2709
|
işlerde atlar — bcCode oturumunda HER işte İLK araç olacak şekilde değiştir */
|
|
2597
2710
|
if (session && session.bcCode) {
|
|
@@ -2639,6 +2752,11 @@ class Engine {
|
|
|
2639
2752
|
/* opencode plan agent: salt-okur zorlaması — araç seti GERÇEKten daralır */
|
|
2640
2753
|
activeTools = activeTools.filter((t) => PLAN_ALLOW_TOOLS.has(t.function.name));
|
|
2641
2754
|
}
|
|
2755
|
+
if (adef && adef.tools) {
|
|
2756
|
+
/* özel ajan araç beyaz listesi — tanımda olmayan araç görünmez */
|
|
2757
|
+
const allow = new Set(adef.tools);
|
|
2758
|
+
activeTools = activeTools.filter((t) => allow.has(t.function.name));
|
|
2759
|
+
}
|
|
2642
2760
|
if (perms.length === 1 && perms[0] === 'chat') {
|
|
2643
2761
|
system +=
|
|
2644
2762
|
'\n\n# KISITLI MOD\nTüm araçların (komut, dosya, web, tarayıcı, hafıza) kapalı. Sadece yazarak cevap ver. ' +
|
|
@@ -2659,6 +2777,9 @@ class Engine {
|
|
|
2659
2777
|
`\n\n# OTURUM NOTLARI (oturum ${session.code || '?'} — bu oturumun önceki konuşma özeti; birebir geçmişi buradan hatırla)\n` +
|
|
2660
2778
|
session.notes;
|
|
2661
2779
|
}
|
|
2780
|
+
if (adef && adef.prompt) {
|
|
2781
|
+
system += `\n\n# AJAN: ${adef.name}\n${adef.prompt}`;
|
|
2782
|
+
}
|
|
2662
2783
|
/* önek-cache disiplini (opencode request.ts:184): araç sırası oturum
|
|
2663
2784
|
boyunca sabit olmalı — alfabetik sıralama sağlayıcı önbelleğini bozmaz */
|
|
2664
2785
|
activeTools = [...activeTools].sort((a, b) =>
|
|
@@ -2680,6 +2801,11 @@ class Engine {
|
|
|
2680
2801
|
const ssel = this.sessionModel.get(String(session.id));
|
|
2681
2802
|
if (ssel) sel = ssel;
|
|
2682
2803
|
}
|
|
2804
|
+
/* özel ajan model override — en son söz ajan tanımının */
|
|
2805
|
+
if (adef && adef.model) {
|
|
2806
|
+
const am = this._resolveRole(String(adef.model));
|
|
2807
|
+
if (am) sel = am;
|
|
2808
|
+
}
|
|
2683
2809
|
if (role && this.roleModels[role]) {
|
|
2684
2810
|
emitSafe(this, session.id, { type: 'status', status: `rol: ${role} → ${sel.providerName} · ${sel.model}` });
|
|
2685
2811
|
}
|
|
@@ -2786,14 +2912,17 @@ class Engine {
|
|
|
2786
2912
|
let wrapNudged = false; // tur limitine yaklaşınca zarif kapanış (bg ajanlar)
|
|
2787
2913
|
let usageTotal = 0; // en yüksek görülen total_tokens — compaction tetiği
|
|
2788
2914
|
let compacted = false; // run başına en fazla 1 compaction
|
|
2915
|
+
/* opencode agent.steps port: özel ajanın kendi tur limiti (2-60) */
|
|
2916
|
+
const runDef = this._agentDefFor(session, !!session.bgJob);
|
|
2917
|
+
const maxTurns = Math.max(2, Math.min(60, (runDef && runDef.steps) || MAX_TURNS));
|
|
2789
2918
|
const recentSigs = []; // doom-loop dedektörü: son araç imzaları
|
|
2790
2919
|
/* Beast Code canlı önizleme: run boyunca üretilen eserleri topla —
|
|
2791
2920
|
iş bitince dahili tarayıcıda CANLI açılır (site/dev server/HTML) */
|
|
2792
2921
|
const bcArtifacts = session.bcCode ? { html: [], serverUrl: null } : null;
|
|
2793
|
-
for (let turn = 0; turn <
|
|
2922
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
2794
2923
|
/* SÜRE SINIRI YOK — ama sonsuz tur da yok: bg ajan son 3 tura gelirken
|
|
2795
|
-
"raporu yaz ve bitir" uyarısı alır;
|
|
2796
|
-
if (session.bgJob && turn ===
|
|
2924
|
+
"raporu yaz ve bitir" uyarısı alır; maxTurns sert tavan olarak kalır. */
|
|
2925
|
+
if (session.bgJob && turn === maxTurns - 3 && !wrapNudged) {
|
|
2797
2926
|
wrapNudged = true;
|
|
2798
2927
|
const wmsg = {
|
|
2799
2928
|
role: 'user',
|
|
@@ -2808,7 +2937,7 @@ class Engine {
|
|
|
2808
2937
|
}
|
|
2809
2938
|
/* opencode port (prompt.ts:1281): ana oturumda son turda zorunlu
|
|
2810
2939
|
nihai cevap — tur limiti sessizce dolup boş 'done' dönmesin */
|
|
2811
|
-
if (!session.bgJob && turn ===
|
|
2940
|
+
if (!session.bgJob && turn === maxTurns - 1 && !wrapNudged) {
|
|
2812
2941
|
wrapNudged = true;
|
|
2813
2942
|
const wmsg = {
|
|
2814
2943
|
role: 'user',
|
|
@@ -2968,20 +3097,28 @@ class Engine {
|
|
|
2968
3097
|
}
|
|
2969
3098
|
// Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
|
|
2970
3099
|
let injectedImage = null;
|
|
3100
|
+
/* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
|
|
3101
|
+
bağlamından AYRI tutulur — yalnız UI'ye gider (kırmızı/yeşil diff) */
|
|
3102
|
+
let diffView = null;
|
|
2971
3103
|
try {
|
|
2972
3104
|
const obj = JSON.parse(out);
|
|
2973
3105
|
if (obj && obj.__injectImage) {
|
|
2974
3106
|
injectedImage = String(obj.__injectImage);
|
|
2975
3107
|
delete obj.__injectImage;
|
|
2976
|
-
out = JSON.stringify(obj);
|
|
2977
3108
|
}
|
|
3109
|
+
if (obj && obj.diffView) {
|
|
3110
|
+
diffView = obj.diffView;
|
|
3111
|
+
delete obj.diffView;
|
|
3112
|
+
}
|
|
3113
|
+
if (injectedImage || diffView) out = JSON.stringify(obj);
|
|
2978
3114
|
} catch {}
|
|
2979
3115
|
const toolMsg = { role: 'tool', tool_call_id: tc.id, name, content: out.slice(0, TOOL_OUT_KEEP * 6) };
|
|
3116
|
+
if (diffView) toolMsg.diffView = diffView; /* oturum dosyasına da düşer — geçmiş açılınca diff yeniden çizilir */
|
|
2980
3117
|
session.messages.push(toolMsg);
|
|
2981
3118
|
try {
|
|
2982
3119
|
this._append(session, toolMsg);
|
|
2983
3120
|
} catch {}
|
|
2984
|
-
emit({ type: 'tool-end', callId: tc.id, ok: !/"error"/.test(out.slice(0, 200)), result: out.slice(0, 4000) });
|
|
3121
|
+
emit({ type: 'tool-end', callId: tc.id, ok: !/"error"/.test(out.slice(0, 200)), result: out.slice(0, 4000), diff: diffView });
|
|
2985
3122
|
if (injectedImage) {
|
|
2986
3123
|
const imgMsg = {
|
|
2987
3124
|
role: 'user',
|
|
@@ -2998,7 +3135,7 @@ class Engine {
|
|
|
2998
3135
|
);
|
|
2999
3136
|
}
|
|
3000
3137
|
emit({ type: 'done', usage: null });
|
|
3001
|
-
this._bgFinish(sid, 'error', 'tur limiti doldu (
|
|
3138
|
+
this._bgFinish(sid, 'error', 'tur limiti doldu (maxTurns)'); } catch (e) {
|
|
3002
3139
|
const aborted = e && (e.name === 'AbortError' || ctrl.signal.aborted);
|
|
3003
3140
|
if (aborted) {
|
|
3004
3141
|
this._clearCrash(); // kullanıcı durdurdu — kurtarma yok
|
|
@@ -3449,7 +3586,9 @@ class Engine {
|
|
|
3449
3586
|
});
|
|
3450
3587
|
}
|
|
3451
3588
|
if (name === 'ocr_read') return JSON.stringify(await this._ocrRead(args || {}, signal));
|
|
3452
|
-
|
|
3589
|
+
/* opencode general-agent portu: alt-ajan da edit/grep/glob kullanır */
|
|
3590
|
+
if (!(name === 'run_command' || name === 'read_file' || name === 'write_file' || name === 'edit_file' ||
|
|
3591
|
+
name === 'list_dir' || name === 'grep' || name === 'glob' ||
|
|
3453
3592
|
name === 'web_search' || name === 'http_fetch' || name === 'python_run')) {
|
|
3454
3593
|
return JSON.stringify({ ok: false, error: `unknown tool ${name}` });
|
|
3455
3594
|
}
|
|
@@ -3461,8 +3600,9 @@ class Engine {
|
|
|
3461
3600
|
|
|
3462
3601
|
/* ---------- tool dispatch ---------- */
|
|
3463
3602
|
|
|
3464
|
-
/* Onay gerektiren araçlar — tepkiyle onay kapısına takılır
|
|
3465
|
-
|
|
3603
|
+
/* Onay gerektiren araçlar — tepkiyle onay kapısına takılır.
|
|
3604
|
+
opencode'da edit+write aynı "edit" iznine takılır — burada da ikisi birlikte */
|
|
3605
|
+
static RISKY_TOOLS = new Set(['run_command', 'write_file', 'edit_file', 'python_run', 'email_send', 'watcher_add']);
|
|
3466
3606
|
|
|
3467
3607
|
/* ANA KOD KİLİDİ: yıkıcı işlem desenleri (korumalı yol + bu desen = engel) */
|
|
3468
3608
|
static DESTRUCTIVE_RE =
|
|
@@ -3498,6 +3638,7 @@ class Engine {
|
|
|
3498
3638
|
'Okumak serbesttir; kod yalnızca kullanıcının kendi eliyle, dışarıdan değiştirilebilir.',
|
|
3499
3639
|
});
|
|
3500
3640
|
if (name === 'write_file' && this._isProtectedPath(args.path)) return deny('yazma');
|
|
3641
|
+
if (name === 'edit_file' && this._isProtectedPath(args.path)) return deny('düzenleme');
|
|
3501
3642
|
if (name === 'run_command' && this._cmdTouchesProtected(String(args.command || ''))) return deny('komutla değiştirme');
|
|
3502
3643
|
if (name === 'python_run' && this._cmdTouchesProtected(String(args.code || ''))) return deny('betikle değiştirme');
|
|
3503
3644
|
return null;
|
|
@@ -3599,7 +3740,15 @@ class Engine {
|
|
|
3599
3740
|
return JSON.stringify(r);
|
|
3600
3741
|
}
|
|
3601
3742
|
if (name === 'run_background') {
|
|
3602
|
-
|
|
3743
|
+
if (args.agent && !agentdefs.get(String(args.agent))) {
|
|
3744
|
+
return JSON.stringify({
|
|
3745
|
+
ok: false,
|
|
3746
|
+
error:
|
|
3747
|
+
`ajan tanımı bulunamadı: ${args.agent} — tanımlı ajanlar: ` +
|
|
3748
|
+
(agentdefs.list().map((d) => d.name).join(', ') || '(yok)'),
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
const r = this.runBackground(sessionId, args.task, args.title, { agent: args.agent ? String(args.agent) : null });
|
|
3603
3752
|
return JSON.stringify({
|
|
3604
3753
|
...r,
|
|
3605
3754
|
note: 'paralel ajan başladı — ana sohbet açık; bittiğinde özet otomatik gelir',
|
|
@@ -3608,7 +3757,7 @@ class Engine {
|
|
|
3608
3757
|
if (name === 'run_background_many') {
|
|
3609
3758
|
const rawList = Array.isArray(args.tasks) ? args.tasks : [];
|
|
3610
3759
|
const tasks = rawList
|
|
3611
|
-
.map((t) => ({ title: String((t && t.title) || ''), task: String((t && t.task) || t || '') }))
|
|
3760
|
+
.map((t) => ({ title: String((t && t.title) || ''), task: String((t && t.task) || t || ''), agent: (t && t.agent) ? String(t.agent) : null }))
|
|
3612
3761
|
.filter((t) => t.task.trim());
|
|
3613
3762
|
const r = tasks.length
|
|
3614
3763
|
? this.runBackgroundMany(sessionId, tasks, String(args.title || ''))
|
|
@@ -3809,11 +3958,14 @@ class Engine {
|
|
|
3809
3958
|
const r = await this.browser.act(name.slice(8), args, signal, { sessionId });
|
|
3810
3959
|
return JSON.stringify(r);
|
|
3811
3960
|
}
|
|
3812
|
-
|
|
3961
|
+
/* opencode tool registry portu: edit_file/grep/glob ana ajanın da araçları —
|
|
3962
|
+
bunlar olmadan model write_file + tam dosya okuma döngüsüne düşer */
|
|
3963
|
+
if (!(name === 'run_command' || name === 'read_file' || name === 'write_file' || name === 'edit_file' ||
|
|
3964
|
+
name === 'list_dir' || name === 'grep' || name === 'glob' ||
|
|
3813
3965
|
name === 'web_search' || name === 'http_fetch' || name === 'python_run')) {
|
|
3814
3966
|
return JSON.stringify({ ok: false, error: `unknown tool ${name}` });
|
|
3815
3967
|
}
|
|
3816
|
-
return await tools.exec(name, args, { cwd: this._sessionWorkspace(sessionId), signal });
|
|
3968
|
+
return await tools.exec(name, args, { cwd: this._sessionWorkspace(sessionId), signal, wantDiff: true });
|
|
3817
3969
|
} catch (e) {
|
|
3818
3970
|
return JSON.stringify({ ok: false, error: String((e && e.message) || e) });
|
|
3819
3971
|
}
|
|
@@ -4178,6 +4330,7 @@ const TOOLS = [
|
|
|
4178
4330
|
properties: {
|
|
4179
4331
|
task: { type: 'string', description: 'Self-contained task description with ALL needed context (paths, URLs, constraints, expected output) — the agent cannot see your context.' },
|
|
4180
4332
|
title: { type: 'string', description: 'Short label shown in the agents tab and notification.' },
|
|
4333
|
+
agent: { type: 'string', description: 'Optional custom agent name (defined in %APPDATA%\\beast\\agents\\*.md) — that agent\u2019s prompt/model/tools/step-limit apply.' },
|
|
4181
4334
|
},
|
|
4182
4335
|
required: ['task'],
|
|
4183
4336
|
},
|
|
@@ -4210,6 +4363,7 @@ const TOOLS = [
|
|
|
4210
4363
|
properties: {
|
|
4211
4364
|
title: { type: 'string', description: 'Short label of this step' },
|
|
4212
4365
|
task: { type: 'string', description: 'Self-contained instruction with all context' },
|
|
4366
|
+
agent: { type: 'string', description: 'Optional custom agent name for this step (%APPDATA%\\beast\\agents\\*.md)' },
|
|
4213
4367
|
},
|
|
4214
4368
|
required: ['task'],
|
|
4215
4369
|
},
|
package/src/agent/skills.js
CHANGED
|
@@ -125,6 +125,7 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
|
|
|
125
125
|
| \`agent/engine.js\` | OTURUM MOTORU: send/_run tur döngüsü, paralel ajanlar (run_background, run_background_many, tasks_list, task_status, task_cancel — task_cancel reason ZORUNLU), superyorizon (takılan işi zorla kapatır), iptal sebep disiplini, flushPendingReports |
|
|
126
126
|
| \`agent/tools.js\` | Araçlar + web_search SIRALI ZİNCİRİ (searchChain: 1 dahili tarayıcı(Google) → 2 Obscura(stealth→DuckDuckGo) → 3 TinyFish(anahtarsa) → 4 python çoklu-motor; sıra Ayarlar→Web Arama'dan değişir), run_command, read/write_file, python_run, http_fetch, deep_search |
|
|
127
127
|
| \`agent/llm.js\` | Sağlayıcı çağrısı + retry: ağ kopmasında 6 deneme + internet dönene kadar bekler; akış ortası kopmada kaldığı yerden devam |
|
|
128
|
+
| \`agent/agentdefs.js\` | Özel ajan tanımları: %APPDATA%\beast\agents\\*.md (frontmatter: model/tools/steps/mode + prompt gövdesi); /agent ile bağlanır, run_background(agent:) ile paralel ajana verilir |
|
|
128
129
|
| \`agent/obscura.js\` | Obscura: otomatik kurulum (GitHub releases, stealth zip) + DuckDuckGo html/lite arama ayrıştırıcı |
|
|
129
130
|
| \`agent/bots.js\` | Bot CRUD, 5 haneli bot kodu, izolasyon; ad-ilk-harf doğrulaması |
|
|
130
131
|
| \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading) + SKILL.md taraması + taslaklar |
|