beast-agent 2.44.14 → 2.45.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 +54 -12
- 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 +488 -27
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).
|
|
@@ -4135,6 +4136,26 @@ function ensureBrowser() {
|
|
|
4135
4136
|
if (/^https?:\/\//i.test(url)) wc.loadURL(url).catch(() => {});
|
|
4136
4137
|
return { action: 'deny' };
|
|
4137
4138
|
});
|
|
4139
|
+
/* JS DİYALOGLARI (alert/confirm/prompt/beforeunload): CDP ile OTOMATİK
|
|
4140
|
+
onaylanır/kapatılır — aksi halde renderer kilitlenir ve TÜM tarayıcı
|
|
4141
|
+
araçları askıda kalır (executeJavaScript diyalog kapanana dek dönmez). */
|
|
4142
|
+
try {
|
|
4143
|
+
if (!wc.debugger.isAttached()) wc.debugger.attach('1.3');
|
|
4144
|
+
wc.debugger.sendCommand('Page.enable').catch(() => {});
|
|
4145
|
+
wc.debugger.on('message', (_ev, method, params) => {
|
|
4146
|
+
if (method === 'Page.javascriptDialogOpening') {
|
|
4147
|
+
browser.lastDialog = {
|
|
4148
|
+
type: String((params && params.type) || 'alert'),
|
|
4149
|
+
message: String((params && params.message) || ''),
|
|
4150
|
+
at: Date.now(),
|
|
4151
|
+
};
|
|
4152
|
+
try { wc.debugger.sendCommand('Page.handleJavaScriptDialog', { accept: true }).catch(() => {}); } catch {}
|
|
4153
|
+
blog('dialog', browser.lastDialog.type + ': ' + browser.lastDialog.message.slice(0, 80));
|
|
4154
|
+
}
|
|
4155
|
+
});
|
|
4156
|
+
} catch (e) {
|
|
4157
|
+
waLog('tarayıcı diyalog köprüsü kurulamadı: ' + String((e && e.message) || e).slice(0, 120));
|
|
4158
|
+
}
|
|
4138
4159
|
// Tarayıcı kapalıyken olayların UI'ı geri açmamasını garantile
|
|
4139
4160
|
const notify = (extra) => {
|
|
4140
4161
|
if (browser.open && win && !win.isDestroyed()) {
|
|
@@ -4251,6 +4272,61 @@ async function browserNavigate(raw, signal, ctx) {
|
|
|
4251
4272
|
});
|
|
4252
4273
|
}
|
|
4253
4274
|
|
|
4275
|
+
/* JS diyalogları (alert/confirm/prompt) CDP ile OTOMATİK yönetilir;
|
|
4276
|
+
bu yardımcı çağrı SIRASINDA oluşan diyaloğu yanıta ekler. */
|
|
4277
|
+
function browserDialogNote(sinceAt) {
|
|
4278
|
+
const d = browser.lastDialog;
|
|
4279
|
+
if (!d || !d.at || d.at <= (sinceAt || 0)) return {};
|
|
4280
|
+
const verb = d.type === 'alert' || d.type === 'prompt' ? 'kapatıldı' : 'onaylandı';
|
|
4281
|
+
return {
|
|
4282
|
+
dialog: { type: d.type, message: d.message },
|
|
4283
|
+
dialogNote: 'JS diyaloğu otomatik ' + verb + ': "' + String(d.message || '').slice(0, 120) + '"',
|
|
4284
|
+
};
|
|
4285
|
+
}
|
|
4286
|
+
|
|
4287
|
+
/* SPA/DOM oturması: readyState complete + içerik uzunluğu İKİ turdur sabitse
|
|
4288
|
+
dön (en çok maxMs). Böylece hidrasyon bitmeden snapshot/okuma yapılmaz. */
|
|
4289
|
+
async function browserSettle(wc, signal, maxMs = 4000) {
|
|
4290
|
+
const t0 = Date.now();
|
|
4291
|
+
let lastLen = -1;
|
|
4292
|
+
let stable = 0;
|
|
4293
|
+
while (Date.now() - t0 < maxMs) {
|
|
4294
|
+
if (signal && signal.aborted) return;
|
|
4295
|
+
let info = null;
|
|
4296
|
+
try {
|
|
4297
|
+
info = await wc.executeJavaScript(
|
|
4298
|
+
'({rs: document.readyState, len: (document.body ? ((document.body.innerText || "").trim().length) : 0)})',
|
|
4299
|
+
true
|
|
4300
|
+
);
|
|
4301
|
+
} catch {
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
if (!info) return;
|
|
4305
|
+
const rs = String(info.rs || '');
|
|
4306
|
+
const len = Number(info.len) || 0;
|
|
4307
|
+
if (rs === 'complete') {
|
|
4308
|
+
if (len > 0 && len === lastLen) stable++;
|
|
4309
|
+
else stable = 0;
|
|
4310
|
+
if (stable >= 2) return;
|
|
4311
|
+
/* boş/az metinli sayfa (canvas/SPA shell): yine de kısa bir hidrasyon penceresi bırak */
|
|
4312
|
+
if (len === 0 && Date.now() - t0 > 1500) return;
|
|
4313
|
+
}
|
|
4314
|
+
lastLen = len;
|
|
4315
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
/* snapshot al (hatayı yut) — navigate/act yanıtlarına gömmek için */
|
|
4320
|
+
async function browserSnapshotNow(wc) {
|
|
4321
|
+
try {
|
|
4322
|
+
const raw = await wc.executeJavaScript(BROWSER_SNAPSHOT_JS, true);
|
|
4323
|
+
const obj = JSON.parse(raw);
|
|
4324
|
+
return obj && typeof obj.count === 'number' ? obj : null;
|
|
4325
|
+
} catch {
|
|
4326
|
+
return null;
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4254
4330
|
async function browserNavigateNow(raw, signal, ctx) {
|
|
4255
4331
|
let url = String(raw || '').trim();
|
|
4256
4332
|
if (!url) return { ok: false, error: 'boş adres' };
|
|
@@ -4262,11 +4338,27 @@ async function browserNavigateNow(raw, signal, ctx) {
|
|
|
4262
4338
|
}
|
|
4263
4339
|
setBrowserOpenForAgent();
|
|
4264
4340
|
const wc = browser.view.webContents;
|
|
4341
|
+
const dlgAt = browser.lastDialog ? browser.lastDialog.at : 0;
|
|
4342
|
+
let failInfo = null;
|
|
4343
|
+
let timedOut = false;
|
|
4265
4344
|
await new Promise((resolve) => {
|
|
4266
4345
|
let settled = false;
|
|
4267
|
-
const finish = () => {
|
|
4268
|
-
|
|
4269
|
-
|
|
4346
|
+
const finish = () => {
|
|
4347
|
+
if (settled) return;
|
|
4348
|
+
settled = true;
|
|
4349
|
+
clearTimeout(timer);
|
|
4350
|
+
cleanup();
|
|
4351
|
+
resolve();
|
|
4352
|
+
};
|
|
4353
|
+
const timer = setTimeout(() => { timedOut = true; finish(); }, 25000);
|
|
4354
|
+
/* did-fail-load: ANA kare hatasını YAKALA ve rapora koy. kod -3 (ABORTED)
|
|
4355
|
+
yönlendirme/SPA geçişlerinde normaldir; alt kare hataları sayılmaz. */
|
|
4356
|
+
const onFail = (_e, code, desc, validatedURL, isMainFrame) => {
|
|
4357
|
+
if (isMainFrame === false) return;
|
|
4358
|
+
if (code === -3) return;
|
|
4359
|
+
failInfo = { code, desc: String(desc || ''), url: String(validatedURL || url) };
|
|
4360
|
+
finish();
|
|
4361
|
+
};
|
|
4270
4362
|
function cleanup() {
|
|
4271
4363
|
clearTimeout(timer);
|
|
4272
4364
|
wc.removeListener('did-finish-load', finish);
|
|
@@ -4274,24 +4366,44 @@ async function browserNavigateNow(raw, signal, ctx) {
|
|
|
4274
4366
|
}
|
|
4275
4367
|
wc.once('did-finish-load', finish);
|
|
4276
4368
|
wc.on('did-fail-load', onFail);
|
|
4277
|
-
wc.loadURL(url).catch(() => {
|
|
4369
|
+
wc.loadURL(url).catch((e) => {
|
|
4370
|
+
failInfo = { code: null, desc: String((e && e.message) || e) };
|
|
4371
|
+
finish();
|
|
4372
|
+
});
|
|
4278
4373
|
});
|
|
4279
4374
|
let title = '';
|
|
4280
4375
|
let finalUrl = url;
|
|
4281
4376
|
try { title = wc.getTitle(); finalUrl = wc.getURL() || url; } catch {}
|
|
4282
4377
|
browserEmit({ open: true, width: browserShownWidth(browserW()), url: finalUrl });
|
|
4283
4378
|
flushBrowserStorage(); // oturum çerezleri diske — ani kapanışta kaybolmasın
|
|
4284
|
-
/*
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
const
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4379
|
+
/* YÜKLEME HATASI: artık "açıldı" denmez — kod/açıklama + çözüm ipucu döner */
|
|
4380
|
+
if (failInfo) {
|
|
4381
|
+
const codeTxt = failInfo.code != null ? ' kod ' + failInfo.code : '';
|
|
4382
|
+
const hint = /ERR_NAME_NOT_RESOLVED|ERR_INTERNET_DISCONNECTED/i.test(failInfo.desc)
|
|
4383
|
+
? ' (DNS/ağ: internet bağlantısını ve adresi kontrol et)'
|
|
4384
|
+
: /ERR_CONNECTION_REFUSED|ERR_CONNECTION_TIMED_OUT|ERR_EMPTY_RESPONSE/i.test(failInfo.desc)
|
|
4385
|
+
? ' (sunucu yanıt vermiyor — adres/port doğru mu?)'
|
|
4386
|
+
: '';
|
|
4387
|
+
return {
|
|
4388
|
+
ok: false,
|
|
4389
|
+
error: 'sayfa yüklenemedi' + codeTxt + ': ' + failInfo.desc + hint,
|
|
4390
|
+
url: failInfo.url || finalUrl,
|
|
4391
|
+
...browserDialogNote(dlgAt),
|
|
4392
|
+
};
|
|
4393
|
+
}
|
|
4394
|
+
/* SPA hidrasyonu: içerik oturmadan snapshot alınmaz (timeout'ta bekleme yok) */
|
|
4395
|
+
await browserSettle(wc, signal, timedOut ? 0 : 4000);
|
|
4396
|
+
const snap = await browserSnapshotNow(wc);
|
|
4397
|
+
let stillLoading = false;
|
|
4398
|
+
try { stillLoading = wc.isLoading(); } catch {}
|
|
4291
4399
|
return {
|
|
4292
4400
|
ok: true,
|
|
4401
|
+
...(timedOut
|
|
4402
|
+
? { warning: 'yükleme 25 sn içinde tamamlanmadı — sayfa kısmen yüklü olabilir; browser_wait/browser_read ile kontrol et', loading: stillLoading }
|
|
4403
|
+
: {}),
|
|
4293
4404
|
url: finalUrl,
|
|
4294
4405
|
title,
|
|
4406
|
+
...browserDialogNote(dlgAt),
|
|
4295
4407
|
...(snap
|
|
4296
4408
|
? { 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
4409
|
: { note: 'sayfa acildi — etkileşimli elemanlar için browser_snapshot al' }),
|
|
@@ -4691,21 +4803,70 @@ async function browserSearchNow(query, signal, ctx) {
|
|
|
4691
4803
|
}
|
|
4692
4804
|
}
|
|
4693
4805
|
|
|
4694
|
-
|
|
4806
|
+
/* Ekran görüntüsü üzerine ref numaralarını ÇİZ: vision modeli kutuları ve
|
|
4807
|
+
numaraları görür, koordinatı tahmin etmek yerine okur. Ref'ler snapshot ile
|
|
4808
|
+
AYNI numaralandırmayı kullanır (snapshot hemen önce alınır). */
|
|
4809
|
+
async function annotateBrowserShot(img, snap) {
|
|
4810
|
+
const lib = canvasLib();
|
|
4811
|
+
if (!lib || !snap || !snap.boxes) return null;
|
|
4812
|
+
const im = await lib.loadImage(img.toPNG());
|
|
4813
|
+
const canvas = lib.createCanvas(im.width, im.height);
|
|
4814
|
+
const ctx = canvas.getContext('2d');
|
|
4815
|
+
ctx.drawImage(im, 0, 0);
|
|
4816
|
+
const vw = Number(snap.vw) || im.width;
|
|
4817
|
+
const k = im.width / Math.max(1, vw);
|
|
4818
|
+
let drawn = 0;
|
|
4819
|
+
for (const [ref, b] of Object.entries(snap.boxes)) {
|
|
4820
|
+
const x = Number(b[0]) * k;
|
|
4821
|
+
const y = Number(b[1]) * k;
|
|
4822
|
+
const w = Number(b[2]) * k;
|
|
4823
|
+
const h = Number(b[3]) * k;
|
|
4824
|
+
if (!(w >= 6 && h >= 6)) continue;
|
|
4825
|
+
if (y > im.height || x > im.width || y + h < 0 || x + w < 0) continue;
|
|
4826
|
+
ctx.strokeStyle = 'rgba(0,190,255,0.95)';
|
|
4827
|
+
ctx.lineWidth = 1.5;
|
|
4828
|
+
ctx.strokeRect(x + 0.5, y + 0.5, Math.max(2, w), Math.max(2, h));
|
|
4829
|
+
const label = String(ref);
|
|
4830
|
+
ctx.font = 'bold 13px Segoe UI, sans-serif';
|
|
4831
|
+
const tw = Math.ceil(ctx.measureText(label).width);
|
|
4832
|
+
const by = y - 16 < 0 ? y + 1 : y - 16;
|
|
4833
|
+
ctx.fillStyle = 'rgba(0,0,0,0.78)';
|
|
4834
|
+
ctx.fillRect(x, by, tw + 8, 16);
|
|
4835
|
+
ctx.fillStyle = '#00d8ff';
|
|
4836
|
+
ctx.fillText(label, x + 4, by + 12.5);
|
|
4837
|
+
if (++drawn >= 90) break;
|
|
4838
|
+
}
|
|
4839
|
+
return canvas.toBuffer('image/jpeg', 78);
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
async function browserScreenshot(signal, opts) {
|
|
4695
4843
|
if (!browser.view || !browser.open) return { ok: false, error: 'tarayıcı açık değil' };
|
|
4696
4844
|
const wc = browser.view.webContents;
|
|
4845
|
+
const annotate = !(opts && opts.annotate === false);
|
|
4846
|
+
const dlgAt = browser.lastDialog ? browser.lastDialog.at : 0;
|
|
4697
4847
|
try {
|
|
4848
|
+
/* ref numaraları görselle eşleşsin: önce taze snapshot */
|
|
4849
|
+
let snap = null;
|
|
4850
|
+
if (annotate) snap = await browserSnapshotNow(wc);
|
|
4698
4851
|
const img = await wc.capturePage();
|
|
4699
4852
|
let out = img;
|
|
4700
4853
|
const sz = img.getSize();
|
|
4701
4854
|
const maxW = 1000; // vision bütçesi için küçült
|
|
4702
4855
|
if (sz.width > maxW) out = img.resize({ width: maxW });
|
|
4703
|
-
|
|
4856
|
+
let jpeg = null;
|
|
4857
|
+
if (annotate) {
|
|
4858
|
+
try { jpeg = await annotateBrowserShot(out, snap); } catch {}
|
|
4859
|
+
}
|
|
4860
|
+
if (!jpeg || !jpeg.length) jpeg = out.toJPEG(72);
|
|
4704
4861
|
if (!jpeg || !jpeg.length) return { ok: false, error: 'görüntü alınamadı' };
|
|
4705
4862
|
return {
|
|
4706
4863
|
ok: true,
|
|
4707
|
-
note:
|
|
4864
|
+
note: snap
|
|
4865
|
+
? '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'
|
|
4866
|
+
: 'ekran görüntüsü bir sonraki adımda sana gösterilecek',
|
|
4708
4867
|
__injectImage: 'data:image/jpeg;base64,' + jpeg.toString('base64'),
|
|
4868
|
+
...(snap ? { snapshot: snap.snapshot, refCount: snap.count } : {}),
|
|
4869
|
+
...browserDialogNote(dlgAt),
|
|
4709
4870
|
url: wc.getURL(),
|
|
4710
4871
|
title: wc.getTitle(),
|
|
4711
4872
|
bytes: jpeg.length,
|
|
@@ -4754,6 +4915,7 @@ const BROWSER_SNAPSHOT_JS = `(function(){
|
|
|
4754
4915
|
window.__beMap={};
|
|
4755
4916
|
const seen=new Set();
|
|
4756
4917
|
const lines=[];
|
|
4918
|
+
const boxes={};
|
|
4757
4919
|
let i=0;
|
|
4758
4920
|
function add(e){
|
|
4759
4921
|
if(!e||seen.has(e)) return;
|
|
@@ -4761,6 +4923,8 @@ const BROWSER_SNAPSHOT_JS = `(function(){
|
|
|
4761
4923
|
if(!__vis(e)||e.disabled||e.getAttribute('aria-disabled')==='true') return;
|
|
4762
4924
|
i++;
|
|
4763
4925
|
window.__beMap[i]=e;
|
|
4926
|
+
const rb=e.getBoundingClientRect();
|
|
4927
|
+
boxes[i]=[Math.round(rb.left),Math.round(rb.top),Math.round(rb.width),Math.round(rb.height)];
|
|
4764
4928
|
const tag=e.tagName.toLowerCase();
|
|
4765
4929
|
const type=(e.getAttribute&&e.getAttribute('type'))||'';
|
|
4766
4930
|
let s='['+i+'] <'+tag+(type?' type='+type:'')+'>';
|
|
@@ -4776,7 +4940,7 @@ const BROWSER_SNAPSHOT_JS = `(function(){
|
|
|
4776
4940
|
lines.push('--- SAYFA ---');
|
|
4777
4941
|
}
|
|
4778
4942
|
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')});
|
|
4943
|
+
return JSON.stringify({count:i,title:document.title,url:location.href,snapshot:lines.join('\\n'),boxes:boxes,vw:window.innerWidth,vh:window.innerHeight});
|
|
4780
4944
|
})()`;
|
|
4781
4945
|
|
|
4782
4946
|
function browserActionJs(kind, args) {
|
|
@@ -4953,6 +5117,10 @@ async function browserSnapshot(signal) {
|
|
|
4953
5117
|
async function browserAct(kind, args, signal) {
|
|
4954
5118
|
if (!browser.view || !browser.open) return { ok: false, error: 'tarayıcı açık değil' };
|
|
4955
5119
|
const wc = browser.view.webContents;
|
|
5120
|
+
const dlgAt = browser.lastDialog ? browser.lastDialog.at : 0;
|
|
5121
|
+
/* DOĞRULAMA: eylem öncesi görsel parmak izi (yalnız anlamlı eylemlerde) */
|
|
5122
|
+
const verify = ['click', 'type', 'select', 'press'].includes(kind) && !(args && args.verify === false);
|
|
5123
|
+
const sigBefore = verify ? await pageSignature(wc) : null;
|
|
4956
5124
|
try {
|
|
4957
5125
|
const raw = await wc.executeJavaScript(browserActionJs(kind, args || {}), true);
|
|
4958
5126
|
let obj = {};
|
|
@@ -4969,6 +5137,20 @@ async function browserAct(kind, args, signal) {
|
|
|
4969
5137
|
wc.on('did-navigate', onNav);
|
|
4970
5138
|
});
|
|
4971
5139
|
}
|
|
5140
|
+
/* SPA: sayfa değiştiyse DOM oturmadan snapshot alınmaz */
|
|
5141
|
+
if (navigated) await browserSettle(wc, signal, 2500);
|
|
5142
|
+
|
|
5143
|
+
/* DOĞRULAMA: eylem sonrası değişim oranı — "tıkladım ama bir şey olmadı" tuzağını yakalar */
|
|
5144
|
+
let changed = null;
|
|
5145
|
+
let changeRatio = null;
|
|
5146
|
+
if (sigBefore) {
|
|
5147
|
+
const sigAfter = await pageSignature(wc);
|
|
5148
|
+
const ratio = computeruse.signatureDiff(sigBefore, sigAfter);
|
|
5149
|
+
if (ratio != null) {
|
|
5150
|
+
changed = ratio >= 0.02;
|
|
5151
|
+
changeRatio = Number(ratio.toFixed(3));
|
|
5152
|
+
}
|
|
5153
|
+
}
|
|
4972
5154
|
|
|
4973
5155
|
blog(
|
|
4974
5156
|
kind,
|
|
@@ -4981,12 +5163,7 @@ async function browserAct(kind, args, signal) {
|
|
|
4981
5163
|
);
|
|
4982
5164
|
|
|
4983
5165
|
/* 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 {}
|
|
5166
|
+
const freshSnap = await browserSnapshotNow(wc);
|
|
4990
5167
|
|
|
4991
5168
|
return {
|
|
4992
5169
|
ok: true,
|
|
@@ -4995,6 +5172,16 @@ async function browserAct(kind, args, signal) {
|
|
|
4995
5172
|
url: wc.getURL(),
|
|
4996
5173
|
title: wc.getTitle(),
|
|
4997
5174
|
navigated,
|
|
5175
|
+
...(changed != null ? { changed, changeRatio } : {}),
|
|
5176
|
+
...(changed === false && ['click', 'type', 'select'].includes(kind)
|
|
5177
|
+
? {
|
|
5178
|
+
warning:
|
|
5179
|
+
'görünür değişiklik yok (changeRatio ' + changeRatio + ') — eylem etkisiz olabilir: ' +
|
|
5180
|
+
'ref bayat olabilir (browser_snapshot al ve güncel ref ile tekrar dene), öğe kapalı/gizli olabilir ' +
|
|
5181
|
+
'ya da tıklama sayfa tarafından engellenmiş olabilir. browser_wait ile kısa bekleme dene.',
|
|
5182
|
+
}
|
|
5183
|
+
: {}),
|
|
5184
|
+
...browserDialogNote(dlgAt),
|
|
4998
5185
|
...(freshSnap ? { snapshot: freshSnap.snapshot, refCount: freshSnap.count } : {}),
|
|
4999
5186
|
...(navigated
|
|
5000
5187
|
? { note: 'sayfa degisti — yanıtta güncel snapshot var; refler eskiyse yeni browser_snapshot al' }
|
|
@@ -5008,6 +5195,61 @@ async function browserAct(kind, args, signal) {
|
|
|
5008
5195
|
}
|
|
5009
5196
|
}
|
|
5010
5197
|
|
|
5198
|
+
/* BEKLE: selector/text/ref görünene (gone:true ise kaybolana) kadar ya da ms
|
|
5199
|
+
kadar bekle. SPA'lar, gecikmeli açılan menüler ve ağ yavaşlığı için —
|
|
5200
|
+
körlemesine tekrar denemek yerine deterministik bekleme. */
|
|
5201
|
+
async function browserWait(args, signal) {
|
|
5202
|
+
if (!browser.view || !browser.open) return { ok: false, error: 'tarayıcı açık değil' };
|
|
5203
|
+
const wc = browser.view.webContents;
|
|
5204
|
+
const a = args || {};
|
|
5205
|
+
const timeout = Math.max(200, Math.min(30000, Number(a.timeout_ms) || 10000));
|
|
5206
|
+
const ms = Math.max(0, Math.min(10000, Number(a.ms) || 0));
|
|
5207
|
+
const hasCond = a.selector != null || a.text != null || a.ref != null;
|
|
5208
|
+
if (!hasCond) {
|
|
5209
|
+
if (!ms) return { ok: false, error: 'selector, text, ref ya da ms ver' };
|
|
5210
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
5211
|
+
return { ok: true, waited_ms: ms, url: wc.getURL(), title: wc.getTitle() };
|
|
5212
|
+
}
|
|
5213
|
+
const gone = !!a.gone;
|
|
5214
|
+
const t0 = Date.now();
|
|
5215
|
+
/* ms verildiyse koşul taramasından ÖNCE sabit bekleme (extra delay) */
|
|
5216
|
+
if (ms) await new Promise((r) => setTimeout(r, ms));
|
|
5217
|
+
const refJs = a.ref != null ? `!!__resolveRef(${JSON.stringify(Number(a.ref))})` : '';
|
|
5218
|
+
const selJs = a.selector != null ? `(function(){try{var el=document.querySelector(${JSON.stringify(String(a.selector))});return !!(el&&__vis(el));}catch(e){return false;}})()` : '';
|
|
5219
|
+
const txtJs = a.text != null ? `((document.body?document.body.innerText:'').toLowerCase().includes(${JSON.stringify(String(a.text).toLowerCase())}))` : '';
|
|
5220
|
+
const probe = `(function(){${BROWSER_JS_HELPERS};try{return !!(${refJs || selJs || txtJs});}catch(e){return false;}})()`;
|
|
5221
|
+
while (Date.now() - t0 < timeout) {
|
|
5222
|
+
if (signal && signal.aborted) return { ok: false, error: 'bekleme iptal edildi' };
|
|
5223
|
+
let found = false;
|
|
5224
|
+
try { found = !!(await wc.executeJavaScript(probe, true)); } catch { found = false; }
|
|
5225
|
+
if (gone ? !found : found) {
|
|
5226
|
+
return {
|
|
5227
|
+
ok: true,
|
|
5228
|
+
waited_ms: Date.now() - t0,
|
|
5229
|
+
...(gone ? { gone: true } : { found: true }),
|
|
5230
|
+
...(a.selector != null ? { selector: String(a.selector) } : {}),
|
|
5231
|
+
...(a.text != null ? { text: String(a.text).slice(0, 60) } : {}),
|
|
5232
|
+
...(a.ref != null ? { ref: Number(a.ref) } : {}),
|
|
5233
|
+
url: wc.getURL(),
|
|
5234
|
+
title: wc.getTitle(),
|
|
5235
|
+
note: 'koşul sağlandı — şimdi eyleme geç; ref lazımsa browser_snapshot ile güncel ref al',
|
|
5236
|
+
};
|
|
5237
|
+
}
|
|
5238
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
5239
|
+
}
|
|
5240
|
+
return {
|
|
5241
|
+
ok: false,
|
|
5242
|
+
error:
|
|
5243
|
+
'bekleme zaman aşımı (' + timeout + ' ms): ' +
|
|
5244
|
+
(a.selector != null ? 'selector bulunamadı: ' + String(a.selector).slice(0, 60)
|
|
5245
|
+
: a.text != null ? 'metin bulunamadı: ' + String(a.text).slice(0, 60)
|
|
5246
|
+
: 'ref mevcut değil: ' + Number(a.ref)) +
|
|
5247
|
+
(gone ? ' (hâlâ görünüyor)' : ''),
|
|
5248
|
+
url: wc.getURL(),
|
|
5249
|
+
title: wc.getTitle(),
|
|
5250
|
+
};
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5011
5253
|
/* oturum açılışında --hidden ile başlarsa pencere gösterme, tepside yaşa */
|
|
5012
5254
|
const startHidden =
|
|
5013
5255
|
process.argv.includes('--hidden') ||
|
|
@@ -13077,8 +13319,131 @@ ipcMain.handle('email:send', (_e, msg) => emailSend(msg || {}));
|
|
|
13077
13319
|
|
|
13078
13320
|
/* ---------- ekran görüntüsü ---------- */
|
|
13079
13321
|
|
|
13080
|
-
/*
|
|
13081
|
-
|
|
13322
|
+
/* Son ekran görüntüsünün kaynak ekran sınırları + görüntü boyutu — computer_act
|
|
13323
|
+
koordinat dönüşümü buradan yapılır (görüntü 1280px'e ölçeklenir). */
|
|
13324
|
+
let lastScreenCapture = null;
|
|
13325
|
+
|
|
13326
|
+
/* Canvas (görüntü üzerine çizim) — yoksa annotasyon sessizce atlanır */
|
|
13327
|
+
let _canvasLib;
|
|
13328
|
+
function canvasLib() {
|
|
13329
|
+
if (_canvasLib !== undefined) return _canvasLib;
|
|
13330
|
+
try {
|
|
13331
|
+
_canvasLib = require('@napi-rs/canvas');
|
|
13332
|
+
} catch {
|
|
13333
|
+
_canvasLib = null;
|
|
13334
|
+
}
|
|
13335
|
+
return _canvasLib;
|
|
13336
|
+
}
|
|
13337
|
+
|
|
13338
|
+
/* Gerçek imleç konumu (400 ms önbellek) — computer_look işaretçisi için */
|
|
13339
|
+
let _cursorCache = { at: 0, x: 0, y: 0 };
|
|
13340
|
+
function cursorPosition() {
|
|
13341
|
+
return new Promise((resolve) => {
|
|
13342
|
+
if (Date.now() - _cursorCache.at < 400) return resolve({ x: _cursorCache.x, y: _cursorCache.y });
|
|
13343
|
+
let out = '';
|
|
13344
|
+
try {
|
|
13345
|
+
const p = spawn(
|
|
13346
|
+
'powershell.exe',
|
|
13347
|
+
['-NoProfile', '-NonInteractive', '-Command', 'Add-Type -AssemblyName System.Windows.Forms; $p=[System.Windows.Forms.Cursor]::Position; "$($p.X),$($p.Y)"'],
|
|
13348
|
+
{ windowsHide: true }
|
|
13349
|
+
);
|
|
13350
|
+
const t = setTimeout(() => { try { p.kill(); } catch {} resolve(null); }, 4000);
|
|
13351
|
+
p.stdout.on('data', (d) => { out += String(d); });
|
|
13352
|
+
p.on('error', () => { clearTimeout(t); resolve(null); });
|
|
13353
|
+
p.on('close', () => {
|
|
13354
|
+
clearTimeout(t);
|
|
13355
|
+
const m = /(\d+)\s*,\s*(\d+)/.exec(out);
|
|
13356
|
+
if (m) {
|
|
13357
|
+
_cursorCache = { at: Date.now(), x: Number(m[1]), y: Number(m[2]) };
|
|
13358
|
+
resolve({ x: _cursorCache.x, y: _cursorCache.y });
|
|
13359
|
+
} else resolve(null);
|
|
13360
|
+
});
|
|
13361
|
+
} catch {
|
|
13362
|
+
resolve(null);
|
|
13363
|
+
}
|
|
13364
|
+
});
|
|
13365
|
+
}
|
|
13366
|
+
|
|
13367
|
+
/* Ekran görüntüsü üzerine 128px ızgara + imleç işareti çiz: model koordinatı
|
|
13368
|
+
görselden okuyabilir. Izgara çizgileri yarı saydam — içeriği boğmaz. */
|
|
13369
|
+
async function annotateScreenShot(img) {
|
|
13370
|
+
const lib = canvasLib();
|
|
13371
|
+
if (!lib) return null;
|
|
13372
|
+
const im = await lib.loadImage(img.toPNG());
|
|
13373
|
+
const canvas = lib.createCanvas(im.width, im.height);
|
|
13374
|
+
const ctx = canvas.getContext('2d');
|
|
13375
|
+
ctx.drawImage(im, 0, 0);
|
|
13376
|
+
const step = 128;
|
|
13377
|
+
ctx.lineWidth = 1;
|
|
13378
|
+
ctx.font = 'bold 12px Segoe UI, sans-serif';
|
|
13379
|
+
for (let x = step; x < im.width; x += step) {
|
|
13380
|
+
ctx.strokeStyle = 'rgba(0,200,255,0.28)';
|
|
13381
|
+
ctx.beginPath();
|
|
13382
|
+
ctx.moveTo(x + 0.5, 0);
|
|
13383
|
+
ctx.lineTo(x + 0.5, im.height);
|
|
13384
|
+
ctx.stroke();
|
|
13385
|
+
const t = String(x);
|
|
13386
|
+
const tw = ctx.measureText(t).width;
|
|
13387
|
+
ctx.fillStyle = 'rgba(0,0,0,0.62)';
|
|
13388
|
+
ctx.fillRect(x + 1, 1, tw + 7, 15);
|
|
13389
|
+
ctx.fillStyle = '#00d8ff';
|
|
13390
|
+
ctx.fillText(t, x + 4, 12.5);
|
|
13391
|
+
}
|
|
13392
|
+
for (let y = step; y < im.height; y += step) {
|
|
13393
|
+
ctx.strokeStyle = 'rgba(0,200,255,0.28)';
|
|
13394
|
+
ctx.beginPath();
|
|
13395
|
+
ctx.moveTo(0, y + 0.5);
|
|
13396
|
+
ctx.lineTo(im.width, y + 0.5);
|
|
13397
|
+
ctx.stroke();
|
|
13398
|
+
const t = String(y);
|
|
13399
|
+
const tw = ctx.measureText(t).width;
|
|
13400
|
+
ctx.fillStyle = 'rgba(0,0,0,0.62)';
|
|
13401
|
+
ctx.fillRect(1, y + 1, tw + 7, 15);
|
|
13402
|
+
ctx.fillStyle = '#00d8ff';
|
|
13403
|
+
ctx.fillText(t, 4, y + 12.5);
|
|
13404
|
+
}
|
|
13405
|
+
try {
|
|
13406
|
+
/* imleç sorgusu en çok 1.5 sn bekletir — look hızlı kalmalı */
|
|
13407
|
+
const cur = await Promise.race([
|
|
13408
|
+
cursorPosition(),
|
|
13409
|
+
new Promise((r) => setTimeout(() => r(null), 1500)),
|
|
13410
|
+
]);
|
|
13411
|
+
const cap = lastScreenCapture;
|
|
13412
|
+
if (cur && cap && cap.w > 0 && cap.h > 0) {
|
|
13413
|
+
const kx = cap.imgW / cap.w;
|
|
13414
|
+
const ky = cap.imgH / cap.h;
|
|
13415
|
+
const cx = (cur.x - cap.x) * kx;
|
|
13416
|
+
const cy = (cur.y - cap.y) * ky;
|
|
13417
|
+
if (cx >= -24 && cy >= -24 && cx <= im.width + 24 && cy <= im.height + 24) {
|
|
13418
|
+
ctx.strokeStyle = '#ff3b5c';
|
|
13419
|
+
ctx.lineWidth = 2;
|
|
13420
|
+
ctx.beginPath();
|
|
13421
|
+
ctx.arc(cx, cy, 14, 0, Math.PI * 2);
|
|
13422
|
+
ctx.stroke();
|
|
13423
|
+
ctx.beginPath();
|
|
13424
|
+
ctx.moveTo(cx - 22, cy);
|
|
13425
|
+
ctx.lineTo(cx + 22, cy);
|
|
13426
|
+
ctx.moveTo(cx, cy - 22);
|
|
13427
|
+
ctx.lineTo(cx, cy + 22);
|
|
13428
|
+
ctx.stroke();
|
|
13429
|
+
const label = 'imlec (' + Math.round(cx) + ',' + Math.round(cy) + ')';
|
|
13430
|
+
const tw = ctx.measureText(label).width;
|
|
13431
|
+
const bx = Math.min(Math.max(2, cx + 18), Math.max(2, im.width - tw - 12));
|
|
13432
|
+
const by = Math.min(Math.max(16, cy - 34), im.height - 6);
|
|
13433
|
+
ctx.fillStyle = 'rgba(0,0,0,0.78)';
|
|
13434
|
+
ctx.fillRect(bx - 3, by - 13, tw + 8, 16);
|
|
13435
|
+
ctx.fillStyle = '#ff8ba0';
|
|
13436
|
+
ctx.fillText(label, bx, by);
|
|
13437
|
+
}
|
|
13438
|
+
}
|
|
13439
|
+
} catch {}
|
|
13440
|
+
return canvas.toBuffer('image/jpeg', 78);
|
|
13441
|
+
}
|
|
13442
|
+
|
|
13443
|
+
/* Ana ekrandan JPEG dataURL yakalar (computer_use ve screen:capture ortak).
|
|
13444
|
+
opts.annotate: ızgara + imleç işareti (yalnız ajan computer_look kullanır;
|
|
13445
|
+
OCR/komut yolları düz görüntü ister). */
|
|
13446
|
+
async function captureScreenDataUrl(opts) {
|
|
13082
13447
|
try {
|
|
13083
13448
|
const sources = await desktopCapturer.getSources({
|
|
13084
13449
|
types: ['screen'],
|
|
@@ -13092,7 +13457,22 @@ async function captureScreenDataUrl() {
|
|
|
13092
13457
|
if (a.width * a.height > b.width * b.height) best = s;
|
|
13093
13458
|
}
|
|
13094
13459
|
const img = best.thumbnail.resize({ width: 1280 });
|
|
13095
|
-
|
|
13460
|
+
/* KAYNAK EKRAN SINIRLARI: ajan görüntüyü 1280px tabanlı okur; act çağrısı
|
|
13461
|
+
bu sınırlara göre gerçek ekran koordinatına çevrilir (çok monitörde ofset dahil) */
|
|
13462
|
+
try {
|
|
13463
|
+
const all = screen.getAllDisplays();
|
|
13464
|
+
const disp = all.find((d) => String(d.id) === String(best.display_id)) || screen.getPrimaryDisplay();
|
|
13465
|
+
const b = (disp && disp.bounds) || { x: 0, y: 0, width: 1280, height: 720 };
|
|
13466
|
+
const size = img.getSize();
|
|
13467
|
+
lastScreenCapture = { x: b.x, y: b.y, w: b.width, h: b.height, imgW: size.width, imgH: size.height };
|
|
13468
|
+
} catch {
|
|
13469
|
+
lastScreenCapture = null;
|
|
13470
|
+
}
|
|
13471
|
+
let jpeg = null;
|
|
13472
|
+
if (opts && opts.annotate) {
|
|
13473
|
+
try { jpeg = await annotateScreenShot(img); } catch {}
|
|
13474
|
+
}
|
|
13475
|
+
if (!jpeg || !jpeg.length) jpeg = img.toJPEG(72);
|
|
13096
13476
|
if (!jpeg || !jpeg.length) return null;
|
|
13097
13477
|
return 'data:image/jpeg;base64,' + jpeg.toString('base64');
|
|
13098
13478
|
} catch {
|
|
@@ -13100,6 +13480,87 @@ async function captureScreenDataUrl() {
|
|
|
13100
13480
|
}
|
|
13101
13481
|
}
|
|
13102
13482
|
|
|
13483
|
+
/* ---- görsel parmak izi: aksiyon "işe yaradı mı" (verify) ---- */
|
|
13484
|
+
function imageSignature(img) {
|
|
13485
|
+
try {
|
|
13486
|
+
if (!img) return null;
|
|
13487
|
+
const small = img.getSize().width > 64 ? img.resize({ width: 64 }) : img;
|
|
13488
|
+
const sz = small.getSize();
|
|
13489
|
+
return computeruse.bitmapSignature(small.toBitmap(), sz.width, sz.height);
|
|
13490
|
+
} catch {
|
|
13491
|
+
return null;
|
|
13492
|
+
}
|
|
13493
|
+
}
|
|
13494
|
+
|
|
13495
|
+
async function pageSignature(wc) {
|
|
13496
|
+
try {
|
|
13497
|
+
return imageSignature(await wc.capturePage());
|
|
13498
|
+
} catch {
|
|
13499
|
+
return null;
|
|
13500
|
+
}
|
|
13501
|
+
}
|
|
13502
|
+
|
|
13503
|
+
/* Ekran değişim imzası — küçük thumbnail ile hızlı (computer_act verify) */
|
|
13504
|
+
async function screenSignature() {
|
|
13505
|
+
try {
|
|
13506
|
+
const sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: { width: 128, height: 72 } });
|
|
13507
|
+
if (!sources.length) return null;
|
|
13508
|
+
let best = sources[0];
|
|
13509
|
+
for (const s of sources) {
|
|
13510
|
+
const a = s.thumbnail.getSize();
|
|
13511
|
+
const b = best.thumbnail.getSize();
|
|
13512
|
+
if (a.width * a.height > b.width * b.height) best = s;
|
|
13513
|
+
}
|
|
13514
|
+
return imageSignature(best.thumbnail);
|
|
13515
|
+
} catch {
|
|
13516
|
+
return null;
|
|
13517
|
+
}
|
|
13518
|
+
}
|
|
13519
|
+
|
|
13520
|
+
const CU_VERIFY_OPS = new Set(['click', 'dblclick', 'rightclick', 'type', 'key', 'drag']);
|
|
13521
|
+
|
|
13522
|
+
/* computer_act köprüsü: model 1280px tabanlı görüntü koordinatı verir —
|
|
13523
|
+
gerçek ekran koordinatına ölçekle (1280'den geniş her ekranda tıklamalar
|
|
13524
|
+
yanlış yere gidiyordu). Aksiyon sonrası görsel değişimi de raporlar. */
|
|
13525
|
+
async function computerActScaled(op, args) {
|
|
13526
|
+
const a = { ...(args || {}) };
|
|
13527
|
+
let cap = lastScreenCapture;
|
|
13528
|
+
if (!cap) {
|
|
13529
|
+
try {
|
|
13530
|
+
const b = screen.getPrimaryDisplay().bounds;
|
|
13531
|
+
const imgH = Math.max(1, Math.round((1280 * b.height) / b.width));
|
|
13532
|
+
cap = { x: b.x, y: b.y, w: b.width, h: b.height, imgW: 1280, imgH };
|
|
13533
|
+
} catch {
|
|
13534
|
+
cap = null;
|
|
13535
|
+
}
|
|
13536
|
+
}
|
|
13537
|
+
const opName = String(op || '').toLowerCase();
|
|
13538
|
+
const scaled = cap && cap.imgW > 0 ? computeruse.scaleArgs(a, cap) : a;
|
|
13539
|
+
const verify = CU_VERIFY_OPS.has(opName) && a.verify !== false;
|
|
13540
|
+
const before = verify ? await screenSignature() : null;
|
|
13541
|
+
let r;
|
|
13542
|
+
try {
|
|
13543
|
+
r = await computeruse.act(opName, scaled);
|
|
13544
|
+
} catch (e) {
|
|
13545
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
13546
|
+
}
|
|
13547
|
+
if (before && r && r.ok) {
|
|
13548
|
+
await new Promise((res) => setTimeout(res, 350)); /* UI tepkisi otursun */
|
|
13549
|
+
const after = await screenSignature();
|
|
13550
|
+
const ratio = computeruse.signatureDiff(before, after);
|
|
13551
|
+
if (ratio != null) {
|
|
13552
|
+
r.changed = ratio >= 0.02;
|
|
13553
|
+
r.changeRatio = Number(ratio.toFixed(3));
|
|
13554
|
+
if (!r.changed && ['click', 'dblclick', 'rightclick', 'type'].includes(opName)) {
|
|
13555
|
+
r.warning =
|
|
13556
|
+
'ekranda görünür değişiklik yok — aksiyon etkisiz olabilir (yanlış pencere/hedef ya da odak kaybı). ' +
|
|
13557
|
+
'computer_look ile durumu kontrol et; focus ile pencereyi öne getirip tekrar dene.';
|
|
13558
|
+
}
|
|
13559
|
+
}
|
|
13560
|
+
}
|
|
13561
|
+
return r;
|
|
13562
|
+
}
|
|
13563
|
+
|
|
13103
13564
|
ipcMain.handle('screen:capture', async () => {
|
|
13104
13565
|
const image = await captureScreenDataUrl();
|
|
13105
13566
|
return image ? { ok: true, image } : { ok: false, error: 'ekran görüntüsü alınamadı' };
|