beast-agent 0.25.6 → 0.25.8
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/bots.js +8 -0
- package/src/agent/discord.js +234 -0
- package/src/agent/engine.js +63 -31
- package/src/main.js +312 -4
- package/src/preload.js +8 -0
- package/src/renderer/i18n.js +18 -0
- package/src/renderer/renderer.js +339 -29
- package/tests/bg-jobs.test.js +5 -10
package/package.json
CHANGED
package/src/agent/bots.js
CHANGED
|
@@ -327,6 +327,14 @@ function update(id, patch) {
|
|
|
327
327
|
changes.push('dış tarayıcı komutu güncellendi');
|
|
328
328
|
b.extCommand = String(patch.extCommand).slice(0, 200);
|
|
329
329
|
}
|
|
330
|
+
if (typeof patch.model === 'string') {
|
|
331
|
+
/* bot bazlı model override — boş string = global seçim */
|
|
332
|
+
const next = patch.model.trim().slice(0, 160);
|
|
333
|
+
if (next !== (b.model || '')) {
|
|
334
|
+
changes.push('model güncellendi');
|
|
335
|
+
b.model = next;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
330
338
|
if (Array.isArray(patch.plugins)) {
|
|
331
339
|
const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
|
|
332
340
|
if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* DISCORD KÖPRÜSÜ — Telegram köprüsünün Discord hali.
|
|
4
|
+
Bağımlılık: yalnızca `ws` (paket zaten kurulu — baileys ile geliyor).
|
|
5
|
+
- Gateway WebSocket: Identify → READY → MESSAGE_CREATE dispatch
|
|
6
|
+
- REST (api/v10): mesaj gönderme (2000 karakter sınırında bölerek)
|
|
7
|
+
- Sunucu (guild) mesajlarında yalnız @mention'a cevap verilir (spam koruması);
|
|
8
|
+
DM'de her izinli kullanıcıya cevap verilir.
|
|
9
|
+
- Bağlantı koparsa backoff ile yeniden bağlanır (yeniden Identify;
|
|
10
|
+
çevrimdışıyken gelen mesajlar backfill edilmez — Telegram long-poll'un aksine).
|
|
11
|
+
ÖNEMLİ: Discord Developer Portal'da bot için "MESSAGE CONTENT INTENT"
|
|
12
|
+
açılmalıdır — yoksa mesaj içerikleri boş gelir. */
|
|
13
|
+
|
|
14
|
+
const WebSocket = require('ws');
|
|
15
|
+
const https = require('https');
|
|
16
|
+
|
|
17
|
+
const API_BASE = 'https://discord.com/api/v10';
|
|
18
|
+
const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json';
|
|
19
|
+
const SEND_CHUNK = 1900; // Discord mesaj sınırı 2000 — güvenli pay
|
|
20
|
+
/* GUILDS(1) | GUILD_MESSAGES(512) | DIRECT_MESSAGES(4096) | MESSAGE_CONTENT(32768) */
|
|
21
|
+
const INTENTS = 1 | 512 | 4096 | 32768;
|
|
22
|
+
|
|
23
|
+
class DiscordBridge {
|
|
24
|
+
constructor({ token, emit, onIncoming }) {
|
|
25
|
+
this.token = String(token || '').trim();
|
|
26
|
+
this.emit = emit || (() => {});
|
|
27
|
+
this.onIncoming = onIncoming || null;
|
|
28
|
+
this.connected = false;
|
|
29
|
+
this.stopping = false;
|
|
30
|
+
this.status = 'disconnected';
|
|
31
|
+
this.user = null; // { id, username, ... }
|
|
32
|
+
this._ws = null;
|
|
33
|
+
this._hbTimer = null;
|
|
34
|
+
this._seq = null;
|
|
35
|
+
this._backoff = 2000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_setStatus(status, user) {
|
|
39
|
+
this.status = status;
|
|
40
|
+
this.connected = status === 'connected';
|
|
41
|
+
this.emit({ type: 'status', status, user: user || null });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* REST çağrısı — JSON (token: "Bot <token>") */
|
|
45
|
+
api(method, path, body) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const payload = body ? JSON.stringify(body) : null;
|
|
48
|
+
const req = https.request(
|
|
49
|
+
`${API_BASE}${path}`,
|
|
50
|
+
{
|
|
51
|
+
method,
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
Authorization: 'Bot ' + this.token,
|
|
55
|
+
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
|
|
56
|
+
},
|
|
57
|
+
timeout: 15000,
|
|
58
|
+
},
|
|
59
|
+
(res) => {
|
|
60
|
+
let data = '';
|
|
61
|
+
res.setEncoding('utf8');
|
|
62
|
+
res.on('data', (c) => (data += c));
|
|
63
|
+
res.on('end', () => {
|
|
64
|
+
try {
|
|
65
|
+
const j = data ? JSON.parse(data) : null;
|
|
66
|
+
if (res.statusCode >= 200 && res.statusCode < 300) resolve(j);
|
|
67
|
+
else reject(new Error(`discord ${path}: HTTP ${res.statusCode} ${(j && j.message) || ''}`.trim()));
|
|
68
|
+
} catch {
|
|
69
|
+
reject(new Error(`discord ${path}: bozuk yanıt`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
|
|
75
|
+
req.on('error', reject);
|
|
76
|
+
if (payload) req.write(payload);
|
|
77
|
+
req.end();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async start() {
|
|
82
|
+
if (!this.token) {
|
|
83
|
+
this._setStatus('error');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
this.stopping = false;
|
|
87
|
+
this._setStatus('connecting');
|
|
88
|
+
try {
|
|
89
|
+
const me = await this.api('GET', '/users/@me');
|
|
90
|
+
this.user = me; // { id, username, ... }
|
|
91
|
+
this._setStatus('connected', '@' + (me.username || 'bot'));
|
|
92
|
+
} catch (e) {
|
|
93
|
+
this._setStatus('error');
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
this._connect();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_connect() {
|
|
101
|
+
if (this.stopping) return;
|
|
102
|
+
clearInterval(this._hbTimer);
|
|
103
|
+
this._hbTimer = null;
|
|
104
|
+
let identified = false;
|
|
105
|
+
const ws = new WebSocket(GATEWAY_URL);
|
|
106
|
+
this._ws = ws;
|
|
107
|
+
|
|
108
|
+
ws.on('message', (raw) => {
|
|
109
|
+
let p = null;
|
|
110
|
+
try { p = JSON.parse(String(raw)); } catch { return; }
|
|
111
|
+
const op = p.op;
|
|
112
|
+
const d = p.d;
|
|
113
|
+
const t = p.t;
|
|
114
|
+
if (typeof p.s === 'number') this._seq = p.s;
|
|
115
|
+
|
|
116
|
+
if (op === 10) {
|
|
117
|
+
/* Hello: heartbeat aralığı gelıyor → hafif erken at (güvenli pay) */
|
|
118
|
+
const iv = (d && d.heartbeat_interval) || 41250;
|
|
119
|
+
clearInterval(this._hbTimer);
|
|
120
|
+
this._hbTimer = setInterval(() => {
|
|
121
|
+
try {
|
|
122
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
|
|
123
|
+
} catch {}
|
|
124
|
+
}, Math.max(5000, Math.floor(iv * 0.8)));
|
|
125
|
+
if (!identified) {
|
|
126
|
+
identified = true;
|
|
127
|
+
try {
|
|
128
|
+
ws.send(
|
|
129
|
+
JSON.stringify({
|
|
130
|
+
op: 2,
|
|
131
|
+
d: {
|
|
132
|
+
token: this.token,
|
|
133
|
+
intents: INTENTS,
|
|
134
|
+
properties: { os: 'windows', browser: 'beast-agent', device: 'beast-agent' },
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
} catch {}
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (op === 1) {
|
|
143
|
+
/* sunucu heartbeat istedi — hemen at */
|
|
144
|
+
try {
|
|
145
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
|
|
146
|
+
} catch {}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (op === 9 || op === 7) {
|
|
150
|
+
/* invalid session / reconnect isteği — kapat, close handler yeniden bağlanır */
|
|
151
|
+
try { ws.close(); } catch {}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (op === 0 && t === 'READY') {
|
|
155
|
+
this._backoff = 2000;
|
|
156
|
+
this._setStatus('connected', '@' + ((d && d.user && d.user.username) || 'bot'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (op === 0 && t === 'MESSAGE_CREATE' && this.onIncoming) {
|
|
160
|
+
try {
|
|
161
|
+
const m = d || {};
|
|
162
|
+
if (!m.author || m.author.bot || m.webhook_id) return;
|
|
163
|
+
let text = String(m.content || '').trim();
|
|
164
|
+
if (!text) return;
|
|
165
|
+
const botId = this.user && this.user.id;
|
|
166
|
+
const mentioned = !!(botId && text.includes('<@' + botId + '>'));
|
|
167
|
+
if (botId) text = text.split('<@' + botId + '>').join(' ').replace(/\s+/g, ' ').trim();
|
|
168
|
+
const payload = {
|
|
169
|
+
text: text.slice(0, 6000),
|
|
170
|
+
senderId: String((m.author && m.author.id) || ''),
|
|
171
|
+
username: String((m.author && m.author.username) || ''),
|
|
172
|
+
senderName: String((m.author && (m.author.global_name || m.author.username)) || ''),
|
|
173
|
+
isGroup: !!m.guild_id,
|
|
174
|
+
mentioned,
|
|
175
|
+
channelId: String(m.channel_id || ''),
|
|
176
|
+
};
|
|
177
|
+
if (!payload.channelId) return;
|
|
178
|
+
/* Sunucu mesajlarında yalnız @mention (spam koruması); DM'de hepsi */
|
|
179
|
+
if (payload.isGroup && !mentioned) return;
|
|
180
|
+
this.onIncoming(payload.channelId, payload);
|
|
181
|
+
} catch {}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
ws.on('close', () => {
|
|
187
|
+
clearInterval(this._hbTimer);
|
|
188
|
+
this._hbTimer = null;
|
|
189
|
+
if (this.stopping) return;
|
|
190
|
+
this._setStatus('connecting');
|
|
191
|
+
const wait = this._backoff;
|
|
192
|
+
this._backoff = Math.min(30000, this._backoff * 2);
|
|
193
|
+
setTimeout(() => this._connect(), wait);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
ws.on('error', () => {
|
|
197
|
+
/* close tetiklenir — yeniden bağlanma orada */
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async stop() {
|
|
202
|
+
this.stopping = true;
|
|
203
|
+
clearInterval(this._hbTimer);
|
|
204
|
+
this._hbTimer = null;
|
|
205
|
+
try {
|
|
206
|
+
if (this._ws) this._ws.close();
|
|
207
|
+
} catch {}
|
|
208
|
+
this._ws = null;
|
|
209
|
+
this.connected = false;
|
|
210
|
+
this.status = 'disconnected';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
snapshot() {
|
|
214
|
+
return {
|
|
215
|
+
status: this.status,
|
|
216
|
+
user: this.user ? '@' + (this.user.username || 'bot') : null,
|
|
217
|
+
connected: this.connected,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Metin gönder — 2000 karakter sınırı için parçalara böl */
|
|
222
|
+
async send(channelId, text) {
|
|
223
|
+
const t = String(text || '');
|
|
224
|
+
if (!t.trim() || !channelId) return false;
|
|
225
|
+
const chunks = [];
|
|
226
|
+
for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
|
|
227
|
+
for (const part of chunks) {
|
|
228
|
+
await this.api('POST', `/channels/${channelId}/messages`, { content: part });
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = { DiscordBridge };
|
package/src/agent/engine.js
CHANGED
|
@@ -35,9 +35,10 @@ const SUP_IDLE_MIN = 2; // ...bu kadar süredir hiç aktivite yoksa TAKILDI
|
|
|
35
35
|
const SUP_LONG_MIN = 6; // bu kadar süredir koşuyorsa ara kontrol nödü
|
|
36
36
|
const SUP_NUDGE_COOLDOWN_MIN = 4; // aynı iş için iki uyarı arası min süre
|
|
37
37
|
const BG_FIX_MAX = 2; // paralel ajanın otomatik öz-kurtarma hakkı (bitince CEO devreye girer)
|
|
38
|
-
/*
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
/* SÜRE SINIRI YOK: bg ajanlar dakika bazında kesilmez. Sonsuz döngü koruması
|
|
39
|
+
üç katmanla sağlanır: 1) aktivite yoksa öz-kurtarma + CEO (stuck denetimi),
|
|
40
|
+
2) MAX_TURNS sert tur tavanı, 3) tur limitine yaklaşınca zarif "raporu yaz"
|
|
41
|
+
uyarısı (_run içinde). Dakika bazlı wrap-up KALDIRILDI (BG_WRAP_MIN). */
|
|
41
42
|
/* #5 hız: eşzamanlı arka plan LLM turu sınırı — rate-limit yemeden maksimum paralellik */
|
|
42
43
|
const BG_MAX_CONCURRENT_DEFAULT = 4;
|
|
43
44
|
/* #17 kalıcı başarısızlıkta owner'a anlık uyarı */
|
|
@@ -203,6 +204,7 @@ class Engine {
|
|
|
203
204
|
this.lockdown = !!opts.lockdown; // varsayılan kısıt (oturum bazlı override edilmezse)
|
|
204
205
|
this.sessionPerm = new Map(); // sessionId -> ['web'] | ['web','read'] | ['chat'] (kişi/bot bazlı izin)
|
|
205
206
|
this.sessionTools = new Map(); // sessionId -> Set(araç adları) — bot skill kısıtı
|
|
207
|
+
this.sessionModel = new Map(); // sessionId -> chain entry — bot bazlı model override (setSessionModel)
|
|
206
208
|
this.resolveBot = opts.resolveBot || null; // botId -> bot bilgisi (main enjekte eder)
|
|
207
209
|
/* bot oturumu hafıza köprüsü: botun kendi SOUL/USER/MEMORY dosyaları */
|
|
208
210
|
this.botMemory = opts.botMemory || null;
|
|
@@ -334,6 +336,16 @@ class Engine {
|
|
|
334
336
|
else this.sessionTools.delete(id);
|
|
335
337
|
}
|
|
336
338
|
|
|
339
|
+
/* BOT MODEL OVERRIDE: her bot farklı model kullanabilir (sel boşsa global seçim).
|
|
340
|
+
sel biçimi: 'providerId::model' — zincirde yoksa sessizce global'e düşer. */
|
|
341
|
+
setSessionModel(sessionId, sel) {
|
|
342
|
+
const id = String(sessionId || '');
|
|
343
|
+
if (!id) return;
|
|
344
|
+
const resolved = sel ? this._resolve(String(sel)) : null;
|
|
345
|
+
if (resolved) this.sessionModel.set(id, resolved);
|
|
346
|
+
else this.sessionModel.delete(id);
|
|
347
|
+
}
|
|
348
|
+
|
|
337
349
|
/* Oturumun bağlı olduğu MÜŞTERİ botu (admin/seasız → null → global hafıza) */
|
|
338
350
|
_sessionBotCtx(session) {
|
|
339
351
|
if (!session || !session.botId || typeof this.resolveBot !== 'function') return null;
|
|
@@ -1286,7 +1298,8 @@ class Engine {
|
|
|
1286
1298
|
'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.',
|
|
1287
1299
|
'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.',
|
|
1288
1300
|
'Kullanıcı "artık hep böyle yap / bunu unutma" tarzı kalıcı talimat verirse kural olarak kaydet: sohbette /rule <metin> kullanmasını söyle VEYA kullanıcı isterse event_subscribe ile olaya bağlan (mail/fiyat/dosya/webhook).',
|
|
1289
|
-
'Kullanıcının mesajında 2+ ayrı iş/hedef varsa (örn "X yap ve sonra Y\u2019i kontrol et") KODLAMAYA/İŞE BAŞLAMADAN önce todo_write ile plan çıkar ve sırayla yürüt; her adımı tamamlarken güncelle.',
|
|
1301
|
+
'Kullanıcının mesajında 2+ ayrı iş/hedef varsa (örn "X yap ve sonra Y\u2019i kontrol et") KODLAMAYA/İŞE BAŞLAMADAN önce todo_write ile plan çıkar ve sırayla yürüt; her adımı tamamlarken güncelle. LİSTE DİSİPLİNİ: her adım bittiği AN status:"done" yap; son cevabını vermeden önce tüm maddeler done olmalı — yapılmayacaksa listeden düş. Listeyi yarım bırakma.',
|
|
1302
|
+
'HIZ KURALI: Bağımsız işleri AYNI turda birden çok tool_calls ile PARALEL ver. Küçük işleri tek tek çağırma — her ayrı araç turu 5-15 sn LLM gecikmesidir: 3+ küçük komutu TEK run_command\u2019te `;` ile zincirle (örn `git status; node -v; dir`), döngülü/çoklu işleri TEK python_run betiğinde topla, çok dosyalık değişikliği TEK script ile yap. Her küçük işlem için ayrı araç çağrısı açmak yavaşlığın 1 numaralı sebebidir.',
|
|
1290
1303
|
this.ceoMode
|
|
1291
1304
|
? 'Bağımsız alt-işleri run_background ile PARALEL ajana devret; işi KENDİN YÜRÜTME — emri ver, takip et, raporla.'
|
|
1292
1305
|
: 'Bağımsız alt-işleri delegate_task ile devret; kendi başına halledebileceğin işleri devretme.',
|
|
@@ -1910,7 +1923,7 @@ class Engine {
|
|
|
1910
1923
|
job.lastActivityAt = nowIso();
|
|
1911
1924
|
}
|
|
1912
1925
|
|
|
1913
|
-
/* Saf sınıflandırma (test edilebilir): null | 'stuck' | '
|
|
1926
|
+
/* Saf sınıflandırma (test edilebilir): null | 'stuck' | 'long' */
|
|
1914
1927
|
static superviseReason(job, nowMs) {
|
|
1915
1928
|
if (!job || job.status !== 'running') return null;
|
|
1916
1929
|
const started = Date.parse(job.startedAt || '') || nowMs;
|
|
@@ -1922,7 +1935,6 @@ class Engine {
|
|
|
1922
1935
|
: Infinity;
|
|
1923
1936
|
if (nudgedMin < SUP_NUDGE_COOLDOWN_MIN) return null; // yeni uyardık — boğmayalım
|
|
1924
1937
|
if (runMin >= SUP_STUCK_START_MIN && idleMin >= SUP_IDLE_MIN) return 'stuck';
|
|
1925
|
-
if (!job.wrapAt && runMin >= BG_WRAP_MIN) return 'wrapup';
|
|
1926
1938
|
if (runMin >= SUP_LONG_MIN) return 'long';
|
|
1927
1939
|
return null;
|
|
1928
1940
|
}
|
|
@@ -1939,11 +1951,6 @@ class Engine {
|
|
|
1939
1951
|
this._bgSelfHeal(job, now);
|
|
1940
1952
|
continue;
|
|
1941
1953
|
}
|
|
1942
|
-
/* zarif bitirme: süre dolunca yeni araştırma yasak → SON RAPORU yazsın */
|
|
1943
|
-
if (reason === 'wrapup') {
|
|
1944
|
-
this._bgWrapUp(job, now);
|
|
1945
|
-
continue;
|
|
1946
|
-
}
|
|
1947
1954
|
job.lastNudgeAt = nowIso();
|
|
1948
1955
|
job.checks = (job.checks || 0) + 1;
|
|
1949
1956
|
const det = this.bgDetail(job.id);
|
|
@@ -1982,24 +1989,6 @@ class Engine {
|
|
|
1982
1989
|
this._bgKick(job, text);
|
|
1983
1990
|
}
|
|
1984
1991
|
|
|
1985
|
-
/* Zarif bitirme: süre dolunca ajanı kesip SON RAPORU yazmasını iste.
|
|
1986
|
-
Uzatma yok, öldürme yok — mevcut bulgularla düzgün kapanış. */
|
|
1987
|
-
_bgWrapUp(job, now) {
|
|
1988
|
-
job.wrapAt = nowIso();
|
|
1989
|
-
job.lastNudgeAt = nowIso();
|
|
1990
|
-
job.checks = (job.checks || 0) + 1;
|
|
1991
|
-
const runMin = Math.max(1, Math.round((now - Date.parse(job.startedAt)) / 60000));
|
|
1992
|
-
const det = this.bgDetail(job.id);
|
|
1993
|
-
const tail = det.ok && det.messages.length ? det.messages.slice(-3).join('\n') : '';
|
|
1994
|
-
const text =
|
|
1995
|
-
`[SÜRE UYARISI] "${job.title}" görevi ${runMin} dk'dır sürüyor — süre doldu.\n` +
|
|
1996
|
-
`YENİ arama/fetch/sayfa açma YAPMA. Şu ana kadar bulduklarınla SON RAPORU şimdi yaz:\n` +
|
|
1997
|
-
`- 3-5 madde: net sonuç + yapılamayanlar açıkça "bulunamadı" diye.\n` +
|
|
1998
|
-
`Raporu yazınca görev tamamlanır — fazladan tur harcama.` +
|
|
1999
|
-
(tail ? `\n\nSon çıktın:\n${tail}` : '');
|
|
2000
|
-
this._bgKick(job, text);
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
1992
|
/* Arka plan oturumuna müdahale: asılı turu kes (ctrl.abort), sonra
|
|
2004
1993
|
kurtarma mesajını bas. abort → eski _run sonlanır → send yeni tur açar.
|
|
2005
1994
|
revive bayrağı: /stop veya silme sonrası bekleyen müdahaleler patlamasın. */
|
|
@@ -2225,6 +2214,11 @@ class Engine {
|
|
|
2225
2214
|
role = 'terminal';
|
|
2226
2215
|
}
|
|
2227
2216
|
let sel = this.modelFor(role);
|
|
2217
|
+
/* bot model override: rol modeli (vision/terminal) yoksa botun kendi seçimi kazanır */
|
|
2218
|
+
if (!role) {
|
|
2219
|
+
const ssel = this.sessionModel.get(String(session.id));
|
|
2220
|
+
if (ssel) sel = ssel;
|
|
2221
|
+
}
|
|
2228
2222
|
if (role && this.roleModels[role]) {
|
|
2229
2223
|
emitSafe(this, session.id, { type: 'status', status: `rol: ${role} → ${sel.providerName} · ${sel.model}` });
|
|
2230
2224
|
}
|
|
@@ -2288,9 +2282,26 @@ class Engine {
|
|
|
2288
2282
|
const sid = session.id;
|
|
2289
2283
|
const emit = (ev) => this.emit({ ...ev, sessionId: sid });
|
|
2290
2284
|
try {
|
|
2291
|
-
if (!this.sel) throw new Error('Model yapılandırılmadı — %APPDATA%\\beast\\config.yaml ve .env kontrol et');
|
|
2285
|
+
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');
|
|
2292
2286
|
|
|
2287
|
+
let nudged = false; // görev listesi disiplini: run başına en fazla 1 hatırlatma
|
|
2288
|
+
let wrapNudged = false; // tur limitine yaklaşınca zarif kapanış (bg ajanlar)
|
|
2293
2289
|
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
2290
|
+
/* SÜRE SINIRI YOK — ama sonsuz tur da yok: bg ajan son 3 tura gelirken
|
|
2291
|
+
"raporu yaz ve bitir" uyarısı alır; MAX_TURNS sert tavan olarak kalır. */
|
|
2292
|
+
if (session.bgJob && turn === MAX_TURNS - 3 && !wrapNudged) {
|
|
2293
|
+
wrapNudged = true;
|
|
2294
|
+
const wmsg = {
|
|
2295
|
+
role: 'user',
|
|
2296
|
+
content:
|
|
2297
|
+
'[TUR LİMİTİ YAKLAŞIYOR] Kalın son turlar: yeni araştırma/iş AÇMA — şu ana kadarki bulgularınla SON RAPORU yaz ve bitir. Yapılamayanları açıkça "bulunamadı" diye belirt.',
|
|
2298
|
+
};
|
|
2299
|
+
session.messages.push(wmsg);
|
|
2300
|
+
try {
|
|
2301
|
+
this._append(session, wmsg);
|
|
2302
|
+
} catch {}
|
|
2303
|
+
emit({ type: 'message', message: wmsg });
|
|
2304
|
+
}
|
|
2294
2305
|
this._bgTrim(session); // #21 bg geçmişini olabildiğince ince tut
|
|
2295
2306
|
emit({ type: 'status', status: 'thinking' });
|
|
2296
2307
|
const res = await this._chatTurn(session, ctrl.signal, (delta) =>
|
|
@@ -2306,6 +2317,27 @@ class Engine {
|
|
|
2306
2317
|
emit({ type: 'message', message: assistant });
|
|
2307
2318
|
|
|
2308
2319
|
if (!res.toolCalls || !res.toolCalls.length) {
|
|
2320
|
+
/* GÖREV LİSTESİ DİSİPLİNİ: ajan işi bitti sanıyor ama listede hâlâ
|
|
2321
|
+
bekleyen/aktif madde varsa BİR KEZ hatırlat ve tura devam et —
|
|
2322
|
+
liste ya tamamlanmalı ya kalan maddeler listeden düşürülmeli. */
|
|
2323
|
+
const todos = session.todos || this.todos.get(sid) || [];
|
|
2324
|
+
const pending = todos.filter((t) => t && t.status !== 'done');
|
|
2325
|
+
if (pending.length && !nudged) {
|
|
2326
|
+
nudged = true;
|
|
2327
|
+
const nudge = {
|
|
2328
|
+
role: 'user',
|
|
2329
|
+
content:
|
|
2330
|
+
'[OTOMATİK HATIRLATMA] Görev listende hâlâ tamamlanmamış maddeler var: ' +
|
|
2331
|
+
pending.map((t) => '"' + t.title + '"').join(', ') +
|
|
2332
|
+
'. Görevi yarım bırakma: eksik adımları ŞİMDİ tamamla; gerçekten yapılmayacaksa listeden düş. todo_write ile listeyi güncelle (status:"done") ve işi kapat.',
|
|
2333
|
+
};
|
|
2334
|
+
session.messages.push(nudge);
|
|
2335
|
+
try {
|
|
2336
|
+
this._append(session, nudge);
|
|
2337
|
+
} catch {}
|
|
2338
|
+
emit({ type: 'message', message: nudge });
|
|
2339
|
+
continue;
|
|
2340
|
+
}
|
|
2309
2341
|
/* cevap tamam → done HEMEN: kullanıcı beklemeden yeni iş yazabilsin.
|
|
2310
2342
|
Not/memory/skill bakımı artık arka planda (_postRunHousekeeping). */
|
|
2311
2343
|
this._clearCrash();
|
|
@@ -3282,7 +3314,7 @@ const TOOLS = [
|
|
|
3282
3314
|
function: {
|
|
3283
3315
|
name: 'todo_write',
|
|
3284
3316
|
description:
|
|
3285
|
-
'Replace the visible task checklist for this chat. Use ONLY for multi-step work (3+ steps); do not use for simple questions. Keep titles short; update statuses as you progress; clear the list when done.',
|
|
3317
|
+
'Replace the visible task checklist for this chat. Use ONLY for multi-step work (3+ steps); do not use for simple questions. Keep titles short; update statuses as you progress; clear the list when done. DISCIPLINE: mark each step done THE MOMENT it is completed; NEVER end your reply while items are still pending/active — the system bounces unfinished lists back.',
|
|
3286
3318
|
parameters: {
|
|
3287
3319
|
type: 'object',
|
|
3288
3320
|
properties: {
|