beast-agent 2.8.3 → 2.10.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/engine.js +121 -7
- package/src/main.js +74 -34
- package/src/renderer/renderer.js +65 -13
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -334,6 +334,9 @@ class Engine {
|
|
|
334
334
|
this._bgGroups = new Map(); // groupId -> {parentId,total,results,dead}
|
|
335
335
|
/* silinen oturum işaretleri: rapor/zincir buraya gönderilmez (hayalet oturum yok) */
|
|
336
336
|
this._deletedSessions = new Set();
|
|
337
|
+
/* OPENCODE STEER: koşan tur sırasında gelen mesajlar — güvenli floş
|
|
338
|
+
noktasına kadar burada bekler (sid -> msg[]) */
|
|
339
|
+
this._steerBuf = new Map();
|
|
337
340
|
/* #17 kalıcı ajan geçmişi: restart sonrası işler + sohbetler kaybolmasın */
|
|
338
341
|
this._bgJobsFile = path.join(this.sessionsDir, 'bg-jobs.json');
|
|
339
342
|
this._loadBgJobsPersisted();
|
|
@@ -1405,6 +1408,7 @@ class Engine {
|
|
|
1405
1408
|
if (cached && cached.code) this._codeIndex.delete(cached.code);
|
|
1406
1409
|
this.cache.delete(String(id));
|
|
1407
1410
|
this.todos.delete(String(id));
|
|
1411
|
+
this._steerBuf && this._steerBuf.delete(String(id));
|
|
1408
1412
|
this.sessionPerm.delete(String(id));
|
|
1409
1413
|
this.sessionTools.delete(String(id));
|
|
1410
1414
|
this._errCount && this._errCount.delete(String(id));
|
|
@@ -2009,7 +2013,7 @@ class Engine {
|
|
|
2009
2013
|
const small = this.modelFor('subagent') || sel || this.sel;
|
|
2010
2014
|
const res = await chatOnce(
|
|
2011
2015
|
small,
|
|
2012
|
-
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2 },
|
|
2016
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2, cacheKey: String(session.id || '') },
|
|
2013
2017
|
{ signal }
|
|
2014
2018
|
);
|
|
2015
2019
|
const text = String(res.content || '').replace(/```(?:markdown)?/gi, '').trim();
|
|
@@ -2172,7 +2176,7 @@ class Engine {
|
|
|
2172
2176
|
`# SOHBET PARÇASI\n${transcript}`;
|
|
2173
2177
|
const res = await chatOnce(
|
|
2174
2178
|
this.sel,
|
|
2175
|
-
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2 },
|
|
2179
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2, cacheKey: String(session.id || '') },
|
|
2176
2180
|
{ signal }
|
|
2177
2181
|
);
|
|
2178
2182
|
const txt = String(res.content || '').trim();
|
|
@@ -2231,7 +2235,7 @@ class Engine {
|
|
|
2231
2235
|
if (this._stopped && !userAction) return false;
|
|
2232
2236
|
if (userAction) this._stopped = false;
|
|
2233
2237
|
const s = this._load(String(sessionId));
|
|
2234
|
-
if (!s
|
|
2238
|
+
if (!s) return false;
|
|
2235
2239
|
|
|
2236
2240
|
let msg;
|
|
2237
2241
|
if (typeof payload === 'string') {
|
|
@@ -2262,6 +2266,11 @@ class Engine {
|
|
|
2262
2266
|
...imgs.map((im) => ({ type: 'image', name: String(im.name || 'resim') })),
|
|
2263
2267
|
...files,
|
|
2264
2268
|
];
|
|
2269
|
+
/* MEDYA KAYDI: WA/Chat UI'dan gelen fotoğraflar ve belgeler OTURUMUN
|
|
2270
|
+
media klasörüne yazılır (orijinal dosya güvende). msg.media jsonl'e
|
|
2271
|
+
düşer; prune/compaction görselleri metne indirse bile kayıp yok. */
|
|
2272
|
+
const media = this._saveMedia(s.id, imgs, files);
|
|
2273
|
+
if (media) msg.media = media;
|
|
2265
2274
|
}
|
|
2266
2275
|
}
|
|
2267
2276
|
|
|
@@ -2274,6 +2283,35 @@ class Engine {
|
|
|
2274
2283
|
/* Önceki tur yanıtlanmadan kaldıysa (hata/durdurma sonrası artık user mesajı)
|
|
2275
2284
|
katı sağlayıcılar art arda user mesajını reddeder: "messages illegal" (400).
|
|
2276
2285
|
Yeni metni o mesajla BİRLEŞTİR — dosyadaki son satır da güncellenir. */
|
|
2286
|
+
/* OPENCODE STEER PORTU (runtime.queue.ts + prompt.ts runLoop): oturum
|
|
2287
|
+
KOŞARKEN gelen mesaj hemen geçmişe yazılmaz — _steerBuf'ta bekler,
|
|
2288
|
+
koşan tur GÜVENLİ floş noktasında (tur başı / çıkış kontrolü) ekler.
|
|
2289
|
+
Böylece araç sonuçlarının arasına sıkışmaz, isteğin yarısındaki mesaj
|
|
2290
|
+
mutasyona uğramaz. UI'ya yine de ANINDA düşer. */
|
|
2291
|
+
if (this.ctrls.has(s.id)) {
|
|
2292
|
+
let buf = this._steerBuf.get(s.id);
|
|
2293
|
+
if (!buf) {
|
|
2294
|
+
buf = [];
|
|
2295
|
+
this._steerBuf.set(s.id, buf);
|
|
2296
|
+
}
|
|
2297
|
+
const lastBuf = buf[buf.length - 1];
|
|
2298
|
+
if (
|
|
2299
|
+
lastBuf && lastBuf.role === 'user' &&
|
|
2300
|
+
!Array.isArray(lastBuf.content) && !Array.isArray(msg.content) &&
|
|
2301
|
+
typeof msg.content === 'string'
|
|
2302
|
+
) {
|
|
2303
|
+
/* ardışık steer'ler tek mesajda birleşir (art arda user reddi olmasın) */
|
|
2304
|
+
lastBuf.content = lastBuf.content + '\n' + msg.content;
|
|
2305
|
+
if (msg.attachments || lastBuf.attachments) {
|
|
2306
|
+
lastBuf.attachments = [...(lastBuf.attachments || []), ...(msg.attachments || [])];
|
|
2307
|
+
}
|
|
2308
|
+
} else {
|
|
2309
|
+
buf.push(msg);
|
|
2310
|
+
}
|
|
2311
|
+
this.emit({ type: 'message', sessionId: s.id, message: msg });
|
|
2312
|
+
this.emit({ type: 'sessions' });
|
|
2313
|
+
return true;
|
|
2314
|
+
}
|
|
2277
2315
|
const lastMsg = s.messages[s.messages.length - 1];
|
|
2278
2316
|
if (
|
|
2279
2317
|
lastMsg && lastMsg.role === 'user' &&
|
|
@@ -2298,6 +2336,57 @@ class Engine {
|
|
|
2298
2336
|
return true;
|
|
2299
2337
|
}
|
|
2300
2338
|
|
|
2339
|
+
/* Steer tamponunu güvenli noktada geçmişe akıtır. Dönüş: bir şey akıtıldı mı. */
|
|
2340
|
+
_flushSteer(sid, session, emit) {
|
|
2341
|
+
const buf = this._steerBuf.get(sid);
|
|
2342
|
+
if (!buf || !buf.length) return false;
|
|
2343
|
+
this._steerBuf.delete(sid);
|
|
2344
|
+
for (const m of buf) {
|
|
2345
|
+
session.messages.push(m);
|
|
2346
|
+
try {
|
|
2347
|
+
this._append(session, m);
|
|
2348
|
+
} catch {}
|
|
2349
|
+
}
|
|
2350
|
+
if (emit) emit({ type: 'sessions' });
|
|
2351
|
+
return true;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
/* MEDYA KAYDI: mesaj eklerindeki fotoğraflar (dataUrl) ve metin belgeleri
|
|
2355
|
+
oturumun media klasörüne yazılır: <sessionsDir>/media/<sid>/zaman-metin.
|
|
2356
|
+
Dönen liste msg.media olarak jsonl'e yazılır — orijinal dosya hiç
|
|
2357
|
+
kaybolmaz (eski görsellerin metne indirilmesinden bağımsız). */
|
|
2358
|
+
_saveMedia(sid, imgs, files) {
|
|
2359
|
+
const out = [];
|
|
2360
|
+
try {
|
|
2361
|
+
const dir = path.join(this.sessionsDir, 'media', String(sid || ''));
|
|
2362
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2363
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
2364
|
+
const safe = (n, fb) => String(n || fb).replace(/[\\/:*?"<>|]/g, '_').slice(0, 80);
|
|
2365
|
+
const stripExt = (n) => String(n || '').replace(/\.[a-z0-9]{1,5}$/i, '');
|
|
2366
|
+
let i = 0;
|
|
2367
|
+
for (const im of imgs) {
|
|
2368
|
+
const m = /^data:([^;,]+)?(;base64)?,(.*)$/.exec(String(im.dataUrl || ''));
|
|
2369
|
+
if (!m) continue;
|
|
2370
|
+
try {
|
|
2371
|
+
const mime = String(m[1] || 'image/png');
|
|
2372
|
+
const ext = (mime.split('/')[1] || 'png').replace(/[^a-z0-9]/gi, '').slice(0, 5) || 'png';
|
|
2373
|
+
const buf = m[2] ? Buffer.from(m[3], 'base64') : Buffer.from(decodeURIComponent(m[3]), 'utf8');
|
|
2374
|
+
const file = stamp + '-' + ++i + '-' + safe(stripExt(im.name), 'resim') + '.' + ext;
|
|
2375
|
+
fs.writeFileSync(path.join(dir, file), buf);
|
|
2376
|
+
out.push({ file, name: String(im.name || 'resim'), kind: 'image' });
|
|
2377
|
+
} catch {}
|
|
2378
|
+
}
|
|
2379
|
+
for (const f of files) {
|
|
2380
|
+
try {
|
|
2381
|
+
const file = stamp + '-' + ++i + '-' + safe(stripExt(f.name), 'dosya') + '.txt';
|
|
2382
|
+
fs.writeFileSync(path.join(dir, file), String(f.content || ''), 'utf8');
|
|
2383
|
+
out.push({ file, name: String(f.name || 'dosya'), kind: 'file' });
|
|
2384
|
+
} catch {}
|
|
2385
|
+
}
|
|
2386
|
+
} catch {}
|
|
2387
|
+
return out.length ? out : null;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2301
2390
|
/* SESSİZ BAĞLAM ENJEKSİYONU: metin oturum geçmişine düşer ama _run()
|
|
2302
2391
|
TETİKLENMEZ — ajan "görür", cevap ÜRETMEZ. WhatsApp gruplarında
|
|
2303
2392
|
@mention moduyla kullanılır: grubun tüm konuşması bağlam olarak
|
|
@@ -3249,6 +3338,10 @@ class Engine {
|
|
|
3249
3338
|
e.name = 'AbortError';
|
|
3250
3339
|
throw e;
|
|
3251
3340
|
}
|
|
3341
|
+
/* STEER FLOŞU (tur başı): koşan tur sırasında gelen mesajlar burada
|
|
3342
|
+
geçmişe eklenir — araç sonuçlarının arasına sıkışmaz, sonraki
|
|
3343
|
+
_chatTurn isteğinde model görür */
|
|
3344
|
+
this._flushSteer(sid, session, emit);
|
|
3252
3345
|
/* SÜRE SINIRI YOK. Ana oturum sınırsız tur koşar (opencode birebir);
|
|
3253
3346
|
yalnız bg ajan son 3 tura gelirken "raporu yaz ve bitir" uyarısı alır. */
|
|
3254
3347
|
if (session.bgJob && turn === maxTurns - 3 && !wrapNudged) {
|
|
@@ -3278,6 +3371,9 @@ class Engine {
|
|
|
3278
3371
|
}
|
|
3279
3372
|
this._bgTrim(session); // #21 bg geçmişini olabildiğince ince tut
|
|
3280
3373
|
emit({ type: 'status', status: 'thinking' });
|
|
3374
|
+
/* opencode runLoop portu: istek kurulmadan ÖNCEKİ mesaj sayısı —
|
|
3375
|
+
koşan tur SIRASINDA gelen (steer) user mesajlarını saptar */
|
|
3376
|
+
const preLen = session.messages.length;
|
|
3281
3377
|
const res = await this._chatTurn(session, ctrl.signal, (delta) =>
|
|
3282
3378
|
emit({ type: 'token', delta })
|
|
3283
3379
|
);
|
|
@@ -3298,6 +3394,20 @@ class Engine {
|
|
|
3298
3394
|
emit({ type: 'message', message: assistant });
|
|
3299
3395
|
|
|
3300
3396
|
if (!res.toolCalls || !res.toolCalls.length) {
|
|
3397
|
+
/* opencode runLoop portu (prompt.ts:1111-1115): koşan tur SIRASINDA
|
|
3398
|
+
yeni user mesajı geldiyse (steer) tur BİTMEZ — tampondaki mesaj
|
|
3399
|
+
geçmişe akıtılır, model sonraki istekte görür ve cevaplar;
|
|
3400
|
+
eski cevap akışı bozulmaz. */
|
|
3401
|
+
if (
|
|
3402
|
+
session.messages.slice(preLen).some((m) => m.role === 'user') ||
|
|
3403
|
+
this._flushSteer(sid, session, emit)
|
|
3404
|
+
) {
|
|
3405
|
+
/* ARA CEVAP: bu cevap 'done' değil (tur steer'le sürüyor) —
|
|
3406
|
+
bağlı kanallara (WA/TG/DC) tek tek iletilir; SON cevap yine
|
|
3407
|
+
done'da gider (çift yok: bu artık 'son' değil). */
|
|
3408
|
+
emit({ type: 'interim-final', message: assistant });
|
|
3409
|
+
continue;
|
|
3410
|
+
}
|
|
3301
3411
|
/* GÖREV LİSTESİ DİSİPLİNİ: ajan işi bitti sanıyor ama listede hâlâ
|
|
3302
3412
|
bekleyen/aktif madde varsa BİR KEZ hatırlat ve tura devam et —
|
|
3303
3413
|
liste ya tamamlanmalı ya kalan maddeler listeden düşürülmeli. */
|
|
@@ -3458,6 +3568,10 @@ class Engine {
|
|
|
3458
3568
|
this._bgFinish(sid, 'error', 'bg görevi tur limitine ulaştı (maxTurns)');
|
|
3459
3569
|
} catch (e) {
|
|
3460
3570
|
const aborted = e && (e.name === 'AbortError' || ctrl.signal.aborted);
|
|
3571
|
+
/* STEER FLOŞU (hata/durdurma): tur sırasında gelen mesajlar yine de
|
|
3572
|
+
geçmişe yazılsın — konuşma bütünlüğü korunur, sonraki tur bağlamı
|
|
3573
|
+
görür (opencode interrupted-part disiplini) */
|
|
3574
|
+
this._flushSteer(sid, session, emit);
|
|
3461
3575
|
if (aborted) {
|
|
3462
3576
|
this._clearCrash(); // kullanıcı durdurdu — kurtarma yok
|
|
3463
3577
|
/* öz-kurtarma/superyorizon kick'i bekliyorsa bu tur BİTİŞ değildir —
|
|
@@ -3571,7 +3685,7 @@ class Engine {
|
|
|
3571
3685
|
const llm = async (sys, user) => {
|
|
3572
3686
|
const res = await chatOnce(
|
|
3573
3687
|
this.sel,
|
|
3574
|
-
{ messages: [{ role: 'user', content: sys + '\n\n' + user }], temperature: 0.1 },
|
|
3688
|
+
{ messages: [{ role: 'user', content: sys + '\n\n' + user }], temperature: 0.1, cacheKey: String(session.id || '') },
|
|
3575
3689
|
{ signal }
|
|
3576
3690
|
);
|
|
3577
3691
|
return String(res.content || '');
|
|
@@ -3607,7 +3721,7 @@ class Engine {
|
|
|
3607
3721
|
|
|
3608
3722
|
const res = await chatOnce(
|
|
3609
3723
|
this.sel,
|
|
3610
|
-
{ messages: [{ role: 'user', content: prompt }], temperature: 0.1 },
|
|
3724
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.1, cacheKey: String(session.id || '') },
|
|
3611
3725
|
{ signal }
|
|
3612
3726
|
);
|
|
3613
3727
|
let parsed = null;
|
|
@@ -3661,7 +3775,7 @@ const skills = require('./skills');
|
|
|
3661
3775
|
|
|
3662
3776
|
const res = await chatOnce(
|
|
3663
3777
|
this.sel,
|
|
3664
|
-
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2 },
|
|
3778
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2, cacheKey: String(session.id || '') },
|
|
3665
3779
|
{}
|
|
3666
3780
|
);
|
|
3667
3781
|
const draft = parseReflectionJson(res.content || '');
|
|
@@ -3745,7 +3859,7 @@ const skills = require('./skills');
|
|
|
3745
3859
|
try {
|
|
3746
3860
|
const res = await chatOnce(
|
|
3747
3861
|
this.sel,
|
|
3748
|
-
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2 },
|
|
3862
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2, cacheKey: 'nightref' },
|
|
3749
3863
|
{ signal: ctrl.signal }
|
|
3750
3864
|
);
|
|
3751
3865
|
return String(res.content || '');
|
package/src/main.js
CHANGED
|
@@ -642,7 +642,7 @@ async function transcribeCloud(provider, buf, mimetype, lang) {
|
|
|
642
642
|
if (lang === 'tr' || lang === 'en') form.append('language', lang);
|
|
643
643
|
const res = await fetch(base + '/audio/transcriptions', {
|
|
644
644
|
method: 'POST',
|
|
645
|
-
headers: { Authorization: 'Bearer ' + key },
|
|
645
|
+
headers: { Authorization: 'Bearer ' + key, 'x-opencode-session': OPENCODE_SESSION },
|
|
646
646
|
body: form,
|
|
647
647
|
});
|
|
648
648
|
if (!res.ok) throw new Error(provider + ' ' + res.status + ': ' + String(await res.text()).slice(0, 180));
|
|
@@ -954,7 +954,11 @@ async function synthesizeSpeech(text) {
|
|
|
954
954
|
const url = String(cfg.baseUrl).replace(/\/+$/, '') + '/audio/speech';
|
|
955
955
|
const res = await fetch(url, {
|
|
956
956
|
method: 'POST',
|
|
957
|
-
headers: {
|
|
957
|
+
headers: {
|
|
958
|
+
'Content-Type': 'application/json',
|
|
959
|
+
Authorization: 'Bearer ' + cfg.key,
|
|
960
|
+
'x-opencode-session': OPENCODE_SESSION,
|
|
961
|
+
},
|
|
958
962
|
body: JSON.stringify({
|
|
959
963
|
model: cfg.model || 'tts-1',
|
|
960
964
|
input: String(text).slice(0, 4000),
|
|
@@ -1726,6 +1730,23 @@ function sessionFileAlive(sid) {
|
|
|
1726
1730
|
|
|
1727
1731
|
/* kanalın yaşayan tek oturumunu döndürür; geçersizse eski bağlamdan devralır,
|
|
1728
1732
|
o da yoksa (ilk kullanım) yeni açar ve tüm jid'leri ona bağlar */
|
|
1733
|
+
/* WA'dan gelen belge orijinallerini oturumun media klasörüne yazar;
|
|
1734
|
+
döner: media/<sid>/<dosya> (relatif yol — ajan read_file ile açabilir) */
|
|
1735
|
+
function saveSessionMedia(sid, name, buf) {
|
|
1736
|
+
try {
|
|
1737
|
+
if (!buf || !buf.length) return null;
|
|
1738
|
+
const dir = path.join(engine.sessionsDir, 'media', String(sid || ''));
|
|
1739
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1740
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
1741
|
+
const safe = String(name || 'dosya').replace(/[\\/:*?"<>|]/g, '_').slice(0, 120);
|
|
1742
|
+
const file = stamp + '-' + safe;
|
|
1743
|
+
fs.writeFileSync(path.join(dir, file), buf);
|
|
1744
|
+
return 'media/' + String(sid || '') + '/' + file;
|
|
1745
|
+
} catch {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1729
1750
|
function ensureWaSession(jid) {
|
|
1730
1751
|
if (!waSingleSid || !sessionFileAlive(waSingleSid)) {
|
|
1731
1752
|
let best = '';
|
|
@@ -2313,17 +2334,10 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
|
|
|
2313
2334
|
waLog(`skip flush: izinli eşleşme yok (sender=+${senderNum || '?'})`);
|
|
2314
2335
|
return;
|
|
2315
2336
|
}
|
|
2337
|
+
/* OPENCODE STEER: oturum meşgulse mesaj bekleMEZ — engine.send tamponuna
|
|
2338
|
+
ekler, koşan tur sonraki istekte görür ve cevaplar (eski cevap bozulmaz).
|
|
2339
|
+
Sıra korunur: floş katmanı debounceli ve sıralı işler. */
|
|
2316
2340
|
let sid = waChats.get(jid) || waSingleSid;
|
|
2317
|
-
if (sid && engine.isBusy(sid)) {
|
|
2318
|
-
/* oturum hâlâ önceki işle uğraşıyor — KAYBETME: sınırı kaldırdık,
|
|
2319
|
-
iş bitene dek her debounce turunda yeniden deneriz; kullanıcıya
|
|
2320
|
-
"işleyemedim" denmez, mesaj boşa gitmez. */
|
|
2321
|
-
if (requeues === 1) {
|
|
2322
|
-
waLog(`flush: oturum meşgul, mesaj kuyrukta bekliyor sid=${sid}`);
|
|
2323
|
-
}
|
|
2324
|
-
await new Promise((r) => setTimeout(r, WA_DEBOUNCE_MS));
|
|
2325
|
-
return processWaMessage(jid, payload, senderNum, requeues + 1);
|
|
2326
|
-
}
|
|
2327
2341
|
if (!isGroup && requeues > 0) {
|
|
2328
2342
|
// retry sonrası izin yeniden kontrol edilir (yukarıda hit zaten alınıyor)
|
|
2329
2343
|
}
|
|
@@ -2429,12 +2443,15 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
|
|
|
2429
2443
|
if (inst && inst.error) {
|
|
2430
2444
|
text += `\n[SKILL.md alındı ama kaydedilemedi]`;
|
|
2431
2445
|
} else {
|
|
2446
|
+
/* BELGE KAYDI: orijinal dosya oturum media klasörüne yazılır —
|
|
2447
|
+
model yalnız metnini görür, orijinale ajan read_file ile ulaşabilir */
|
|
2448
|
+
const savedDoc = saveSessionMedia(sid, md.name, md.buf);
|
|
2432
2449
|
const dt = documentToText(md);
|
|
2433
2450
|
if (dt && dt.trim()) {
|
|
2434
2451
|
attachments.push({ type: 'file', name: md.name, content: dt.slice(0, 20000) });
|
|
2435
|
-
text += `\n[belge alındı: ${md.name}]`;
|
|
2452
|
+
text += `\n[belge alındı: ${md.name}${savedDoc ? ' · kaydedildi: ' + savedDoc : ''}]`;
|
|
2436
2453
|
} else {
|
|
2437
|
-
text += `\n[belge alındı ama okunamadı: ${md.name}]`;
|
|
2454
|
+
text += `\n[belge alındı ama okunamadı: ${md.name}${savedDoc ? ' · kaydedildi: ' + savedDoc : ''}]`;
|
|
2438
2455
|
}
|
|
2439
2456
|
}
|
|
2440
2457
|
}
|
|
@@ -2626,12 +2643,9 @@ async function processTgMessage(chatId, payload, requeues = 0) {
|
|
|
2626
2643
|
tgLog(`skip flush: izinli eşleşme yok (sender=${payload.senderId || '?'})`);
|
|
2627
2644
|
return;
|
|
2628
2645
|
}
|
|
2646
|
+
/* OPENCODE STEER: meşgulse bekleme — engine.send tampona ekler, koşan tur
|
|
2647
|
+
sonraki istekte görür (WA ile aynı mantık) */
|
|
2629
2648
|
let sid = tgChats.get(chatId) || tgSingleSid;
|
|
2630
|
-
if (sid && engine.isBusy(sid)) {
|
|
2631
|
-
/* oturum meşgul — WA ile aynı: kaybetme, iş bitene dek yeniden dene */
|
|
2632
|
-
await new Promise((r) => setTimeout(r, TG_DEBOUNCE_MS));
|
|
2633
|
-
return processTgMessage(chatId, payload, requeues + 1);
|
|
2634
|
-
}
|
|
2635
2649
|
/* KANAL TEK OTURUMU: eskisi silinmedikçe hep aynı oturumdan devam */
|
|
2636
2650
|
sid = ensureTgSession(chatId);
|
|
2637
2651
|
/* Kişi bazlı granül izin: all/web/read/chat */
|
|
@@ -2840,12 +2854,9 @@ async function processDcMessage(channelId, payload) {
|
|
|
2840
2854
|
dcLog(`skip flush: izinli eşleşme yok (sender=${payload.senderId || '?'})`);
|
|
2841
2855
|
return;
|
|
2842
2856
|
}
|
|
2857
|
+
/* OPENCODE STEER: meşgulse bekleme — engine.send tampona ekler, koşan tur
|
|
2858
|
+
sonraki istekte görür (WA/TG ile aynı mantık) */
|
|
2843
2859
|
let sid = dcChats.get(channelId) || dcSingleSid;
|
|
2844
|
-
if (sid && engine.isBusy(sid)) {
|
|
2845
|
-
/* oturum meşgul — WA/TG ile aynı: kaybetme, iş bitene dek yeniden dene */
|
|
2846
|
-
await new Promise((r) => setTimeout(r, DC_DEBOUNCE_MS));
|
|
2847
|
-
return processDcMessage(channelId, payload, 1);
|
|
2848
|
-
}
|
|
2849
2860
|
/* KANAL TEK OTURUMU: eskisi silinmedikçe hep aynı oturumdan devam */
|
|
2850
2861
|
sid = ensureDcSession(channelId);
|
|
2851
2862
|
/* Kişi bazlı granül izin: all/web/read/chat */
|
|
@@ -3091,6 +3102,34 @@ function reloadBackend() {
|
|
|
3091
3102
|
})();
|
|
3092
3103
|
}
|
|
3093
3104
|
}
|
|
3105
|
+
/* OPENCODE STEER ARA CEVABI: koşan tur sırasında gelen mesajın cevabı.
|
|
3106
|
+
'done' YOK (tur steer'le sürüyor) — bu yüzden done akışı YAZMAZ.
|
|
3107
|
+
Bağlı kanallara (WA/TG/DC) tek tek gider; SON cevap yine done'da. */
|
|
3108
|
+
if (ev.type === 'interim-final' && ev.message) {
|
|
3109
|
+
const txt = typeof ev.message.content === 'string' ? ev.message.content.trim() : '';
|
|
3110
|
+
if (txt) {
|
|
3111
|
+
(async () => {
|
|
3112
|
+
try {
|
|
3113
|
+
if (wa && wa.connected) {
|
|
3114
|
+
const jid = waReplyJid(ev.sessionId);
|
|
3115
|
+
if (jid) await sendWaSafe(jid, txt);
|
|
3116
|
+
}
|
|
3117
|
+
} catch {}
|
|
3118
|
+
try {
|
|
3119
|
+
if (tg && tg.connected) {
|
|
3120
|
+
const hit = [...tgChats.entries()].find(([, s]) => s === ev.sessionId);
|
|
3121
|
+
if (hit) await sendTgSafe(hit[0], txt);
|
|
3122
|
+
}
|
|
3123
|
+
} catch {}
|
|
3124
|
+
try {
|
|
3125
|
+
if (dc && dc.connected) {
|
|
3126
|
+
const hit = [...dcChats.entries()].find(([, s]) => s === ev.sessionId);
|
|
3127
|
+
if (hit) await sendDcSafe(hit[0], txt);
|
|
3128
|
+
}
|
|
3129
|
+
} catch {}
|
|
3130
|
+
})();
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3094
3133
|
// Telegram oturumlarının son cevabını geri gönder (WA ile aynı akış)
|
|
3095
3134
|
if ((ev.type === 'done' || ev.type === 'error') && tg && tg.connected) {
|
|
3096
3135
|
const hitT = [...tgChats.entries()].find(([, s]) => s === ev.sessionId);
|
|
@@ -5149,6 +5188,14 @@ function queueDesktopMessage(sessionId, text) {
|
|
|
5149
5188
|
chatQueueOfflineAdd(sid, { text: t, attachments: hasAtts ? text.attachments : undefined });
|
|
5150
5189
|
return;
|
|
5151
5190
|
}
|
|
5191
|
+
/* OPENCODE STEER: koşan tur varken mesaj ANINDA konuşmaya eklenir —
|
|
5192
|
+
ajan sonraki istekte görür, eski cevap akışı bozulmaz. Kuyrukta beklemesine
|
|
5193
|
+
gerek yok; boşta debounce penceresi yine birleştirir. */
|
|
5194
|
+
if (engine.isBusy(sid)) {
|
|
5195
|
+
waLog(`desktop: oturum meşgul — mesaj steer olarak konuşmaya eklendi sid=${sid}`);
|
|
5196
|
+
engine.send(sid, hasAtts ? { text: t, attachments: text.attachments } : t, { userAction: true });
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5152
5199
|
let q = desktopQueue.get(sid);
|
|
5153
5200
|
if (!q) {
|
|
5154
5201
|
q = { timer: null, msgs: [] };
|
|
@@ -5156,16 +5203,9 @@ function queueDesktopMessage(sessionId, text) {
|
|
|
5156
5203
|
}
|
|
5157
5204
|
q.msgs.push({ text: t, attachments: hasAtts ? text.attachments : undefined });
|
|
5158
5205
|
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
clearTimeout(q.timer);
|
|
5163
|
-
q.timer = setTimeout(() => flushDesktop(sid).catch(() => {}), DESKTOP_DEBOUNCE_MS);
|
|
5164
|
-
} else {
|
|
5165
|
-
/* agent çalışıyor: pencere yok, iş bitince hepsi birlikte gider.
|
|
5166
|
-
Bekleyen paket olduğunda 'done' olayı flushDesktop'u çağırır. */
|
|
5167
|
-
waLog(`desktop: oturum meşgul, mesaj beklemede (${q.msgs.length}) sid=${sid}`);
|
|
5168
|
-
}
|
|
5206
|
+
/* agent boşta: kısa pencere — hızlı ikinci mesaj ilkine eklenir */
|
|
5207
|
+
clearTimeout(q.timer);
|
|
5208
|
+
q.timer = setTimeout(() => flushDesktop(sid).catch(() => {}), DESKTOP_DEBOUNCE_MS);
|
|
5169
5209
|
}
|
|
5170
5210
|
|
|
5171
5211
|
async function flushDesktop(sessionId) {
|
package/src/renderer/renderer.js
CHANGED
|
@@ -5402,9 +5402,25 @@ function opText(w) {
|
|
|
5402
5402
|
return `${sym} ${w.value ?? '?'}`;
|
|
5403
5403
|
}
|
|
5404
5404
|
|
|
5405
|
-
|
|
5405
|
+
/* kısa saat: bugünse "14:32", başka günse "07.09 14:32" */
|
|
5406
|
+
function fmtWhen(iso) {
|
|
5407
|
+
if (!iso) return '';
|
|
5408
|
+
const d = new Date(iso);
|
|
5409
|
+
if (isNaN(d.getTime())) return '';
|
|
5410
|
+
const t = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
5411
|
+
if (d.toDateString() === new Date().toDateString()) return t;
|
|
5412
|
+
return d.toLocaleDateString([], { day: '2-digit', month: '2-digit' }) + ' ' + t;
|
|
5413
|
+
}
|
|
5414
|
+
|
|
5415
|
+
let lastWatchKey = '';
|
|
5416
|
+
async function renderWatchersModal(force) {
|
|
5406
5417
|
let rows = [];
|
|
5407
5418
|
try { rows = (await beast.watchersList()) || []; } catch {}
|
|
5419
|
+
/* canlı yenilemede içerik değişmediyse DOM'a dokunma — buton tıklamaları
|
|
5420
|
+
yeniden kurulurken yutulmasın */
|
|
5421
|
+
const key = JSON.stringify(rows);
|
|
5422
|
+
if (!force && key === lastWatchKey) return;
|
|
5423
|
+
lastWatchKey = key;
|
|
5408
5424
|
els.watchList.innerHTML = '';
|
|
5409
5425
|
if (!rows.length) {
|
|
5410
5426
|
els.watchList.innerHTML = '<div class="mini-empty">' + _t('w_empty') + '</div>';
|
|
@@ -5443,9 +5459,13 @@ async function renderWatchersModal() {
|
|
|
5443
5459
|
}
|
|
5444
5460
|
}
|
|
5445
5461
|
|
|
5446
|
-
|
|
5462
|
+
let lastCronKey = '';
|
|
5463
|
+
async function renderCronModal(force) {
|
|
5447
5464
|
let jobs = [];
|
|
5448
5465
|
try { jobs = (await beast.cronList()) || []; } catch {}
|
|
5466
|
+
const key = JSON.stringify(jobs);
|
|
5467
|
+
if (!force && key === lastCronKey) return;
|
|
5468
|
+
lastCronKey = key;
|
|
5449
5469
|
els.cronModalList.innerHTML = '';
|
|
5450
5470
|
if (!jobs.length) {
|
|
5451
5471
|
els.cronModalList.innerHTML = '<div class="mini-empty">' + _t('cr_modal_empty') + '</div>';
|
|
@@ -5454,11 +5474,15 @@ async function renderCronModal() {
|
|
|
5454
5474
|
for (const j of jobs) {
|
|
5455
5475
|
const row = document.createElement('div');
|
|
5456
5476
|
row.className = 'mini-row' + (j.enabled ? '' : ' off');
|
|
5477
|
+
/* ANLIK DURUM: sıradaki tetiklenme + son çalışma (varsa) */
|
|
5478
|
+
const live =
|
|
5479
|
+
(j.enabled && j.nextRunAt ? ` · sonraki <b>${escapeHtml(fmtWhen(j.nextRunAt))}</b>` : '') +
|
|
5480
|
+
(j.lastRunAt ? ` · son ${escapeHtml(fmtWhen(j.lastRunAt))}` : '');
|
|
5457
5481
|
row.innerHTML =
|
|
5458
5482
|
`<span class="mr-dot"></span>` +
|
|
5459
5483
|
`<div class="mr-main">` +
|
|
5460
5484
|
`<div class="mr-name">${escapeHtml(j.name || j.id)}</div>` +
|
|
5461
|
-
`<div class="mr-meta"><b>${escapeHtml(j.schedule)}</b> · ${escapeHtml(String(j.prompt || '').slice(0, 70))}</div>` +
|
|
5485
|
+
`<div class="mr-meta"><b>${escapeHtml(j.schedule)}</b> · ${escapeHtml(String(j.prompt || '').slice(0, 70))}${live}</div>` +
|
|
5462
5486
|
`</div>`;
|
|
5463
5487
|
const btns = document.createElement('div');
|
|
5464
5488
|
btns.style.cssText = 'display:flex;gap:6px;flex:none';
|
|
@@ -5488,14 +5512,41 @@ function toggleMini(overlay, hide) {
|
|
|
5488
5512
|
overlay.hidden = !!hide;
|
|
5489
5513
|
}
|
|
5490
5514
|
|
|
5515
|
+
/* CANLI YENİLEME: mini modal açık kaldığı sürece periyodik taze çekim yapar;
|
|
5516
|
+
render fonksiyonları içerik değişmemişse DOM'u yeniden kurmaz. Kapalıysa
|
|
5517
|
+
zamanlayıcı kendini durdurur. */
|
|
5518
|
+
let miniRefreshTimer = null;
|
|
5519
|
+
function startMiniRefresh() {
|
|
5520
|
+
stopMiniRefresh();
|
|
5521
|
+
miniRefreshTimer = setInterval(async () => {
|
|
5522
|
+
if (els.watchOverlay && !els.watchOverlay.hidden) await renderWatchersModal();
|
|
5523
|
+
else if (els.cronOverlay && !els.cronOverlay.hidden) await renderCronModal();
|
|
5524
|
+
else stopMiniRefresh();
|
|
5525
|
+
}, 4000);
|
|
5526
|
+
}
|
|
5527
|
+
function stopMiniRefresh() {
|
|
5528
|
+
if (miniRefreshTimer) { clearInterval(miniRefreshTimer); miniRefreshTimer = null; }
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5531
|
+
function closeWatchModal() {
|
|
5532
|
+
toggleMini(els.watchOverlay, true);
|
|
5533
|
+
stopMiniRefresh();
|
|
5534
|
+
}
|
|
5535
|
+
function closeCronModal() {
|
|
5536
|
+
toggleMini(els.cronOverlay, true);
|
|
5537
|
+
stopMiniRefresh();
|
|
5538
|
+
}
|
|
5539
|
+
|
|
5491
5540
|
async function openWatchModal() {
|
|
5492
5541
|
toggleMini(els.watchOverlay, false);
|
|
5493
|
-
await renderWatchersModal();
|
|
5542
|
+
await renderWatchersModal(true); /* her basışta TAZE çekim */
|
|
5543
|
+
startMiniRefresh();
|
|
5494
5544
|
}
|
|
5495
5545
|
|
|
5496
5546
|
async function openCronModal() {
|
|
5497
5547
|
toggleMini(els.cronOverlay, false);
|
|
5498
|
-
await renderCronModal();
|
|
5548
|
+
await renderCronModal(true); /* her basışta TAZE çekim */
|
|
5549
|
+
startMiniRefresh();
|
|
5499
5550
|
}
|
|
5500
5551
|
|
|
5501
5552
|
/* ---------------- state / controls ---------------- */
|
|
@@ -5671,8 +5722,9 @@ async function sendCurrent() {
|
|
|
5671
5722
|
if (!busy) {
|
|
5672
5723
|
setBusy(true);
|
|
5673
5724
|
} else {
|
|
5674
|
-
/* agent çalışıyor — mesaj
|
|
5675
|
-
|
|
5725
|
+
/* agent çalışıyor — mesaj ANINDA konuşmaya eklenir (opencode steer):
|
|
5726
|
+
koşan tur sonraki istekte görür, eski cevap akışı bozulmaz */
|
|
5727
|
+
setStatus('mesaj ajanın akışına eklendi — sıradaki adımda değerlendirir');
|
|
5676
5728
|
}
|
|
5677
5729
|
beast.send(activeId, { text, attachments: atts });
|
|
5678
5730
|
}
|
|
@@ -6203,13 +6255,13 @@ async function init() {
|
|
|
6203
6255
|
/* cron modülü — olay bağlama */
|
|
6204
6256
|
if (els.watchBtn) els.watchBtn.addEventListener('click', openWatchModal);
|
|
6205
6257
|
if (els.cronBtn) els.cronBtn.addEventListener('click', openCronModal);
|
|
6206
|
-
if (els.watchClose) els.watchClose.addEventListener('click',
|
|
6207
|
-
if (els.cronClose) els.cronClose.addEventListener('click',
|
|
6258
|
+
if (els.watchClose) els.watchClose.addEventListener('click', closeWatchModal);
|
|
6259
|
+
if (els.cronClose) els.cronClose.addEventListener('click', closeCronModal);
|
|
6208
6260
|
if (els.watchOverlay) els.watchOverlay.addEventListener('click', (e) => {
|
|
6209
|
-
if (e.target === els.watchOverlay)
|
|
6261
|
+
if (e.target === els.watchOverlay) closeWatchModal();
|
|
6210
6262
|
});
|
|
6211
6263
|
if (els.cronOverlay) els.cronOverlay.addEventListener('click', (e) => {
|
|
6212
|
-
if (e.target === els.cronOverlay)
|
|
6264
|
+
if (e.target === els.cronOverlay) closeCronModal();
|
|
6213
6265
|
});
|
|
6214
6266
|
els.cronPreset.addEventListener('change', () => {
|
|
6215
6267
|
if (els.cronPreset.value !== '__custom') els.cronSchedule.value = els.cronPreset.value;
|
|
@@ -6294,8 +6346,8 @@ async function init() {
|
|
|
6294
6346
|
document.addEventListener('keydown', (e) => {
|
|
6295
6347
|
if (e.key === 'Escape') {
|
|
6296
6348
|
if ($('#botOverlay') && !$('#botOverlay').hidden) { botOverlaySetOpen(false); return; }
|
|
6297
|
-
if (els.watchOverlay && !els.watchOverlay.hidden) {
|
|
6298
|
-
if (els.cronOverlay && !els.cronOverlay.hidden) {
|
|
6349
|
+
if (els.watchOverlay && !els.watchOverlay.hidden) { closeWatchModal(); return; }
|
|
6350
|
+
if (els.cronOverlay && !els.cronOverlay.hidden) { closeCronModal(); return; }
|
|
6299
6351
|
if (!els.settingsOverlay.hidden) closeSettings();
|
|
6300
6352
|
}
|
|
6301
6353
|
// Ctrl+Shift+S: ekran görüntüsü al → vision'a gönderilmek üzere ekle
|