beast-agent 0.30.0 → 1.0.1
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 +8 -3
- package/src/agent/engine.js +463 -21
- package/src/agent/llm.js +52 -3
- package/src/agent/obscura.js +26 -10
- package/src/agent/skills.js +2 -2
- package/src/agent/tools.js +519 -7
- package/src/main.js +28 -13
- package/src/renderer/i18n.js +7 -7
- package/src/renderer/index.html +5 -2
- package/src/renderer/renderer.js +154 -5
- package/src/renderer/style.css +45 -4
- package/.env.example +0 -8
- package/config.example.yaml +0 -39
- package/eng.traineddata +0 -0
- package/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
- package/store/skills.json +0 -5
- package/tests/approval.test.js +0 -56
- package/tests/bg-jobs.test.js +0 -424
- package/tests/bots-name.test.js +0 -42
- package/tests/bus.test.js +0 -46
- package/tests/computeruse-context.test.js +0 -69
- package/tests/cron.test.js +0 -95
- package/tests/engine.test.js +0 -203
- package/tests/eval.test.js +0 -91
- package/tests/fallout.test.js +0 -188
- package/tests/llm.test.js +0 -205
- package/tests/memory.test.js +0 -33
- package/tests/memoryloop.test.js +0 -26
- package/tests/notegen.test.js +0 -63
- package/tests/obscura.test.js +0 -124
- package/tests/owner.test.js +0 -32
- package/tests/python.test.js +0 -76
- package/tests/research.test.js +0 -116
- package/tests/scenarios.json +0 -81
- package/tests/sessioncode.test.js +0 -50
- package/tests/setup.js +0 -9
- package/tests/tokens.test.js +0 -42
- package/tests/tools.test.js +0 -84
- package/tests/usage.test.js +0 -44
- package/tests/v11.test.js +0 -76
- package/tests/watchers.test.js +0 -206
- package/tests/watext.test.js +0 -53
- package/tests/whatsapp.test.js +0 -103
- package/tests/wherewasi.test.js +0 -40
package/src/agent/engine.js
CHANGED
|
@@ -63,6 +63,38 @@ const THINK_LEVELS = [
|
|
|
63
63
|
{ v: 5, label: 'Max', effort: 'max' },
|
|
64
64
|
];
|
|
65
65
|
|
|
66
|
+
/* ---------- opencode motoru port: bağlam bütçesi + compaction + prune ----------
|
|
67
|
+
Kaynak: opencode-dev/packages/opencode/src/session/{overflow,compaction}.ts
|
|
68
|
+
Sabitler birebir alınmıştır (COMPACTION_BUFFER 20k, PRUNE_PROTECT 40k,
|
|
69
|
+
PRUNE_MINIMUM 20k, DOOM_LOOP 3, özet çıktı kırpma 2000 karakter). */
|
|
70
|
+
const COMPACTION_BUFFER = 20000; // overflow.ts:8 — özet turu için ayrılan pay
|
|
71
|
+
const OUTPUT_TOKEN_MAX = 32000; // transform.ts:18 — çıktı tavanı (rezerv hesabı)
|
|
72
|
+
const PRUNE_PROTECT = 40000; // compaction.ts:29 — en yeni araç çıktısı koruması
|
|
73
|
+
const PRUNE_MINIMUM = 20000; // compaction.ts:28 — bu kadar kazanç yoksa dokunma
|
|
74
|
+
const SUMMARY_OUT_KEEP = 2000; // compaction.ts:30 — özetteki araç çıktısı kırpma
|
|
75
|
+
const MIN_PRESERVE_RECENT = 2000; // compaction.ts:32 — korunan kuyruk alt sınırı
|
|
76
|
+
const MAX_PRESERVE_RECENT = 15000; // compaction.ts:33 — korunan kuyruk üst sınırı
|
|
77
|
+
const DOOM_LOOP_THRESHOLD = 3; // processor.ts:29 — aynı araç+argüman sınırı
|
|
78
|
+
const HISTORY_BUDGET_MAX = 80000; // dinamik bütçe tavanı (cache'siz sağlayıcı koruması)
|
|
79
|
+
|
|
80
|
+
/* Bilinen model ailelerinin bağlam penceresi (models.dev değerleri).
|
|
81
|
+
Eşleşmezse 128k varsayılır — asla gerçek limitin üstünü tahmin etme. */
|
|
82
|
+
const MODEL_CONTEXT_TABLE = [
|
|
83
|
+
[/gemini-[23]/i, 1000000],
|
|
84
|
+
[/gpt-5|gpt-4\.1|gpt-4o|o[134]-/i, 400000],
|
|
85
|
+
[/glm-4\.[67]/i, 200000],
|
|
86
|
+
[/claude/i, 200000],
|
|
87
|
+
[/grok-[34]/i, 256000],
|
|
88
|
+
[/kimi/i, 256000],
|
|
89
|
+
[/qwen.*coder/i, 256000],
|
|
90
|
+
[/deepseek|qwen|glm|llama|mistral|minimax/i, 131072],
|
|
91
|
+
];
|
|
92
|
+
function modelContextOf(sel) {
|
|
93
|
+
const s = String((sel && sel.model) || '');
|
|
94
|
+
for (const [re, ctx] of MODEL_CONTEXT_TABLE) if (re.test(s)) return ctx;
|
|
95
|
+
return 131072;
|
|
96
|
+
}
|
|
97
|
+
|
|
66
98
|
/* Çıktı biçimi kuralı: # ve * karakteri yasak (tüm ajanlar için ortak metin) */
|
|
67
99
|
const FORMAT_RULES =
|
|
68
100
|
'ÇIKTI BİÇİMİ (ZORUNLU):\n' +
|
|
@@ -83,12 +115,21 @@ const PERM_TOOL_SETS = {
|
|
|
83
115
|
read: new Set([
|
|
84
116
|
'web_search', 'http_fetch', 'deep_search',
|
|
85
117
|
'browser_open', 'browser_read', 'browser_snapshot',
|
|
86
|
-
'list_dir', 'read_file',
|
|
118
|
+
'list_dir', 'read_file', 'grep', 'glob',
|
|
87
119
|
]),
|
|
88
120
|
chat: new Set([]), // sadece sohbet
|
|
89
121
|
};
|
|
90
122
|
const PERM_LEVELS = ['all', 'web', 'read', 'chat'];
|
|
91
123
|
|
|
124
|
+
/* opencode plan agent portu: PLAN modu PROMPT düzeyinde değil GERÇEKten
|
|
125
|
+
salt-okurdur — yazma/çalıştırma araçları setten düşer, sadece plan çıkar */
|
|
126
|
+
const PLAN_ALLOW_TOOLS = new Set([
|
|
127
|
+
'read_file', 'list_dir', 'grep', 'glob',
|
|
128
|
+
'web_search', 'http_fetch', 'deep_search',
|
|
129
|
+
'browser_open', 'browser_read', 'browser_snapshot',
|
|
130
|
+
'todo_write', 'memory_search', 'kb_search', 'ocr_read',
|
|
131
|
+
]);
|
|
132
|
+
|
|
92
133
|
/* İzin değerini normalize eder: 'all' → ['all'], 'web' → ['web'],
|
|
93
134
|
'web,read' / ['web','read'] → ['web','read'] (sıra PERM_LEVELS'e göre dizilir).
|
|
94
135
|
Geçersiz değerler atılır; hiçbiri kalmazsa boş dizi döner. */
|
|
@@ -103,7 +144,7 @@ function normalizePerms(p) {
|
|
|
103
144
|
Bunların hepsi run_background ile paralel ajana devredilir — CEO sadece
|
|
104
145
|
konuşur, planlar, emir verir ve takip eder. */
|
|
105
146
|
const CEO_EXEC_TOOLS = new Set([
|
|
106
|
-
'run_command', 'read_file', 'write_file', 'list_dir',
|
|
147
|
+
'run_command', 'read_file', 'write_file', 'edit_file', 'list_dir',
|
|
107
148
|
'python_run',
|
|
108
149
|
'web_search', 'http_fetch', 'deep_search',
|
|
109
150
|
'browser_open', 'browser_read', 'browser_screenshot', 'browser_snapshot',
|
|
@@ -629,7 +670,14 @@ class Engine {
|
|
|
629
670
|
}
|
|
630
671
|
const res = await chatStreamAuto(
|
|
631
672
|
c,
|
|
632
|
-
{
|
|
673
|
+
{
|
|
674
|
+
messages: msgs,
|
|
675
|
+
tools: activeTools,
|
|
676
|
+
reasoningEffort: this._thinkEffortFor(session),
|
|
677
|
+
/* opencode cache disiplini (transform.ts:1270): oturum-sabit
|
|
678
|
+
önbellek anahtarı — destekleyen sağlayıcıda önek cache'i tutar */
|
|
679
|
+
cacheKey: String(session.id || ''),
|
|
680
|
+
},
|
|
633
681
|
{
|
|
634
682
|
signal,
|
|
635
683
|
onDelta,
|
|
@@ -929,6 +977,10 @@ class Engine {
|
|
|
929
977
|
} else if (rec.t === 'notes') {
|
|
930
978
|
session.notes = String(rec.text || '');
|
|
931
979
|
session.notesAt = Number(rec.at) || 0;
|
|
980
|
+
} else if (rec.t === 'summary') {
|
|
981
|
+
/* opencode compaction: konuşma özeti — birebir geçmişin yerine geçmez,
|
|
982
|
+
payload'da system'den sonra sabit user mesajı olarak girer */
|
|
983
|
+
if (rec.text) session.summary = String(rec.text);
|
|
932
984
|
} else if (rec.t === 'botdm') {
|
|
933
985
|
/* botlar arası DM oturumu — admin izleyebilir, sidebar'da görünmez */
|
|
934
986
|
session.isBotDm = true;
|
|
@@ -1129,6 +1181,7 @@ class Engine {
|
|
|
1129
1181
|
s.messages = [];
|
|
1130
1182
|
delete s.notes;
|
|
1131
1183
|
delete s.notesAt;
|
|
1184
|
+
delete s.summary; // özet de sıfırlanır — taze başlangıç
|
|
1132
1185
|
this.todos.set(sid, []);
|
|
1133
1186
|
this.emit({ type: 'sessions' });
|
|
1134
1187
|
return true;
|
|
@@ -1224,6 +1277,30 @@ class Engine {
|
|
|
1224
1277
|
|
|
1225
1278
|
/* ---------- prompt assembly (the frugal part) ---------- */
|
|
1226
1279
|
|
|
1280
|
+
/* opencode instruction.ts port: workspace'teki proje talimatları (AGENTS.md,
|
|
1281
|
+
CLAUDE.md, CONTEXT.md) Beast Code system promptuna girer. Oturum başına
|
|
1282
|
+
BİR KEZ okunup session'da saklanır — system promptu epoch boyunca sabit
|
|
1283
|
+
kalır, önek cache bozulmaz (dosya sonradan değişse bile). */
|
|
1284
|
+
_projectInstructions(session) {
|
|
1285
|
+
if (!session) return '';
|
|
1286
|
+
if (session._projCtx !== undefined) return session._projCtx;
|
|
1287
|
+
let out = '';
|
|
1288
|
+
try {
|
|
1289
|
+
const ws = (session && session.workspace) || this.workspace;
|
|
1290
|
+
for (const name of ['AGENTS.md', 'CLAUDE.md', 'CONTEXT.md']) {
|
|
1291
|
+
let txt = '';
|
|
1292
|
+
try { txt = fs.readFileSync(path.join(ws, name), 'utf8'); } catch {}
|
|
1293
|
+
txt = String(txt || '').trim();
|
|
1294
|
+
if (!txt) continue;
|
|
1295
|
+
out += (out ? '\n\n' : '') + `--- ${name} ---\n` + txt.slice(0, 6000);
|
|
1296
|
+
if (out.length >= 8000) break;
|
|
1297
|
+
}
|
|
1298
|
+
} catch {}
|
|
1299
|
+
out = out.slice(0, 9000);
|
|
1300
|
+
session._projCtx = out;
|
|
1301
|
+
return out;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1227
1304
|
/* Beast Code (IDE paneli): VS Code hızında çalışsın diye SECMET prompt.
|
|
1228
1305
|
OpenCode kodlama disiplini gömülü: bağlam → plan → küçük diff → doğrula.
|
|
1229
1306
|
Hafıza embedding araması, skills taraması, kişilik/kural blokları YOK —
|
|
@@ -1231,7 +1308,9 @@ class Engine {
|
|
|
1231
1308
|
buildBcSystem(session) {
|
|
1232
1309
|
const nowD = new Date();
|
|
1233
1310
|
const localDate = nowD.toLocaleDateString('tr-TR');
|
|
1234
|
-
|
|
1311
|
+
/* önek-cache disiplini (opencode Context Epoch): dakika yerine saat —
|
|
1312
|
+
system prompt her dakika değişse sağlayıcı önbelleği sürekli bozulur */
|
|
1313
|
+
const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
|
|
1235
1314
|
const mode = String((session && session.bcMode) || 'auto').toLowerCase();
|
|
1236
1315
|
const modeBlock =
|
|
1237
1316
|
mode === 'plan'
|
|
@@ -1239,22 +1318,26 @@ class Engine {
|
|
|
1239
1318
|
: mode === 'build'
|
|
1240
1319
|
? 'Ç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.'
|
|
1241
1320
|
: 'ÇALIŞMA MODU: OTOMATİK ⚡ — önce 2-4 satırlık mini plan (todo_write), sonra hemen uygula + doğrula.';
|
|
1321
|
+
const proj = this._projectInstructions(session);
|
|
1242
1322
|
return (
|
|
1243
1323
|
'Sen BEAST CODE\u2019sun — IDE panelinde çalışan, OpenCode disiplinli hızlı bir kodlama ajanı. VS Code gibi çevik ol.\n' +
|
|
1244
1324
|
`Çalışma klasörü: ${(session && session.workspace) || this.workspace}\n` +
|
|
1245
1325
|
`Yerel zaman: ${localDate} ${localTime}\n` +
|
|
1246
1326
|
`${modeBlock}\n` +
|
|
1327
|
+
(proj
|
|
1328
|
+
? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n'
|
|
1329
|
+
: '') +
|
|
1247
1330
|
'WORKFLOW (her işte bu sıra):\n' +
|
|
1248
|
-
'1) BAĞLAM: değiştirmeden önce ilgili dosyaları OKU
|
|
1331
|
+
'1) BAĞLAM: değiştirmeden önce ilgili dosyaları OKU — read_file satır numaralı döner (N: içerik), offset/limit ile büyük dosyayı pencerele; içerik araması için grep (regex), dosya adı araması için glob kullan; varsayım yapma, mevcut stili/konvansiyonu takip et.\n' +
|
|
1249
1332
|
'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' +
|
|
1250
|
-
'3) EDİT:
|
|
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' +
|
|
1251
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' +
|
|
1252
1335
|
'5) RAPOR: 1-3 satır — ne değişti + doğrulama sonucu (ör. "npm test ✓ 154/154"). Uzun açıklama yok.\n' +
|
|
1253
1336
|
'HIZ KURALLARI:\n' +
|
|
1254
1337
|
'- Kodu ve dosyaları DOĞRUDAN write_file ile yaz; basit dosya oluşturma/düzenleme için Python scripti yazma; python_run yalnız gerçek hesap/veri işleme gerekiyorsa.\n' +
|
|
1255
1338
|
'- Bağımsız araç çağrılarını AYNI TURDA paralel ver (birden çok read_file tek turda).\n' +
|
|
1256
1339
|
'- Dosyayı kullanıcı söylememişse list_dir ile yapıyı görüp kendin karar ver.\n' +
|
|
1257
|
-
'- Run_command PowerShell ortamında çalışır (Windows).\n' +
|
|
1340
|
+
'- 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' +
|
|
1258
1341
|
'- Soru sorma, sohbet etme; uygulayıp özetle. Kullanıcı /plan /build /auto ile modu değiştirir.\n' +
|
|
1259
1342
|
FORMAT_RULES
|
|
1260
1343
|
);
|
|
@@ -1303,7 +1386,9 @@ class Engine {
|
|
|
1303
1386
|
/* Beast Code (bcCode) oturumları buildBcSystem kullanır — buraya düşmez */
|
|
1304
1387
|
const nowD = new Date();
|
|
1305
1388
|
const localDate = nowD.toLocaleDateString('tr-TR');
|
|
1306
|
-
|
|
1389
|
+
/* önek-cache disiplini (opencode Context Epoch): dakika yerine saat —
|
|
1390
|
+
system prompt her dakika değişse sağlayıcı önbelleği sürekli bozulur */
|
|
1391
|
+
const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
|
|
1307
1392
|
let tz = '';
|
|
1308
1393
|
try {
|
|
1309
1394
|
tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
|
|
@@ -1329,6 +1414,7 @@ class Engine {
|
|
|
1329
1414
|
'# ARAÇ KURALLARI\n' +
|
|
1330
1415
|
[
|
|
1331
1416
|
'Basit soruları araç kullanmadan doğrudan cevapla — hız önceliklidir.',
|
|
1417
|
+
'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/limit ile pencerele.',
|
|
1332
1418
|
'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).',
|
|
1333
1419
|
'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.',
|
|
1334
1420
|
'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.',
|
|
@@ -1393,7 +1479,34 @@ class Engine {
|
|
|
1393
1479
|
return 0;
|
|
1394
1480
|
}
|
|
1395
1481
|
|
|
1396
|
-
|
|
1482
|
+
/* ---------- opencode port: bağlam matematiği (overflow.ts) ---------- */
|
|
1483
|
+
|
|
1484
|
+
/* Seçili model için özet payı düşülmüş kullanılabilir girdi alanı */
|
|
1485
|
+
_usableContext(sel) {
|
|
1486
|
+
const ctx = modelContextOf(sel);
|
|
1487
|
+
if (!ctx) return 0;
|
|
1488
|
+
/* overflow.ts:14-19: reserved = min(COMPACTION_BUFFER, maxOutput) */
|
|
1489
|
+
const reserved = Math.min(COMPACTION_BUFFER, OUTPUT_TOKEN_MAX);
|
|
1490
|
+
return Math.max(0, ctx - reserved);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/* Dinamik geçmiş bütçesi: model bağlamı genişse 18k sabitinin ÜSTÜNE çıkar
|
|
1494
|
+
(asla altına inmez); cache'siz sağlayıcılarda şişmeyi HISTORY_BUDGET_MAX sınırlar */
|
|
1495
|
+
_historyBudget(sel) {
|
|
1496
|
+
const usable = this._usableContext(sel);
|
|
1497
|
+
if (!usable) return this.historyTokenBudget;
|
|
1498
|
+
return Math.max(this.historyTokenBudget, Math.min(Math.floor(usable * 0.85), HISTORY_BUDGET_MAX));
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/* Geçmiş + sistem + araç şemaları için kaba toplam token tahmini */
|
|
1502
|
+
_contextEstimate(session, sel) {
|
|
1503
|
+
const msgs = session.messages.reduce((a, m) => a + estMsgTokens(m), 0);
|
|
1504
|
+
const toolsCost = TOOLS.length ? estTokens(JSON.stringify(TOOLS)) : 0;
|
|
1505
|
+
return Math.ceil((msgs + toolsCost + 3500) * this.tokRatio); // 3500 ≈ system payı
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/* assistant(tool_calls) + sonuç tool mesajlarını BÖLMEYEN birimler */
|
|
1509
|
+
_msgUnits(messages) {
|
|
1397
1510
|
const units = [];
|
|
1398
1511
|
let i = 0;
|
|
1399
1512
|
while (i < messages.length) {
|
|
@@ -1408,19 +1521,22 @@ class Engine {
|
|
|
1408
1521
|
i++;
|
|
1409
1522
|
}
|
|
1410
1523
|
}
|
|
1411
|
-
|
|
1524
|
+
return units;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
_buildPayload(system, messages, notes, budget, summary) {
|
|
1528
|
+
const units = this._msgUnits(messages);
|
|
1529
|
+
const B = Math.max(2000, Number(budget) || this.historyTokenBudget);
|
|
1412
1530
|
const maxUnits = notes ? NOTES_MAX_UNITS : units.length; // notlar varken pencereyi sıkılaştır
|
|
1413
1531
|
const picked = [];
|
|
1414
1532
|
let used = 0;
|
|
1415
1533
|
for (let u = units.length - 1; u >= 0; u--) {
|
|
1416
1534
|
if (picked.length >= maxUnits) break;
|
|
1417
1535
|
const cost = units[u].reduce(
|
|
1418
|
-
(a, m) =>
|
|
1419
|
-
a +
|
|
1420
|
-
clamp(Math.ceil(estMsgTokens(m) * this.tokRatio), 1, this.historyTokenBudget),
|
|
1536
|
+
(a, m) => a + clamp(Math.ceil(estMsgTokens(m) * this.tokRatio), 1, B),
|
|
1421
1537
|
0
|
|
1422
1538
|
);
|
|
1423
|
-
if (picked.length && used + cost >
|
|
1539
|
+
if (picked.length && used + cost > B) break;
|
|
1424
1540
|
picked.unshift(units[u]);
|
|
1425
1541
|
used += cost;
|
|
1426
1542
|
}
|
|
@@ -1442,7 +1558,20 @@ class Engine {
|
|
|
1442
1558
|
flat[k] = { ...m, content: String(m.content).slice(0, TOOL_OUT_KEEP) + '\n…[kırpıldı]' };
|
|
1443
1559
|
}
|
|
1444
1560
|
}
|
|
1445
|
-
|
|
1561
|
+
/* opencode compaction replay (message-v2.ts:521-572): özet, system'den
|
|
1562
|
+
HEMEN sonra sabit user mesajı olarak girer → önek cache'e uygun
|
|
1563
|
+
(compaction sonrası yalnız eklemeli büyür, system bir daha değişmez) */
|
|
1564
|
+
const head = [];
|
|
1565
|
+
if (summary) {
|
|
1566
|
+
head.push({
|
|
1567
|
+
role: 'user',
|
|
1568
|
+
content:
|
|
1569
|
+
'[ÖNCEKİ KONUŞMANIN ÖZETİ — birebir geçmişi buradan hatırla]\n' +
|
|
1570
|
+
summary +
|
|
1571
|
+
'\n[Konuşma aşağıdaki mesajlarla DEVAM EDİYOR]',
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
return [{ role: 'system', content: system }, ...head, ...flat];
|
|
1446
1575
|
}
|
|
1447
1576
|
|
|
1448
1577
|
_lastUserText(session) {
|
|
@@ -1505,6 +1634,7 @@ class Engine {
|
|
|
1505
1634
|
if (seen++ >= keepFrom) msgLines.push(line);
|
|
1506
1635
|
}
|
|
1507
1636
|
out.push(...msgLines);
|
|
1637
|
+
out.push(JSON.stringify({ t: 'summary', text: session.summary || '', at: 0 }));
|
|
1508
1638
|
out.push(JSON.stringify({ t: 'compacted', at: nowIso(), droppedBefore: keepFrom, noteBased: true }));
|
|
1509
1639
|
const tmp = file + '.tmp';
|
|
1510
1640
|
fs.writeFileSync(tmp, out.join('\n') + '\n');
|
|
@@ -1512,6 +1642,202 @@ class Engine {
|
|
|
1512
1642
|
} catch {}
|
|
1513
1643
|
}
|
|
1514
1644
|
|
|
1645
|
+
/* ---------- opencode port: gerçek compaction + prune (compaction.ts) ---------- */
|
|
1646
|
+
|
|
1647
|
+
/* Head bölgesini düz metne serileştirir (compaction.ts:54-85 port).
|
|
1648
|
+
Araç çıktıları SUMMARY_OUT_KEEP ile kırpılır — özet çağrısı küçük kalır. */
|
|
1649
|
+
_serializeHead(msgs) {
|
|
1650
|
+
const lines = [];
|
|
1651
|
+
for (const m of msgs) {
|
|
1652
|
+
if (m.role === 'user') {
|
|
1653
|
+
lines.push('[Kullanıcı]: ' + this._plainText(m).slice(0, 1500));
|
|
1654
|
+
} else if (m.role === 'assistant') {
|
|
1655
|
+
const txt = this._plainText(m).trim();
|
|
1656
|
+
if (txt) lines.push('[Asistan]: ' + txt.slice(0, 1200));
|
|
1657
|
+
for (const tc of m.tool_calls || []) {
|
|
1658
|
+
let args = '';
|
|
1659
|
+
try {
|
|
1660
|
+
args = JSON.stringify(JSON.parse((tc.function && tc.function.arguments) || '{}'));
|
|
1661
|
+
} catch {
|
|
1662
|
+
args = String((tc.function && tc.function.arguments) || '');
|
|
1663
|
+
}
|
|
1664
|
+
lines.push(`[Araç çağrısı]: ${tc.function && tc.function.name}(${args.slice(0, 800)})`);
|
|
1665
|
+
}
|
|
1666
|
+
} else if (m.role === 'tool') {
|
|
1667
|
+
const out = String(m.content || '');
|
|
1668
|
+
lines.push(
|
|
1669
|
+
'[Araç sonucu]: ' + (out.length <= SUMMARY_OUT_KEEP ? out : out.slice(0, SUMMARY_OUT_KEEP) + '\n[kırpıldı]')
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
return lines.filter(Boolean).join('\n');
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/* compaction.ts:319-557 port. Bağlam taşarsa: kuyruk (son ~%25) birebir
|
|
1677
|
+
korunur, baş metne serileştirilip TEK küçük model çağrısıyla özetlenir,
|
|
1678
|
+
geçmiş [özet + kuyruk] olarak yeniden yazılır. Döndürür: compact oldu mu */
|
|
1679
|
+
async _compactHistory(session, sel, signal) {
|
|
1680
|
+
try {
|
|
1681
|
+
const usable = this._usableContext(sel) || this.historyTokenBudget * 2;
|
|
1682
|
+
const budget = clamp(Math.floor(usable * 0.25), MIN_PRESERVE_RECENT, MAX_PRESERVE_RECENT);
|
|
1683
|
+
const units = this._msgUnits(session.messages);
|
|
1684
|
+
if (units.length < 3) return false;
|
|
1685
|
+
let keepIdx = 0;
|
|
1686
|
+
let used = 0;
|
|
1687
|
+
for (let u = units.length - 1; u >= 0; u--) {
|
|
1688
|
+
const cost = units[u].reduce((a, m) => a + Math.ceil(estMsgTokens(m) * this.tokRatio), 0);
|
|
1689
|
+
if (used + cost > budget) break;
|
|
1690
|
+
keepIdx = u;
|
|
1691
|
+
used += cost;
|
|
1692
|
+
}
|
|
1693
|
+
if (keepIdx <= 0) return false; // özetlenecek head yok
|
|
1694
|
+
const headMsgs = units.slice(0, keepIdx).flat();
|
|
1695
|
+
const transcript = this._serializeHead(headMsgs);
|
|
1696
|
+
if (!transcript.trim()) return false;
|
|
1697
|
+
|
|
1698
|
+
/* core/session/compaction.ts:16-56 şablonu (Türkçe başlıklarla) */
|
|
1699
|
+
const prev = session.summary
|
|
1700
|
+
? `<önceki-özet>\n${session.summary}\n</önceki-özet>\n\n` +
|
|
1701
|
+
'önceki-özet, konuşmadan önceki her şeyi özetler: ikisini BİRLEŞTİREREK yeni özet çıkar. ' +
|
|
1702
|
+
'önceki-özet bu birleşimden sonra atılır — yeni özete taşımadığın her şey KAYBOLUR. ' +
|
|
1703
|
+
'Konuşma daha yenidir; çelişkide konuşma kazanır. Biten işleri "Aktif"ten "Tamamlanan"a taşı.\n\n'
|
|
1704
|
+
: '';
|
|
1705
|
+
const prompt =
|
|
1706
|
+
'Aşağıdaki <konuşma> etiketindeki sohbet geçmişinden, işe başka bir ajan devam edecek şekilde özet çıkar.\n' +
|
|
1707
|
+
'Tam olarak <şablon> içindeki Markdown yapısını koru; şablon etiketlerini yanıtına yazma.\n' +
|
|
1708
|
+
'<şablon>\n' +
|
|
1709
|
+
'## Amaç\n- [kullanıcının ne yapmaya çalıştığı, 1-2 cümle]\n\n' +
|
|
1710
|
+
'## Önemli Detaylar\n- [kısıtlar, kararlar ve gerekçeleri, önemli olgular, kesin yollar/sayılar; yoksa "(yok)"]\n\n' +
|
|
1711
|
+
'## İş Durumu\n### Tamamlanan\n- [biten işler, doğrulanan olgular; yoksa "(yok)"]\n\n' +
|
|
1712
|
+
'### Aktif\n- [sürüyen işler, yarım değişiklikler; yoksa "(yok)"]\n\n' +
|
|
1713
|
+
'### Engelli\n- [hatalar, bilinmeyenler; yoksa "(yok)"]\n\n' +
|
|
1714
|
+
'## Sonraki Adım\n1. [somut ilk eylem; yoksa "(yok)"]\n\n' +
|
|
1715
|
+
'## İlgili Dosyalar\n- [dosya/klasör yolu: neden önemli; yoksa "(yok)"]\n' +
|
|
1716
|
+
'</şablon>\n\n' +
|
|
1717
|
+
'Kurallar:\n- Boş olsa bile her bölümü koru.\n- Kısa madde maddeler yaz, paragraf kurma.\n' +
|
|
1718
|
+
'- Dosya yollarını, komutları, hata metinlerini, URL\u2019leri BİREBİR aktar.\n' +
|
|
1719
|
+
'- Özetleme yaptığını asla belirtme.\n\n' +
|
|
1720
|
+
prev +
|
|
1721
|
+
`<konuşma>\n${transcript}\n</konuşma>`;
|
|
1722
|
+
|
|
1723
|
+
emitSafe(this, session.id, {
|
|
1724
|
+
type: 'status',
|
|
1725
|
+
status: `⇩ bağlam doldu — opencode tarzı özetleme devrede (head ${headMsgs.length} mesaj, kuyruk ${session.messages.length - headMsgs.length} korunuyor)`,
|
|
1726
|
+
});
|
|
1727
|
+
const small = this.modelFor('subagent') || sel || this.sel;
|
|
1728
|
+
const res = await chatOnce(
|
|
1729
|
+
small,
|
|
1730
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2 },
|
|
1731
|
+
{ signal }
|
|
1732
|
+
);
|
|
1733
|
+
const text = String(res.content || '').replace(/```(?:markdown)?/gi, '').trim();
|
|
1734
|
+
if (!text) return false;
|
|
1735
|
+
session.summary = text.slice(0, 12000);
|
|
1736
|
+
const tail = units.slice(keepIdx).flat();
|
|
1737
|
+
session.messages = tail;
|
|
1738
|
+
session.notesAt = clamp(session.notesAt || 0, 0, tail.length);
|
|
1739
|
+
this._persistCompaction(session);
|
|
1740
|
+
emitSafe(this, session.id, {
|
|
1741
|
+
type: 'status',
|
|
1742
|
+
status: `✓ bağlam özeti yazıldı — geçmiş ${headMsgs.length + tail.length} → ${tail.length} mesaj, önek cache tazelendi`,
|
|
1743
|
+
});
|
|
1744
|
+
return true;
|
|
1745
|
+
} catch {
|
|
1746
|
+
return false; // compaction asla sohbeti bozmasın
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/* Compaction sonrası disk yeniden yazımı: meta/bot/todo/notes/summary +
|
|
1751
|
+
kuyruk mesajları. Atomik — patlarsa eski dosya sağlam kalır. */
|
|
1752
|
+
_persistCompaction(session) {
|
|
1753
|
+
try {
|
|
1754
|
+
const file = this._file(session.id);
|
|
1755
|
+
let meta = null, meta2 = null, bot = null, todo = null;
|
|
1756
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
1757
|
+
if (!line.trim()) continue;
|
|
1758
|
+
let r;
|
|
1759
|
+
try { r = JSON.parse(line); } catch { continue; }
|
|
1760
|
+
if (r.t === 'meta') meta = line;
|
|
1761
|
+
else if (r.t === 'meta2') meta2 = line;
|
|
1762
|
+
else if (r.t === 'bot') bot = line;
|
|
1763
|
+
else if (r.t === 'todo') todo = line; // append-only: son satır kazanır
|
|
1764
|
+
}
|
|
1765
|
+
const out = [];
|
|
1766
|
+
if (meta) out.push(meta);
|
|
1767
|
+
else out.push(JSON.stringify({ t: 'meta', id: session.id, code: session.code, createdAt: session.createdAt }));
|
|
1768
|
+
if (meta2) out.push(meta2);
|
|
1769
|
+
if (bot) out.push(bot);
|
|
1770
|
+
if (todo) out.push(todo);
|
|
1771
|
+
out.push(JSON.stringify({ t: 'notes', text: session.notes || '', at: session.notesAt || 0 }));
|
|
1772
|
+
out.push(JSON.stringify({ t: 'summary', text: session.summary || '', at: nowIso() }));
|
|
1773
|
+
for (const m of session.messages) out.push(JSON.stringify({ t: 'msg', ...m }));
|
|
1774
|
+
out.push(JSON.stringify({ t: 'compacted', at: nowIso(), summary: true }));
|
|
1775
|
+
const tmp = file + '.tmp';
|
|
1776
|
+
fs.writeFileSync(tmp, out.join('\n') + '\n');
|
|
1777
|
+
fs.renameSync(tmp, file);
|
|
1778
|
+
} catch {}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
/* Taşma kontrolü (overflow.ts:22-34): gerçek usage varsa onu esas al,
|
|
1782
|
+
yoksa tahmini kullan. usable'ın %95'i dolunca compaction tetiklenir. */
|
|
1783
|
+
_overContext(session, sel, lastUsageTotal) {
|
|
1784
|
+
const usable = this._usableContext(sel);
|
|
1785
|
+
if (!usable) return false;
|
|
1786
|
+
const total =
|
|
1787
|
+
lastUsageTotal ||
|
|
1788
|
+
session.messages.reduce((a, m) => a + estMsgTokens(m), 0) * this.tokRatio + 6000;
|
|
1789
|
+
return total >= usable * 0.95;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
/* compaction.ts:273-317 prune port: sondan geriye yürür, en yeni kullanıcı
|
|
1793
|
+
turunu korur; PRUNE_PROTECT (40k) aşan eski araç çıktılarını temizler.
|
|
1794
|
+
En az PRUNE_MINIMUM (20k) kazanç yoksa hiç dokunmaz. tool_call_id çiftleri
|
|
1795
|
+
korunur — sadece içerik temizlenir, önek cache bozulmaz. Döndürür: kazanılan token */
|
|
1796
|
+
_pruneSession(session) {
|
|
1797
|
+
try {
|
|
1798
|
+
if (this.ctrls.has(session.id)) return 0;
|
|
1799
|
+
const msgs = session.messages;
|
|
1800
|
+
let total = 0, pruned = 0, turns = 0;
|
|
1801
|
+
const targets = [];
|
|
1802
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
1803
|
+
const m = msgs[i];
|
|
1804
|
+
if (m.role === 'user') turns++;
|
|
1805
|
+
if (turns < 2) continue; // en yeni tur dokunulmaz
|
|
1806
|
+
if (m.role === 'tool' && typeof m.content === 'string' && m.content.length > 400) {
|
|
1807
|
+
const est = Math.ceil(estTokens(m.content) * this.tokRatio);
|
|
1808
|
+
total += est;
|
|
1809
|
+
if (total > PRUNE_PROTECT) {
|
|
1810
|
+
pruned += est;
|
|
1811
|
+
targets.push(m);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (pruned <= PRUNE_MINIMUM) return 0;
|
|
1816
|
+
const marker = '[eski araç çıktısı temizlendi — özet ve son çıktılar yeterli]';
|
|
1817
|
+
const ids = new Map(targets.map((m) => [m.tool_call_id, marker]));
|
|
1818
|
+
for (const m of targets) m.content = marker;
|
|
1819
|
+
try {
|
|
1820
|
+
const file = this._file(session.id);
|
|
1821
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
1822
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1823
|
+
if (!lines[i].trim()) continue;
|
|
1824
|
+
let r;
|
|
1825
|
+
try { r = JSON.parse(lines[i]); } catch { continue; }
|
|
1826
|
+
if (r.t === 'msg' && r.role === 'tool' && ids.has(r.tool_call_id)) {
|
|
1827
|
+
r.content = marker;
|
|
1828
|
+
lines[i] = JSON.stringify({ t: 'msg', ...r });
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const tmp = file + '.tmp';
|
|
1832
|
+
fs.writeFileSync(tmp, lines.join('\n'));
|
|
1833
|
+
fs.renameSync(tmp, file);
|
|
1834
|
+
} catch {}
|
|
1835
|
+
return pruned;
|
|
1836
|
+
} catch {
|
|
1837
|
+
return 0;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1515
1841
|
/* Cevap verildikten sonra çağrılır: son not update'ten bu yana NOTES_EVERY
|
|
1516
1842
|
(14) mesaj biriktiginde özet çıkarıp oturum notlarını günceller.
|
|
1517
1843
|
Notlar oturum dosyasına (code ile eşli) yazılır ve _chatTurn'da aynı
|
|
@@ -2308,6 +2634,10 @@ class Engine {
|
|
|
2308
2634
|
/* CEO: uygulayıcı araçlar kapalı — her şey paralel ajana devredilir */
|
|
2309
2635
|
activeTools = activeTools.filter((t) => !CEO_EXEC_TOOLS.has(t.function.name));
|
|
2310
2636
|
}
|
|
2637
|
+
if (session.bcCode && session.bcMode === 'plan') {
|
|
2638
|
+
/* opencode plan agent: salt-okur zorlaması — araç seti GERÇEKten daralır */
|
|
2639
|
+
activeTools = activeTools.filter((t) => PLAN_ALLOW_TOOLS.has(t.function.name));
|
|
2640
|
+
}
|
|
2311
2641
|
if (perms.length === 1 && perms[0] === 'chat') {
|
|
2312
2642
|
system +=
|
|
2313
2643
|
'\n\n# KISITLI MOD\nTüm araçların (komut, dosya, web, tarayıcı, hafıza) kapalı. Sadece yazarak cevap ver. ' +
|
|
@@ -2328,7 +2658,15 @@ class Engine {
|
|
|
2328
2658
|
`\n\n# OTURUM NOTLARI (oturum ${session.code || '?'} — bu oturumun önceki konuşma özeti; birebir geçmişi buradan hatırla)\n` +
|
|
2329
2659
|
session.notes;
|
|
2330
2660
|
}
|
|
2331
|
-
|
|
2661
|
+
/* önek-cache disiplini (opencode request.ts:184): araç sırası oturum
|
|
2662
|
+
boyunca sabit olmalı — alfabetik sıralama sağlayıcı önbelleğini bozmaz */
|
|
2663
|
+
activeTools = [...activeTools].sort((a, b) =>
|
|
2664
|
+
String(a.function.name).localeCompare(String(b.function.name))
|
|
2665
|
+
);
|
|
2666
|
+
/* opencode port: model bağlamına göre dinamik bütçe + compaction özeti */
|
|
2667
|
+
const selEarly = this.sessionModel.get(String(session.id)) || this.modelFor(null) || this.sel;
|
|
2668
|
+
const dynBudget = this._historyBudget(selEarly);
|
|
2669
|
+
let payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
|
|
2332
2670
|
// Vision model sadece mesajda görsel varsa devreye girer; terminal ise prompt'un
|
|
2333
2671
|
// shell/command işi olduğunda. Aksi halde ana model kullanılır.
|
|
2334
2672
|
let role = this._lastUserHasImage(session) ? 'vision' : null;
|
|
@@ -2375,7 +2713,7 @@ class Engine {
|
|
|
2375
2713
|
type: 'status',
|
|
2376
2714
|
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)`,
|
|
2377
2715
|
});
|
|
2378
|
-
payload = this._buildPayload(system, session.messages, session.notes);
|
|
2716
|
+
payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
|
|
2379
2717
|
res = await this._streamWithFallbacks(
|
|
2380
2718
|
session,
|
|
2381
2719
|
payload,
|
|
@@ -2398,6 +2736,38 @@ class Engine {
|
|
|
2398
2736
|
return res;
|
|
2399
2737
|
}
|
|
2400
2738
|
|
|
2739
|
+
/* opencode port (prompt.ts:96-100 + processor cleanup): önceki tur yarıda
|
|
2740
|
+
kesildiyse (abort/hata), sonucu gelmeyen tool çağrılarına sentetik hata
|
|
2741
|
+
yanıtı yazılır — katı sağlayıcıdaki "messages illegal" (400) önlenir */
|
|
2742
|
+
_repairOrphanTools(session) {
|
|
2743
|
+
try {
|
|
2744
|
+
const msgs = session.messages;
|
|
2745
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
2746
|
+
const m = msgs[i];
|
|
2747
|
+
if (m.role === 'user') return; // temiz sınır — sorun yok
|
|
2748
|
+
if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
2749
|
+
const answered = new Set();
|
|
2750
|
+
for (let j = i + 1; j < msgs.length; j++) {
|
|
2751
|
+
if (msgs[j].role === 'tool') answered.add(msgs[j].tool_call_id);
|
|
2752
|
+
}
|
|
2753
|
+
for (const tc of m.tool_calls) {
|
|
2754
|
+
if (!tc || !tc.id || answered.has(tc.id)) continue;
|
|
2755
|
+
const toolMsg = {
|
|
2756
|
+
role: 'tool',
|
|
2757
|
+
tool_call_id: tc.id,
|
|
2758
|
+
name: (tc.function && tc.function.name) || '',
|
|
2759
|
+
content:
|
|
2760
|
+
'[araç çalıştırması kesildi — tur iptal/kesinti nedeniyle tamamlanamadı; gerekiyorsa yeniden çağır]',
|
|
2761
|
+
};
|
|
2762
|
+
msgs.push(toolMsg);
|
|
2763
|
+
try { this._append(session, toolMsg); } catch {}
|
|
2764
|
+
}
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
} catch {}
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2401
2771
|
async _run(session) {
|
|
2402
2772
|
const ctrl = new AbortController();
|
|
2403
2773
|
this.ctrls.set(session.id, ctrl);
|
|
@@ -2406,8 +2776,16 @@ class Engine {
|
|
|
2406
2776
|
try {
|
|
2407
2777
|
if (!this.sel && !this.sessionModel.get(String(session.id))) throw new Error('Model yapılandırılmadı — %APPDATA%\\beast\\config.yaml ve .env kontrol et');
|
|
2408
2778
|
|
|
2779
|
+
/* opencode port: yetim tool_calls onarımı — önceki tur iptal/kesintiye
|
|
2780
|
+
uğradıysa sonuç bekleyen araç çağrılarına sentetik hata yanıtı yazılır;
|
|
2781
|
+
yoksa katı sağlayıcılar sonraki istekte 400 verir */
|
|
2782
|
+
this._repairOrphanTools(session);
|
|
2783
|
+
|
|
2409
2784
|
let nudged = false; // görev listesi disiplini: run başına en fazla 1 hatırlatma
|
|
2410
2785
|
let wrapNudged = false; // tur limitine yaklaşınca zarif kapanış (bg ajanlar)
|
|
2786
|
+
let usageTotal = 0; // en yüksek görülen total_tokens — compaction tetiği
|
|
2787
|
+
let compacted = false; // run başına en fazla 1 compaction
|
|
2788
|
+
const recentSigs = []; // doom-loop dedektörü: son araç imzaları
|
|
2411
2789
|
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
2412
2790
|
/* SÜRE SINIRI YOK — ama sonsuz tur da yok: bg ajan son 3 tura gelirken
|
|
2413
2791
|
"raporu yaz ve bitir" uyarısı alır; MAX_TURNS sert tavan olarak kalır. */
|
|
@@ -2424,11 +2802,40 @@ class Engine {
|
|
|
2424
2802
|
} catch {}
|
|
2425
2803
|
emit({ type: 'message', message: wmsg });
|
|
2426
2804
|
}
|
|
2805
|
+
/* opencode port (prompt.ts:1281): ana oturumda son turda zorunlu
|
|
2806
|
+
nihai cevap — tur limiti sessizce dolup boş 'done' dönmesin */
|
|
2807
|
+
if (!session.bgJob && turn === MAX_TURNS - 1 && !wrapNudged) {
|
|
2808
|
+
wrapNudged = true;
|
|
2809
|
+
const wmsg = {
|
|
2810
|
+
role: 'user',
|
|
2811
|
+
content:
|
|
2812
|
+
'[SON TUR] Tur limitine ulaşıldı: YENİ araç çağırma — eldeki bilgilerle NİHAİ cevabını şimdi ver. Eksikler varsa açıkça belirt.',
|
|
2813
|
+
};
|
|
2814
|
+
session.messages.push(wmsg);
|
|
2815
|
+
try {
|
|
2816
|
+
this._append(session, wmsg);
|
|
2817
|
+
} catch {}
|
|
2818
|
+
emit({ type: 'message', message: wmsg });
|
|
2819
|
+
}
|
|
2820
|
+
/* opencode port (prompt.ts:1161 + overflow.ts): bağlam dolduysa
|
|
2821
|
+
compaction checkpoint — kuyruk korunur, baş özetlenir */
|
|
2822
|
+
const selNow = this.sessionModel.get(String(sid)) || this.modelFor(null) || this.sel;
|
|
2823
|
+
if (!compacted && this._overContext(session, selNow, usageTotal)) {
|
|
2824
|
+
compacted = true;
|
|
2825
|
+
await this._compactHistory(session, selNow, ctrl.signal);
|
|
2826
|
+
}
|
|
2427
2827
|
this._bgTrim(session); // #21 bg geçmişini olabildiğince ince tut
|
|
2428
2828
|
emit({ type: 'status', status: 'thinking' });
|
|
2429
2829
|
const res = await this._chatTurn(session, ctrl.signal, (delta) =>
|
|
2430
2830
|
emit({ type: 'token', delta })
|
|
2431
2831
|
);
|
|
2832
|
+
/* opencode port (processor.ts:477-482): step-finish usage'ı compaction
|
|
2833
|
+
tetiğine besle — gerçek toplam biliniyorsa tahmine gerek kalmaz */
|
|
2834
|
+
const uTot =
|
|
2835
|
+
res.usage &&
|
|
2836
|
+
(res.usage.total_tokens ||
|
|
2837
|
+
(res.usage.prompt_tokens || 0) + (res.usage.completion_tokens || 0));
|
|
2838
|
+
if (uTot > usageTotal) usageTotal = uTot;
|
|
2432
2839
|
|
|
2433
2840
|
const assistant = { role: 'assistant', content: res.content || '' };
|
|
2434
2841
|
if (res.toolCalls && res.toolCalls.length) assistant.tool_calls = res.toolCalls;
|
|
@@ -2464,6 +2871,7 @@ class Engine {
|
|
|
2464
2871
|
Not/memory/skill bakımı artık arka planda (_postRunHousekeeping). */
|
|
2465
2872
|
this._clearCrash();
|
|
2466
2873
|
this._maybeCompact(sid);
|
|
2874
|
+
this._pruneSession(session); // opencode port: eski araç çıktıları temizlenir (fork edilen prune)
|
|
2467
2875
|
emit({ type: 'done', usage: res.usage || null, meta: res.meta || null });
|
|
2468
2876
|
this._bgFinish(sid, 'done');
|
|
2469
2877
|
this.flushPendingReports(sid);
|
|
@@ -2473,16 +2881,45 @@ class Engine {
|
|
|
2473
2881
|
|
|
2474
2882
|
// Paralel yürütme — bağımsız çağrılar beklemesin
|
|
2475
2883
|
session.toolsSinceReflect = (session.toolsSinceReflect || 0) + res.toolCalls.length;
|
|
2884
|
+
if (!this._knownToolNames) this._knownToolNames = new Set(TOOLS.map((t) => t.function.name));
|
|
2476
2885
|
await Promise.all(
|
|
2477
2886
|
res.toolCalls.map(async (tc) => {
|
|
2478
|
-
|
|
2887
|
+
let name = tc.function && tc.function.name;
|
|
2888
|
+
/* opencode port (llm.ts:296-312 repairToolCall): model aracı adını
|
|
2889
|
+
yanlış yazdıysa büyük/küçük harf normalizasyonuyle onar — tur kaybı yok */
|
|
2890
|
+
if (name && !this._knownToolNames.has(name)) {
|
|
2891
|
+
const fixed = TOOLS.find((t) => t.function.name.toLowerCase() === String(name).trim().toLowerCase());
|
|
2892
|
+
if (fixed) {
|
|
2893
|
+
emitSafe(this, sid, {
|
|
2894
|
+
type: 'status',
|
|
2895
|
+
status: `🔧 araç adı düzeltildi: ${name} → ${fixed.function.name}`,
|
|
2896
|
+
});
|
|
2897
|
+
name = fixed.function.name;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2479
2900
|
let args = {};
|
|
2480
2901
|
try {
|
|
2481
2902
|
args = JSON.parse((tc.function && tc.function.arguments) || '{}');
|
|
2482
2903
|
} catch {}
|
|
2483
2904
|
emit({ type: 'tool-start', callId: tc.id, name, args });
|
|
2484
2905
|
emit({ type: 'status', status: name });
|
|
2485
|
-
|
|
2906
|
+
/* opencode port (processor.ts:29 + 356-380): aynı araç + birebir
|
|
2907
|
+
aynı argüman 3 kez koştuysa 4.'yü çalıştırma — döngüye para/hız
|
|
2908
|
+
akıtma; modele hata bildirimiyle yol göster */
|
|
2909
|
+
const sig = name + '\u0000' + String((tc.function && tc.function.arguments) || '');
|
|
2910
|
+
const dups = recentSigs.reduce((a, s) => a + (s === sig ? 1 : 0), 0);
|
|
2911
|
+
recentSigs.push(sig);
|
|
2912
|
+
let out;
|
|
2913
|
+
if (dups >= DOOM_LOOP_THRESHOLD) {
|
|
2914
|
+
out = JSON.stringify({
|
|
2915
|
+
error:
|
|
2916
|
+
`doom-loop: ${name} aynı argümanlarla ${DOOM_LOOP_THRESHOLD} kez çalıştı ve hep aynı sonucu verdi. ` +
|
|
2917
|
+
'Aynı çağrıyı tekrarlamak işe yaramaz — farklı bir yöntem/argüman dene ya da eldeki bilgilerle nihai cevabı ver.',
|
|
2918
|
+
});
|
|
2919
|
+
emitSafe(this, sid, { type: 'status', status: `⛔ doom-loop: ${name} tekrarı engellendi` });
|
|
2920
|
+
} else {
|
|
2921
|
+
out = await this._execTool(name, args, ctrl.signal, sid);
|
|
2922
|
+
}
|
|
2486
2923
|
// Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
|
|
2487
2924
|
let injectedImage = null;
|
|
2488
2925
|
try {
|
|
@@ -2853,7 +3290,7 @@ class Engine {
|
|
|
2853
3290
|
t.function.name !== 'delegate_task' &&
|
|
2854
3291
|
t.function.name !== 'set_reminder' &&
|
|
2855
3292
|
!t.function.name.startsWith('email_')
|
|
2856
|
-
);
|
|
3293
|
+
).sort((a, b) => String(a.function.name).localeCompare(String(b.function.name))); // önek-cache: sabit sıra
|
|
2857
3294
|
const role = 'subagent';
|
|
2858
3295
|
const sel = this.modelFor(role) || this.sel;
|
|
2859
3296
|
if (this.roleModels[role]) {
|
|
@@ -2875,7 +3312,12 @@ class Engine {
|
|
|
2875
3312
|
for (let turn = 0; turn < SUB_MAX_TURNS; turn++) {
|
|
2876
3313
|
const res = await chatStreamAuto(
|
|
2877
3314
|
sel,
|
|
2878
|
-
{
|
|
3315
|
+
{
|
|
3316
|
+
messages: [{ role: 'system', content: system }, ...msgs.slice(1)],
|
|
3317
|
+
tools: subTools,
|
|
3318
|
+
reasoningEffort: this._thinkEffort(),
|
|
3319
|
+
cacheKey: String(sessionId || 'subagent'),
|
|
3320
|
+
},
|
|
2879
3321
|
{ signal: ctrl.signal }
|
|
2880
3322
|
);
|
|
2881
3323
|
const assistant = { role: 'assistant', content: res.content || '' };
|