beast-agent 2.2.1 → 2.3.2
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 +7 -1
- package/src/agent/edgetts.js +201 -0
- package/src/agent/engine.js +93 -1
- package/src/agent/nightref.js +404 -0
- package/src/main.js +937 -57
- package/src/preload.js +9 -2
- package/src/renderer/handsfree.js +633 -0
- package/src/renderer/i18n.js +32 -6
- package/src/renderer/index.html +21 -7
- package/src/renderer/renderer.js +506 -81
- package/src/renderer/style.css +114 -24
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beast-agent",
|
|
3
3
|
"productName": "Beast Agent",
|
|
4
|
-
"version": "2.2
|
|
4
|
+
"version": "2.3.2",
|
|
5
5
|
"description": "Ultra-fast local agent shell for Windows.",
|
|
6
6
|
"author": "algokodcom (AlgoKod)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"electron-builder": "^26.15.3"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
+
"@huggingface/transformers": "3.7.5",
|
|
51
52
|
"@pdf-lib/fontkit": "^1.1.1",
|
|
52
53
|
"@whiskeysockets/baileys": "^7.0.0-rc14",
|
|
53
54
|
"@xenova/transformers": "^2.17.2",
|
|
@@ -102,6 +103,11 @@
|
|
|
102
103
|
"asarUnpack": [
|
|
103
104
|
"node_modules/ffmpeg-static/**",
|
|
104
105
|
"node_modules/@xenova/transformers/**",
|
|
106
|
+
"node_modules/@huggingface/transformers/**",
|
|
107
|
+
"node_modules/onnxruntime-node/**",
|
|
108
|
+
"node_modules/onnxruntime-web/**",
|
|
109
|
+
"node_modules/onnxruntime-common/**",
|
|
110
|
+
"node_modules/sharp/**",
|
|
105
111
|
"**/node_modules/@napi-rs/canvas/**",
|
|
106
112
|
"**/node_modules/tesseract.js/**",
|
|
107
113
|
"**/node_modules/tesseract.js-core/**"
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast Edge TTS: Microsoft Edge'in ÜCRETSİZ sinir ağı seslendirme servisi
|
|
4
|
+
(edge-tts protokolünün Node portu — bağımlılık yok, ws yeterli).
|
|
5
|
+
Türkçe sesler: tr-TR-AhmetNeural (erkek), tr-TR-EmelNeural (kadın).
|
|
6
|
+
Çıktı: MP3 buffer (24kHz 48kbps mono) — WhatsApp sesli not ve chat TTS uyumlu.
|
|
7
|
+
|
|
8
|
+
Protokol: wss speech.platform.bing.com → speech.config (text) → SSML (binary,
|
|
9
|
+
2 bayt BE başlık uzunluğu) → binary mp3 parçaları → "Path:turn.end" bitiş.
|
|
10
|
+
2024+ DRM: Sec-MS-GEC (5 dk'lık pencere SHA256) + Sec-MS-GEC-Version zorunlu. */
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const WebSocket = require('ws');
|
|
14
|
+
|
|
15
|
+
const TRUSTED_CLIENT_TOKEN = '6A5AA1D4EAFF4E9FB37E23D68491D6F4';
|
|
16
|
+
/* Eski uç (speech.platform.bing.com) 2025-08'de 403 vermeye başladı —
|
|
17
|
+
Edge'in YENİ uç noktası: api.msedgeservices.com, param adı Ocp-Apim-Subscription-Key
|
|
18
|
+
(değer aynı TrustedClientToken). Bakınız rany2/edge-tts #401 → 7.2.7 düzeltmesi. */
|
|
19
|
+
const WSS_URL = 'wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1';
|
|
20
|
+
const GEC_VERSION = '1-139.0.3405.102';
|
|
21
|
+
const EDGE_UA =
|
|
22
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' +
|
|
23
|
+
'Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0';
|
|
24
|
+
const CHROMIUM_ORIGIN = 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold';
|
|
25
|
+
const WIN_EPOCH = 11644473600;
|
|
26
|
+
const TICKS_DIVISOR = 10000000; // 100-ns ticks
|
|
27
|
+
|
|
28
|
+
/* Kullanılabilir popüler sesler (UI'da listelenir) */
|
|
29
|
+
const EDGE_VOICES = [
|
|
30
|
+
{ id: 'tr-TR-AhmetNeural', name: 'Ahmet (Türkçe, erkek)' },
|
|
31
|
+
{ id: 'tr-TR-EmelNeural', name: 'Emel (Türkçe, kadın)' },
|
|
32
|
+
{ id: 'en-US-GuyNeural', name: 'Guy (English, male)' },
|
|
33
|
+
{ id: 'en-US-AriaNeural', name: 'Aria (English, female)' },
|
|
34
|
+
{ id: 'de-DE-KatjaNeural', name: 'Katja (Deutsch, weiblich)' },
|
|
35
|
+
{ id: 'ar-SA-HamedNeural', name: 'Hamed (العربية)' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function uuidNoDash() {
|
|
39
|
+
return crypto.randomUUID().replace(/-/g, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Sec-MS-GEC — edge-tts drm.py birebir:
|
|
43
|
+
ticks = unix_sn + WIN_EPOCH; ticks -= ticks % 300; ticks *= S_TO_NS/100 (1e7);
|
|
44
|
+
hash( f"{ticks:.0f}" + token ).upper(). Float matematiği Python'la aynı →
|
|
45
|
+
birebir aynı string çıkar. */
|
|
46
|
+
function secMsGec() {
|
|
47
|
+
let ticks = Date.now() / 1000; // unix saniye
|
|
48
|
+
ticks += WIN_EPOCH; // Windows file-time epoch
|
|
49
|
+
ticks -= ticks % 300; // en yakın 5 dakikaya aşağı yuvarla
|
|
50
|
+
ticks *= 1e7; // 100-nanoSN tick (S_TO_NS/100)
|
|
51
|
+
return crypto
|
|
52
|
+
.createHash('sha256')
|
|
53
|
+
.update(Math.round(ticks).toString() + TRUSTED_CLIENT_TOKEN, 'ascii')
|
|
54
|
+
.digest('hex')
|
|
55
|
+
.toUpperCase();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* TTS'e girmeden önce metni sadeleştir: markdown/kod/emoji/URL gürültüsü sesi bozar */
|
|
59
|
+
function sanitizeForSpeech(text) {
|
|
60
|
+
return String(text || '')
|
|
61
|
+
.replace(/```[\s\S]*?```/g, ' kod bloğu ')
|
|
62
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
63
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
|
|
64
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
65
|
+
.replace(/https?:\/\/\S+/g, ' link ')
|
|
66
|
+
.replace(/[*_#>|~]+/g, ' ')
|
|
67
|
+
.replace(
|
|
68
|
+
/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}\u{2190}-\u{21FF}\u{2B00}-\u{2BFF}]/gu,
|
|
69
|
+
' '
|
|
70
|
+
)
|
|
71
|
+
.replace(/\s+/g, ' ')
|
|
72
|
+
.trim()
|
|
73
|
+
.slice(0, 3000);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ssmlFor(text, voice, rate, pitch) {
|
|
77
|
+
const lang = String(voice || '').split('-').slice(0, 2).join('-') || 'tr-TR';
|
|
78
|
+
const body = String(text || '')
|
|
79
|
+
.replace(/&/g, '&')
|
|
80
|
+
.replace(/</g, '<')
|
|
81
|
+
.replace(/>/g, '>');
|
|
82
|
+
return (
|
|
83
|
+
"<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='" + lang + "'>" +
|
|
84
|
+
"<voice name='" + (voice || 'tr-TR-AhmetNeural') + "'>" +
|
|
85
|
+
"<prosody pitch='" + (pitch || '+0Hz') + "' rate='" + (rate || '+0%') + "' volume='+0%'>" +
|
|
86
|
+
body +
|
|
87
|
+
'</prosody></voice></speak>'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ts() {
|
|
92
|
+
return new Date().toISOString();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Metni Edge TTS ile seslendirir.
|
|
97
|
+
* @returns {Promise<Buffer>} mp3 audio
|
|
98
|
+
*/
|
|
99
|
+
function synthesize(text, { voice, rate, pitch } = {}) {
|
|
100
|
+
const clean = sanitizeForSpeech(text);
|
|
101
|
+
if (!clean) return Promise.reject(new Error('boş metin'));
|
|
102
|
+
const v = String(voice || 'tr-TR-AhmetNeural').trim();
|
|
103
|
+
const connectionId = uuidNoDash();
|
|
104
|
+
const url =
|
|
105
|
+
WSS_URL +
|
|
106
|
+
'?Ocp-Apim-Subscription-Key=' + TRUSTED_CLIENT_TOKEN +
|
|
107
|
+
'&Sec-MS-GEC=' + secMsGec() +
|
|
108
|
+
'&Sec-MS-GEC-Version=' + GEC_VERSION +
|
|
109
|
+
'&ConnectionId=' + connectionId;
|
|
110
|
+
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
let ws;
|
|
113
|
+
try {
|
|
114
|
+
ws = new WebSocket(url, {
|
|
115
|
+
headers: {
|
|
116
|
+
Origin: CHROMIUM_ORIGIN,
|
|
117
|
+
'User-Agent': EDGE_UA,
|
|
118
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
119
|
+
},
|
|
120
|
+
handshakeTimeout: 12000,
|
|
121
|
+
});
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return reject(e);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const chunks = [];
|
|
127
|
+
let done = false;
|
|
128
|
+
const finish = (err, buf) => {
|
|
129
|
+
if (done) return;
|
|
130
|
+
done = true;
|
|
131
|
+
try { if (ws && ws.readyState === WebSocket.OPEN) ws.close(); } catch {}
|
|
132
|
+
try { if (ws) ws.terminate(); } catch {}
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
if (err) reject(err);
|
|
135
|
+
else resolve(buf);
|
|
136
|
+
};
|
|
137
|
+
const timer = setTimeout(() => finish(new Error('edge tts zaman aşımı')), 30000);
|
|
138
|
+
|
|
139
|
+
ws.on('open', () => {
|
|
140
|
+
const config =
|
|
141
|
+
'X-Timestamp:' + ts() + '\r\n' +
|
|
142
|
+
'Content-Type:application/json; charset=utf-8\r\n' +
|
|
143
|
+
'Path:speech.config\r\n\r\n' +
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
context: {
|
|
146
|
+
synthesis: {
|
|
147
|
+
audio: {
|
|
148
|
+
metadataoptions: { sentenceBoundaryEnabled: 'false', wordBoundaryEnabled: 'false' },
|
|
149
|
+
outputFormat: 'audio-24khz-48kbitrate-mono-mp3',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
ws.send(config, (err) => { if (err) finish(err); });
|
|
155
|
+
|
|
156
|
+
const ssmlHeader =
|
|
157
|
+
'X-RequestId:' + uuidNoDash() + '\r\n' +
|
|
158
|
+
'Content-Type:application/ssml+xml\r\n' +
|
|
159
|
+
'X-Timestamp:' + ts() + 'Z\r\n' +
|
|
160
|
+
'Path:ssml\r\n\r\n';
|
|
161
|
+
const ssmlBody = ssmlFor(clean, v, rate, pitch);
|
|
162
|
+
const header = Buffer.from(ssmlHeader, 'utf8');
|
|
163
|
+
const body = Buffer.from(ssmlBody, 'utf8');
|
|
164
|
+
const len = Buffer.alloc(2);
|
|
165
|
+
len.writeUInt16BE(header.length, 0);
|
|
166
|
+
ws.send(Buffer.concat([len, header, body]), (err) => { if (err) finish(err); });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
ws.on('message', (data, isBinary) => {
|
|
170
|
+
try {
|
|
171
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
172
|
+
if (!isBinary) {
|
|
173
|
+
const msg = buf.toString('utf8');
|
|
174
|
+
if (msg.includes('Path:turn.end')) {
|
|
175
|
+
const audio = Buffer.concat(chunks);
|
|
176
|
+
if (!audio.length) finish(new Error('edge tts boş ses döndü'));
|
|
177
|
+
else finish(null, audio);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (buf.length < 2) return;
|
|
182
|
+
const headerLen = buf.readUInt16BE(0);
|
|
183
|
+
const header = buf.slice(2, 2 + headerLen).toString('utf8');
|
|
184
|
+
if (header.includes('Path:audio.metadata')) return; // kelime sınırları — ses değil
|
|
185
|
+
const audio = buf.slice(2 + headerLen);
|
|
186
|
+
if (audio.length) chunks.push(audio);
|
|
187
|
+
} catch {}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
ws.on('error', (e) => finish(e));
|
|
191
|
+
ws.on('close', () => {
|
|
192
|
+
if (!done) {
|
|
193
|
+
const audio = Buffer.concat(chunks);
|
|
194
|
+
if (audio.length) finish(null, audio);
|
|
195
|
+
else finish(new Error('edge tts bağlantı kapandı (ses yok)'));
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { synthesize, sanitizeForSpeech, EDGE_VOICES };
|
package/src/agent/engine.js
CHANGED
|
@@ -14,6 +14,7 @@ const research = require('./research');
|
|
|
14
14
|
const agentdefs = require('./agentdefs');
|
|
15
15
|
const memory = require('./memory');
|
|
16
16
|
const mem0 = require('./mem0');
|
|
17
|
+
const nightref = require('./nightref');
|
|
17
18
|
const skills = require('./skills');
|
|
18
19
|
const mcp = require('./mcp');
|
|
19
20
|
const { estTokens, estMsgTokens } = require('./tokens');
|
|
@@ -290,6 +291,12 @@ class Engine {
|
|
|
290
291
|
this._codeIndex = new Map(); // kısa oturum kodu -> session id
|
|
291
292
|
/* yansıma: oturumda 5+ yeni araç çağrısında skill taslağı denenir */
|
|
292
293
|
this.reflection = { enabled: opts.reflection !== false, minTools: 3 };
|
|
294
|
+
/* GECE YANSIMASI: her gece (varsayılan 03:30) hafıza sıkılaştırma + günlük
|
|
295
|
+
öğrenme journal'i. Ayarlardan kapatılabilir (nightReflect:false),
|
|
296
|
+
saati BEAST_REFLECT_AT / nightReflectAt ile değiştirilebilir. */
|
|
297
|
+
this.nightReflect = opts.nightReflect !== false;
|
|
298
|
+
this.nightReflectAt = String(opts.nightReflectAt || process.env.BEAST_REFLECT_AT || '').trim() || nightref.DEFAULT_AT;
|
|
299
|
+
this._nrRunning = false;
|
|
293
300
|
/* OTOMATİK SKİLL SİSTEMİ: öğrenilen prosedür taslak onayı beklemeden
|
|
294
301
|
kurulu skill olur; mevcut skillin daha iyisi bulunursa üzerine günceller */
|
|
295
302
|
this.autoSkills = opts.autoSkills !== false;
|
|
@@ -331,6 +338,16 @@ class Engine {
|
|
|
331
338
|
try { this._supervise(); } catch {}
|
|
332
339
|
}, SUP_CHECK_MS);
|
|
333
340
|
if (this._supTimer.unref) this._supTimer.unref();
|
|
341
|
+
/* gece yansıma tick'i: 10 dk'da bir hedef saati kontrol eder;
|
|
342
|
+
app gece kapalıysa açılıştan 3 dk sonra catch-up dener */
|
|
343
|
+
this._nrTimer = setInterval(() => {
|
|
344
|
+
try { this._nightRefTick(); } catch {}
|
|
345
|
+
}, 10 * 60 * 1000);
|
|
346
|
+
if (this._nrTimer.unref) this._nrTimer.unref();
|
|
347
|
+
const nrBoot = setTimeout(() => {
|
|
348
|
+
try { this._nightRefTick(); } catch {}
|
|
349
|
+
}, 3 * 60 * 1000);
|
|
350
|
+
if (nrBoot.unref) nrBoot.unref();
|
|
334
351
|
skills.seedIfEmpty();
|
|
335
352
|
agentdefs.seedIfEmpty();
|
|
336
353
|
}
|
|
@@ -2772,12 +2789,16 @@ class Engine {
|
|
|
2772
2789
|
} catch {}
|
|
2773
2790
|
}
|
|
2774
2791
|
|
|
2775
|
-
/* Eski motor örneğini kapat (reloadBackend) — supervisor zamanlayıcısı durur */
|
|
2792
|
+
/* Eski motor örneğini kapat (reloadBackend) — supervisor + gece yansıma zamanlayıcısı durur */
|
|
2776
2793
|
dispose() {
|
|
2777
2794
|
if (this._supTimer) {
|
|
2778
2795
|
clearInterval(this._supTimer);
|
|
2779
2796
|
this._supTimer = null;
|
|
2780
2797
|
}
|
|
2798
|
+
if (this._nrTimer) {
|
|
2799
|
+
clearInterval(this._nrTimer);
|
|
2800
|
+
this._nrTimer = null;
|
|
2801
|
+
}
|
|
2781
2802
|
}
|
|
2782
2803
|
|
|
2783
2804
|
/* Arka plan oturumu 'done' olduğunda ana sohbete özet basar (main çağırır) */
|
|
@@ -3591,6 +3612,77 @@ const skills = require('./skills');
|
|
|
3591
3612
|
return r.ok ? skills.slugify(draft.name) : null;
|
|
3592
3613
|
}
|
|
3593
3614
|
|
|
3615
|
+
/* ---------- gece yansıması: hafıza sıkılaştırma + günlük öğrenme ----------
|
|
3616
|
+
|
|
3617
|
+
Her gece hedef saatte (nightReflectAt) otomatik; app kapalıysa açılışta
|
|
3618
|
+
yakalanır. İçerik: bugün ne öğrendim (journal), MEMORY.md'de gereksiz ne
|
|
3619
|
+
var (doğrulanmış drop/merge), bağlam sıkılaştırma raporu (token tasarrufu).
|
|
3620
|
+
Elle de tetiklenebilir: runNightReflection({ manual: true }). */
|
|
3621
|
+
_nightRefTick() {
|
|
3622
|
+
if (!this.nightReflect || this._stopped) return;
|
|
3623
|
+
if (!this.sel || this._nrRunning) return;
|
|
3624
|
+
const last = nightref.readLast(memory.memDir());
|
|
3625
|
+
if (!nightref.due({ now: new Date(), lastAt: last && last.at, at: this.nightReflectAt })) return;
|
|
3626
|
+
this.runNightReflection({}).catch(() => {});
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
async runNightReflection({ manual = false } = {}) {
|
|
3630
|
+
if (this._nrRunning) return { ok: false, error: 'gece yansıması zaten çalışıyor' };
|
|
3631
|
+
if (!this.sel) return { ok: false, error: 'model seçili değil' };
|
|
3632
|
+
this._nrRunning = true;
|
|
3633
|
+
try {
|
|
3634
|
+
const last = nightref.readLast(memory.memDir());
|
|
3635
|
+
const sinceIso = (last && last.at) || null;
|
|
3636
|
+
/* kapsamda olan oturumlar: son yansımadan sonra güncellenenler;
|
|
3637
|
+
mesajlarda zaman damgası olmadığından oturum başına son dilim alınır */
|
|
3638
|
+
const cutoff = sinceIso ? new Date(sinceIso).getTime() : nightref.startOfDay(new Date()).getTime();
|
|
3639
|
+
const sessions = [];
|
|
3640
|
+
let used = 0;
|
|
3641
|
+
for (const v of this.listSessions()) {
|
|
3642
|
+
if (sessions.length >= 12) break;
|
|
3643
|
+
if (new Date(v.updatedAt).getTime() < cutoff) continue;
|
|
3644
|
+
let s;
|
|
3645
|
+
try { s = this._load(v.id); } catch { continue; }
|
|
3646
|
+
if (!s.messages.length) continue;
|
|
3647
|
+
const tr = this._renderTranscript(
|
|
3648
|
+
s.messages.filter((m) => m.role !== 'tool').slice(-nightref.TRANSCRIPT_TAIL)
|
|
3649
|
+
);
|
|
3650
|
+
if (!tr.trim()) continue;
|
|
3651
|
+
const slice = tr.slice(0, nightref.DAY_TRANSCRIPT_CAP - used);
|
|
3652
|
+
if (!slice.trim()) break;
|
|
3653
|
+
used += slice.length;
|
|
3654
|
+
sessions.push({ id: v.id, title: v.title, updatedAt: v.updatedAt, transcript: slice });
|
|
3655
|
+
if (used >= nightref.DAY_TRANSCRIPT_CAP) break;
|
|
3656
|
+
}
|
|
3657
|
+
const llm = async (prompt) => {
|
|
3658
|
+
const ctrl = new AbortController();
|
|
3659
|
+
const kill = setTimeout(() => ctrl.abort(), nightref.LLM_TIMEOUT_MS);
|
|
3660
|
+
try {
|
|
3661
|
+
const res = await chatOnce(
|
|
3662
|
+
this.sel,
|
|
3663
|
+
{ messages: [{ role: 'user', content: prompt }], temperature: 0.2 },
|
|
3664
|
+
{ signal: ctrl.signal }
|
|
3665
|
+
);
|
|
3666
|
+
return String(res.content || '');
|
|
3667
|
+
} finally {
|
|
3668
|
+
clearTimeout(kill);
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
return await nightref.run({
|
|
3672
|
+
llm,
|
|
3673
|
+
memory,
|
|
3674
|
+
mem0Enabled: this.mem0Enabled,
|
|
3675
|
+
sessions,
|
|
3676
|
+
sinceIso,
|
|
3677
|
+
manual,
|
|
3678
|
+
now: new Date(),
|
|
3679
|
+
log: (msg) => log.info('gece-yansima', msg),
|
|
3680
|
+
});
|
|
3681
|
+
} finally {
|
|
3682
|
+
this._nrRunning = false;
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3594
3686
|
/* ---------- BOTLAR ARASI DM (FEATURE) ----------
|
|
3595
3687
|
Admin bot, 5 haneli kodla başka bota özel mesaj atar; hedef botun cevabı
|
|
3596
3688
|
senkron döner. İzolasyon: DM turları gizli pair oturumunda yürür, bot_dm
|