beast-agent 2.44.14 → 2.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/computeruse.js +217 -69
- package/src/agent/engine.js +70 -18
- package/src/agent/opencode/toolmap.js +3 -1
- package/src/agent/tools.js +251 -16
- package/src/agent/watext.js +2 -0
- package/src/main.js +694 -27
- package/src/preload.js +5 -0
- package/src/renderer/index.html +18 -0
- package/src/renderer/renderer.js +307 -0
- package/src/renderer/style.css +125 -1
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, net: electronNet, clipboard, Notification } = require('electron');
|
|
3
|
+
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, screen, session, net: electronNet, clipboard, Notification } = require('electron');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const http = require('http');
|
|
@@ -2292,7 +2292,7 @@ function botToolSet(cfg) {
|
|
|
2292
2292
|
]);
|
|
2293
2293
|
if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('webfetch'); set.add('deep_search'); }
|
|
2294
2294
|
if (s.browser) {
|
|
2295
|
-
for (const t of ['browser_open', 'browser_read', 'browser_screenshot', 'browser_snapshot', 'browser_click', 'browser_type', 'browser_press', 'browser_scroll', 'browser_select', 'ocr_read', 'computer_look']) set.add(t);
|
|
2295
|
+
for (const t of ['browser_open', 'browser_read', 'browser_screenshot', 'browser_snapshot', 'browser_click', 'browser_type', 'browser_press', 'browser_scroll', 'browser_select', 'browser_wait', 'ocr_read', 'computer_look']) set.add(t);
|
|
2296
2296
|
}
|
|
2297
2297
|
if (s.email) { set.add('email_list'); set.add('email_read'); set.add('email_send'); }
|
|
2298
2298
|
if (s.run_command) {
|
|
@@ -3259,7 +3259,7 @@ function reloadBackend() {
|
|
|
3259
3259
|
},
|
|
3260
3260
|
computer: {
|
|
3261
3261
|
look: captureScreenDataUrl,
|
|
3262
|
-
act: (op, args) =>
|
|
3262
|
+
act: (op, args) => computerActScaled(op, args),
|
|
3263
3263
|
},
|
|
3264
3264
|
ocr: (o) => ocrRead(o),
|
|
3265
3265
|
email: { list: emailList, read: emailRead, send: emailSend },
|
|
@@ -3270,6 +3270,7 @@ function reloadBackend() {
|
|
|
3270
3270
|
screenshot: (s) => browserScreenshot(s),
|
|
3271
3271
|
snapshot: (s) => browserSnapshot(s),
|
|
3272
3272
|
act: (k, a, s) => browserAct(k, a, s),
|
|
3273
|
+
wait: (a, s) => browserWait(a, s),
|
|
3273
3274
|
},
|
|
3274
3275
|
research: {
|
|
3275
3276
|
/* deep_search'ün "sayfayı GİZLİ tarayıcıda açıp oku" parçası (Electron-only).
|
|
@@ -3310,6 +3311,7 @@ function reloadBackend() {
|
|
|
3310
3311
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
3311
3312
|
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
3312
3313
|
sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
|
|
3314
|
+
mcpChatFlushOnDone(ev); /* MCP sohbet kuyruğunu iş bitiminde boşalt */
|
|
3313
3315
|
finFlushOnDone(ev); /* Finance trader döngüsü: iş bitince sıradaki turu planla */
|
|
3314
3316
|
/* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
|
|
3315
3317
|
preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
|
|
@@ -4135,6 +4137,26 @@ function ensureBrowser() {
|
|
|
4135
4137
|
if (/^https?:\/\//i.test(url)) wc.loadURL(url).catch(() => {});
|
|
4136
4138
|
return { action: 'deny' };
|
|
4137
4139
|
});
|
|
4140
|
+
/* JS DİYALOGLARI (alert/confirm/prompt/beforeunload): CDP ile OTOMATİK
|
|
4141
|
+
onaylanır/kapatılır — aksi halde renderer kilitlenir ve TÜM tarayıcı
|
|
4142
|
+
araçları askıda kalır (executeJavaScript diyalog kapanana dek dönmez). */
|
|
4143
|
+
try {
|
|
4144
|
+
if (!wc.debugger.isAttached()) wc.debugger.attach('1.3');
|
|
4145
|
+
wc.debugger.sendCommand('Page.enable').catch(() => {});
|
|
4146
|
+
wc.debugger.on('message', (_ev, method, params) => {
|
|
4147
|
+
if (method === 'Page.javascriptDialogOpening') {
|
|
4148
|
+
browser.lastDialog = {
|
|
4149
|
+
type: String((params && params.type) || 'alert'),
|
|
4150
|
+
message: String((params && params.message) || ''),
|
|
4151
|
+
at: Date.now(),
|
|
4152
|
+
};
|
|
4153
|
+
try { wc.debugger.sendCommand('Page.handleJavaScriptDialog', { accept: true }).catch(() => {}); } catch {}
|
|
4154
|
+
blog('dialog', browser.lastDialog.type + ': ' + browser.lastDialog.message.slice(0, 80));
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
} catch (e) {
|
|
4158
|
+
waLog('tarayıcı diyalog köprüsü kurulamadı: ' + String((e && e.message) || e).slice(0, 120));
|
|
4159
|
+
}
|
|
4138
4160
|
// Tarayıcı kapalıyken olayların UI'ı geri açmamasını garantile
|
|
4139
4161
|
const notify = (extra) => {
|
|
4140
4162
|
if (browser.open && win && !win.isDestroyed()) {
|
|
@@ -4251,6 +4273,61 @@ async function browserNavigate(raw, signal, ctx) {
|
|
|
4251
4273
|
});
|
|
4252
4274
|
}
|
|
4253
4275
|
|
|
4276
|
+
/* JS diyalogları (alert/confirm/prompt) CDP ile OTOMATİK yönetilir;
|
|
4277
|
+
bu yardımcı çağrı SIRASINDA oluşan diyaloğu yanıta ekler. */
|
|
4278
|
+
function browserDialogNote(sinceAt) {
|
|
4279
|
+
const d = browser.lastDialog;
|
|
4280
|
+
if (!d || !d.at || d.at <= (sinceAt || 0)) return {};
|
|
4281
|
+
const verb = d.type === 'alert' || d.type === 'prompt' ? 'kapatıldı' : 'onaylandı';
|
|
4282
|
+
return {
|
|
4283
|
+
dialog: { type: d.type, message: d.message },
|
|
4284
|
+
dialogNote: 'JS diyaloğu otomatik ' + verb + ': "' + String(d.message || '').slice(0, 120) + '"',
|
|
4285
|
+
};
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
/* SPA/DOM oturması: readyState complete + içerik uzunluğu İKİ turdur sabitse
|
|
4289
|
+
dön (en çok maxMs). Böylece hidrasyon bitmeden snapshot/okuma yapılmaz. */
|
|
4290
|
+
async function browserSettle(wc, signal, maxMs = 4000) {
|
|
4291
|
+
const t0 = Date.now();
|
|
4292
|
+
let lastLen = -1;
|
|
4293
|
+
let stable = 0;
|
|
4294
|
+
while (Date.now() - t0 < maxMs) {
|
|
4295
|
+
if (signal && signal.aborted) return;
|
|
4296
|
+
let info = null;
|
|
4297
|
+
try {
|
|
4298
|
+
info = await wc.executeJavaScript(
|
|
4299
|
+
'({rs: document.readyState, len: (document.body ? ((document.body.innerText || "").trim().length) : 0)})',
|
|
4300
|
+
true
|
|
4301
|
+
);
|
|
4302
|
+
} catch {
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4305
|
+
if (!info) return;
|
|
4306
|
+
const rs = String(info.rs || '');
|
|
4307
|
+
const len = Number(info.len) || 0;
|
|
4308
|
+
if (rs === 'complete') {
|
|
4309
|
+
if (len > 0 && len === lastLen) stable++;
|
|
4310
|
+
else stable = 0;
|
|
4311
|
+
if (stable >= 2) return;
|
|
4312
|
+
/* boş/az metinli sayfa (canvas/SPA shell): yine de kısa bir hidrasyon penceresi bırak */
|
|
4313
|
+
if (len === 0 && Date.now() - t0 > 1500) return;
|
|
4314
|
+
}
|
|
4315
|
+
lastLen = len;
|
|
4316
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
/* snapshot al (hatayı yut) — navigate/act yanıtlarına gömmek için */
|
|
4321
|
+
async function browserSnapshotNow(wc) {
|
|
4322
|
+
try {
|
|
4323
|
+
const raw = await wc.executeJavaScript(BROWSER_SNAPSHOT_JS, true);
|
|
4324
|
+
const obj = JSON.parse(raw);
|
|
4325
|
+
return obj && typeof obj.count === 'number' ? obj : null;
|
|
4326
|
+
} catch {
|
|
4327
|
+
return null;
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4254
4331
|
async function browserNavigateNow(raw, signal, ctx) {
|
|
4255
4332
|
let url = String(raw || '').trim();
|
|
4256
4333
|
if (!url) return { ok: false, error: 'boş adres' };
|
|
@@ -4262,11 +4339,27 @@ async function browserNavigateNow(raw, signal, ctx) {
|
|
|
4262
4339
|
}
|
|
4263
4340
|
setBrowserOpenForAgent();
|
|
4264
4341
|
const wc = browser.view.webContents;
|
|
4342
|
+
const dlgAt = browser.lastDialog ? browser.lastDialog.at : 0;
|
|
4343
|
+
let failInfo = null;
|
|
4344
|
+
let timedOut = false;
|
|
4265
4345
|
await new Promise((resolve) => {
|
|
4266
4346
|
let settled = false;
|
|
4267
|
-
const finish = () => {
|
|
4268
|
-
|
|
4269
|
-
|
|
4347
|
+
const finish = () => {
|
|
4348
|
+
if (settled) return;
|
|
4349
|
+
settled = true;
|
|
4350
|
+
clearTimeout(timer);
|
|
4351
|
+
cleanup();
|
|
4352
|
+
resolve();
|
|
4353
|
+
};
|
|
4354
|
+
const timer = setTimeout(() => { timedOut = true; finish(); }, 25000);
|
|
4355
|
+
/* did-fail-load: ANA kare hatasını YAKALA ve rapora koy. kod -3 (ABORTED)
|
|
4356
|
+
yönlendirme/SPA geçişlerinde normaldir; alt kare hataları sayılmaz. */
|
|
4357
|
+
const onFail = (_e, code, desc, validatedURL, isMainFrame) => {
|
|
4358
|
+
if (isMainFrame === false) return;
|
|
4359
|
+
if (code === -3) return;
|
|
4360
|
+
failInfo = { code, desc: String(desc || ''), url: String(validatedURL || url) };
|
|
4361
|
+
finish();
|
|
4362
|
+
};
|
|
4270
4363
|
function cleanup() {
|
|
4271
4364
|
clearTimeout(timer);
|
|
4272
4365
|
wc.removeListener('did-finish-load', finish);
|
|
@@ -4274,24 +4367,44 @@ async function browserNavigateNow(raw, signal, ctx) {
|
|
|
4274
4367
|
}
|
|
4275
4368
|
wc.once('did-finish-load', finish);
|
|
4276
4369
|
wc.on('did-fail-load', onFail);
|
|
4277
|
-
wc.loadURL(url).catch(() => {
|
|
4370
|
+
wc.loadURL(url).catch((e) => {
|
|
4371
|
+
failInfo = { code: null, desc: String((e && e.message) || e) };
|
|
4372
|
+
finish();
|
|
4373
|
+
});
|
|
4278
4374
|
});
|
|
4279
4375
|
let title = '';
|
|
4280
4376
|
let finalUrl = url;
|
|
4281
4377
|
try { title = wc.getTitle(); finalUrl = wc.getURL() || url; } catch {}
|
|
4282
4378
|
browserEmit({ open: true, width: browserShownWidth(browserW()), url: finalUrl });
|
|
4283
4379
|
flushBrowserStorage(); // oturum çerezleri diske — ani kapanışta kaybolmasın
|
|
4284
|
-
/*
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
const
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4380
|
+
/* YÜKLEME HATASI: artık "açıldı" denmez — kod/açıklama + çözüm ipucu döner */
|
|
4381
|
+
if (failInfo) {
|
|
4382
|
+
const codeTxt = failInfo.code != null ? ' kod ' + failInfo.code : '';
|
|
4383
|
+
const hint = /ERR_NAME_NOT_RESOLVED|ERR_INTERNET_DISCONNECTED/i.test(failInfo.desc)
|
|
4384
|
+
? ' (DNS/ağ: internet bağlantısını ve adresi kontrol et)'
|
|
4385
|
+
: /ERR_CONNECTION_REFUSED|ERR_CONNECTION_TIMED_OUT|ERR_EMPTY_RESPONSE/i.test(failInfo.desc)
|
|
4386
|
+
? ' (sunucu yanıt vermiyor — adres/port doğru mu?)'
|
|
4387
|
+
: '';
|
|
4388
|
+
return {
|
|
4389
|
+
ok: false,
|
|
4390
|
+
error: 'sayfa yüklenemedi' + codeTxt + ': ' + failInfo.desc + hint,
|
|
4391
|
+
url: failInfo.url || finalUrl,
|
|
4392
|
+
...browserDialogNote(dlgAt),
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
/* SPA hidrasyonu: içerik oturmadan snapshot alınmaz (timeout'ta bekleme yok) */
|
|
4396
|
+
await browserSettle(wc, signal, timedOut ? 0 : 4000);
|
|
4397
|
+
const snap = await browserSnapshotNow(wc);
|
|
4398
|
+
let stillLoading = false;
|
|
4399
|
+
try { stillLoading = wc.isLoading(); } catch {}
|
|
4291
4400
|
return {
|
|
4292
4401
|
ok: true,
|
|
4402
|
+
...(timedOut
|
|
4403
|
+
? { warning: 'yükleme 25 sn içinde tamamlanmadı — sayfa kısmen yüklü olabilir; browser_wait/browser_read ile kontrol et', loading: stillLoading }
|
|
4404
|
+
: {}),
|
|
4293
4405
|
url: finalUrl,
|
|
4294
4406
|
title,
|
|
4407
|
+
...browserDialogNote(dlgAt),
|
|
4295
4408
|
...(snap
|
|
4296
4409
|
? { snapshot: snap.snapshot, refCount: snap.count, note: 'sayfa acildi — güncel snapshot hazır (' + snap.count + ' ref); ref numarasıyla browser_click/browser_type ile devam et' }
|
|
4297
4410
|
: { note: 'sayfa acildi — etkileşimli elemanlar için browser_snapshot al' }),
|
|
@@ -4691,21 +4804,70 @@ async function browserSearchNow(query, signal, ctx) {
|
|
|
4691
4804
|
}
|
|
4692
4805
|
}
|
|
4693
4806
|
|
|
4694
|
-
|
|
4807
|
+
/* Ekran görüntüsü üzerine ref numaralarını ÇİZ: vision modeli kutuları ve
|
|
4808
|
+
numaraları görür, koordinatı tahmin etmek yerine okur. Ref'ler snapshot ile
|
|
4809
|
+
AYNI numaralandırmayı kullanır (snapshot hemen önce alınır). */
|
|
4810
|
+
async function annotateBrowserShot(img, snap) {
|
|
4811
|
+
const lib = canvasLib();
|
|
4812
|
+
if (!lib || !snap || !snap.boxes) return null;
|
|
4813
|
+
const im = await lib.loadImage(img.toPNG());
|
|
4814
|
+
const canvas = lib.createCanvas(im.width, im.height);
|
|
4815
|
+
const ctx = canvas.getContext('2d');
|
|
4816
|
+
ctx.drawImage(im, 0, 0);
|
|
4817
|
+
const vw = Number(snap.vw) || im.width;
|
|
4818
|
+
const k = im.width / Math.max(1, vw);
|
|
4819
|
+
let drawn = 0;
|
|
4820
|
+
for (const [ref, b] of Object.entries(snap.boxes)) {
|
|
4821
|
+
const x = Number(b[0]) * k;
|
|
4822
|
+
const y = Number(b[1]) * k;
|
|
4823
|
+
const w = Number(b[2]) * k;
|
|
4824
|
+
const h = Number(b[3]) * k;
|
|
4825
|
+
if (!(w >= 6 && h >= 6)) continue;
|
|
4826
|
+
if (y > im.height || x > im.width || y + h < 0 || x + w < 0) continue;
|
|
4827
|
+
ctx.strokeStyle = 'rgba(0,190,255,0.95)';
|
|
4828
|
+
ctx.lineWidth = 1.5;
|
|
4829
|
+
ctx.strokeRect(x + 0.5, y + 0.5, Math.max(2, w), Math.max(2, h));
|
|
4830
|
+
const label = String(ref);
|
|
4831
|
+
ctx.font = 'bold 13px Segoe UI, sans-serif';
|
|
4832
|
+
const tw = Math.ceil(ctx.measureText(label).width);
|
|
4833
|
+
const by = y - 16 < 0 ? y + 1 : y - 16;
|
|
4834
|
+
ctx.fillStyle = 'rgba(0,0,0,0.78)';
|
|
4835
|
+
ctx.fillRect(x, by, tw + 8, 16);
|
|
4836
|
+
ctx.fillStyle = '#00d8ff';
|
|
4837
|
+
ctx.fillText(label, x + 4, by + 12.5);
|
|
4838
|
+
if (++drawn >= 90) break;
|
|
4839
|
+
}
|
|
4840
|
+
return canvas.toBuffer('image/jpeg', 78);
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
async function browserScreenshot(signal, opts) {
|
|
4695
4844
|
if (!browser.view || !browser.open) return { ok: false, error: 'tarayıcı açık değil' };
|
|
4696
4845
|
const wc = browser.view.webContents;
|
|
4846
|
+
const annotate = !(opts && opts.annotate === false);
|
|
4847
|
+
const dlgAt = browser.lastDialog ? browser.lastDialog.at : 0;
|
|
4697
4848
|
try {
|
|
4849
|
+
/* ref numaraları görselle eşleşsin: önce taze snapshot */
|
|
4850
|
+
let snap = null;
|
|
4851
|
+
if (annotate) snap = await browserSnapshotNow(wc);
|
|
4698
4852
|
const img = await wc.capturePage();
|
|
4699
4853
|
let out = img;
|
|
4700
4854
|
const sz = img.getSize();
|
|
4701
4855
|
const maxW = 1000; // vision bütçesi için küçült
|
|
4702
4856
|
if (sz.width > maxW) out = img.resize({ width: maxW });
|
|
4703
|
-
|
|
4857
|
+
let jpeg = null;
|
|
4858
|
+
if (annotate) {
|
|
4859
|
+
try { jpeg = await annotateBrowserShot(out, snap); } catch {}
|
|
4860
|
+
}
|
|
4861
|
+
if (!jpeg || !jpeg.length) jpeg = out.toJPEG(72);
|
|
4704
4862
|
if (!jpeg || !jpeg.length) return { ok: false, error: 'görüntü alınamadı' };
|
|
4705
4863
|
return {
|
|
4706
4864
|
ok: true,
|
|
4707
|
-
note:
|
|
4865
|
+
note: snap
|
|
4866
|
+
? 'ekran görüntüsü bir sonraki adımda gösterilecek — üzerindeki numaralar snapshot ref\'leriyle AYNIDIR; tıklamak için o ref\'i kullan'
|
|
4867
|
+
: 'ekran görüntüsü bir sonraki adımda sana gösterilecek',
|
|
4708
4868
|
__injectImage: 'data:image/jpeg;base64,' + jpeg.toString('base64'),
|
|
4869
|
+
...(snap ? { snapshot: snap.snapshot, refCount: snap.count } : {}),
|
|
4870
|
+
...browserDialogNote(dlgAt),
|
|
4709
4871
|
url: wc.getURL(),
|
|
4710
4872
|
title: wc.getTitle(),
|
|
4711
4873
|
bytes: jpeg.length,
|
|
@@ -4754,6 +4916,7 @@ const BROWSER_SNAPSHOT_JS = `(function(){
|
|
|
4754
4916
|
window.__beMap={};
|
|
4755
4917
|
const seen=new Set();
|
|
4756
4918
|
const lines=[];
|
|
4919
|
+
const boxes={};
|
|
4757
4920
|
let i=0;
|
|
4758
4921
|
function add(e){
|
|
4759
4922
|
if(!e||seen.has(e)) return;
|
|
@@ -4761,6 +4924,8 @@ const BROWSER_SNAPSHOT_JS = `(function(){
|
|
|
4761
4924
|
if(!__vis(e)||e.disabled||e.getAttribute('aria-disabled')==='true') return;
|
|
4762
4925
|
i++;
|
|
4763
4926
|
window.__beMap[i]=e;
|
|
4927
|
+
const rb=e.getBoundingClientRect();
|
|
4928
|
+
boxes[i]=[Math.round(rb.left),Math.round(rb.top),Math.round(rb.width),Math.round(rb.height)];
|
|
4764
4929
|
const tag=e.tagName.toLowerCase();
|
|
4765
4930
|
const type=(e.getAttribute&&e.getAttribute('type'))||'';
|
|
4766
4931
|
let s='['+i+'] <'+tag+(type?' type='+type:'')+'>';
|
|
@@ -4776,7 +4941,7 @@ const BROWSER_SNAPSHOT_JS = `(function(){
|
|
|
4776
4941
|
lines.push('--- SAYFA ---');
|
|
4777
4942
|
}
|
|
4778
4943
|
for(const e of document.querySelectorAll(sel)){ if(i>=100) break; add(e); }
|
|
4779
|
-
return JSON.stringify({count:i,title:document.title,url:location.href,snapshot:lines.join('\\n')});
|
|
4944
|
+
return JSON.stringify({count:i,title:document.title,url:location.href,snapshot:lines.join('\\n'),boxes:boxes,vw:window.innerWidth,vh:window.innerHeight});
|
|
4780
4945
|
})()`;
|
|
4781
4946
|
|
|
4782
4947
|
function browserActionJs(kind, args) {
|
|
@@ -4953,6 +5118,10 @@ async function browserSnapshot(signal) {
|
|
|
4953
5118
|
async function browserAct(kind, args, signal) {
|
|
4954
5119
|
if (!browser.view || !browser.open) return { ok: false, error: 'tarayıcı açık değil' };
|
|
4955
5120
|
const wc = browser.view.webContents;
|
|
5121
|
+
const dlgAt = browser.lastDialog ? browser.lastDialog.at : 0;
|
|
5122
|
+
/* DOĞRULAMA: eylem öncesi görsel parmak izi (yalnız anlamlı eylemlerde) */
|
|
5123
|
+
const verify = ['click', 'type', 'select', 'press'].includes(kind) && !(args && args.verify === false);
|
|
5124
|
+
const sigBefore = verify ? await pageSignature(wc) : null;
|
|
4956
5125
|
try {
|
|
4957
5126
|
const raw = await wc.executeJavaScript(browserActionJs(kind, args || {}), true);
|
|
4958
5127
|
let obj = {};
|
|
@@ -4969,6 +5138,20 @@ async function browserAct(kind, args, signal) {
|
|
|
4969
5138
|
wc.on('did-navigate', onNav);
|
|
4970
5139
|
});
|
|
4971
5140
|
}
|
|
5141
|
+
/* SPA: sayfa değiştiyse DOM oturmadan snapshot alınmaz */
|
|
5142
|
+
if (navigated) await browserSettle(wc, signal, 2500);
|
|
5143
|
+
|
|
5144
|
+
/* DOĞRULAMA: eylem sonrası değişim oranı — "tıkladım ama bir şey olmadı" tuzağını yakalar */
|
|
5145
|
+
let changed = null;
|
|
5146
|
+
let changeRatio = null;
|
|
5147
|
+
if (sigBefore) {
|
|
5148
|
+
const sigAfter = await pageSignature(wc);
|
|
5149
|
+
const ratio = computeruse.signatureDiff(sigBefore, sigAfter);
|
|
5150
|
+
if (ratio != null) {
|
|
5151
|
+
changed = ratio >= 0.02;
|
|
5152
|
+
changeRatio = Number(ratio.toFixed(3));
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
4972
5155
|
|
|
4973
5156
|
blog(
|
|
4974
5157
|
kind,
|
|
@@ -4981,12 +5164,7 @@ async function browserAct(kind, args, signal) {
|
|
|
4981
5164
|
);
|
|
4982
5165
|
|
|
4983
5166
|
/* HIZ: eylem cevabına taze snapshot göm — model ayrı browser_snapshot çağırmaz (tur sayısı yarıya iner) */
|
|
4984
|
-
|
|
4985
|
-
try {
|
|
4986
|
-
const sraw = await wc.executeJavaScript(BROWSER_SNAPSHOT_JS, true);
|
|
4987
|
-
const sobj = JSON.parse(sraw);
|
|
4988
|
-
if (sobj && typeof sobj.count === 'number') freshSnap = sobj;
|
|
4989
|
-
} catch {}
|
|
5167
|
+
const freshSnap = await browserSnapshotNow(wc);
|
|
4990
5168
|
|
|
4991
5169
|
return {
|
|
4992
5170
|
ok: true,
|
|
@@ -4995,6 +5173,16 @@ async function browserAct(kind, args, signal) {
|
|
|
4995
5173
|
url: wc.getURL(),
|
|
4996
5174
|
title: wc.getTitle(),
|
|
4997
5175
|
navigated,
|
|
5176
|
+
...(changed != null ? { changed, changeRatio } : {}),
|
|
5177
|
+
...(changed === false && ['click', 'type', 'select'].includes(kind)
|
|
5178
|
+
? {
|
|
5179
|
+
warning:
|
|
5180
|
+
'görünür değişiklik yok (changeRatio ' + changeRatio + ') — eylem etkisiz olabilir: ' +
|
|
5181
|
+
'ref bayat olabilir (browser_snapshot al ve güncel ref ile tekrar dene), öğe kapalı/gizli olabilir ' +
|
|
5182
|
+
'ya da tıklama sayfa tarafından engellenmiş olabilir. browser_wait ile kısa bekleme dene.',
|
|
5183
|
+
}
|
|
5184
|
+
: {}),
|
|
5185
|
+
...browserDialogNote(dlgAt),
|
|
4998
5186
|
...(freshSnap ? { snapshot: freshSnap.snapshot, refCount: freshSnap.count } : {}),
|
|
4999
5187
|
...(navigated
|
|
5000
5188
|
? { note: 'sayfa degisti — yanıtta güncel snapshot var; refler eskiyse yeni browser_snapshot al' }
|
|
@@ -5008,6 +5196,61 @@ async function browserAct(kind, args, signal) {
|
|
|
5008
5196
|
}
|
|
5009
5197
|
}
|
|
5010
5198
|
|
|
5199
|
+
/* BEKLE: selector/text/ref görünene (gone:true ise kaybolana) kadar ya da ms
|
|
5200
|
+
kadar bekle. SPA'lar, gecikmeli açılan menüler ve ağ yavaşlığı için —
|
|
5201
|
+
körlemesine tekrar denemek yerine deterministik bekleme. */
|
|
5202
|
+
async function browserWait(args, signal) {
|
|
5203
|
+
if (!browser.view || !browser.open) return { ok: false, error: 'tarayıcı açık değil' };
|
|
5204
|
+
const wc = browser.view.webContents;
|
|
5205
|
+
const a = args || {};
|
|
5206
|
+
const timeout = Math.max(200, Math.min(30000, Number(a.timeout_ms) || 10000));
|
|
5207
|
+
const ms = Math.max(0, Math.min(10000, Number(a.ms) || 0));
|
|
5208
|
+
const hasCond = a.selector != null || a.text != null || a.ref != null;
|
|
5209
|
+
if (!hasCond) {
|
|
5210
|
+
if (!ms) return { ok: false, error: 'selector, text, ref ya da ms ver' };
|
|
5211
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
5212
|
+
return { ok: true, waited_ms: ms, url: wc.getURL(), title: wc.getTitle() };
|
|
5213
|
+
}
|
|
5214
|
+
const gone = !!a.gone;
|
|
5215
|
+
const t0 = Date.now();
|
|
5216
|
+
/* ms verildiyse koşul taramasından ÖNCE sabit bekleme (extra delay) */
|
|
5217
|
+
if (ms) await new Promise((r) => setTimeout(r, ms));
|
|
5218
|
+
const refJs = a.ref != null ? `!!__resolveRef(${JSON.stringify(Number(a.ref))})` : '';
|
|
5219
|
+
const selJs = a.selector != null ? `(function(){try{var el=document.querySelector(${JSON.stringify(String(a.selector))});return !!(el&&__vis(el));}catch(e){return false;}})()` : '';
|
|
5220
|
+
const txtJs = a.text != null ? `((document.body?document.body.innerText:'').toLowerCase().includes(${JSON.stringify(String(a.text).toLowerCase())}))` : '';
|
|
5221
|
+
const probe = `(function(){${BROWSER_JS_HELPERS};try{return !!(${refJs || selJs || txtJs});}catch(e){return false;}})()`;
|
|
5222
|
+
while (Date.now() - t0 < timeout) {
|
|
5223
|
+
if (signal && signal.aborted) return { ok: false, error: 'bekleme iptal edildi' };
|
|
5224
|
+
let found = false;
|
|
5225
|
+
try { found = !!(await wc.executeJavaScript(probe, true)); } catch { found = false; }
|
|
5226
|
+
if (gone ? !found : found) {
|
|
5227
|
+
return {
|
|
5228
|
+
ok: true,
|
|
5229
|
+
waited_ms: Date.now() - t0,
|
|
5230
|
+
...(gone ? { gone: true } : { found: true }),
|
|
5231
|
+
...(a.selector != null ? { selector: String(a.selector) } : {}),
|
|
5232
|
+
...(a.text != null ? { text: String(a.text).slice(0, 60) } : {}),
|
|
5233
|
+
...(a.ref != null ? { ref: Number(a.ref) } : {}),
|
|
5234
|
+
url: wc.getURL(),
|
|
5235
|
+
title: wc.getTitle(),
|
|
5236
|
+
note: 'koşul sağlandı — şimdi eyleme geç; ref lazımsa browser_snapshot ile güncel ref al',
|
|
5237
|
+
};
|
|
5238
|
+
}
|
|
5239
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
5240
|
+
}
|
|
5241
|
+
return {
|
|
5242
|
+
ok: false,
|
|
5243
|
+
error:
|
|
5244
|
+
'bekleme zaman aşımı (' + timeout + ' ms): ' +
|
|
5245
|
+
(a.selector != null ? 'selector bulunamadı: ' + String(a.selector).slice(0, 60)
|
|
5246
|
+
: a.text != null ? 'metin bulunamadı: ' + String(a.text).slice(0, 60)
|
|
5247
|
+
: 'ref mevcut değil: ' + Number(a.ref)) +
|
|
5248
|
+
(gone ? ' (hâlâ görünüyor)' : ''),
|
|
5249
|
+
url: wc.getURL(),
|
|
5250
|
+
title: wc.getTitle(),
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5011
5254
|
/* oturum açılışında --hidden ile başlarsa pencere gösterme, tepside yaşa */
|
|
5012
5255
|
const startHidden =
|
|
5013
5256
|
process.argv.includes('--hidden') ||
|
|
@@ -6393,6 +6636,211 @@ ipcMain.handle('mcp:refresh', async (_e, name) => {
|
|
|
6393
6636
|
}
|
|
6394
6637
|
});
|
|
6395
6638
|
|
|
6639
|
+
/* ---------------- BEAST MCP SOHBETİ (MCP modu SAĞ panel) ----------------
|
|
6640
|
+
MCP moduna özel, ANA SOHBETTEN TAMAMEN AYRI oturum: bgTitle 'Beast MCP'
|
|
6641
|
+
(listSessions gizler, listMcpSessions listeler; geçmiş seçici eski
|
|
6642
|
+
oturumları açabilir). MCP sunucu araçları zaten tüm engine turlarına
|
|
6643
|
+
enjekte edilir; panel yalnız oturum yaşam döngüsünü yönetir.
|
|
6644
|
+
Gönderim main chat disiplininin aynısı: boşta kısa debounce (ard arda
|
|
6645
|
+
mesajlar birleşir), meşgulken steer (koşan tura anında eklenir). */
|
|
6646
|
+
let mcpChatSid = ''; /* aktif MCP sohbet oturumu */
|
|
6647
|
+
let mcpChatFresh = false; /* 'Yeni' istendi: eski sohbete DÖNME, taze aç */
|
|
6648
|
+
const mcpChatQueue = new Map(); /* 'mcp' → { timer, msgs[] } */
|
|
6649
|
+
const MCP_CHAT_DEBOUNCE_MS = 900;
|
|
6650
|
+
|
|
6651
|
+
function mcpChatCreate() {
|
|
6652
|
+
const s = engine._load(engine.createSession().id);
|
|
6653
|
+
s.messages = s.messages || [];
|
|
6654
|
+
s.bgTitle = 'Beast MCP'; /* _view.isBg → ana sohbet geçmişi listesinde gizli */
|
|
6655
|
+
try {
|
|
6656
|
+
fs.appendFileSync(
|
|
6657
|
+
engine._file(s.id),
|
|
6658
|
+
JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast MCP', at: new Date().toISOString() }) + '\n'
|
|
6659
|
+
);
|
|
6660
|
+
} catch {}
|
|
6661
|
+
engine.cache.set(s.id, s);
|
|
6662
|
+
mcpChatSid = s.id;
|
|
6663
|
+
mcpChatFresh = false;
|
|
6664
|
+
return s;
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6667
|
+
function mcpChatSession() {
|
|
6668
|
+
if (mcpChatSid) {
|
|
6669
|
+
try {
|
|
6670
|
+
const s = engine.cache.get(mcpChatSid) || engine._load(mcpChatSid);
|
|
6671
|
+
if (s && s.bgTitle === 'Beast MCP') return s;
|
|
6672
|
+
} catch {}
|
|
6673
|
+
mcpChatSid = '';
|
|
6674
|
+
}
|
|
6675
|
+
/* 'Yeni' istenmediyse son MCP sohbetine dön: uygulama yeniden açıldığında
|
|
6676
|
+
sohbet kaldığı yerden sürer (geçmiş seçiciden eski oturumlar açılabilir) */
|
|
6677
|
+
if (!mcpChatFresh) {
|
|
6678
|
+
try {
|
|
6679
|
+
const last = engine.listMcpSessions(50).find((it) => it.count > 0);
|
|
6680
|
+
if (last) {
|
|
6681
|
+
const s = engine._load(last.id);
|
|
6682
|
+
if (s && s.bgTitle === 'Beast MCP') {
|
|
6683
|
+
engine.cache.set(s.id, s);
|
|
6684
|
+
mcpChatSid = s.id;
|
|
6685
|
+
return s;
|
|
6686
|
+
}
|
|
6687
|
+
}
|
|
6688
|
+
} catch {}
|
|
6689
|
+
}
|
|
6690
|
+
return mcpChatCreate();
|
|
6691
|
+
}
|
|
6692
|
+
|
|
6693
|
+
/* geçmiş yükü: yalnız metin mesajları (tool çağrıları panele basılmaz) */
|
|
6694
|
+
function mcpChatMessages(s) {
|
|
6695
|
+
const msgs = [];
|
|
6696
|
+
for (const m of s.messages || []) {
|
|
6697
|
+
if (m.tool_calls) continue;
|
|
6698
|
+
const txt = bcMsgText(m);
|
|
6699
|
+
if (!txt) continue;
|
|
6700
|
+
msgs.push({ role: m.role === 'assistant' ? 'assistant' : 'user', text: txt.slice(0, 4000) });
|
|
6701
|
+
}
|
|
6702
|
+
return msgs.slice(-200);
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6705
|
+
/* geçmiş listesi: boş oturumlar gizlenir (aktif olan daima görünür) */
|
|
6706
|
+
function mcpChatHistory() {
|
|
6707
|
+
let items = [];
|
|
6708
|
+
try { items = engine.listMcpSessions(100); } catch {}
|
|
6709
|
+
return items.filter((it) => it.id === mcpChatSid || it.count > 0);
|
|
6710
|
+
}
|
|
6711
|
+
|
|
6712
|
+
function mcpChatQueuePush(text, attachments) {
|
|
6713
|
+
let q = mcpChatQueue.get('mcp');
|
|
6714
|
+
if (!q) {
|
|
6715
|
+
q = { timer: null, msgs: [] };
|
|
6716
|
+
mcpChatQueue.set('mcp', q);
|
|
6717
|
+
}
|
|
6718
|
+
q.msgs.push({
|
|
6719
|
+
text,
|
|
6720
|
+
attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
|
|
6721
|
+
});
|
|
6722
|
+
return q;
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
function mcpChatFlush() {
|
|
6726
|
+
const q = mcpChatQueue.get('mcp');
|
|
6727
|
+
if (!q || !q.msgs.length) return;
|
|
6728
|
+
const s = mcpChatSession();
|
|
6729
|
+
if (engine.isBusy(s.id)) return; /* hâlâ çalışıyor — steer yolu mesajı zaten aldı */
|
|
6730
|
+
let merged = '';
|
|
6731
|
+
let mergedAtts = null;
|
|
6732
|
+
for (const m of q.msgs) {
|
|
6733
|
+
if (m.text) merged += (merged ? '\n' : '') + m.text;
|
|
6734
|
+
if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
|
|
6735
|
+
}
|
|
6736
|
+
mcpChatQueue.delete('mcp');
|
|
6737
|
+
clearTimeout(q.timer);
|
|
6738
|
+
if (!merged.trim() && !mergedAtts) return;
|
|
6739
|
+
try {
|
|
6740
|
+
engine.send(s.id, mergedAtts ? { text: merged, attachments: mergedAtts } : merged, { userAction: true });
|
|
6741
|
+
} catch (e) {
|
|
6742
|
+
/* senkron patlama: panel busy'de kilitlenmesin — hata olayı düş */
|
|
6743
|
+
try {
|
|
6744
|
+
if (win && !win.isDestroyed()) {
|
|
6745
|
+
win.webContents.send('agent:event', { sessionId: s.id, type: 'error', error: String((e && e.message) || e) });
|
|
6746
|
+
}
|
|
6747
|
+
} catch {}
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6751
|
+
function mcpChatFlushOnDone(ev) {
|
|
6752
|
+
if (!ev || (ev.type !== 'done' && ev.type !== 'error') || !ev.sessionId) return;
|
|
6753
|
+
if (mcpChatQueue.has('mcp') && String(ev.sessionId) === String(mcpChatSid)) {
|
|
6754
|
+
setTimeout(() => { try { mcpChatFlush(); } catch {} }, 150);
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
|
|
6758
|
+
ipcMain.handle('mcp:chat:open', async (_e, payload) => {
|
|
6759
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
6760
|
+
/* bekleyen debounce kuyruğu varsa ÖNCE eski oturuma teslim et (mesaj kaybolmaz) */
|
|
6761
|
+
try {
|
|
6762
|
+
if (mcpChatQueue.has('mcp') && mcpChatSid) mcpChatFlush();
|
|
6763
|
+
} catch {}
|
|
6764
|
+
const q = mcpChatQueue.get('mcp');
|
|
6765
|
+
if (q) {
|
|
6766
|
+
clearTimeout(q.timer);
|
|
6767
|
+
mcpChatQueue.delete('mcp');
|
|
6768
|
+
}
|
|
6769
|
+
const id = String((payload && payload.id) || '');
|
|
6770
|
+
let s;
|
|
6771
|
+
if (id) {
|
|
6772
|
+
try { s = engine._load(id); } catch { return { ok: false, error: 'oturum açılamadı' }; }
|
|
6773
|
+
if (s.bgTitle !== 'Beast MCP') return { ok: false, error: 'bu oturum MCP sohbeti değil' };
|
|
6774
|
+
mcpChatSid = id;
|
|
6775
|
+
mcpChatFresh = false;
|
|
6776
|
+
} else {
|
|
6777
|
+
s = mcpChatSession();
|
|
6778
|
+
}
|
|
6779
|
+
engine.cache.set(s.id, s);
|
|
6780
|
+
return {
|
|
6781
|
+
ok: true,
|
|
6782
|
+
sessionId: s.id,
|
|
6783
|
+
busy: !!engine.isBusy(s.id),
|
|
6784
|
+
messages: mcpChatMessages(s),
|
|
6785
|
+
items: mcpChatHistory(),
|
|
6786
|
+
};
|
|
6787
|
+
});
|
|
6788
|
+
|
|
6789
|
+
ipcMain.handle('mcp:chat:send', async (_e, payload) => {
|
|
6790
|
+
const text = String((payload && payload.msg) || '').trim();
|
|
6791
|
+
const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
|
|
6792
|
+
if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
|
|
6793
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
6794
|
+
if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
|
|
6795
|
+
const s = mcpChatSession();
|
|
6796
|
+
if (engine.isBusy(s.id)) {
|
|
6797
|
+
/* MAIN CHAT STEER: koşan tur varken mesaj ANINDA konuşmaya eklenir */
|
|
6798
|
+
engine.send(s.id, attachments.length ? { text, attachments } : text, { userAction: true });
|
|
6799
|
+
return { ok: true, sessionId: s.id, steered: true };
|
|
6800
|
+
}
|
|
6801
|
+
const q = mcpChatQueuePush(text, attachments);
|
|
6802
|
+
clearTimeout(q.timer);
|
|
6803
|
+
q.timer = setTimeout(() => { try { mcpChatFlush(); } catch {} }, MCP_CHAT_DEBOUNCE_MS);
|
|
6804
|
+
return { ok: true, sessionId: s.id, pending: true };
|
|
6805
|
+
});
|
|
6806
|
+
|
|
6807
|
+
ipcMain.handle('mcp:chat:stop', async () => {
|
|
6808
|
+
const q = mcpChatQueue.get('mcp');
|
|
6809
|
+
if (q) {
|
|
6810
|
+
clearTimeout(q.timer);
|
|
6811
|
+
mcpChatQueue.delete('mcp');
|
|
6812
|
+
}
|
|
6813
|
+
const sid = mcpChatSid;
|
|
6814
|
+
const wasBusy = sid ? engine.isBusy(sid) : false;
|
|
6815
|
+
let r = false;
|
|
6816
|
+
if (wasBusy) {
|
|
6817
|
+
try { r = engine.interrupt(sid, 'kullanıcı MCP sohbet panelinden ■ ile durdurdu'); } catch {}
|
|
6818
|
+
}
|
|
6819
|
+
return { ok: true, wasBusy, interrupted: r };
|
|
6820
|
+
});
|
|
6821
|
+
|
|
6822
|
+
ipcMain.handle('mcp:chat:new', async () => {
|
|
6823
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
6824
|
+
const sid = mcpChatSid;
|
|
6825
|
+
if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
|
|
6826
|
+
const q = mcpChatQueue.get('mcp');
|
|
6827
|
+
if (q) {
|
|
6828
|
+
clearTimeout(q.timer);
|
|
6829
|
+
mcpChatQueue.delete('mcp');
|
|
6830
|
+
}
|
|
6831
|
+
mcpChatSid = '';
|
|
6832
|
+
mcpChatFresh = true; /* sonraki oturum TAZE açılır; eski sohbet geçmişte kalır */
|
|
6833
|
+
return { ok: true, sessionId: '', messages: [], items: mcpChatHistory() };
|
|
6834
|
+
});
|
|
6835
|
+
|
|
6836
|
+
ipcMain.handle('mcp:chat:history', async () => {
|
|
6837
|
+
try {
|
|
6838
|
+
return { ok: true, items: mcpChatHistory() };
|
|
6839
|
+
} catch (e) {
|
|
6840
|
+
return { ok: false, error: String((e && e.message) || e), items: [] };
|
|
6841
|
+
}
|
|
6842
|
+
});
|
|
6843
|
+
|
|
6396
6844
|
/* ---------------- FALLOUT IPC ---------------- */
|
|
6397
6845
|
|
|
6398
6846
|
function defaultFallout() {
|
|
@@ -13077,8 +13525,131 @@ ipcMain.handle('email:send', (_e, msg) => emailSend(msg || {}));
|
|
|
13077
13525
|
|
|
13078
13526
|
/* ---------- ekran görüntüsü ---------- */
|
|
13079
13527
|
|
|
13080
|
-
/*
|
|
13081
|
-
|
|
13528
|
+
/* Son ekran görüntüsünün kaynak ekran sınırları + görüntü boyutu — computer_act
|
|
13529
|
+
koordinat dönüşümü buradan yapılır (görüntü 1280px'e ölçeklenir). */
|
|
13530
|
+
let lastScreenCapture = null;
|
|
13531
|
+
|
|
13532
|
+
/* Canvas (görüntü üzerine çizim) — yoksa annotasyon sessizce atlanır */
|
|
13533
|
+
let _canvasLib;
|
|
13534
|
+
function canvasLib() {
|
|
13535
|
+
if (_canvasLib !== undefined) return _canvasLib;
|
|
13536
|
+
try {
|
|
13537
|
+
_canvasLib = require('@napi-rs/canvas');
|
|
13538
|
+
} catch {
|
|
13539
|
+
_canvasLib = null;
|
|
13540
|
+
}
|
|
13541
|
+
return _canvasLib;
|
|
13542
|
+
}
|
|
13543
|
+
|
|
13544
|
+
/* Gerçek imleç konumu (400 ms önbellek) — computer_look işaretçisi için */
|
|
13545
|
+
let _cursorCache = { at: 0, x: 0, y: 0 };
|
|
13546
|
+
function cursorPosition() {
|
|
13547
|
+
return new Promise((resolve) => {
|
|
13548
|
+
if (Date.now() - _cursorCache.at < 400) return resolve({ x: _cursorCache.x, y: _cursorCache.y });
|
|
13549
|
+
let out = '';
|
|
13550
|
+
try {
|
|
13551
|
+
const p = spawn(
|
|
13552
|
+
'powershell.exe',
|
|
13553
|
+
['-NoProfile', '-NonInteractive', '-Command', 'Add-Type -AssemblyName System.Windows.Forms; $p=[System.Windows.Forms.Cursor]::Position; "$($p.X),$($p.Y)"'],
|
|
13554
|
+
{ windowsHide: true }
|
|
13555
|
+
);
|
|
13556
|
+
const t = setTimeout(() => { try { p.kill(); } catch {} resolve(null); }, 4000);
|
|
13557
|
+
p.stdout.on('data', (d) => { out += String(d); });
|
|
13558
|
+
p.on('error', () => { clearTimeout(t); resolve(null); });
|
|
13559
|
+
p.on('close', () => {
|
|
13560
|
+
clearTimeout(t);
|
|
13561
|
+
const m = /(\d+)\s*,\s*(\d+)/.exec(out);
|
|
13562
|
+
if (m) {
|
|
13563
|
+
_cursorCache = { at: Date.now(), x: Number(m[1]), y: Number(m[2]) };
|
|
13564
|
+
resolve({ x: _cursorCache.x, y: _cursorCache.y });
|
|
13565
|
+
} else resolve(null);
|
|
13566
|
+
});
|
|
13567
|
+
} catch {
|
|
13568
|
+
resolve(null);
|
|
13569
|
+
}
|
|
13570
|
+
});
|
|
13571
|
+
}
|
|
13572
|
+
|
|
13573
|
+
/* Ekran görüntüsü üzerine 128px ızgara + imleç işareti çiz: model koordinatı
|
|
13574
|
+
görselden okuyabilir. Izgara çizgileri yarı saydam — içeriği boğmaz. */
|
|
13575
|
+
async function annotateScreenShot(img) {
|
|
13576
|
+
const lib = canvasLib();
|
|
13577
|
+
if (!lib) return null;
|
|
13578
|
+
const im = await lib.loadImage(img.toPNG());
|
|
13579
|
+
const canvas = lib.createCanvas(im.width, im.height);
|
|
13580
|
+
const ctx = canvas.getContext('2d');
|
|
13581
|
+
ctx.drawImage(im, 0, 0);
|
|
13582
|
+
const step = 128;
|
|
13583
|
+
ctx.lineWidth = 1;
|
|
13584
|
+
ctx.font = 'bold 12px Segoe UI, sans-serif';
|
|
13585
|
+
for (let x = step; x < im.width; x += step) {
|
|
13586
|
+
ctx.strokeStyle = 'rgba(0,200,255,0.28)';
|
|
13587
|
+
ctx.beginPath();
|
|
13588
|
+
ctx.moveTo(x + 0.5, 0);
|
|
13589
|
+
ctx.lineTo(x + 0.5, im.height);
|
|
13590
|
+
ctx.stroke();
|
|
13591
|
+
const t = String(x);
|
|
13592
|
+
const tw = ctx.measureText(t).width;
|
|
13593
|
+
ctx.fillStyle = 'rgba(0,0,0,0.62)';
|
|
13594
|
+
ctx.fillRect(x + 1, 1, tw + 7, 15);
|
|
13595
|
+
ctx.fillStyle = '#00d8ff';
|
|
13596
|
+
ctx.fillText(t, x + 4, 12.5);
|
|
13597
|
+
}
|
|
13598
|
+
for (let y = step; y < im.height; y += step) {
|
|
13599
|
+
ctx.strokeStyle = 'rgba(0,200,255,0.28)';
|
|
13600
|
+
ctx.beginPath();
|
|
13601
|
+
ctx.moveTo(0, y + 0.5);
|
|
13602
|
+
ctx.lineTo(im.width, y + 0.5);
|
|
13603
|
+
ctx.stroke();
|
|
13604
|
+
const t = String(y);
|
|
13605
|
+
const tw = ctx.measureText(t).width;
|
|
13606
|
+
ctx.fillStyle = 'rgba(0,0,0,0.62)';
|
|
13607
|
+
ctx.fillRect(1, y + 1, tw + 7, 15);
|
|
13608
|
+
ctx.fillStyle = '#00d8ff';
|
|
13609
|
+
ctx.fillText(t, 4, y + 12.5);
|
|
13610
|
+
}
|
|
13611
|
+
try {
|
|
13612
|
+
/* imleç sorgusu en çok 1.5 sn bekletir — look hızlı kalmalı */
|
|
13613
|
+
const cur = await Promise.race([
|
|
13614
|
+
cursorPosition(),
|
|
13615
|
+
new Promise((r) => setTimeout(() => r(null), 1500)),
|
|
13616
|
+
]);
|
|
13617
|
+
const cap = lastScreenCapture;
|
|
13618
|
+
if (cur && cap && cap.w > 0 && cap.h > 0) {
|
|
13619
|
+
const kx = cap.imgW / cap.w;
|
|
13620
|
+
const ky = cap.imgH / cap.h;
|
|
13621
|
+
const cx = (cur.x - cap.x) * kx;
|
|
13622
|
+
const cy = (cur.y - cap.y) * ky;
|
|
13623
|
+
if (cx >= -24 && cy >= -24 && cx <= im.width + 24 && cy <= im.height + 24) {
|
|
13624
|
+
ctx.strokeStyle = '#ff3b5c';
|
|
13625
|
+
ctx.lineWidth = 2;
|
|
13626
|
+
ctx.beginPath();
|
|
13627
|
+
ctx.arc(cx, cy, 14, 0, Math.PI * 2);
|
|
13628
|
+
ctx.stroke();
|
|
13629
|
+
ctx.beginPath();
|
|
13630
|
+
ctx.moveTo(cx - 22, cy);
|
|
13631
|
+
ctx.lineTo(cx + 22, cy);
|
|
13632
|
+
ctx.moveTo(cx, cy - 22);
|
|
13633
|
+
ctx.lineTo(cx, cy + 22);
|
|
13634
|
+
ctx.stroke();
|
|
13635
|
+
const label = 'imlec (' + Math.round(cx) + ',' + Math.round(cy) + ')';
|
|
13636
|
+
const tw = ctx.measureText(label).width;
|
|
13637
|
+
const bx = Math.min(Math.max(2, cx + 18), Math.max(2, im.width - tw - 12));
|
|
13638
|
+
const by = Math.min(Math.max(16, cy - 34), im.height - 6);
|
|
13639
|
+
ctx.fillStyle = 'rgba(0,0,0,0.78)';
|
|
13640
|
+
ctx.fillRect(bx - 3, by - 13, tw + 8, 16);
|
|
13641
|
+
ctx.fillStyle = '#ff8ba0';
|
|
13642
|
+
ctx.fillText(label, bx, by);
|
|
13643
|
+
}
|
|
13644
|
+
}
|
|
13645
|
+
} catch {}
|
|
13646
|
+
return canvas.toBuffer('image/jpeg', 78);
|
|
13647
|
+
}
|
|
13648
|
+
|
|
13649
|
+
/* Ana ekrandan JPEG dataURL yakalar (computer_use ve screen:capture ortak).
|
|
13650
|
+
opts.annotate: ızgara + imleç işareti (yalnız ajan computer_look kullanır;
|
|
13651
|
+
OCR/komut yolları düz görüntü ister). */
|
|
13652
|
+
async function captureScreenDataUrl(opts) {
|
|
13082
13653
|
try {
|
|
13083
13654
|
const sources = await desktopCapturer.getSources({
|
|
13084
13655
|
types: ['screen'],
|
|
@@ -13092,7 +13663,22 @@ async function captureScreenDataUrl() {
|
|
|
13092
13663
|
if (a.width * a.height > b.width * b.height) best = s;
|
|
13093
13664
|
}
|
|
13094
13665
|
const img = best.thumbnail.resize({ width: 1280 });
|
|
13095
|
-
|
|
13666
|
+
/* KAYNAK EKRAN SINIRLARI: ajan görüntüyü 1280px tabanlı okur; act çağrısı
|
|
13667
|
+
bu sınırlara göre gerçek ekran koordinatına çevrilir (çok monitörde ofset dahil) */
|
|
13668
|
+
try {
|
|
13669
|
+
const all = screen.getAllDisplays();
|
|
13670
|
+
const disp = all.find((d) => String(d.id) === String(best.display_id)) || screen.getPrimaryDisplay();
|
|
13671
|
+
const b = (disp && disp.bounds) || { x: 0, y: 0, width: 1280, height: 720 };
|
|
13672
|
+
const size = img.getSize();
|
|
13673
|
+
lastScreenCapture = { x: b.x, y: b.y, w: b.width, h: b.height, imgW: size.width, imgH: size.height };
|
|
13674
|
+
} catch {
|
|
13675
|
+
lastScreenCapture = null;
|
|
13676
|
+
}
|
|
13677
|
+
let jpeg = null;
|
|
13678
|
+
if (opts && opts.annotate) {
|
|
13679
|
+
try { jpeg = await annotateScreenShot(img); } catch {}
|
|
13680
|
+
}
|
|
13681
|
+
if (!jpeg || !jpeg.length) jpeg = img.toJPEG(72);
|
|
13096
13682
|
if (!jpeg || !jpeg.length) return null;
|
|
13097
13683
|
return 'data:image/jpeg;base64,' + jpeg.toString('base64');
|
|
13098
13684
|
} catch {
|
|
@@ -13100,6 +13686,87 @@ async function captureScreenDataUrl() {
|
|
|
13100
13686
|
}
|
|
13101
13687
|
}
|
|
13102
13688
|
|
|
13689
|
+
/* ---- görsel parmak izi: aksiyon "işe yaradı mı" (verify) ---- */
|
|
13690
|
+
function imageSignature(img) {
|
|
13691
|
+
try {
|
|
13692
|
+
if (!img) return null;
|
|
13693
|
+
const small = img.getSize().width > 64 ? img.resize({ width: 64 }) : img;
|
|
13694
|
+
const sz = small.getSize();
|
|
13695
|
+
return computeruse.bitmapSignature(small.toBitmap(), sz.width, sz.height);
|
|
13696
|
+
} catch {
|
|
13697
|
+
return null;
|
|
13698
|
+
}
|
|
13699
|
+
}
|
|
13700
|
+
|
|
13701
|
+
async function pageSignature(wc) {
|
|
13702
|
+
try {
|
|
13703
|
+
return imageSignature(await wc.capturePage());
|
|
13704
|
+
} catch {
|
|
13705
|
+
return null;
|
|
13706
|
+
}
|
|
13707
|
+
}
|
|
13708
|
+
|
|
13709
|
+
/* Ekran değişim imzası — küçük thumbnail ile hızlı (computer_act verify) */
|
|
13710
|
+
async function screenSignature() {
|
|
13711
|
+
try {
|
|
13712
|
+
const sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: { width: 128, height: 72 } });
|
|
13713
|
+
if (!sources.length) return null;
|
|
13714
|
+
let best = sources[0];
|
|
13715
|
+
for (const s of sources) {
|
|
13716
|
+
const a = s.thumbnail.getSize();
|
|
13717
|
+
const b = best.thumbnail.getSize();
|
|
13718
|
+
if (a.width * a.height > b.width * b.height) best = s;
|
|
13719
|
+
}
|
|
13720
|
+
return imageSignature(best.thumbnail);
|
|
13721
|
+
} catch {
|
|
13722
|
+
return null;
|
|
13723
|
+
}
|
|
13724
|
+
}
|
|
13725
|
+
|
|
13726
|
+
const CU_VERIFY_OPS = new Set(['click', 'dblclick', 'rightclick', 'type', 'key', 'drag']);
|
|
13727
|
+
|
|
13728
|
+
/* computer_act köprüsü: model 1280px tabanlı görüntü koordinatı verir —
|
|
13729
|
+
gerçek ekran koordinatına ölçekle (1280'den geniş her ekranda tıklamalar
|
|
13730
|
+
yanlış yere gidiyordu). Aksiyon sonrası görsel değişimi de raporlar. */
|
|
13731
|
+
async function computerActScaled(op, args) {
|
|
13732
|
+
const a = { ...(args || {}) };
|
|
13733
|
+
let cap = lastScreenCapture;
|
|
13734
|
+
if (!cap) {
|
|
13735
|
+
try {
|
|
13736
|
+
const b = screen.getPrimaryDisplay().bounds;
|
|
13737
|
+
const imgH = Math.max(1, Math.round((1280 * b.height) / b.width));
|
|
13738
|
+
cap = { x: b.x, y: b.y, w: b.width, h: b.height, imgW: 1280, imgH };
|
|
13739
|
+
} catch {
|
|
13740
|
+
cap = null;
|
|
13741
|
+
}
|
|
13742
|
+
}
|
|
13743
|
+
const opName = String(op || '').toLowerCase();
|
|
13744
|
+
const scaled = cap && cap.imgW > 0 ? computeruse.scaleArgs(a, cap) : a;
|
|
13745
|
+
const verify = CU_VERIFY_OPS.has(opName) && a.verify !== false;
|
|
13746
|
+
const before = verify ? await screenSignature() : null;
|
|
13747
|
+
let r;
|
|
13748
|
+
try {
|
|
13749
|
+
r = await computeruse.act(opName, scaled);
|
|
13750
|
+
} catch (e) {
|
|
13751
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
13752
|
+
}
|
|
13753
|
+
if (before && r && r.ok) {
|
|
13754
|
+
await new Promise((res) => setTimeout(res, 350)); /* UI tepkisi otursun */
|
|
13755
|
+
const after = await screenSignature();
|
|
13756
|
+
const ratio = computeruse.signatureDiff(before, after);
|
|
13757
|
+
if (ratio != null) {
|
|
13758
|
+
r.changed = ratio >= 0.02;
|
|
13759
|
+
r.changeRatio = Number(ratio.toFixed(3));
|
|
13760
|
+
if (!r.changed && ['click', 'dblclick', 'rightclick', 'type'].includes(opName)) {
|
|
13761
|
+
r.warning =
|
|
13762
|
+
'ekranda görünür değişiklik yok — aksiyon etkisiz olabilir (yanlış pencere/hedef ya da odak kaybı). ' +
|
|
13763
|
+
'computer_look ile durumu kontrol et; focus ile pencereyi öne getirip tekrar dene.';
|
|
13764
|
+
}
|
|
13765
|
+
}
|
|
13766
|
+
}
|
|
13767
|
+
return r;
|
|
13768
|
+
}
|
|
13769
|
+
|
|
13103
13770
|
ipcMain.handle('screen:capture', async () => {
|
|
13104
13771
|
const image = await captureScreenDataUrl();
|
|
13105
13772
|
return image ? { ok: true, image } : { ok: false, error: 'ekran görüntüsü alınamadı' };
|