beast-agent 0.25.4 → 0.25.6
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 +53 -39
- package/src/agent/llm.js +46 -1
- package/src/agent/skills.js +8 -1
- package/src/agent/tools.js +20 -14
- package/src/main.js +26 -36
- package/tests/llm.test.js +81 -1
- package/tests/python.test.js +3 -2
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const { chatStream, chatOnce } = require('./llm');
|
|
9
|
+
const { chatStream, chatStreamAuto, chatOnce } = require('./llm');
|
|
10
10
|
const { chatCompletionsUrl } = require('./config');
|
|
11
11
|
const tools = require('./tools');
|
|
12
|
+
const research = require('./research');
|
|
12
13
|
const memory = require('./memory');
|
|
13
14
|
const skills = require('./skills');
|
|
14
15
|
const { estTokens, estMsgTokens } = require('./tokens');
|
|
@@ -608,7 +609,7 @@ class Engine {
|
|
|
608
609
|
}
|
|
609
610
|
}
|
|
610
611
|
}
|
|
611
|
-
const res = await
|
|
612
|
+
const res = await chatStreamAuto(
|
|
612
613
|
c,
|
|
613
614
|
{ messages: msgs, tools: activeTools, reasoningEffort: this._thinkEffortFor(session) },
|
|
614
615
|
{
|
|
@@ -1300,6 +1301,7 @@ class Engine {
|
|
|
1300
1301
|
'CONUŞMA ODAĞI SENDE KALSIN: kullanıcı seninle konuşurken iş çıkmışsa — uzun da olsa UFACIK da (tek komutluk dizin listesi, tek dosya okuma, tek arama…) — run_background ile PARALEL ajana devret; ana sohbet hiçbir işi beklemez; bittiğinde özet otomatik düşer.',
|
|
1301
1302
|
'Python işleri için python_run kullan: küçük betikler inline code ile; tekrarlayan işler %APPDATA%\\beast\\scripts klasöründeki dosyalarla (ör. news.py = RSS haber toplayıcı: args ["--limit","8","--json"]). Python kurulu olmasa bile ilk çağrıda taşınabilir gömülü runtime otomatik iner.',
|
|
1302
1303
|
'PYTHON DURUMU: makinede sistem Python\'u görünmese bile ŞAŞIRMA ve "python yok" DEME — python_run aracı kendi taşınabilir runtime\'ını (%APPDATA%\\beast\\py\\python.exe) otomatik indirir/kullanır ve bu klasör run_command PATH\'inde önceliklidir; yani run_command içinde de `python` çalışır. Ham Google/Bing scrape yerine önce web_search aracını kullan (zaten Python çoklu-motor + Exa destekli), script gerektiğinde python_run yaz.',
|
|
1304
|
+
'PDF ÇIKTI KURALI: PDF üretirken pip\u2019ten pdf paketi (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) KURMA/KULLANMA — bunlar Türkçe karakterleri bozar. Doğru kit Node tarafında ZATEN kurulu: `pdf-lib` + `@pdf-lib/fontkit` (Türkçe font gömme) ve `pdfkit`. python_run ile DEĞİL; write_file ile .js script yazıp run_command ile `node script.js` çalıştır. md→pdf çevirici YOKTUR ve kurulmaz: kullanıcıya rapor/özet/belge çıktısı vereceksen .md dosyası gönderme — aynı içeriği DOĞRUDAN pdf-lib/pdfkit ile PDF olarak üret ve send_file ile o PDF\u2019i gönder. Ayrıntılı örnekler: pdf skill\u2019i (SKILL.md).',
|
|
1303
1305
|
'Eski bir hafıza kaydına ihtiyacın olursa memory_search ile ara; kalıcı bilgi/birikim için kb_search kullan, yeni bilgi öğrenirsen kb_add ile kaynak belirt.',
|
|
1304
1306
|
'Bir oturumda 3+ kez memory_write yaptıysan iş bitince memory_hygiene çağır (duplike/eskime temizliği).',
|
|
1305
1307
|
'BEAST KAYNAK KORUMASI: Beast\u2019in kendi kurulum/kaynak kod klasörü KİLİTLİDİR — oraya dosya yazamaz, silamaz, komut/betikle değiştiremezsin; okumak serbest. Kullanıcı kodu ancak kendi eliyle dışarıdan değiştirir; böyle bir istek gelirse yapamayacağını söyle ve kullanıcının elle yapması için yol göster.',
|
|
@@ -2116,6 +2118,7 @@ class Engine {
|
|
|
2116
2118
|
`- Döngülü işleri (çok URL/dosya/sayfa, tekrarlı parse-hesap) TEK python_run betiğinde topluca bitir.\n` +
|
|
2117
2119
|
`- 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` +
|
|
2118
2120
|
`- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
|
|
2121
|
+
`- PDF gerekirse pip\u2019ten paket kurma (Türkçe bozar); Node\u2019un kurulu \`pdf-lib\`+fontkit\u2019iyle .js script yazıp \`node\` ile çalıştır. md→pdf çevirme: belge çıktısını doğrudan PDF olarak üret.\n` +
|
|
2119
2122
|
`- ARAŞTIRMA SINIRI: 3-5 kaynak yeter; süre hedefi ~3 dakika. 2-3 denemede bulunamayan bilgiyi BIRAK — bulabildiğin kısmi sonucu raporla ve neyi bulamadığını yaz. Kapalı/gizli içerik peşinde koşma.\n` +
|
|
2120
2123
|
FORMAT_RULES + '\n' +
|
|
2121
2124
|
`SON ÇIKTI: 3-5 satırlık net sonuç raporu, madde madde. Soru sorma, sohbet etme.`
|
|
@@ -2695,7 +2698,7 @@ class Engine {
|
|
|
2695
2698
|
];
|
|
2696
2699
|
try {
|
|
2697
2700
|
for (let turn = 0; turn < SUB_MAX_TURNS; turn++) {
|
|
2698
|
-
const res = await
|
|
2701
|
+
const res = await chatStreamAuto(
|
|
2699
2702
|
sel,
|
|
2700
2703
|
{ messages: [{ role: 'system', content: system }, ...msgs.slice(1)], tools: subTools, reasoningEffort: this._thinkEffort() },
|
|
2701
2704
|
{ signal: ctrl.signal }
|
|
@@ -2720,6 +2723,40 @@ class Engine {
|
|
|
2720
2723
|
}
|
|
2721
2724
|
}
|
|
2722
2725
|
|
|
2726
|
+
/* web_search zinciri — web_search VE deep_search aynı zinciri kullanır:
|
|
2727
|
+
1) dahili tarayıcı (DİREK GOOGLE — ücretsiz + AI cevabı)
|
|
2728
|
+
2) TinyFish (tarayıcı sorun çıkarsa HEMEN devreye girer — anahtar girildiyse)
|
|
2729
|
+
3) python çoklu-motor (ddgs / DDG+Bing+Mojeek)
|
|
2730
|
+
4) Exa (anahtar girildiyse, son çare)
|
|
2731
|
+
Tarayıcıda CAPTCHA/"olağandışı trafik"/boş sonuç algılanırsa 10 dk boyunca
|
|
2732
|
+
tarayıcı ATLANIR — her arama TinyFish'ten başlar, süre dolunca tarayıcı geri döner. */
|
|
2733
|
+
async _webSearchChain(q, n, sessionId, signal) {
|
|
2734
|
+
let r = null;
|
|
2735
|
+
let banned = false;
|
|
2736
|
+
const browserUsable = this.browser && typeof this.browser.search === 'function';
|
|
2737
|
+
if (Date.now() < (this._webSearchBrowserBanUntil || 0)) banned = true;
|
|
2738
|
+
if (browserUsable && !banned) {
|
|
2739
|
+
try { r = await this.browser.search(q, signal, { sessionId }); } catch {}
|
|
2740
|
+
if (!r || !r.ok || !(r.results || []).length) {
|
|
2741
|
+
/* tarayıcı sorunlu — 10 dk atla, alternatif zincir hemen devrede */
|
|
2742
|
+
this._webSearchBrowserBanUntil = Date.now() + 10 * 60 * 1000;
|
|
2743
|
+
r = null;
|
|
2744
|
+
banned = true;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
if (!r || !r.ok || !(r.results || []).length) {
|
|
2748
|
+
try { r = await tools.tinyfishSearch(q, n, signal); } catch {}
|
|
2749
|
+
}
|
|
2750
|
+
if (!r || !r.ok || !(r.results || []).length) {
|
|
2751
|
+
try {
|
|
2752
|
+
r = JSON.parse(await tools.exec('web_search', { query: q, max_results: n }, { cwd: this._sessionWorkspace(sessionId), signal }));
|
|
2753
|
+
} catch {}
|
|
2754
|
+
}
|
|
2755
|
+
const out = r || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
|
|
2756
|
+
if (out && out.ok && banned) out.note = 'tarayıcı araması 10 dk askıda (CAPTCHA/trafik) — TinyFish/alternatif zincir kullanıldı';
|
|
2757
|
+
return out;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2723
2760
|
/* Dahili OCR aracı: görüntüden metin çıkarır (görsel desteklemeyen modeller için).
|
|
2724
2761
|
Kaynak: 'browser' (dahili tarayıcı görüntüsü, varsayılan) | 'screen' (masaüstü) | dosya yolu. */
|
|
2725
2762
|
async _ocrRead(args, signal, sessionId) {
|
|
@@ -3070,49 +3107,26 @@ class Engine {
|
|
|
3070
3107
|
}
|
|
3071
3108
|
if (name === 'deep_search') {
|
|
3072
3109
|
/* agentic derin araştırma: çoklu sorgu + gizli tarayıcıda sayfa okuma.
|
|
3073
|
-
|
|
3110
|
+
ARAMA zinciri web_search ile BİREBİR AYNI — eski sürüm yalnız python
|
|
3111
|
+
motorlarını (ddgs/DDG/Bing) kullanıyordu; CAPTCHA/engel sonucu motorlar
|
|
3112
|
+
boş dönünce "tüm motorlar boş döndü" hatası veriyordu. Artık önce
|
|
3113
|
+
DAHİLİ TARAYICI (Google) denenir, sonra TinyFish → python → Exa. */
|
|
3074
3114
|
try {
|
|
3075
3115
|
emitSafe(this, sessionId, { type: 'status', status: 'derin araştırma: çoklu sorgu + gizli sayfa okuma' });
|
|
3076
3116
|
} catch {}
|
|
3077
|
-
const
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3117
|
+
const deps = {
|
|
3118
|
+
search: (q) => this._webSearchChain(q, 10, sessionId, signal),
|
|
3119
|
+
};
|
|
3120
|
+
if (this.research && typeof this.research.readPage === 'function') {
|
|
3121
|
+
deps.readPage = (u) => this.research.readPage(u, signal);
|
|
3122
|
+
}
|
|
3123
|
+
return JSON.stringify(await research.deepSearch(args || {}, deps, signal));
|
|
3081
3124
|
}
|
|
3082
3125
|
if (name === 'web_search') {
|
|
3083
|
-
/* SIRA: 1) dahili tarayıcı (DİREK GOOGLE — ücretsiz + AI cevabı)
|
|
3084
|
-
2) TinyFish (tarayıcı sorun çıkarsa HEMEN devreye girer — anahtar girildiyse)
|
|
3085
|
-
3) python çoklu-motor (ddgs / DDG+Bing+Mojeek)
|
|
3086
|
-
4) Exa (anahtar girildiyse, son çare)
|
|
3087
|
-
Tarayıcıda CAPTCHA/"olağandışı trafik"/boş sonuç algılanırsa 10 dk boyunca
|
|
3088
|
-
tarayıcı ATLANIR — her arama TinyFish'ten başlar, süre dolunca tarayıcı geri döner. */
|
|
3089
3126
|
const q = String((args && args.query) || '');
|
|
3090
3127
|
const n = Number((args && args.max_results) || 8);
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
const browserUsable = this.browser && typeof this.browser.search === 'function';
|
|
3094
|
-
if (Date.now() < (this._webSearchBrowserBanUntil || 0)) banned = true;
|
|
3095
|
-
if (browserUsable && !banned) {
|
|
3096
|
-
try { r = await this.browser.search(q, signal, { sessionId }); } catch {}
|
|
3097
|
-
if (!r || !r.ok || !(r.results || []).length) {
|
|
3098
|
-
/* tarayıcı sorunlu — 10 dk atla, alternatif zincir hemen devrede */
|
|
3099
|
-
this._webSearchBrowserBanUntil = Date.now() + 10 * 60 * 1000;
|
|
3100
|
-
r = null;
|
|
3101
|
-
banned = true;
|
|
3102
|
-
}
|
|
3103
|
-
}
|
|
3104
|
-
if (!r || !r.ok || !(r.results || []).length) {
|
|
3105
|
-
try { r = await tools.tinyfishSearch(q, n, signal); } catch {}
|
|
3106
|
-
}
|
|
3107
|
-
if (!r || !r.ok || !(r.results || []).length) {
|
|
3108
|
-
try {
|
|
3109
|
-
r = JSON.parse(await tools.exec('web_search', args, { cwd: this._sessionWorkspace(sessionId), signal }));
|
|
3110
|
-
} catch {}
|
|
3111
|
-
}
|
|
3112
|
-
const out = r || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
|
|
3113
|
-
if (out && out.ok && banned) out.note = 'tarayıcı araması 10 dk askıda (CAPTCHA/trafik) — TinyFish/alternatif zincir kullanıldı';
|
|
3114
|
-
return JSON.stringify(out);
|
|
3115
|
-
}
|
|
3128
|
+
return JSON.stringify(await this._webSearchChain(q, n, sessionId, signal));
|
|
3129
|
+
}
|
|
3116
3130
|
if (name === 'ocr_read') {
|
|
3117
3131
|
return JSON.stringify(await this._ocrRead(args || {}, signal, sessionId));
|
|
3118
3132
|
}
|
package/src/agent/llm.js
CHANGED
|
@@ -188,6 +188,51 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, omitReas
|
|
|
188
188
|
return { content, reasoning, toolCalls, usage, finishReason };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/* usage toplama: devam turları gerçek faturalamayı yansıtsın (prompt yeniden sayılır) */
|
|
192
|
+
function sumUsage(a, b) {
|
|
193
|
+
if (!a) return b || null;
|
|
194
|
+
if (!b) return a;
|
|
195
|
+
const out = {};
|
|
196
|
+
for (const k of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
197
|
+
if (typeof a[k] === 'number' && typeof b[k] === 'number') out[k] = a[k] + b[k];
|
|
198
|
+
else out[k] = b[k] !== undefined ? b[k] : a[k];
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* OTOMATİK DEVAM: model cevabı çıkış limiti (finish_reason='length') yüzünden
|
|
204
|
+
kelime ortasında kesilirse — özellikle free modellerin düşük varsayılan
|
|
205
|
+
limitlerinde olur — yarım cümle kullanıcıya gitmesin. Kaldığı yerden devam
|
|
206
|
+
istenip metin birleştirilir (en fazla 2 devam turu). Araç çağrılı yanıtlarda
|
|
207
|
+
ve içerik hiç gelmediyse devreye girmez; orijinal mesaj dizisi DEĞİŞTİRİLMEZ. */
|
|
208
|
+
const CONTINUE_PROMPT =
|
|
209
|
+
'Yanıtın çıkış limiti nedeniyle ortadan kesildi. Kaldığın yerden DEVAM ET — baştan başlama, özetleme, açıklama yapma; yalnızca kesilen kısmın DEVAMINI yaz.';
|
|
210
|
+
|
|
211
|
+
async function chatStreamAuto(sel, body, opts = {}) {
|
|
212
|
+
const first = await chatStream(sel, body, opts);
|
|
213
|
+
if (first.finishReason !== 'length' || !first.content || (first.toolCalls && first.toolCalls.length)) {
|
|
214
|
+
return first;
|
|
215
|
+
}
|
|
216
|
+
let full = first.content;
|
|
217
|
+
let last = first;
|
|
218
|
+
for (let i = 0; i < 2; i++) {
|
|
219
|
+
const msgs2 = body.messages.slice();
|
|
220
|
+
msgs2.push({ role: 'assistant', content: full });
|
|
221
|
+
msgs2.push({ role: 'user', content: CONTINUE_PROMPT });
|
|
222
|
+
let cont;
|
|
223
|
+
try {
|
|
224
|
+
cont = await chatStream(sel, { ...body, messages: msgs2 }, opts);
|
|
225
|
+
} catch {
|
|
226
|
+
break; // devam turu patlarsa elimizdeki kısmi yanıtla döneriz
|
|
227
|
+
}
|
|
228
|
+
if (!cont.content || (cont.toolCalls && cont.toolCalls.length)) break;
|
|
229
|
+
full += cont.content;
|
|
230
|
+
last = { ...cont, content: full, usage: sumUsage(first.usage, cont.usage) };
|
|
231
|
+
if (cont.finishReason !== 'length') break;
|
|
232
|
+
}
|
|
233
|
+
return last;
|
|
234
|
+
}
|
|
235
|
+
|
|
191
236
|
async function chatOnce(sel, body, { signal, onRetry } = {}) {
|
|
192
237
|
const res = await withRetries(
|
|
193
238
|
async () => {
|
|
@@ -216,4 +261,4 @@ async function chatOnce(sel, body, { signal, onRetry } = {}) {
|
|
|
216
261
|
};
|
|
217
262
|
}
|
|
218
263
|
|
|
219
|
-
module.exports = { chatStream, chatOnce, withRetries, friendlyError };
|
|
264
|
+
module.exports = { chatStream, chatStreamAuto, chatOnce, withRetries, friendlyError, sumUsage };
|
package/src/agent/skills.js
CHANGED
|
@@ -232,14 +232,21 @@ Zorunlu kurallar:
|
|
|
232
232
|
},
|
|
233
233
|
{
|
|
234
234
|
folder: 'pdf',
|
|
235
|
+
force: true,
|
|
235
236
|
body: `---
|
|
236
237
|
name: pdf
|
|
237
238
|
description: PDF işleme rehberi — metin çıkarma (pdf-parse), Türkçe karakterli PDF oluşturma/değiştirme (pdf-lib/pdfkit), sayfa→görsel (pdf-to-img) ve taranmış PDF için OCR (tesseract.js). Kullanıcı bir .pdf dosyasından bahsederse ÖNCE bu skill'i oku.
|
|
238
|
-
version: 1.
|
|
239
|
+
version: 1.1.0
|
|
239
240
|
---
|
|
240
241
|
|
|
241
242
|
# PDF İşleme Rehberi
|
|
242
243
|
|
|
244
|
+
## EN KRİTİK KURALLAR (önce bunlar)
|
|
245
|
+
|
|
246
|
+
1. Türkçe karakterli PDF için pip\u2019ten paket KURMA/KULLANMA (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) — bunlar ğ ş ı İ karakterlerini bozar. Doğru kit Node\u2019ta ZATEN kurulu: \`pdf-lib\` + \`@pdf-lib/fontkit\` ve \`pdfkit\`. Script\u2019i write_file ile .js yaz, run_command ile \`node script.js\` çalıştır (python_run İLE DEĞİL).
|
|
247
|
+
2. md→pdf çevirici YOKTUR ve kurulmaz — kullanıcıya rapor/özet/belge çıktısı vereceksen .md gönderme; aynı içeriği doğrudan PDF olarak ÜRET ve o dosyayı send_file ile gönder.
|
|
248
|
+
3. \`pdf-parse\` ile \`pdf-to-img\` aynı process\u2019e yüklenmez (aşağıdaki çakışma kuralı).
|
|
249
|
+
|
|
243
250
|
Kurulu paketler ve rolleri:
|
|
244
251
|
|
|
245
252
|
| İstek | Paket |
|
package/src/agent/tools.js
CHANGED
|
@@ -501,21 +501,27 @@ async function fetchWithTimeout(url, opts = {}, timeoutMs = 20000, signal) {
|
|
|
501
501
|
async function webSearch(query, { maxResults = 8, signal } = {}) {
|
|
502
502
|
const q = String(query || '').trim().slice(0, 400);
|
|
503
503
|
if (!q) return { ok: false, error: 'boş sorgu' };
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
504
|
+
let res;
|
|
505
|
+
try {
|
|
506
|
+
res = await fetchWithTimeout(
|
|
507
|
+
'https://html.duckduckgo.com/html/',
|
|
508
|
+
{
|
|
509
|
+
method: 'POST',
|
|
510
|
+
headers: {
|
|
511
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
512
|
+
'User-Agent': UA,
|
|
513
|
+
Accept: 'text/html',
|
|
514
|
+
},
|
|
515
|
+
body: new URLSearchParams({ q }).toString(),
|
|
512
516
|
},
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
517
|
+
20000,
|
|
518
|
+
signal
|
|
519
|
+
);
|
|
520
|
+
} catch (e) {
|
|
521
|
+
/* ağ hatası/rate-limit exception fırlatmasın — zincir devam edebilsin */
|
|
522
|
+
return { ok: false, error: 'DDG erişilemedi: ' + String((e && e.message) || e) };
|
|
523
|
+
}
|
|
524
|
+
if (!res.ok) return { ok: false, error: `DDG HTTP ${res.status}` };
|
|
519
525
|
const html = await res.text();
|
|
520
526
|
const results = parseDdgResults(html, Math.min(Math.max(Number(maxResults) || 8, 1), 12));
|
|
521
527
|
if (!results.length) {
|
package/src/main.js
CHANGED
|
@@ -149,8 +149,7 @@ function startNpmUpdateWatch() {
|
|
|
149
149
|
check();
|
|
150
150
|
setInterval(check, 6 * 60 * 60 * 1000);
|
|
151
151
|
}
|
|
152
|
-
const { htmlToText, setExaKey, setTinyfishKey
|
|
153
|
-
const research = require('./agent/research');
|
|
152
|
+
const { htmlToText, setExaKey, setTinyfishKey } = require('./agent/tools');
|
|
154
153
|
const { waToolLine } = require('./agent/watext');
|
|
155
154
|
|
|
156
155
|
/* #3 merkezî log sistemine process-seviye hataları da düşsün */
|
|
@@ -1184,7 +1183,7 @@ async function waFlush(jid) {
|
|
|
1184
1183
|
if (payload.participant && !merged.participant) merged.participant = payload.participant;
|
|
1185
1184
|
if (payload.participantPn && !merged.participantPn) merged.participantPn = payload.participantPn;
|
|
1186
1185
|
if (payload.participantUsername && !merged.participantUsername) merged.participantUsername = payload.participantUsername;
|
|
1187
|
-
if (payload.mentioned) merged.mentioned = true;
|
|
1186
|
+
if (payload.mentioned) merged.mentioned = true;
|
|
1188
1187
|
if (!senderNum && sn) senderNum = sn;
|
|
1189
1188
|
}
|
|
1190
1189
|
try {
|
|
@@ -2100,13 +2099,9 @@ function reloadBackend() {
|
|
|
2100
2099
|
act: (k, a, s) => browserAct(k, a, s),
|
|
2101
2100
|
},
|
|
2102
2101
|
research: {
|
|
2103
|
-
/* deep_search
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
research.deepSearch(args || {}, {
|
|
2107
|
-
search: (q) => webSearchFast(q, { maxResults: 10, signal }),
|
|
2108
|
-
readPage: (u) => researchRead(u, signal),
|
|
2109
|
-
}, signal),
|
|
2102
|
+
/* deep_search'ün "sayfayı GİZLİ tarayıcıda açıp oku" parçası (Electron-only).
|
|
2103
|
+
Arama zinciri engine._webSearchChain içinde web_search ile birebir aynı. */
|
|
2104
|
+
readPage: (u, s) => researchRead(u, s),
|
|
2110
2105
|
},
|
|
2111
2106
|
emit: (ev) => {
|
|
2112
2107
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
@@ -2598,26 +2593,13 @@ function browserGate(job) {
|
|
|
2598
2593
|
__trafficTail = p.catch(() => {});
|
|
2599
2594
|
return p;
|
|
2600
2595
|
}
|
|
2601
|
-
/*
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
} catch {
|
|
2609
|
-
return false;
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
|
|
2613
|
-
/* bg ajan için açma stratejisi: kapalıysa GİZLİ aç; zaten açıksa mevcut
|
|
2614
|
-
görünürlüğe dokunma (kullanıcı paneli izliyorsa aniden saklamayalım) */
|
|
2615
|
-
function setBrowserOpenForAgent(ctx) {
|
|
2616
|
-
if (isBgBrowserCtx(ctx)) {
|
|
2617
|
-
if (!browser.open) setBrowserOpen(true, false);
|
|
2618
|
-
return;
|
|
2619
|
-
}
|
|
2620
|
-
setBrowserOpen(true);
|
|
2596
|
+
/* AJAN TARAYICI STRATEJİSİ — TÜM oturumlar (ana sohbet + WhatsApp botları + paralel ajanlar):
|
|
2597
|
+
ajan tarayıcıyı KENDİ açıyorsa hep GİZLİ açılır — panel ekrana fırlamaz,
|
|
2598
|
+
Paralel Ajan Konsolu kapanmaz. Kullanıcı izlemek isterse tarayıcı düğmesine
|
|
2599
|
+
basar (browser:toggle gizli paneli görünür kılar). Zaten açıksa (kullanıcı
|
|
2600
|
+
paneli açık tutuyorsa) görünürlüğe dokunulmaz. */
|
|
2601
|
+
function setBrowserOpenForAgent() {
|
|
2602
|
+
if (!browser.open) setBrowserOpen(true, false);
|
|
2621
2603
|
}
|
|
2622
2604
|
|
|
2623
2605
|
async function browserNavigate(raw, signal, ctx) {
|
|
@@ -2636,7 +2618,7 @@ async function browserNavigateNow(raw, signal, ctx) {
|
|
|
2636
2618
|
? 'https://' + url
|
|
2637
2619
|
: 'https://duckduckgo.com/?q=' + encodeURIComponent(url);
|
|
2638
2620
|
}
|
|
2639
|
-
setBrowserOpenForAgent(
|
|
2621
|
+
setBrowserOpenForAgent();
|
|
2640
2622
|
const wc = browser.view.webContents;
|
|
2641
2623
|
await new Promise((resolve) => {
|
|
2642
2624
|
let settled = false;
|
|
@@ -2863,7 +2845,7 @@ async function browserSearchNow(query, signal, ctx) {
|
|
|
2863
2845
|
if (signal && signal.aborted) return null;
|
|
2864
2846
|
const q = String(query || '').trim();
|
|
2865
2847
|
if (!q) return null;
|
|
2866
|
-
setBrowserOpenForAgent(
|
|
2848
|
+
setBrowserOpenForAgent();
|
|
2867
2849
|
const wc = browser.view && browser.view.webContents;
|
|
2868
2850
|
if (!wc) return null;
|
|
2869
2851
|
|
|
@@ -4430,7 +4412,7 @@ ipcMain.handle('update:install', () => {
|
|
|
4430
4412
|
if (!isNpmMode()) return { ok: false, error: NPM_ONLY_TEXT };
|
|
4431
4413
|
npmUpdateViaCmd();
|
|
4432
4414
|
return { ok: true, npm: true };
|
|
4433
|
-
});
|
|
4415
|
+
});
|
|
4434
4416
|
|
|
4435
4417
|
ipcMain.handle('update:setAuto', (_e, cfg) => {
|
|
4436
4418
|
if (cfg && typeof cfg.autoCheck === 'boolean') settings.autoCheckUpdate = cfg.autoCheck;
|
|
@@ -4665,7 +4647,12 @@ ipcMain.handle('cron:runNow', (_e, id) => {
|
|
|
4665
4647
|
|
|
4666
4648
|
/* ---------------- tarayıcı IPC ---------------- */
|
|
4667
4649
|
ipcMain.handle('browser:toggle', () => {
|
|
4668
|
-
/*
|
|
4650
|
+
/* gizli çalışan ajan paneli varsa düğme ONU GÖRÜNÜR yapar; değilse aç/kapa.
|
|
4651
|
+
(ajanlar tarayıcıyı hep gizli açar — kullanıcı izlemek isterse buradan gösterir) */
|
|
4652
|
+
if (browser.open && !browser.visible) {
|
|
4653
|
+
setBrowserOpen(true, true);
|
|
4654
|
+
return { open: browser.open, visible: browser.visible };
|
|
4655
|
+
}
|
|
4669
4656
|
setBrowserOpen(!browser.open, true);
|
|
4670
4657
|
return { open: browser.open, visible: browser.visible };
|
|
4671
4658
|
});
|
|
@@ -5322,12 +5309,15 @@ function runNpmGlobalInstall() {
|
|
|
5322
5309
|
});
|
|
5323
5310
|
}
|
|
5324
5311
|
|
|
5325
|
-
/* auth login etkileşimlidir (tarayıcı açar) — kullanıcı görsün diye ayrı pencere
|
|
5312
|
+
/* auth login etkileşimlidir (tarayıcı açar) — kullanıcı görsün diye ayrı pencere.
|
|
5313
|
+
-ExecutionPolicy Bypass: SADECE bu pencere için — Windows varsayılan "Restricted"
|
|
5314
|
+
politikası npm'in opencode.ps1 shim'ini bloklar ("running scripts is disabled")
|
|
5315
|
+
→ taze makinede tek tık kurulum patlamasın; sistem politikası DEĞİŞMEZ. */
|
|
5326
5316
|
function openZenAuthWindow() {
|
|
5327
5317
|
try {
|
|
5328
5318
|
spawn(
|
|
5329
5319
|
'cmd.exe',
|
|
5330
|
-
['/c', 'start', '', 'powershell.exe', '-NoExit', '-Command', 'opencode auth login'],
|
|
5320
|
+
['/c', 'start', '', 'powershell.exe', '-NoExit', '-ExecutionPolicy', 'Bypass', '-Command', 'opencode auth login'],
|
|
5331
5321
|
{ detached: true, windowsHide: false, stdio: 'ignore' }
|
|
5332
5322
|
).unref();
|
|
5333
5323
|
return true;
|
package/tests/llm.test.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require('./setup');
|
|
4
4
|
const test = require('node:test');
|
|
5
5
|
const assert = require('node:assert');
|
|
6
|
-
const { withRetries, friendlyError } = require('../src/agent/llm');
|
|
6
|
+
const { withRetries, friendlyError, sumUsage, chatStreamAuto } = require('../src/agent/llm');
|
|
7
7
|
|
|
8
8
|
test('withRetries: 503 sonrası başarılı isteği tekrarlar', async () => {
|
|
9
9
|
let calls = 0;
|
|
@@ -54,3 +54,83 @@ test('friendlyError ipuçları içerir', () => {
|
|
|
54
54
|
assert.match(m, /kullanılamıyor/);
|
|
55
55
|
assert.match(m, /model seçici/);
|
|
56
56
|
});
|
|
57
|
+
|
|
58
|
+
test('sumUsage: sayısal alanları toplar (devam turu faturalaması)', () => {
|
|
59
|
+
const u = sumUsage(
|
|
60
|
+
{ prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
|
61
|
+
{ prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }
|
|
62
|
+
);
|
|
63
|
+
assert.deepEqual(u, { prompt_tokens: 18, completion_tokens: 8, total_tokens: 26 });
|
|
64
|
+
assert.equal(sumUsage(null, { total_tokens: 3 }).total_tokens, 3);
|
|
65
|
+
assert.equal(sumUsage({ total_tokens: 7 }, null).total_tokens, 7);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/* SSE mock sunucusu: finish_reason=length → otomatik devam turu açılır */
|
|
69
|
+
function sseServer(script) {
|
|
70
|
+
const http = require('node:http');
|
|
71
|
+
const calls = [];
|
|
72
|
+
const srv = http.createServer((req, res) => {
|
|
73
|
+
let body = '';
|
|
74
|
+
req.on('data', (c) => (body += c));
|
|
75
|
+
req.on('end', () => {
|
|
76
|
+
calls.push(JSON.parse(body).messages);
|
|
77
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream' });
|
|
78
|
+
for (const ev of script(calls.length, calls[calls.length - 1])) res.write(ev);
|
|
79
|
+
res.end();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
return { srv, calls };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test('chatStreamAuto: length kesintisinde kaldığı yerden devam eder', async () => {
|
|
86
|
+
const { srv, calls } = sseServer((n) => {
|
|
87
|
+
if (n === 1) {
|
|
88
|
+
return [
|
|
89
|
+
'data: ' + JSON.stringify({ choices: [{ delta: { content: 'Merhaba dun' } }] }) + '\n\n',
|
|
90
|
+
'data: ' + JSON.stringify({ choices: [{ delta: { content: 'ya basladi' }, finish_reason: 'length' }] }) + '\n\n',
|
|
91
|
+
'data: [DONE]\n\n',
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
return [
|
|
95
|
+
'data: ' + JSON.stringify({ choices: [{ delta: { content: 'yor devam' } }] }) + '\n\n',
|
|
96
|
+
'data: ' + JSON.stringify({ choices: [{ delta: {}, finish_reason: 'stop' }] }) + '\n\n',
|
|
97
|
+
'data: [DONE]\n\n',
|
|
98
|
+
];
|
|
99
|
+
});
|
|
100
|
+
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
|
|
101
|
+
try {
|
|
102
|
+
const port = srv.address().port;
|
|
103
|
+
const sel = { url: `http://127.0.0.1:${port}/v1/chat/completions`, key: 'k', model: 'm' };
|
|
104
|
+
const deltas = [];
|
|
105
|
+
const r = await chatStreamAuto(sel, { messages: [{ role: 'user', content: 'selam' }] }, { onDelta: (d) => deltas.push(d) });
|
|
106
|
+
assert.equal(r.content, 'Merhaba dunya basladiyor devam');
|
|
107
|
+
assert.equal(r.finishReason, 'stop');
|
|
108
|
+
assert.equal(calls.length, 2);
|
|
109
|
+
// 2. turda devam mesajları eklenmiş olmalı (orijinal dizi kopyalanır)
|
|
110
|
+
const m2 = calls[1];
|
|
111
|
+
assert.equal(m2[m2.length - 2].role, 'assistant');
|
|
112
|
+
assert.equal(m2[m2.length - 1].role, 'user');
|
|
113
|
+
assert.match(m2[m2.length - 1].content, /DEVAM/);
|
|
114
|
+
// streaming kesintisiz: 3 delta
|
|
115
|
+
assert.deepEqual(deltas.join(''), r.content);
|
|
116
|
+
} finally {
|
|
117
|
+
srv.close();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('chatStreamAuto: normal (stop) yanıtta hiç devam turu açılmaz', async () => {
|
|
122
|
+
const { srv, calls } = sseServer(() => [
|
|
123
|
+
'data: ' + JSON.stringify({ choices: [{ delta: { content: 'tam cevap' }, finish_reason: 'stop' }] }) + '\n\n',
|
|
124
|
+
'data: [DONE]\n\n',
|
|
125
|
+
]);
|
|
126
|
+
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
|
|
127
|
+
try {
|
|
128
|
+
const port = srv.address().port;
|
|
129
|
+
const sel = { url: `http://127.0.0.1:${port}/v1/chat/completions`, key: 'k', model: 'm' };
|
|
130
|
+
const r = await chatStreamAuto(sel, { messages: [{ role: 'user', content: 'selam' }] });
|
|
131
|
+
assert.equal(r.content, 'tam cevap');
|
|
132
|
+
assert.equal(calls.length, 1);
|
|
133
|
+
} finally {
|
|
134
|
+
srv.close();
|
|
135
|
+
}
|
|
136
|
+
});
|
package/tests/python.test.js
CHANGED
|
@@ -48,8 +48,9 @@ test('paketlenmiş python scriptleri mevcut ve tohumlanır', () => {
|
|
|
48
48
|
/* canlı: python hızlı arama yolu (ağ varsa gerçek sonuç, yoksa JS fallback) */
|
|
49
49
|
test('webSearchFast: python yolu ya da fallback sonuç döner', async function () {
|
|
50
50
|
const r = await tools.webSearchFast('electron builder portable windows', { maxResults: 5 });
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
/* rate-limit/CAPTCHA turlarında motorlar ok:false dönebilir — kablaj testi;
|
|
52
|
+
ağ nazik davranınca sonuç şekli doğrulanır, aksi halde skip */
|
|
53
|
+
if (!r || !r.ok || !r.results.length) return this.skip('motorlar bu turda sonuç döndürmedi (ağ/rate-limit) — kablaj doğru');
|
|
53
54
|
assert.ok(r.results[0].title && r.results[0].url);
|
|
54
55
|
}, { timeout: 40000 });
|
|
55
56
|
|