beast-agent 0.26.0 → 0.26.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/bin/beast-agent.js +34 -17
- package/package.json +2 -2
- package/scripts/fix-electron.js +126 -0
- package/src/agent/bots.js +8 -0
- package/src/agent/discord.js +268 -0
- package/src/agent/engine.js +135 -77
- package/src/agent/llm.js +46 -17
- package/src/agent/research.js +133 -0
- package/src/agent/skills.js +8 -1
- package/src/agent/tools.js +53 -14
- package/src/agent/watext.js +4 -0
- package/src/main.js +534 -351
- package/src/preload.js +8 -5
- package/src/renderer/i18n.js +19 -55
- package/src/renderer/index.html +1 -3
- package/src/renderer/renderer.js +336 -155
- package/src/renderer/style.css +1 -60
- package/tests/bg-jobs.test.js +5 -10
- package/tests/llm.test.js +81 -1
- package/tests/python.test.js +3 -2
- package/tests/research.test.js +116 -0
package/src/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session } = require('electron');
|
|
3
|
+
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session, net: electronNet } = require('electron');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const http = require('http');
|
|
@@ -16,6 +16,7 @@ const skillsMod = require('./agent/skills');
|
|
|
16
16
|
const storeMod = require('./agent/store');
|
|
17
17
|
const { WhatsAppBridge } = require('./agent/whatsapp');
|
|
18
18
|
const { TelegramBridge } = require('./agent/telegram');
|
|
19
|
+
const { DiscordBridge } = require('./agent/discord');
|
|
19
20
|
const cron = require('./cron');
|
|
20
21
|
const watchers = require('./agent/watchers');
|
|
21
22
|
const usageMod = require('./agent/usage');
|
|
@@ -229,6 +230,11 @@ let tg = null;
|
|
|
229
230
|
let tgChats = new Map(); // telegram chatId -> aktif session id
|
|
230
231
|
let tgHistory = new Map(); // chatId -> [sid,...]
|
|
231
232
|
const TG_HISTORY_CAP = 20;
|
|
233
|
+
let dc = null;
|
|
234
|
+
let dcChats = new Map(); // discord channelId -> aktif session id
|
|
235
|
+
let dcHistory = new Map(); // channelId -> [sid,...]
|
|
236
|
+
const DC_HISTORY_CAP = 20;
|
|
237
|
+
const DC_CHATS_FILE = path.join(APP_DIR, 'dc-chats.json');
|
|
232
238
|
let tray = null;
|
|
233
239
|
app.isQuitting = false;
|
|
234
240
|
|
|
@@ -1183,7 +1189,7 @@ async function waFlush(jid) {
|
|
|
1183
1189
|
if (payload.participant && !merged.participant) merged.participant = payload.participant;
|
|
1184
1190
|
if (payload.participantPn && !merged.participantPn) merged.participantPn = payload.participantPn;
|
|
1185
1191
|
if (payload.participantUsername && !merged.participantUsername) merged.participantUsername = payload.participantUsername;
|
|
1186
|
-
if (payload.mentioned) merged.mentioned = true;
|
|
1192
|
+
if (payload.mentioned) merged.mentioned = true;
|
|
1187
1193
|
if (!senderNum && sn) senderNum = sn;
|
|
1188
1194
|
}
|
|
1189
1195
|
try {
|
|
@@ -1442,7 +1448,7 @@ function botToolSet(cfg) {
|
|
|
1442
1448
|
'event_list', 'event_subscribe', 'event_unsubscribe',
|
|
1443
1449
|
'watcher_add', 'watcher_list', 'watcher_remove',
|
|
1444
1450
|
]);
|
|
1445
|
-
if (s.web_search) { set.add('web_search'); set.add('http_fetch'); }
|
|
1451
|
+
if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('deep_search'); }
|
|
1446
1452
|
if (s.browser) {
|
|
1447
1453
|
for (const t of ['browser_open', 'browser_read', 'browser_screenshot', 'browser_snapshot', 'browser_click', 'browser_type', 'browser_press', 'browser_scroll', 'browser_select', 'ocr_read']) set.add(t);
|
|
1448
1454
|
}
|
|
@@ -1711,6 +1717,7 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
|
|
|
1711
1717
|
} else {
|
|
1712
1718
|
engine.setSessionTools(sid, null);
|
|
1713
1719
|
}
|
|
1720
|
+
engine.setSessionModel(sid, botCfg && !botCfg.admin ? (botCfg.model || null) : null);
|
|
1714
1721
|
waLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}`);
|
|
1715
1722
|
|
|
1716
1723
|
const participantName = payload.participant ? '+' + String(payload.participant).split('@')[0].split(':')[0] : '';
|
|
@@ -1979,6 +1986,7 @@ async function processTgMessage(chatId, payload, requeues = 0) {
|
|
|
1979
1986
|
} else {
|
|
1980
1987
|
engine.setSessionTools(sid, null);
|
|
1981
1988
|
}
|
|
1989
|
+
engine.setSessionModel(sid, botCfg && !botCfg.admin ? (botCfg.model || null) : null);
|
|
1982
1990
|
tgLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}`);
|
|
1983
1991
|
|
|
1984
1992
|
/* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin */
|
|
@@ -2037,6 +2045,225 @@ async function restartTg() {
|
|
|
2037
2045
|
}
|
|
2038
2046
|
}
|
|
2039
2047
|
|
|
2048
|
+
/* ---------- DISCORD: allow list — WA/TG ile aynı mantık ----------
|
|
2049
|
+
Liste formatı: [{ id:'123456789' | '@kullanici_adi', name, perm, bot_id }, '*']
|
|
2050
|
+
Eşleşme: sayısal ID birebir, @username büyük/küçük harf duyarsız. */
|
|
2051
|
+
function dcLog(line) {
|
|
2052
|
+
try { log.info('discord', line); } catch {}
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function dcFind(senderId, username) {
|
|
2056
|
+
const list = settings.dcAllow || [];
|
|
2057
|
+
if (!list.length) return null; // boş liste = kimseye cevap yok
|
|
2058
|
+
const id = String(senderId || '').trim();
|
|
2059
|
+
const uname = String(username || '').replace(/^@/, '').toLowerCase();
|
|
2060
|
+
for (const e of list) {
|
|
2061
|
+
if (e === '*') return { id: '*', name: '' };
|
|
2062
|
+
const eid = typeof e === 'string' ? e.trim() : String((e && e.id) || '').trim();
|
|
2063
|
+
if (!eid) continue;
|
|
2064
|
+
if (eid === '*') return { id: '*', name: '' };
|
|
2065
|
+
if (eid.startsWith('@')) {
|
|
2066
|
+
if (uname && eid.slice(1).toLowerCase() === uname) {
|
|
2067
|
+
return typeof e === 'string' ? { id: eid, name: '' } : e;
|
|
2068
|
+
}
|
|
2069
|
+
} else if (id && eid === id) {
|
|
2070
|
+
return typeof e === 'string' ? { id: eid, name: '' } : e;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
(function dcChatsLoad() {
|
|
2077
|
+
try {
|
|
2078
|
+
const raw = JSON.parse(fs.readFileSync(DC_CHATS_FILE, 'utf8'));
|
|
2079
|
+
if (raw && typeof raw.chats === 'object') {
|
|
2080
|
+
for (const [c, s] of Object.entries(raw.chats)) {
|
|
2081
|
+
if (typeof s === 'string') dcChats.set(c, s);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
if (raw && typeof raw.history === 'object') {
|
|
2085
|
+
for (const [c, arr] of Object.entries(raw.history)) {
|
|
2086
|
+
if (Array.isArray(arr)) dcHistory.set(c, arr.filter((x) => typeof x === 'string').slice(-DC_HISTORY_CAP));
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
for (const [c, s] of dcChats.entries()) {
|
|
2090
|
+
const h = dcHistory.get(c) || [];
|
|
2091
|
+
if (!h.includes(s)) h.push(s);
|
|
2092
|
+
dcHistory.set(c, h.slice(-DC_HISTORY_CAP));
|
|
2093
|
+
}
|
|
2094
|
+
} catch {}
|
|
2095
|
+
})();
|
|
2096
|
+
|
|
2097
|
+
function saveDcChats() {
|
|
2098
|
+
try {
|
|
2099
|
+
fs.writeFileSync(
|
|
2100
|
+
DC_CHATS_FILE,
|
|
2101
|
+
JSON.stringify({
|
|
2102
|
+
chats: Object.fromEntries(dcChats),
|
|
2103
|
+
history: Object.fromEntries([...dcHistory.entries()].map(([c, a]) => [c, a.slice(-DC_HISTORY_CAP)])),
|
|
2104
|
+
})
|
|
2105
|
+
);
|
|
2106
|
+
} catch {}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
function dcRememberSession(channelId, sid) {
|
|
2110
|
+
const h = dcHistory.get(channelId) || [];
|
|
2111
|
+
if (!h.includes(sid)) h.push(sid);
|
|
2112
|
+
dcHistory.set(channelId, h.slice(-DC_HISTORY_CAP));
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
const DC_DEBOUNCE_MS = 4500;
|
|
2116
|
+
const dcQueue = new Map(); // channelId -> { payloads[] }
|
|
2117
|
+
|
|
2118
|
+
function dcQueuePush(channelId, payload) {
|
|
2119
|
+
let q = dcQueue.get(channelId);
|
|
2120
|
+
if (!q) {
|
|
2121
|
+
q = { payloads: [] };
|
|
2122
|
+
dcQueue.set(channelId, q);
|
|
2123
|
+
}
|
|
2124
|
+
q.payloads.push(payload);
|
|
2125
|
+
clearTimeout(q.timer);
|
|
2126
|
+
dcLog(`queue: mesaj kuyruğa girdi channel=${channelId} toplam=${q.payloads.length} (4.5 sn birleştirme)`);
|
|
2127
|
+
q.timer = setTimeout(() => {
|
|
2128
|
+
dcFlush(channelId).catch((e) => dcLog(`flush KRASİ: ${String((e && e.stack) || e)}`));
|
|
2129
|
+
}, DC_DEBOUNCE_MS);
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
async function dcFlush(channelId) {
|
|
2133
|
+
const q = dcQueue.get(channelId);
|
|
2134
|
+
if (!q) return;
|
|
2135
|
+
dcQueue.delete(channelId);
|
|
2136
|
+
const merged = { text: '', senderId: '', username: '', senderName: '' };
|
|
2137
|
+
for (const p of q.payloads) {
|
|
2138
|
+
if (p.text) merged.text += (merged.text ? '\n' : '') + p.text;
|
|
2139
|
+
if (!merged.senderId && p.senderId) { merged.senderId = p.senderId; merged.username = p.username; merged.senderName = p.senderName; }
|
|
2140
|
+
}
|
|
2141
|
+
await processDcMessage(channelId, merged);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
async function handleDcIncoming(channelId, payload) {
|
|
2145
|
+
try {
|
|
2146
|
+
const hit = dcFind(payload.senderId, payload.username);
|
|
2147
|
+
dcLog(
|
|
2148
|
+
`incoming channel=${channelId} sender=${payload.senderId || '?'} user=${payload.username || '-'} allowed=${!!hit}` +
|
|
2149
|
+
(hit && hit.name ? ' name=' + hit.name : '')
|
|
2150
|
+
);
|
|
2151
|
+
if (!hit) return; // allowlist dışı yoksay
|
|
2152
|
+
/* İsimsiz kayıt: güvenlik için cevap verme — kullanıcıyı ayarlara yönlendir */
|
|
2153
|
+
if (hit.id !== '*' && !hit.name) {
|
|
2154
|
+
dcLog(`skip: isimsiz kayıt (${hit.id}) — cevap verilmedi, Entegrasyonlar'da isim ekle`);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
resumeServices(); // pause durumunda gelen mesaj servisleri canlandırır
|
|
2158
|
+
dcQueuePush(String(channelId), payload);
|
|
2159
|
+
} catch (e) {
|
|
2160
|
+
dcLog(`handleDcIncoming KRASİ: ${String((e && e.stack) || e)}`);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
async function processDcMessage(channelId, payload) {
|
|
2165
|
+
const hit = dcFind(payload.senderId, payload.username);
|
|
2166
|
+
if (!hit) {
|
|
2167
|
+
dcLog(`skip flush: izinli eşleşme yok (sender=${payload.senderId || '?'})`);
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
let sid = dcChats.get(channelId);
|
|
2171
|
+
if (sid && engine.isBusy(sid)) {
|
|
2172
|
+
/* oturum meşgul — WA/TG ile aynı: kaybetme, iş bitene dek yeniden dene */
|
|
2173
|
+
await new Promise((r) => setTimeout(r, DC_DEBOUNCE_MS));
|
|
2174
|
+
return processDcMessage(channelId, payload, 1);
|
|
2175
|
+
}
|
|
2176
|
+
if (!sid) {
|
|
2177
|
+
const v = engine.createSession();
|
|
2178
|
+
sid = v.id;
|
|
2179
|
+
dcChats.set(channelId, sid);
|
|
2180
|
+
dcRememberSession(channelId, sid);
|
|
2181
|
+
saveDcChats();
|
|
2182
|
+
} else {
|
|
2183
|
+
dcRememberSession(channelId, sid);
|
|
2184
|
+
}
|
|
2185
|
+
/* Kişi bazlı granül izin: all/web/read/chat */
|
|
2186
|
+
let perm = hit.perm || (hit.lockdown ? 'chat' : 'all');
|
|
2187
|
+
engine.setSessionPerm(sid, perm);
|
|
2188
|
+
|
|
2189
|
+
/* BOT SİSTEMİ: izinli kayıtta bot_id yoksa beast'e düşer (WA/TG ile aynı) */
|
|
2190
|
+
let botId = hit && hit.bot_id ? String(hit.bot_id) : 'beast';
|
|
2191
|
+
if (!bots.get(botId)) {
|
|
2192
|
+
if (botId !== 'beast') dcLog(`bot="${botId}" yok — kayıt botsuz, beast (admin) botuna yönlendirildi`);
|
|
2193
|
+
botId = 'beast';
|
|
2194
|
+
}
|
|
2195
|
+
engine.setSessionBot(sid, botId);
|
|
2196
|
+
const botCfg = bots.get(botId);
|
|
2197
|
+
if (botCfg && !botCfg.admin) {
|
|
2198
|
+
const eff = moreRestrictivePerm(perm, botCfg.perm || 'all');
|
|
2199
|
+
if (eff !== perm) {
|
|
2200
|
+
perm = eff;
|
|
2201
|
+
engine.setSessionPerm(sid, eff);
|
|
2202
|
+
}
|
|
2203
|
+
engine.setSessionTools(sid, botToolSet(botCfg));
|
|
2204
|
+
} else {
|
|
2205
|
+
engine.setSessionTools(sid, null);
|
|
2206
|
+
}
|
|
2207
|
+
engine.setSessionModel(sid, botCfg && !botCfg.admin ? (botCfg.model || null) : null);
|
|
2208
|
+
dcLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}`);
|
|
2209
|
+
|
|
2210
|
+
/* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin */
|
|
2211
|
+
const isOwner = !!hit.owner;
|
|
2212
|
+
const roleTag = isOwner
|
|
2213
|
+
? 'SAHİBİN (talepleri önceliklidir)'
|
|
2214
|
+
: 'MİSAFİR (izinli ama sahibin sözü önceliklidir)';
|
|
2215
|
+
const label =
|
|
2216
|
+
(hit.name || payload.senderName || '?') +
|
|
2217
|
+
(payload.username ? ` (@${payload.username})` : '') +
|
|
2218
|
+
` — ${roleTag}`;
|
|
2219
|
+
let text = `[Discord — gönderen: ${label}]`;
|
|
2220
|
+
if (!isOwner) {
|
|
2221
|
+
text += `\n[NOT: Bu kişi SAHİP DEĞİL, misafirdir. Sahibin ayarlarını/verilerini değiştirme; kalıcı hafızaya misafire özel bilgi yazma.]`;
|
|
2222
|
+
}
|
|
2223
|
+
text += `\n${String(payload.text || '').slice(0, 6000)}`;
|
|
2224
|
+
engine.send(sid, { text: text.slice(0, 8000), attachments: [] });
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
async function sendDcSafe(channelId, text) {
|
|
2228
|
+
if (!dc) return false;
|
|
2229
|
+
try {
|
|
2230
|
+
return !!(await dc.send(channelId, text));
|
|
2231
|
+
} catch (e) {
|
|
2232
|
+
dcLog(`send hata channel=${channelId}: ${String((e && e.message) || e)}`);
|
|
2233
|
+
return false;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
function ensureDc() {
|
|
2238
|
+
if (!dc) {
|
|
2239
|
+
dc = new DiscordBridge({
|
|
2240
|
+
token: settings.dcToken || '',
|
|
2241
|
+
emit: (ev) => {
|
|
2242
|
+
if (ev.type === 'status') dcLog(`status=${ev.status}${ev.user ? ' user=' + ev.user : ''}`);
|
|
2243
|
+
if (ev.type === 'warn') dcLog('⚠ ' + String(ev.text || ''));
|
|
2244
|
+
if (win && !win.isDestroyed()) win.webContents.send('dc:event', ev);
|
|
2245
|
+
},
|
|
2246
|
+
onIncoming: handleDcIncoming,
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
return dc;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/* token değişimi / yeniden başlatma: eski köprüyü kapat, yenisini aç */
|
|
2253
|
+
async function restartDc() {
|
|
2254
|
+
if (dc) {
|
|
2255
|
+
try { await dc.stop(); } catch {}
|
|
2256
|
+
dc = null;
|
|
2257
|
+
}
|
|
2258
|
+
if (!settings.dcToken) return;
|
|
2259
|
+
const b = ensureDc();
|
|
2260
|
+
try {
|
|
2261
|
+
await b.start();
|
|
2262
|
+
} catch (e) {
|
|
2263
|
+
dcLog(`start başarısız: ${String((e && e.message) || e)}`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2040
2267
|
function reloadBackend() {
|
|
2041
2268
|
if (engine && typeof engine.dispose === 'function') {
|
|
2042
2269
|
try { engine.dispose(); } catch {}
|
|
@@ -2091,13 +2318,18 @@ function reloadBackend() {
|
|
|
2091
2318
|
ocr: (o) => ocrRead(o),
|
|
2092
2319
|
email: { list: emailList, read: emailRead, send: emailSend },
|
|
2093
2320
|
browser: {
|
|
2094
|
-
openUrl: (u, s) => browserNavigate(u, s),
|
|
2095
|
-
search: (q, s) => browserSearch(q, s),
|
|
2321
|
+
openUrl: (u, s, ctx) => browserNavigate(u, s, ctx),
|
|
2322
|
+
search: (q, s, ctx) => browserSearch(q, s, ctx),
|
|
2096
2323
|
readText: (s) => browserRead(s),
|
|
2097
2324
|
screenshot: (s) => browserScreenshot(s),
|
|
2098
2325
|
snapshot: (s) => browserSnapshot(s),
|
|
2099
2326
|
act: (k, a, s) => browserAct(k, a, s),
|
|
2100
2327
|
},
|
|
2328
|
+
research: {
|
|
2329
|
+
/* deep_search'ün "sayfayı GİZLİ tarayıcıda açıp oku" parçası (Electron-only).
|
|
2330
|
+
Arama zinciri engine._webSearchChain içinde web_search ile birebir aynı. */
|
|
2331
|
+
readPage: (u, s) => researchRead(u, s),
|
|
2332
|
+
},
|
|
2101
2333
|
emit: (ev) => {
|
|
2102
2334
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
2103
2335
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
@@ -2201,6 +2433,27 @@ function reloadBackend() {
|
|
|
2201
2433
|
})();
|
|
2202
2434
|
}
|
|
2203
2435
|
}
|
|
2436
|
+
// Discord oturumlarının son cevabını geri gönder (TG ile aynı akış)
|
|
2437
|
+
if ((ev.type === 'done' || ev.type === 'error') && dc && dc.connected) {
|
|
2438
|
+
const hitD = [...dcChats.entries()].find(([, s]) => s === ev.sessionId);
|
|
2439
|
+
if (hitD) {
|
|
2440
|
+
const dchid = hitD[0];
|
|
2441
|
+
(async () => {
|
|
2442
|
+
try {
|
|
2443
|
+
if (ev.type === 'error') {
|
|
2444
|
+
await sendDcSafe(dchid, 'Bir aksilik oldu: ' + String(ev.error || '').slice(0, 200));
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
if (!ev.aborted) {
|
|
2448
|
+
const s = engine.openSession(ev.sessionId);
|
|
2449
|
+
const lastA = [...s.messages].reverse().find((m) => m.role === 'assistant' && m.content);
|
|
2450
|
+
const txt = typeof (lastA && lastA.content) === 'string' ? lastA.content : '';
|
|
2451
|
+
if (txt.trim()) await sendDcSafe(dchid, txt);
|
|
2452
|
+
}
|
|
2453
|
+
} catch {}
|
|
2454
|
+
})();
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2204
2457
|
},
|
|
2205
2458
|
});
|
|
2206
2459
|
return engine.publicState();
|
|
@@ -2327,10 +2580,6 @@ function ensureDesktopShortcut() {
|
|
|
2327
2580
|
}
|
|
2328
2581
|
|
|
2329
2582
|
app.whenReady().then(() => {
|
|
2330
|
-
try { applyProxy(); } catch {} // proxy ayarı yüklüyse tüm çıkışa uygula
|
|
2331
|
-
if (settings.warp && settings.warp.enabled) {
|
|
2332
|
-
startWarp().catch((e) => log.info('main', 'warp açılışta başlatılamadı: ' + String((e && e.message) || e)));
|
|
2333
|
-
}
|
|
2334
2583
|
// Tailscale modu: paketli uygulamada Windows ile otomatik başlat (sessiz, tepside)
|
|
2335
2584
|
if (app.isPackaged) {
|
|
2336
2585
|
/* DAĞITIM KARARI: EXE/portable kurulum desteklenmiyor — tek yol npm.
|
|
@@ -2395,6 +2644,11 @@ app.whenReady().then(() => {
|
|
|
2395
2644
|
ensureTg().start().catch((e) => tgLog('autostart failed: ' + String((e && e.message) || e)));
|
|
2396
2645
|
}
|
|
2397
2646
|
|
|
2647
|
+
// Discord köprüsünü otomatik başlat (token kayıtlıysa)
|
|
2648
|
+
if (settings.dcToken) {
|
|
2649
|
+
ensureDc().start().catch((e) => dcLog('autostart failed: ' + String((e && e.message) || e)));
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2398
2652
|
app.on('activate', () => {
|
|
2399
2653
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
2400
2654
|
});
|
|
@@ -2402,7 +2656,6 @@ app.whenReady().then(() => {
|
|
|
2402
2656
|
app.on('before-quit', () => {
|
|
2403
2657
|
app.isQuitting = true;
|
|
2404
2658
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
2405
|
-
try { stopWarp(); } catch {} // warp-plus yardımcısını da kapat
|
|
2406
2659
|
});
|
|
2407
2660
|
|
|
2408
2661
|
if (process.argv.includes('--smoke')) {
|
|
@@ -2558,10 +2811,13 @@ function setBrowserOpen(v, forceVisible) {
|
|
|
2558
2811
|
return;
|
|
2559
2812
|
}
|
|
2560
2813
|
|
|
2561
|
-
// AÇMA — görünürlük:
|
|
2562
|
-
//
|
|
2814
|
+
// AÇMA — görünürlük: forceVisible true/false ise onu uygula;
|
|
2815
|
+
// belirtilmemişse kullanıcı tercihi (settings.browserHeadless) belirler.
|
|
2816
|
+
// PARALEL AJANLAR (bg oturum) her zaman forceVisible=false ile çağırır →
|
|
2817
|
+
// tarayıcı gizli modda çalışır, kullanıcı ekranı ve ajan konsolu rahatsız edilmez.
|
|
2563
2818
|
browser.open = true;
|
|
2564
|
-
browser.visible =
|
|
2819
|
+
browser.visible =
|
|
2820
|
+
forceVisible === true ? true : forceVisible === false ? false : settings.browserHeadless !== true;
|
|
2565
2821
|
ensureBrowser();
|
|
2566
2822
|
if (!browser.started) {
|
|
2567
2823
|
browser.started = true;
|
|
@@ -2590,14 +2846,23 @@ function browserGate(job) {
|
|
|
2590
2846
|
__trafficTail = p.catch(() => {});
|
|
2591
2847
|
return p;
|
|
2592
2848
|
}
|
|
2593
|
-
|
|
2849
|
+
/* AJAN TARAYICI STRATEJİSİ — TÜM oturumlar (ana sohbet + WhatsApp botları + paralel ajanlar):
|
|
2850
|
+
ajan tarayıcıyı KENDİ açıyorsa hep GİZLİ açılır — panel ekrana fırlamaz,
|
|
2851
|
+
Paralel Ajan Konsolu kapanmaz. Kullanıcı izlemek isterse tarayıcı düğmesine
|
|
2852
|
+
basar (browser:toggle gizli paneli görünür kılar). Zaten açıksa (kullanıcı
|
|
2853
|
+
paneli açık tutuyorsa) görünürlüğe dokunulmaz. */
|
|
2854
|
+
function setBrowserOpenForAgent() {
|
|
2855
|
+
if (!browser.open) setBrowserOpen(true, false);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
async function browserNavigate(raw, signal, ctx) {
|
|
2594
2859
|
return browserGate(async () => {
|
|
2595
2860
|
await browserTrafficWait();
|
|
2596
|
-
return browserNavigateNow(raw, signal);
|
|
2861
|
+
return browserNavigateNow(raw, signal, ctx);
|
|
2597
2862
|
});
|
|
2598
2863
|
}
|
|
2599
2864
|
|
|
2600
|
-
async function browserNavigateNow(raw, signal) {
|
|
2865
|
+
async function browserNavigateNow(raw, signal, ctx) {
|
|
2601
2866
|
let url = String(raw || '').trim();
|
|
2602
2867
|
if (!url) return { ok: false, error: 'boş adres' };
|
|
2603
2868
|
if (!/^https?:\/\//i.test(url)) {
|
|
@@ -2606,7 +2871,7 @@ async function browserNavigateNow(raw, signal) {
|
|
|
2606
2871
|
? 'https://' + url
|
|
2607
2872
|
: 'https://duckduckgo.com/?q=' + encodeURIComponent(url);
|
|
2608
2873
|
}
|
|
2609
|
-
|
|
2874
|
+
setBrowserOpenForAgent();
|
|
2610
2875
|
const wc = browser.view.webContents;
|
|
2611
2876
|
await new Promise((resolve) => {
|
|
2612
2877
|
let settled = false;
|
|
@@ -2668,6 +2933,120 @@ function flushBrowserStorage() {
|
|
|
2668
2933
|
} catch {}
|
|
2669
2934
|
}
|
|
2670
2935
|
|
|
2936
|
+
/* ---------- GİZLİ ARAŞTIRMA TARAYICISI (deep_search) ----------
|
|
2937
|
+
deep_search'ün "sayfayı açıp oku" adımı burada çalışır: WebContentsView
|
|
2938
|
+
HİÇbir pencereye eklenmez (kullanıcı hiçbir şey görmez) ama gerçek Chromium
|
|
2939
|
+
çalışır — JS/SPA sayfalar render olur, innerText okunur. Panel (browser.view)
|
|
2940
|
+
hiç meşgul edilmez. Görsel/font/media indirme hız için iptal edilir.
|
|
2941
|
+
2 slot = en fazla 2 sayfa aynı anda okunur (ayrı view, çakışma yok). */
|
|
2942
|
+
const researchPool = { views: [null, null], queue: [], slots: [false, false] };
|
|
2943
|
+
const RESEARCH_PAGE_TIMEOUT = 25000;
|
|
2944
|
+
const RESEARCH_SETTLE_MS = 900;
|
|
2945
|
+
const RESEARCH_CONTENT_CAP = 4500;
|
|
2946
|
+
|
|
2947
|
+
function researchAcquire() {
|
|
2948
|
+
return new Promise((resolve) => {
|
|
2949
|
+
researchPool.queue.push(resolve);
|
|
2950
|
+
researchPump();
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
function researchRelease(idx) {
|
|
2955
|
+
researchPool.slots[idx] = false;
|
|
2956
|
+
researchPump();
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
function researchPump() {
|
|
2960
|
+
for (let i = 0; i < researchPool.slots.length; i++) {
|
|
2961
|
+
if (!researchPool.slots[i] && researchPool.queue.length) {
|
|
2962
|
+
researchPool.slots[i] = true;
|
|
2963
|
+
researchPool.queue.shift()(i);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
function researchViewAt(idx) {
|
|
2969
|
+
let v = researchPool.views[idx];
|
|
2970
|
+
if (v && v.webContents && !v.webContents.isDestroyed()) return v;
|
|
2971
|
+
v = new WebContentsView({
|
|
2972
|
+
webPreferences: {
|
|
2973
|
+
partition: 'research', // kalıcı olmayan bölüm — çerez birikmez
|
|
2974
|
+
contextIsolation: true,
|
|
2975
|
+
nodeIntegration: false,
|
|
2976
|
+
sandbox: true,
|
|
2977
|
+
spellcheck: false,
|
|
2978
|
+
backgroundThrottling: false, // gizli çalışırken zamanlayıcılar yavaşlamasın
|
|
2979
|
+
},
|
|
2980
|
+
});
|
|
2981
|
+
const wc = v.webContents;
|
|
2982
|
+
try {
|
|
2983
|
+
/* Google/bazı siteler Electron UA'yı reddeder — gerçek Chrome kimliği */
|
|
2984
|
+
const chromeUA = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`;
|
|
2985
|
+
wc.setUserAgent(chromeUA);
|
|
2986
|
+
const ses = session.fromPartition('research');
|
|
2987
|
+
if (ses && ses.setUserAgent) ses.setUserAgent(chromeUA, 'tr-TR,tr;q=0.9,en;q=0.8');
|
|
2988
|
+
} catch {}
|
|
2989
|
+
try { wc.setWindowOpenHandler(() => ({ action: 'deny' })); } catch {}
|
|
2990
|
+
try {
|
|
2991
|
+
session.fromPartition('research').setPermissionRequestHandler((_w, _p, cb) => cb(false));
|
|
2992
|
+
} catch {}
|
|
2993
|
+
try {
|
|
2994
|
+
/* hız: araştırma okuması için görsel/font/media gereksiz — iptal et */
|
|
2995
|
+
session.fromPartition('research').webRequest.onBeforeRequest({ urls: ['*://*/*'] }, (details, cb) => {
|
|
2996
|
+
cb({ cancel: ['image', 'media', 'font'].includes(details.resourceType) });
|
|
2997
|
+
});
|
|
2998
|
+
} catch {}
|
|
2999
|
+
try {
|
|
3000
|
+
session.fromPartition('research').on('will-download', (_e, item) => { try { item.cancel(); } catch {} });
|
|
3001
|
+
} catch {}
|
|
3002
|
+
wc.on('render-process-gone', () => { researchPool.views[idx] = null; });
|
|
3003
|
+
researchPool.views[idx] = v;
|
|
3004
|
+
return v;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
async function researchRead(rawUrl, signal) {
|
|
3008
|
+
const url = String(rawUrl || '').trim();
|
|
3009
|
+
if (!/^https?:\/\//i.test(url)) return { ok: false, url, error: 'geçersiz adres' };
|
|
3010
|
+
if (signal && signal.aborted) return { ok: false, url, error: 'iptal edildi' };
|
|
3011
|
+
const idx = await researchAcquire();
|
|
3012
|
+
try {
|
|
3013
|
+
const view = researchViewAt(idx);
|
|
3014
|
+
if (!view) return { ok: false, url, error: 'araştırma tarayıcısı oluşturulamadı' };
|
|
3015
|
+
const wc = view.webContents;
|
|
3016
|
+
const loaded = await new Promise((resolve) => {
|
|
3017
|
+
let settled = false;
|
|
3018
|
+
const finish = (v) => { if (!settled) { settled = true; clearTimeout(timer); wc.removeListener('did-finish-load', onDone); wc.removeListener('did-fail-load', onFail); resolve(v); } };
|
|
3019
|
+
const timer = setTimeout(() => finish(false), RESEARCH_PAGE_TIMEOUT);
|
|
3020
|
+
const onDone = () => finish(true);
|
|
3021
|
+
const onFail = (_e, code) => { if (code !== -3) finish(false); };
|
|
3022
|
+
wc.once('did-finish-load', onDone);
|
|
3023
|
+
wc.on('did-fail-load', onFail);
|
|
3024
|
+
wc.loadURL(url).catch(() => {});
|
|
3025
|
+
});
|
|
3026
|
+
/* SPA hidrasyonu için kısa settle; metin boşsa bir kez daha dene */
|
|
3027
|
+
await new Promise((r) => setTimeout(r, RESEARCH_SETTLE_MS));
|
|
3028
|
+
let title = '';
|
|
3029
|
+
let finalUrl = url;
|
|
3030
|
+
try { title = wc.getTitle() || ''; finalUrl = wc.getURL() || url; } catch {}
|
|
3031
|
+
const grab = async () => {
|
|
3032
|
+
try { return String((await wc.executeJavaScript('(document.body&&document.body.innerText)||""', true)) || ''); } catch { return ''; }
|
|
3033
|
+
};
|
|
3034
|
+
let text = (await grab()).replace(/\n{3,}/g, '\n\n').trim();
|
|
3035
|
+
if (!text) {
|
|
3036
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
3037
|
+
text = (await grab()).replace(/\n{3,}/g, '\n\n').trim();
|
|
3038
|
+
}
|
|
3039
|
+
if (!text) {
|
|
3040
|
+
return { ok: false, url: finalUrl, title, error: loaded ? 'sayfa metni boş (tam JS-görsel veya engelli sayfa olabilir)' : 'sayfa yüklenemedi (zaman aşımı/hata)' };
|
|
3041
|
+
}
|
|
3042
|
+
return { ok: true, url: finalUrl, title, truncated: text.length > RESEARCH_CONTENT_CAP, content: text.slice(0, RESEARCH_CONTENT_CAP) };
|
|
3043
|
+
} catch (e) {
|
|
3044
|
+
return { ok: false, url, error: String((e && e.message) || e) };
|
|
3045
|
+
} finally {
|
|
3046
|
+
researchRelease(idx);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
|
|
2671
3050
|
/* Dahili OCR: görsel desteklemeyen modeller için tesseract.js ile metin okuma.
|
|
2672
3051
|
Dil verisi ilk kullanımda %APPDATA%\beast\tessdata'ya iner, sonra offline çalışır. */
|
|
2673
3052
|
const _ocrWorkers = new Map(); // lang -> worker (her çağrıda yeniden init olmasın)
|
|
@@ -2705,21 +3084,21 @@ async function ocrRead({ image, lang = 'tur+eng' } = {}) {
|
|
|
2705
3084
|
Numara: view'i google.com'a bir kez açıp aramaları SAYFA İÇİ fetch() ile
|
|
2706
3085
|
yapmak (kullanıcının console'da yaptığı gibi) — sayfa bile değişmeden
|
|
2707
3086
|
DOMParser ile sonuç çekilir. Fetch olmazsa direk gezinme fallback'i var. */
|
|
2708
|
-
async function browserSearch(query, signal) {
|
|
3087
|
+
async function browserSearch(query, signal, ctx) {
|
|
2709
3088
|
/* paralel ajan sorguları trafik kapısından sırayla geçer */
|
|
2710
3089
|
return browserGate(async () => {
|
|
2711
3090
|
await browserTrafficWait();
|
|
2712
|
-
return browserSearchNow(query, signal);
|
|
3091
|
+
return browserSearchNow(query, signal, ctx);
|
|
2713
3092
|
});
|
|
2714
3093
|
}
|
|
2715
3094
|
|
|
2716
|
-
async function browserSearchNow(query, signal) {
|
|
3095
|
+
async function browserSearchNow(query, signal, ctx) {
|
|
2717
3096
|
try {
|
|
2718
3097
|
if (!win || win.isDestroyed()) return null;
|
|
2719
3098
|
if (signal && signal.aborted) return null;
|
|
2720
3099
|
const q = String(query || '').trim();
|
|
2721
3100
|
if (!q) return null;
|
|
2722
|
-
|
|
3101
|
+
setBrowserOpenForAgent();
|
|
2723
3102
|
const wc = browser.view && browser.view.webContents;
|
|
2724
3103
|
if (!wc) return null;
|
|
2725
3104
|
|
|
@@ -3325,6 +3704,7 @@ ipcMain.handle('sessions:create', () => {
|
|
|
3325
3704
|
} else {
|
|
3326
3705
|
engine.setSessionTools(v.id, null);
|
|
3327
3706
|
}
|
|
3707
|
+
engine.setSessionModel(v.id, b && !b.admin ? (b.model || null) : null);
|
|
3328
3708
|
return v;
|
|
3329
3709
|
});
|
|
3330
3710
|
ipcMain.handle('sessions:open', (_e, id) => engine.openSession(id));
|
|
@@ -3344,7 +3724,13 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
|
|
|
3344
3724
|
if (!sess) {
|
|
3345
3725
|
try { sess = engine._load(sid); } catch {}
|
|
3346
3726
|
}
|
|
3347
|
-
if (sess && !sess.botId)
|
|
3727
|
+
if (sess && !sess.botId) {
|
|
3728
|
+
engine.setSessionBot(sid, actBot.id);
|
|
3729
|
+
/* tam bağlama: izin + araç seti + botun kendi modeli (sessions:create ile aynı) */
|
|
3730
|
+
engine.setSessionPerm(sid, actBot.perm || 'all');
|
|
3731
|
+
engine.setSessionTools(sid, botToolSet(actBot));
|
|
3732
|
+
engine.setSessionModel(sid, actBot.model || null);
|
|
3733
|
+
}
|
|
3348
3734
|
}
|
|
3349
3735
|
} catch {}
|
|
3350
3736
|
if (t === '/stop' || t === '/start') {
|
|
@@ -3748,27 +4134,21 @@ function flushDesktopOnDone(ev) {
|
|
|
3748
4134
|
- Bağlantı geri gelince (DNS kontrolü) sırayla otomatik gönderilir
|
|
3749
4135
|
- Renderer'a 'net' / 'netQueue' olayları gider: ⏳ kuyruk balonu + toast */
|
|
3750
4136
|
const NET_CHECK_HOSTS = ['one.one.one.one', 'dns.google'];
|
|
4137
|
+
const NET_LOOKUP_HOSTS = ['www.google.com', 'www.microsoft.com'];
|
|
4138
|
+
const NET_HTTP_PROBES = [
|
|
4139
|
+
'http://www.msftconnecttest.com/connecttest.txt',
|
|
4140
|
+
'http://cp.cloudflare.com/generate_204',
|
|
4141
|
+
'http://connectivitycheck.gstatic.com/generate_204',
|
|
4142
|
+
];
|
|
3751
4143
|
const NET_CHECK_MS = 8000;
|
|
3752
4144
|
const NET_CHECK_TIMEOUT = 4000;
|
|
3753
|
-
const
|
|
3754
|
-
let __netFailStreak = 0;
|
|
3755
|
-
|
|
3756
|
-
/* Gerçek HTTP yoklaması: DNS yerine sayfa çekmeyi dener — proxy açıksa proxy
|
|
3757
|
-
üzerinden gider (kullanıcı trafiğinin gördüğü interneti ölçer) */
|
|
3758
|
-
async function httpProbe() {
|
|
3759
|
-
for (const u of NET_PROBE_URLS) {
|
|
3760
|
-
try {
|
|
3761
|
-
const res = await (__origFetch || fetch)(u, { signal: AbortSignal.timeout(5000) });
|
|
3762
|
-
if (res.ok) return true;
|
|
3763
|
-
} catch {}
|
|
3764
|
-
}
|
|
3765
|
-
return false;
|
|
3766
|
-
}
|
|
4145
|
+
const NET_OFFLINE_STRIKES = 2; // üst üste bu kadar başarısız turda offline ilan edilir
|
|
3767
4146
|
const CHAT_QUEUE_MAX = 50; // kuyruk üst sınırı — taşarsa en eski düşer
|
|
3768
4147
|
|
|
3769
4148
|
let netOnline = true; // son bilinen bağlantı durumu (başlangıçta iyimser)
|
|
3770
4149
|
let netCheckedOnce = false;
|
|
3771
4150
|
let netCheckBusy = false;
|
|
4151
|
+
let netFailStreak = 0;
|
|
3772
4152
|
let chatQueueFlushing = false;
|
|
3773
4153
|
const chatOfflineQueue = []; // { key, sessionId, text, attachments, at }
|
|
3774
4154
|
|
|
@@ -3852,8 +4232,42 @@ async function flushChatQueue() {
|
|
|
3852
4232
|
}
|
|
3853
4233
|
}
|
|
3854
4234
|
|
|
3855
|
-
/* gerçek internet
|
|
3856
|
-
|
|
4235
|
+
/* gerçek internet kontrolü — TEK yöntem yanıltıcı olabilir:
|
|
4236
|
+
dns.resolve (c-ares) sistem çözümleyicisini atlar; mobil ağ/hotspot/VPN ve
|
|
4237
|
+
ISS DNS engellemelerinde (ör. 1.1.1.1, dns.google) internet VARken bile
|
|
4238
|
+
başarısız çıkar. Bu yüzden katmanlı deniyoruz; HERHANGİ bir katman başarılıysa
|
|
4239
|
+
internet VAR sayılır:
|
|
4240
|
+
1) HTTP connectivity endpoint'leri (http modülü dns.lookup = OS çözümleyicisi
|
|
4241
|
+
kullanır — tarayıcı gibi; captive portal/proxy/mobil ağ hepsinde çalışır)
|
|
4242
|
+
2) dns.lookup (Windows sistem çözümleyicisi — hosts dosyası/VPN/NRPT dahil)
|
|
4243
|
+
+ dns.resolve (doğrudan DNS sunucusu)
|
|
4244
|
+
3) OS'in kendi bağlantı durumu (Electron net.isOnline — Windows NCSI)
|
|
4245
|
+
Ayrıca tek başarısız tur offline ilan etmez (2 üst üste başarısız tur gerekir):
|
|
4246
|
+
geçici DNS gecikmesi mesajları gereksiz kuyruğa atmaz. */
|
|
4247
|
+
function httpProbe(url) {
|
|
4248
|
+
return new Promise((resolve) => {
|
|
4249
|
+
let done = false;
|
|
4250
|
+
const fin = (v) => { if (!done) { done = true; resolve(v); } };
|
|
4251
|
+
try {
|
|
4252
|
+
const req = http.get(url, { timeout: NET_CHECK_TIMEOUT }, (res) => {
|
|
4253
|
+
res.resume(); // gövdeyi tüket — soket serbest kalsın
|
|
4254
|
+
const ok = !!res.statusCode && res.statusCode < 500;
|
|
4255
|
+
try { res.destroy(); } catch {}
|
|
4256
|
+
fin(ok);
|
|
4257
|
+
});
|
|
4258
|
+
req.on('timeout', () => { try { req.destroy(); } catch {} fin(false); });
|
|
4259
|
+
req.on('error', () => fin(false));
|
|
4260
|
+
} catch { fin(false); }
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
function lookupProbe(host) {
|
|
4265
|
+
return new Promise((resolve) => {
|
|
4266
|
+
const t = setTimeout(() => resolve(false), NET_CHECK_TIMEOUT);
|
|
4267
|
+
dns.lookup(host, (err) => { clearTimeout(t); resolve(!err); });
|
|
4268
|
+
});
|
|
4269
|
+
}
|
|
4270
|
+
|
|
3857
4271
|
function dnsProbe(host) {
|
|
3858
4272
|
return new Promise((resolve) => {
|
|
3859
4273
|
const t = setTimeout(() => resolve(false), NET_CHECK_TIMEOUT);
|
|
@@ -3868,37 +4282,37 @@ async function netCheck() {
|
|
|
3868
4282
|
if (netCheckBusy) return;
|
|
3869
4283
|
netCheckBusy = true;
|
|
3870
4284
|
try {
|
|
3871
|
-
let ok =
|
|
3872
|
-
|
|
3873
|
-
|
|
4285
|
+
let ok = (await Promise.all(NET_HTTP_PROBES.map(httpProbe))).some(Boolean);
|
|
4286
|
+
if (!ok) {
|
|
4287
|
+
const lookups = [
|
|
4288
|
+
...NET_LOOKUP_HOSTS.map(lookupProbe),
|
|
4289
|
+
...NET_CHECK_HOSTS.map(dnsProbe),
|
|
4290
|
+
];
|
|
4291
|
+
ok = (await Promise.all(lookups)).some(Boolean);
|
|
3874
4292
|
}
|
|
3875
4293
|
if (!ok) {
|
|
3876
|
-
|
|
3877
|
-
HTTP yoklamasıyla DOĞRULA — tarayıcının gördüğü internete yakın sinyal */
|
|
3878
|
-
ok = await httpProbe();
|
|
4294
|
+
try { ok = electronNet.isOnline() === true; } catch {}
|
|
3879
4295
|
}
|
|
3880
4296
|
const first = !netCheckedOnce;
|
|
3881
4297
|
const was = netOnline;
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
if (netOnline && __netFailStreak < 3) { netCheckedOnce = true; return; }
|
|
4298
|
+
netCheckedOnce = true;
|
|
4299
|
+
if (ok) {
|
|
4300
|
+
netFailStreak = 0;
|
|
4301
|
+
netOnline = true;
|
|
3887
4302
|
} else {
|
|
3888
|
-
|
|
4303
|
+
netFailStreak++;
|
|
4304
|
+
if (!was || netFailStreak >= NET_OFFLINE_STRIKES) netOnline = false;
|
|
3889
4305
|
}
|
|
3890
|
-
netOnline
|
|
3891
|
-
netCheckedOnce = true;
|
|
3892
|
-
if (was !== ok || first) {
|
|
4306
|
+
if (netOnline !== was || first) {
|
|
3893
4307
|
try {
|
|
3894
|
-
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'net', online:
|
|
4308
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'net', online: netOnline });
|
|
3895
4309
|
} catch {}
|
|
3896
|
-
if (
|
|
4310
|
+
if (netOnline) {
|
|
3897
4311
|
log.info('main', 'bağlantı geri geldi — offline kuyruk kontrol ediliyor');
|
|
3898
4312
|
chatQueueEmit(); // renderer: pill/toast güncellensin
|
|
3899
4313
|
flushChatQueue().catch(() => {});
|
|
3900
4314
|
} else {
|
|
3901
|
-
log.info('main',
|
|
4315
|
+
log.info('main', `internet bağlantısı yok — mesajlar kuyruğa alınacak (streak=${netFailStreak})`);
|
|
3902
4316
|
chatQueueEmit();
|
|
3903
4317
|
}
|
|
3904
4318
|
}
|
|
@@ -3908,9 +4322,21 @@ async function netCheck() {
|
|
|
3908
4322
|
}
|
|
3909
4323
|
|
|
3910
4324
|
ipcMain.handle('model:set', (_e, sel) => {
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4325
|
+
/* MÜŞTERİ botu aktifken picker seçimi O BOTUN modelini değiştirir;
|
|
4326
|
+
Beast (admin) aktifken global seçim değişir. */
|
|
4327
|
+
const act = settings.activeBotId ? bots.get(settings.activeBotId) : null;
|
|
4328
|
+
if (act && !act.admin) {
|
|
4329
|
+
try { bots.update(act.id, { model: String(sel || '') }); } catch {}
|
|
4330
|
+
try {
|
|
4331
|
+
for (const v of engine.listSessions()) {
|
|
4332
|
+
if (v.botId === act.id) engine.setSessionModel(v.id, sel || null);
|
|
4333
|
+
}
|
|
4334
|
+
} catch {}
|
|
4335
|
+
} else {
|
|
4336
|
+
settings.modelOverride = sel;
|
|
4337
|
+
saveSettings();
|
|
4338
|
+
engine.setModelOverride(sel);
|
|
4339
|
+
}
|
|
3914
4340
|
return engine.publicState();
|
|
3915
4341
|
});
|
|
3916
4342
|
|
|
@@ -4136,294 +4562,6 @@ ipcMain.handle('wa:groups:set', (_e, cfg) => {
|
|
|
4136
4562
|
return settings.waGroups;
|
|
4137
4563
|
});
|
|
4138
4564
|
|
|
4139
|
-
/* ---------------- Proxy (Ayarlar → Proxy) ----------------
|
|
4140
|
-
Kullanıcının verdiği proxy(ler) üzerinden çıkış: LLM API'leri, http_fetch,
|
|
4141
|
-
TinyFish/Exa, python motorları (env) ve dahili tarayıcı (Chromium proxyRules).
|
|
4142
|
-
Çoklu adres = Node fetch tarafında round-robin rotasyon. SOCKS5 yalnız tarayıcıda. */
|
|
4143
|
-
let __proxyIdx = 0;
|
|
4144
|
-
let __proxyAgents = null; // url -> undici.ProxyAgent
|
|
4145
|
-
let __origFetch = null;
|
|
4146
|
-
|
|
4147
|
-
function proxyUrlList() {
|
|
4148
|
-
return String((settings.proxy && settings.proxy.urls) || '')
|
|
4149
|
-
.split(/[\n,;]+/)
|
|
4150
|
-
.map((s) => s.trim())
|
|
4151
|
-
.filter((s) => /^(https?|socks5):\/\//i.test(s));
|
|
4152
|
-
}
|
|
4153
|
-
|
|
4154
|
-
function proxyAgentFor(url) {
|
|
4155
|
-
const undici = require('undici');
|
|
4156
|
-
if (!__proxyAgents) __proxyAgents = new Map();
|
|
4157
|
-
if (!__proxyAgents.has(url)) {
|
|
4158
|
-
if (/^socks5:/i.test(url)) __proxyAgents.set(url, new undici.Agent({ connect: socks5Connector(url), connections: 64 }));
|
|
4159
|
-
else __proxyAgents.set(url, new undici.ProxyAgent(url));
|
|
4160
|
-
}
|
|
4161
|
-
return __proxyAgents.get(url);
|
|
4162
|
-
}
|
|
4163
|
-
|
|
4164
|
-
/* Minimal SOCKS5 CONNECT connector (undici Agent.connect imzası).
|
|
4165
|
-
WARP (warp-plus) dahil tüm socks5 proxy'leri Node fetch'te çalıştırır. */
|
|
4166
|
-
function socks5Connector(socksUrl) {
|
|
4167
|
-
const net = require('net');
|
|
4168
|
-
const tls = require('tls');
|
|
4169
|
-
const u = new URL(socksUrl);
|
|
4170
|
-
const shost = u.hostname;
|
|
4171
|
-
const sport = Number(u.port) || 1080;
|
|
4172
|
-
const suser = decodeURIComponent(u.username || '');
|
|
4173
|
-
const spass = decodeURIComponent(u.password || '');
|
|
4174
|
-
return function connect(opts, cb) {
|
|
4175
|
-
let stage = 0; // 0 greet · 1 auth · 2 conn
|
|
4176
|
-
let buf = Buffer.alloc(0);
|
|
4177
|
-
const socket = net.connect({ host: shost, port: sport });
|
|
4178
|
-
const fail = (err) => { try { socket.destroy(); } catch {} cb(err); };
|
|
4179
|
-
const onData = (d) => {
|
|
4180
|
-
buf = Buffer.concat([buf, d]);
|
|
4181
|
-
if (stage === 0) {
|
|
4182
|
-
if (buf.length < 2) return;
|
|
4183
|
-
if (buf[0] !== 5) return fail(new Error('socks5: geçersiz yanıt'));
|
|
4184
|
-
const method = buf[1];
|
|
4185
|
-
buf = buf.subarray(2);
|
|
4186
|
-
if (method === 2) {
|
|
4187
|
-
stage = 1;
|
|
4188
|
-
const ub = Buffer.from(suser), pb = Buffer.from(spass);
|
|
4189
|
-
socket.write(Buffer.concat([Buffer.from([1, ub.length]), ub, Buffer.from([pb.length]), pb]));
|
|
4190
|
-
} else if (method === 0) sendReq();
|
|
4191
|
-
else return fail(new Error('socks5: desteklenmeyen kimlik doğrulama'));
|
|
4192
|
-
} else if (stage === 1) {
|
|
4193
|
-
if (buf.length < 2) return;
|
|
4194
|
-
buf = buf.subarray(2);
|
|
4195
|
-
sendReq();
|
|
4196
|
-
} else if (stage === 2) {
|
|
4197
|
-
if (buf.length < 5) return;
|
|
4198
|
-
if (buf[0] !== 5 || buf[1] !== 0) return fail(new Error('socks5: bağlantı reddedildi (' + buf[1] + ')'));
|
|
4199
|
-
const need = 6 + (buf[3] === 1 ? 4 : buf[3] === 4 ? 16 : 1 + buf[4]);
|
|
4200
|
-
if (buf.length < need) return;
|
|
4201
|
-
buf = Buffer.alloc(0);
|
|
4202
|
-
socket.off('data', onData);
|
|
4203
|
-
if (opts.protocol === 'https:') {
|
|
4204
|
-
const tlsSock = tls.connect({ socket, servername: opts.servername || opts.hostname });
|
|
4205
|
-
tlsSock.on('error', fail);
|
|
4206
|
-
tlsSock.once('secureConnect', () => cb(null, tlsSock));
|
|
4207
|
-
} else cb(null, socket);
|
|
4208
|
-
}
|
|
4209
|
-
};
|
|
4210
|
-
socket.on('error', fail);
|
|
4211
|
-
socket.on('connect', () => {
|
|
4212
|
-
/* SOCKS5 greeting: şifre varsa user/pass(2), yoksa no-auth(0) yöntemi öner */
|
|
4213
|
-
socket.write(suser ? Buffer.from([5, 2, 0, 2]) : Buffer.from([5, 1, 0]));
|
|
4214
|
-
});
|
|
4215
|
-
socket.on('data', onData);
|
|
4216
|
-
function sendReq() {
|
|
4217
|
-
stage = 2;
|
|
4218
|
-
const host = opts.hostname || opts.host;
|
|
4219
|
-
const port = Number(opts.port) || (opts.protocol === 'https:' ? 443 : 80);
|
|
4220
|
-
const hb = Buffer.from(String(host), 'utf8');
|
|
4221
|
-
socket.write(Buffer.concat([Buffer.from([5, 1, 0, 3, hb.length]), hb, Buffer.from([(port >> 8) & 255, port & 255])]));
|
|
4222
|
-
}
|
|
4223
|
-
};
|
|
4224
|
-
}
|
|
4225
|
-
|
|
4226
|
-
function applyProxy() {
|
|
4227
|
-
const enabled = !!(settings.proxy && settings.proxy.enabled);
|
|
4228
|
-
const allUrls = enabled ? proxyUrlList() : [];
|
|
4229
|
-
const httpUrls = allUrls.filter((u) => /^https?:\/\//i.test(u));
|
|
4230
|
-
|
|
4231
|
-
/* 1) Node fetch (LLM API + http_fetch + TinyFish/Exa + GitHub...) — global sarmalayıcı.
|
|
4232
|
-
http(s) VE socks5 (socks5Connector) destekli — http(s) proxy'lerde rotasyon var. */
|
|
4233
|
-
if (enabled && allUrls.length) {
|
|
4234
|
-
if (!__origFetch) {
|
|
4235
|
-
__origFetch = globalThis.fetch;
|
|
4236
|
-
globalThis.fetch = (input, init = {}) => {
|
|
4237
|
-
const list = proxyUrlList();
|
|
4238
|
-
if (!list.length) return __origFetch(input, init);
|
|
4239
|
-
const p = list[Math.abs(__proxyIdx++) % list.length];
|
|
4240
|
-
return __origFetch(input, { ...init, dispatcher: proxyAgentFor(p) });
|
|
4241
|
-
};
|
|
4242
|
-
}
|
|
4243
|
-
} else if (__origFetch) {
|
|
4244
|
-
globalThis.fetch = __origFetch;
|
|
4245
|
-
__origFetch = null;
|
|
4246
|
-
}
|
|
4247
|
-
|
|
4248
|
-
/* 2) python/cmd çocuk süreçleri (ddgs, requests... env'den okur) */
|
|
4249
|
-
if (enabled && httpUrls.length) {
|
|
4250
|
-
process.env.HTTP_PROXY = httpUrls[0];
|
|
4251
|
-
process.env.HTTPS_PROXY = httpUrls[0];
|
|
4252
|
-
process.env.ALL_PROXY = httpUrls[0];
|
|
4253
|
-
process.env.NO_PROXY = 'localhost,127.0.0.1';
|
|
4254
|
-
} else {
|
|
4255
|
-
delete process.env.HTTP_PROXY;
|
|
4256
|
-
delete process.env.HTTPS_PROXY;
|
|
4257
|
-
delete process.env.ALL_PROXY;
|
|
4258
|
-
delete process.env.NO_PROXY;
|
|
4259
|
-
}
|
|
4260
|
-
|
|
4261
|
-
/* 3) dahili tarayıcı (Chromium; socks5 destekler) */
|
|
4262
|
-
try {
|
|
4263
|
-
const ses = session.fromPartition('persist:browser');
|
|
4264
|
-
if (ses) {
|
|
4265
|
-
if (enabled && allUrls.length) ses.setProxy({ proxyRules: allUrls[0] }).catch(() => {});
|
|
4266
|
-
else ses.setProxy({ mode: 'direct' }).catch(() => {});
|
|
4267
|
-
}
|
|
4268
|
-
} catch {}
|
|
4269
|
-
}
|
|
4270
|
-
|
|
4271
|
-
ipcMain.handle('proxy:get', () => ({
|
|
4272
|
-
enabled: !!(settings.proxy && settings.proxy.enabled),
|
|
4273
|
-
urls: String((settings.proxy && settings.proxy.urls) || ''),
|
|
4274
|
-
}));
|
|
4275
|
-
|
|
4276
|
-
ipcMain.handle('proxy:set', (_e, cfg) => {
|
|
4277
|
-
settings.proxy = {
|
|
4278
|
-
enabled: !!(cfg && cfg.enabled),
|
|
4279
|
-
urls: String((cfg && cfg.urls) || '').slice(0, 2000),
|
|
4280
|
-
};
|
|
4281
|
-
saveSettings();
|
|
4282
|
-
applyProxy();
|
|
4283
|
-
return { ok: true, enabled: settings.proxy.enabled, urls: settings.proxy.urls };
|
|
4284
|
-
});
|
|
4285
|
-
|
|
4286
|
-
ipcMain.handle('proxy:test', async () => {
|
|
4287
|
-
const allUrls = proxyUrlList();
|
|
4288
|
-
if (!allUrls.length) return { ok: false, error: 'proxy adresi girilmemiş' };
|
|
4289
|
-
const u = allUrls[Math.abs(__proxyIdx++) % allUrls.length];
|
|
4290
|
-
const t0 = Date.now();
|
|
4291
|
-
try {
|
|
4292
|
-
const res = await (__origFetch || fetch)('https://api.ipify.org?format=json', {
|
|
4293
|
-
dispatcher: proxyAgentFor(u),
|
|
4294
|
-
signal: AbortSignal.timeout(15000),
|
|
4295
|
-
});
|
|
4296
|
-
const j = await res.json();
|
|
4297
|
-
return { ok: true, ip: j.ip, proxy: u, ms: Date.now() - t0 };
|
|
4298
|
-
} catch (e) {
|
|
4299
|
-
return { ok: false, error: String((e && e.message) || e).slice(0, 160), proxy: u };
|
|
4300
|
-
}
|
|
4301
|
-
});
|
|
4302
|
-
|
|
4303
|
-
/* ---------------- Cloudflare WARP (warp-plus) ----------------
|
|
4304
|
-
Açık kaynak warp-plus tek binary: yerel socks5 (127.0.0.1:8086) açar, o port
|
|
4305
|
-
Cloudflare WARP ağına tünel olur. Bedava, üyeliksiz (profil otomatik oluşur).
|
|
4306
|
-
Beast: indirme + başlatma + watchdog + kapatma. Sadece Beast trafiği geçer. */
|
|
4307
|
-
const WARP_PORT = 8086;
|
|
4308
|
-
const WARP_PROXY_LINE = 'socks5://127.0.0.1:' + WARP_PORT;
|
|
4309
|
-
let warpChild = null;
|
|
4310
|
-
let warpWatchdog = null;
|
|
4311
|
-
let warpStopping = false;
|
|
4312
|
-
|
|
4313
|
-
function warpBinPath() { return path.join(APP_DIR, 'bin', 'warp-plus.exe'); }
|
|
4314
|
-
function warpDir() { return path.join(APP_DIR, 'warp'); }
|
|
4315
|
-
function warpEnabled() { return !!(settings.warp && settings.warp.enabled); }
|
|
4316
|
-
|
|
4317
|
-
async function ensureWarpPlusBinary() {
|
|
4318
|
-
const bin = warpBinPath();
|
|
4319
|
-
if (fs.existsSync(bin)) return bin;
|
|
4320
|
-
fs.mkdirSync(path.dirname(bin), { recursive: true });
|
|
4321
|
-
log.info('main', 'warp-plus indiriliyor…');
|
|
4322
|
-
const rel = await (__origFetch || fetch)('https://api.github.com/repos/bepass-org/warp-plus/releases/latest', {
|
|
4323
|
-
headers: { 'User-Agent': 'beast-agent', Accept: 'application/vnd.github+json' },
|
|
4324
|
-
signal: AbortSignal.timeout(20000),
|
|
4325
|
-
});
|
|
4326
|
-
if (!rel.ok) throw new Error('sürüm bilgisi alınamadı (' + rel.status + ')');
|
|
4327
|
-
const j = await rel.json();
|
|
4328
|
-
const asset = (j.assets || []).find((a) => /warp-plus_windows-amd64\.zip$/i.test(a.name || ''));
|
|
4329
|
-
if (!asset) throw new Error('windows paketi bulunamadı');
|
|
4330
|
-
const zipPath = bin + '.zip';
|
|
4331
|
-
const res = await (__origFetch || fetch)(asset.browser_download_url, { signal: AbortSignal.timeout(300000) });
|
|
4332
|
-
if (!res.ok) throw new Error('indirme başarısız (' + res.status + ')');
|
|
4333
|
-
fs.writeFileSync(zipPath, Buffer.from(await res.arrayBuffer()));
|
|
4334
|
-
await new Promise((resolve, reject) => {
|
|
4335
|
-
spawn('powershell.exe', ['-NoProfile', '-Command', `Expand-Archive -Force -LiteralPath '${zipPath}' -DestinationPath '${path.dirname(bin)}'`])
|
|
4336
|
-
.on('exit', (c) => (c === 0 ? resolve() : reject(new Error('zip açılamadı'))))
|
|
4337
|
-
.on('error', reject);
|
|
4338
|
-
});
|
|
4339
|
-
try { fs.unlinkSync(zipPath); } catch {}
|
|
4340
|
-
if (!fs.existsSync(bin)) throw new Error('warp-plus.exe açılamadı');
|
|
4341
|
-
log.info('main', 'warp-plus indirildi: ' + bin);
|
|
4342
|
-
return bin;
|
|
4343
|
-
}
|
|
4344
|
-
|
|
4345
|
-
function warpPortAlive() {
|
|
4346
|
-
const net = require('net');
|
|
4347
|
-
return new Promise((resolve) => {
|
|
4348
|
-
const s = net.connect({ host: '127.0.0.1', port: WARP_PORT, timeout: 1200 }, () => { s.destroy(); resolve(true); });
|
|
4349
|
-
s.on('error', () => resolve(false));
|
|
4350
|
-
s.on('timeout', () => { s.destroy(); resolve(false); });
|
|
4351
|
-
});
|
|
4352
|
-
}
|
|
4353
|
-
|
|
4354
|
-
async function startWarp() {
|
|
4355
|
-
if (warpChild) return { ok: true };
|
|
4356
|
-
const bin = await ensureWarpPlusBinary();
|
|
4357
|
-
fs.mkdirSync(warpDir(), { recursive: true });
|
|
4358
|
-
try {
|
|
4359
|
-
warpChild = spawn(bin, [], { windowsHide: true, cwd: warpDir() });
|
|
4360
|
-
} catch (e) {
|
|
4361
|
-
return { ok: false, error: String((e && e.message) || e) };
|
|
4362
|
-
}
|
|
4363
|
-
warpChild.stdout.on('data', () => {});
|
|
4364
|
-
warpChild.stderr.on('data', () => {});
|
|
4365
|
-
warpChild.on('exit', () => {
|
|
4366
|
-
warpChild = null;
|
|
4367
|
-
if (warpEnabled() && !warpStopping) {
|
|
4368
|
-
/* watchdog: beklenmedik çıkışta 5 sn sonra yeniden başlat */
|
|
4369
|
-
clearTimeout(warpWatchdog);
|
|
4370
|
-
warpWatchdog = setTimeout(() => { startWarp().catch(() => {}); }, 5000);
|
|
4371
|
-
}
|
|
4372
|
-
});
|
|
4373
|
-
for (let i = 0; i < 180; i++) {
|
|
4374
|
-
if (await warpPortAlive()) { log.info('main', 'warp-plus hazır (127.0.0.1:' + WARP_PORT + ')'); return { ok: true }; }
|
|
4375
|
-
if (!warpChild) break;
|
|
4376
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
4377
|
-
}
|
|
4378
|
-
return { ok: false, error: 'warp-plus başlatılamadı (port açılmadı)' };
|
|
4379
|
-
}
|
|
4380
|
-
|
|
4381
|
-
function stopWarp() {
|
|
4382
|
-
warpStopping = true;
|
|
4383
|
-
clearTimeout(warpWatchdog);
|
|
4384
|
-
try { if (warpChild) spawn('taskkill', ['/pid', String(warpChild.pid), '/T', '/F'], { windowsHide: true }); } catch {}
|
|
4385
|
-
try { if (warpChild) warpChild.kill(); } catch {}
|
|
4386
|
-
warpChild = null;
|
|
4387
|
-
setTimeout(() => { warpStopping = false; }, 1500);
|
|
4388
|
-
}
|
|
4389
|
-
|
|
4390
|
-
ipcMain.handle('warp:get', () => ({
|
|
4391
|
-
enabled: warpEnabled(),
|
|
4392
|
-
running: !!warpChild,
|
|
4393
|
-
installed: fs.existsSync(warpBinPath()),
|
|
4394
|
-
line: WARP_PROXY_LINE,
|
|
4395
|
-
}));
|
|
4396
|
-
|
|
4397
|
-
ipcMain.handle('warp:set', async (_e, on) => {
|
|
4398
|
-
const want = !!on;
|
|
4399
|
-
settings.warp = { enabled: want };
|
|
4400
|
-
saveSettings();
|
|
4401
|
-
if (want) {
|
|
4402
|
-
const r = await startWarp();
|
|
4403
|
-
if (!r.ok) {
|
|
4404
|
-
settings.warp = { enabled: false };
|
|
4405
|
-
saveSettings();
|
|
4406
|
-
return { ok: false, error: r.error || 'başlatılamadı' };
|
|
4407
|
-
}
|
|
4408
|
-
if (!settings.proxy) settings.proxy = { enabled: true, urls: '' };
|
|
4409
|
-
const lines = proxyUrlList();
|
|
4410
|
-
if (!lines.includes(WARP_PROXY_LINE)) lines.push(WARP_PROXY_LINE);
|
|
4411
|
-
settings.proxy.urls = lines.join('\n');
|
|
4412
|
-
settings.proxy.enabled = true; // WARP açıldıysa trafik ondan geçsin
|
|
4413
|
-
saveSettings();
|
|
4414
|
-
applyProxy();
|
|
4415
|
-
return { ok: true, enabled: true };
|
|
4416
|
-
}
|
|
4417
|
-
/* kapat: satırı listeden çıkar, başka proxy yoksa master'ı da kapat */
|
|
4418
|
-
const rest = proxyUrlList().filter((u) => u !== WARP_PROXY_LINE);
|
|
4419
|
-
settings.proxy.urls = rest.join('\n');
|
|
4420
|
-
if (!rest.length) settings.proxy.enabled = false;
|
|
4421
|
-
saveSettings();
|
|
4422
|
-
applyProxy();
|
|
4423
|
-
stopWarp();
|
|
4424
|
-
return { ok: true, enabled: false };
|
|
4425
|
-
});
|
|
4426
|
-
|
|
4427
4565
|
ipcMain.handle('settings:get', () => {
|
|
4428
4566
|
const out = JSON.parse(JSON.stringify(settings));
|
|
4429
4567
|
/* Sırların renderera düz metin gitmesini engelle */
|
|
@@ -4546,7 +4684,7 @@ ipcMain.handle('update:install', () => {
|
|
|
4546
4684
|
if (!isNpmMode()) return { ok: false, error: NPM_ONLY_TEXT };
|
|
4547
4685
|
npmUpdateViaCmd();
|
|
4548
4686
|
return { ok: true, npm: true };
|
|
4549
|
-
});
|
|
4687
|
+
});
|
|
4550
4688
|
|
|
4551
4689
|
ipcMain.handle('update:setAuto', (_e, cfg) => {
|
|
4552
4690
|
if (cfg && typeof cfg.autoCheck === 'boolean') settings.autoCheckUpdate = cfg.autoCheck;
|
|
@@ -4781,7 +4919,12 @@ ipcMain.handle('cron:runNow', (_e, id) => {
|
|
|
4781
4919
|
|
|
4782
4920
|
/* ---------------- tarayıcı IPC ---------------- */
|
|
4783
4921
|
ipcMain.handle('browser:toggle', () => {
|
|
4784
|
-
/*
|
|
4922
|
+
/* gizli çalışan ajan paneli varsa düğme ONU GÖRÜNÜR yapar; değilse aç/kapa.
|
|
4923
|
+
(ajanlar tarayıcıyı hep gizli açar — kullanıcı izlemek isterse buradan gösterir) */
|
|
4924
|
+
if (browser.open && !browser.visible) {
|
|
4925
|
+
setBrowserOpen(true, true);
|
|
4926
|
+
return { open: browser.open, visible: browser.visible };
|
|
4927
|
+
}
|
|
4785
4928
|
setBrowserOpen(!browser.open, true);
|
|
4786
4929
|
return { open: browser.open, visible: browser.visible };
|
|
4787
4930
|
});
|
|
@@ -5438,12 +5581,15 @@ function runNpmGlobalInstall() {
|
|
|
5438
5581
|
});
|
|
5439
5582
|
}
|
|
5440
5583
|
|
|
5441
|
-
/* auth login etkileşimlidir (tarayıcı açar) — kullanıcı görsün diye ayrı pencere
|
|
5584
|
+
/* auth login etkileşimlidir (tarayıcı açar) — kullanıcı görsün diye ayrı pencere.
|
|
5585
|
+
-ExecutionPolicy Bypass: SADECE bu pencere için — Windows varsayılan "Restricted"
|
|
5586
|
+
politikası npm'in opencode.ps1 shim'ini bloklar ("running scripts is disabled")
|
|
5587
|
+
→ taze makinede tek tık kurulum patlamasın; sistem politikası DEĞİŞMEZ. */
|
|
5442
5588
|
function openZenAuthWindow() {
|
|
5443
5589
|
try {
|
|
5444
5590
|
spawn(
|
|
5445
5591
|
'cmd.exe',
|
|
5446
|
-
['/c', 'start', '', 'powershell.exe', '-NoExit', '-Command', 'opencode auth login'],
|
|
5592
|
+
['/c', 'start', '', 'powershell.exe', '-NoExit', '-ExecutionPolicy', 'Bypass', '-Command', 'opencode auth login'],
|
|
5447
5593
|
{ detached: true, windowsHide: false, stdio: 'ignore' }
|
|
5448
5594
|
).unref();
|
|
5449
5595
|
return true;
|
|
@@ -5668,6 +5814,42 @@ ipcMain.handle('tg:allow:set', (_e, list) => {
|
|
|
5668
5814
|
});
|
|
5669
5815
|
ipcMain.handle('tg:sessions', () => [...tgChats.values()]);
|
|
5670
5816
|
|
|
5817
|
+
/* ---------- Discord IPC ---------- */
|
|
5818
|
+
ipcMain.handle('dc:status:get', () => {
|
|
5819
|
+
if (!dc) return { configured: !!settings.dcToken, status: 'disconnected', user: null, connected: false };
|
|
5820
|
+
return { configured: true, ...dc.snapshot() };
|
|
5821
|
+
});
|
|
5822
|
+
|
|
5823
|
+
/* token kaydet + köprüyü (yeniden) başlat */
|
|
5824
|
+
ipcMain.handle('dc:set', async (_e, token) => {
|
|
5825
|
+
const t = String(token || '').trim();
|
|
5826
|
+
if (t) settings.dcToken = t;
|
|
5827
|
+
saveSettings();
|
|
5828
|
+
await restartDc();
|
|
5829
|
+
return { configured: !!settings.dcToken, ...(dc ? dc.snapshot() : { status: 'disconnected', user: null }) };
|
|
5830
|
+
});
|
|
5831
|
+
|
|
5832
|
+
ipcMain.handle('dc:start', async () => {
|
|
5833
|
+
if (!settings.dcToken) return { ok: false, error: 'token yok — önce bot tokenı gir' };
|
|
5834
|
+
await restartDc();
|
|
5835
|
+
return { ok: true, ...(dc ? dc.snapshot() : {}) };
|
|
5836
|
+
});
|
|
5837
|
+
|
|
5838
|
+
ipcMain.handle('dc:stop', async () => {
|
|
5839
|
+
if (dc) {
|
|
5840
|
+
try { await dc.stop(); } catch {}
|
|
5841
|
+
}
|
|
5842
|
+
return { ok: true };
|
|
5843
|
+
});
|
|
5844
|
+
|
|
5845
|
+
ipcMain.handle('dc:allow:get', () => settings.dcAllow || []);
|
|
5846
|
+
ipcMain.handle('dc:allow:set', (_e, list) => {
|
|
5847
|
+
settings.dcAllow = Array.isArray(list) ? list : [];
|
|
5848
|
+
saveSettings();
|
|
5849
|
+
return settings.dcAllow;
|
|
5850
|
+
});
|
|
5851
|
+
ipcMain.handle('dc:sessions', () => [...dcChats.values()]);
|
|
5852
|
+
|
|
5671
5853
|
/* ---------- e-posta IPC ---------- */
|
|
5672
5854
|
|
|
5673
5855
|
ipcMain.handle('email:get', () => {
|
|
@@ -5929,6 +6111,7 @@ ipcMain.handle('bots:update', (_e, { id, patch }) => {
|
|
|
5929
6111
|
if ((v.botId || 'beast') === String(id || '')) {
|
|
5930
6112
|
const cfg = bots.get(String(id));
|
|
5931
6113
|
engine.setSessionTools(v.id, cfg && !cfg.admin ? botToolSet(cfg) : null);
|
|
6114
|
+
engine.setSessionModel(v.id, cfg && !cfg.admin ? (cfg.model || null) : null);
|
|
5932
6115
|
}
|
|
5933
6116
|
}
|
|
5934
6117
|
} catch {}
|