beast-agent 2.44.13 → 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 +527 -31
- package/src/renderer/renderer.js +59 -29
package/package.json
CHANGED
package/src/agent/computeruse.js
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
mouse/klavye PowerShell üzerinden user32 API ile sürülür.
|
|
6
6
|
|
|
7
7
|
Güvenlik: yalnızca engine araç çağrılarıyla çalışır; koordinat sınırlı,
|
|
8
|
-
hız limiting'li (aksiyon arası min gecikme) ve yazma metni kırpılır.
|
|
8
|
+
hız limiting'li (aksiyon arası min gecikme) ve yazma metni kırpılır.
|
|
9
|
+
|
|
10
|
+
KOORDİNAT SÖZLEŞMESİ: computer_look görüntüsü 1280px genişliğe ölçeklenir;
|
|
11
|
+
1280 tabanlı koordinatların GERÇEK ekrana çevrilmesi main.js'teki
|
|
12
|
+
computerActScaled sarmalayıcısında yapılır. Bu modül gerçek koordinat alır. */
|
|
9
13
|
|
|
10
14
|
const { execFile } = require('child_process');
|
|
11
15
|
|
|
@@ -29,14 +33,141 @@ function ps(script) {
|
|
|
29
33
|
});
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
/** Sanal masaüstü çözünürlüğü */
|
|
33
|
-
|
|
36
|
+
/** Sanal masaüstü çözünürlüğü (30 sn önbellek: her aksiyonda PS açma maliyeti olmasın) */
|
|
37
|
+
let sizeCache = { at: 0, w: 0, h: 0 };
|
|
38
|
+
async function screenSize(force) {
|
|
39
|
+
if (!force && sizeCache.w && Date.now() - sizeCache.at < 30000) {
|
|
40
|
+
return { w: sizeCache.w, h: sizeCache.h };
|
|
41
|
+
}
|
|
34
42
|
const out = await ps(
|
|
35
43
|
'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width.ToString() + "x" + [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height'
|
|
36
44
|
);
|
|
37
45
|
const m = out.match(/(\d+)x(\d+)/);
|
|
38
46
|
if (!m) throw new Error('çözünürlük alınamadı');
|
|
39
|
-
|
|
47
|
+
sizeCache = { at: Date.now(), w: Number(m[1]), h: Number(m[2]) };
|
|
48
|
+
return { w: sizeCache.w, h: sizeCache.h };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* koordinat kelepçesi: sanal masaüstünde negatif değerler geçerli (sol/üst monitör) */
|
|
52
|
+
function clampCoord(v) {
|
|
53
|
+
const n = Math.round(Number(v));
|
|
54
|
+
if (!Number.isFinite(n)) return null;
|
|
55
|
+
return Math.max(-32000, Math.min(32000, n));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* computer_look görüntüsü 1280px genişliğe ölçeklenir; modelden gelen 1280
|
|
59
|
+
tabanlı koordinatları GERÇEK ekran koordinatına çevirir (çok monitörde
|
|
60
|
+
ekran ofseti dahil). Saf fonksiyon — test edilebilir. cap:
|
|
61
|
+
{ x, y, w, h, imgW, imgH } (main.js captureScreenDataUrl doldurur). */
|
|
62
|
+
function scaleArgs(a, cap) {
|
|
63
|
+
const out = { ...(a || {}) };
|
|
64
|
+
if (!cap || !cap.imgW || !cap.imgH) return out;
|
|
65
|
+
const kx = cap.w / cap.imgW;
|
|
66
|
+
const ky = cap.h / cap.imgH;
|
|
67
|
+
for (const key of ['x', 'x2']) {
|
|
68
|
+
if (out[key] != null && out[key] !== '') out[key] = Math.round(cap.x + Number(out[key]) * kx);
|
|
69
|
+
}
|
|
70
|
+
for (const key of ['y', 'y2']) {
|
|
71
|
+
if (out[key] != null && out[key] !== '') out[key] = Math.round(cap.y + Number(out[key]) * ky);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const MOUSE_CLASS = `
|
|
77
|
+
Add-Type @"
|
|
78
|
+
using System;
|
|
79
|
+
using System.Runtime.InteropServices;
|
|
80
|
+
public class BMouse {
|
|
81
|
+
[DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
|
|
82
|
+
[DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, int dx, int dy, int dwData, UIntPtr dwExtraInfo);
|
|
83
|
+
}
|
|
84
|
+
"@`;
|
|
85
|
+
|
|
86
|
+
const LMB_DOWN = 0x0002;
|
|
87
|
+
const LMB_UP = 0x0004;
|
|
88
|
+
const RMB_DOWN = 0x0008;
|
|
89
|
+
const RMB_UP = 0x0010;
|
|
90
|
+
const WHEEL = 0x0800;
|
|
91
|
+
|
|
92
|
+
/* Tek PS sürecinde: imleci taşı + (gerekiyorsa) tık(lar) — eski sürüm her
|
|
93
|
+
adım için ayrı süreç açıyordu (tık başına 2-3 spawn → ~1 sn gecikme). */
|
|
94
|
+
function mouseScript({ x, y, down, up, dbl, settleMs = 35 }) {
|
|
95
|
+
const ev = (flags) => `[BMouse]::mouse_event(${flags}, 0, 0, 0, [UIntPtr]::Zero)`;
|
|
96
|
+
let body = '';
|
|
97
|
+
if (x != null && y != null) {
|
|
98
|
+
body += `[BMouse]::SetCursorPos(${x}, ${y}) | Out-Null\nStart-Sleep -Milliseconds ${settleMs}\n`;
|
|
99
|
+
}
|
|
100
|
+
if (down && up) {
|
|
101
|
+
body += `${ev(down)}\nStart-Sleep -Milliseconds 45\n${ev(up)}\n`;
|
|
102
|
+
if (dbl) body += `Start-Sleep -Milliseconds 70\n${ev(down)}\nStart-Sleep -Milliseconds 45\n${ev(up)}\n`;
|
|
103
|
+
}
|
|
104
|
+
return `${MOUSE_CLASS}\n${body}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* tek çağrıda tekerlek: eski sürüm |dy|*3 ayrı süreç açıyordu (dy=10 → 30 spawn) */
|
|
108
|
+
function wheelScript(x, y, dy) {
|
|
109
|
+
const delta = Math.round(dy * 720); // eski davranış: tik başına ±240 × 3 × dy
|
|
110
|
+
return (
|
|
111
|
+
`${MOUSE_CLASS}\n` +
|
|
112
|
+
`[BMouse]::SetCursorPos(${x}, ${y}) | Out-Null\n` +
|
|
113
|
+
`[BMouse]::mouse_event(${WHEEL}, 0, 0, ${delta}, [UIntPtr]::Zero)\n`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* sürükle-bırak: basılı tut → ara noktalardan geç → bırak */
|
|
118
|
+
function dragScript(x1, y1, x2, y2) {
|
|
119
|
+
const steps = 12;
|
|
120
|
+
return (
|
|
121
|
+
`${MOUSE_CLASS}\n` +
|
|
122
|
+
`[BMouse]::SetCursorPos(${x1}, ${y1}) | Out-Null\n` +
|
|
123
|
+
`Start-Sleep -Milliseconds 60\n` +
|
|
124
|
+
`[BMouse]::mouse_event(${LMB_DOWN}, 0, 0, 0, [UIntPtr]::Zero)\n` +
|
|
125
|
+
`for ($i = 1; $i -le ${steps}; $i++) {\n` +
|
|
126
|
+
` $nx = [int](${x1} + (${x2} - ${x1}) * $i / ${steps})\n` +
|
|
127
|
+
` $ny = [int](${y1} + (${y2} - ${y1}) * $i / ${steps})\n` +
|
|
128
|
+
` [BMouse]::SetCursorPos($nx, $ny) | Out-Null\n` +
|
|
129
|
+
` Start-Sleep -Milliseconds 18\n` +
|
|
130
|
+
`}\n` +
|
|
131
|
+
`Start-Sleep -Milliseconds 40\n` +
|
|
132
|
+
`[BMouse]::mouse_event(${LMB_UP}, 0, 0, 0, [UIntPtr]::Zero)\n`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* pencere başlığına göre öne getir (menü/pencere odaklama için) */
|
|
137
|
+
function focusScript(titleB64) {
|
|
138
|
+
return `
|
|
139
|
+
Add-Type @"
|
|
140
|
+
using System;
|
|
141
|
+
using System.Text;
|
|
142
|
+
using System.Runtime.InteropServices;
|
|
143
|
+
public class BWin {
|
|
144
|
+
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
145
|
+
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc cb, IntPtr lParam);
|
|
146
|
+
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
|
|
147
|
+
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
|
148
|
+
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
149
|
+
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
150
|
+
public static IntPtr Found = IntPtr.Zero;
|
|
151
|
+
public static string Needle = "";
|
|
152
|
+
public static bool Cb(IntPtr h, IntPtr l) {
|
|
153
|
+
if (!IsWindowVisible(h)) return true;
|
|
154
|
+
var sb = new StringBuilder(512);
|
|
155
|
+
GetWindowText(h, sb, 512);
|
|
156
|
+
var t = sb.ToString();
|
|
157
|
+
if (t.Length > 0 && t.IndexOf(Needle, StringComparison.OrdinalIgnoreCase) >= 0) { Found = h; return false; }
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
"@
|
|
162
|
+
$n = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("${titleB64}"))
|
|
163
|
+
[BWin]::Needle = $n
|
|
164
|
+
[BWin]::EnumWindows([BWin+EnumWindowsProc][BWin]::Cb, [IntPtr]::Zero) | Out-Null
|
|
165
|
+
if ([BWin]::Found -ne [IntPtr]::Zero) {
|
|
166
|
+
[BWin]::ShowWindow([BWin]::Found, 9) | Out-Null
|
|
167
|
+
$r = [BWin]::SetForegroundWindow([BWin]::Found)
|
|
168
|
+
if ($r) { 'ok' } else { 'noforeground' }
|
|
169
|
+
} else { 'notfound' }
|
|
170
|
+
`;
|
|
40
171
|
}
|
|
41
172
|
|
|
42
173
|
/**
|
|
@@ -45,36 +176,54 @@ async function screenSize() {
|
|
|
45
176
|
* dblclick x y → çift tık
|
|
46
177
|
* rightclick x y → sağ tık
|
|
47
178
|
* move x y → imleci taşı
|
|
48
|
-
*
|
|
179
|
+
* hover x y → taşı + menü açılsın diye bekle
|
|
180
|
+
* drag x y x2 y2 → sürükle-bırak
|
|
181
|
+
* focus title → başlığı eşleşen pencereyi öne getir
|
|
182
|
+
* type text → klavyeyle yaz (pano köprüsü; pano geri yüklenir)
|
|
49
183
|
* key combo → "enter", "ctrl+s", "alt+tab", "win" ...
|
|
50
|
-
* scroll
|
|
184
|
+
* scroll x y dy → tekerlek (tek çağrı)
|
|
51
185
|
*/
|
|
52
186
|
async function act(op, args = {}) {
|
|
53
187
|
await pace();
|
|
54
|
-
const x =
|
|
55
|
-
const y =
|
|
188
|
+
const x = clampCoord(args.x == null ? 0 : args.x);
|
|
189
|
+
const y = clampCoord(args.y == null ? 0 : args.y);
|
|
56
190
|
switch (op) {
|
|
57
191
|
case 'move':
|
|
58
|
-
await
|
|
192
|
+
await ps(mouseScript({ x, y }));
|
|
59
193
|
return { ok: true };
|
|
194
|
+
case 'hover':
|
|
195
|
+
await ps(mouseScript({ x, y }));
|
|
196
|
+
await sleep(280);
|
|
197
|
+
return { ok: true, at: { x, y } };
|
|
60
198
|
case 'click':
|
|
61
|
-
await
|
|
62
|
-
await clickSimple('left');
|
|
199
|
+
await ps(mouseScript({ x, y, down: LMB_DOWN, up: LMB_UP }));
|
|
63
200
|
return { ok: true, at: { x, y } };
|
|
64
201
|
case 'dblclick':
|
|
65
|
-
await
|
|
66
|
-
await clickSimple('left');
|
|
67
|
-
await sleep(70);
|
|
68
|
-
await clickSimple('left');
|
|
202
|
+
await ps(mouseScript({ x, y, down: LMB_DOWN, up: LMB_UP, dbl: true }));
|
|
69
203
|
return { ok: true, at: { x, y } };
|
|
70
204
|
case 'rightclick':
|
|
71
|
-
await
|
|
72
|
-
await clickSimple('right');
|
|
205
|
+
await ps(mouseScript({ x, y, down: RMB_DOWN, up: RMB_UP }));
|
|
73
206
|
return { ok: true, at: { x, y } };
|
|
207
|
+
case 'drag': {
|
|
208
|
+
const x2 = clampCoord(args.x2);
|
|
209
|
+
const y2 = clampCoord(args.y2);
|
|
210
|
+
if (x == null || y == null || x2 == null || y2 == null) {
|
|
211
|
+
return { ok: false, error: 'drag için x,y,x2,y2 gerekli' };
|
|
212
|
+
}
|
|
213
|
+
await ps(dragScript(x, y, x2, y2));
|
|
214
|
+
return { ok: true, from: { x, y }, to: { x2, y2 } };
|
|
215
|
+
}
|
|
216
|
+
case 'focus': {
|
|
217
|
+
const title = String(args.title || args.text || '').trim().slice(0, 120);
|
|
218
|
+
if (!title) return { ok: false, error: 'focus için title gerekli' };
|
|
219
|
+
const out = await ps(focusScript(Buffer.from(title, 'utf8').toString('base64')));
|
|
220
|
+
if (out.includes('ok')) return { ok: true, focused: true, title };
|
|
221
|
+
if (out.includes('noforeground')) return { ok: true, focused: false, title, note: 'pencere bulundu ama öne getirilemedi' };
|
|
222
|
+
return { ok: false, error: `pencere bulunamadı: ${title}` };
|
|
223
|
+
}
|
|
74
224
|
case 'type': {
|
|
75
225
|
const text = String(args.text ?? '').slice(0, 2000);
|
|
76
226
|
if (!text) return { ok: false, error: 'boş metin' };
|
|
77
|
-
/* Add-Type SendInput tabanlı Unicode typer */
|
|
78
227
|
await ps(buildTyperScript(text));
|
|
79
228
|
return { ok: true, chars: text.length };
|
|
80
229
|
}
|
|
@@ -87,11 +236,7 @@ async function act(op, args = {}) {
|
|
|
87
236
|
case 'scroll': {
|
|
88
237
|
const dy = Math.max(-10, Math.min(10, Math.round(Number(args.dy) || 0)));
|
|
89
238
|
if (!dy) return { ok: false, error: 'dy gerekli (-10..10)' };
|
|
90
|
-
await
|
|
91
|
-
const clicks = Math.abs(dy) * 3;
|
|
92
|
-
for (let i = 0; i < clicks; i++) {
|
|
93
|
-
await wheel(dy > 0 ? 1 : -1); // pozitif dy = aşağı
|
|
94
|
-
}
|
|
239
|
+
await ps(wheelScript(x || 640, y || 360, dy));
|
|
95
240
|
return { ok: true, dy };
|
|
96
241
|
}
|
|
97
242
|
default:
|
|
@@ -105,38 +250,20 @@ function sleep(ms) {
|
|
|
105
250
|
|
|
106
251
|
/* ---------- PowerShell snippet üreticileri ---------- */
|
|
107
252
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
using System;
|
|
111
|
-
using System.Runtime.InteropServices;
|
|
112
|
-
public class BM {
|
|
113
|
-
[DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
|
|
114
|
-
}
|
|
115
|
-
"@
|
|
116
|
-
[BM]::SetCursorPos(%%X%%, %%Y%%) | Out-Null
|
|
117
|
-
`;
|
|
118
|
-
|
|
119
|
-
const WHEEL_TPL = `
|
|
120
|
-
Add-Type @"
|
|
121
|
-
using System;
|
|
122
|
-
using System.Runtime.InteropServices;
|
|
123
|
-
public class BW {
|
|
124
|
-
[DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);
|
|
125
|
-
}
|
|
126
|
-
"@
|
|
127
|
-
[BW]::mouse_event(0x0800, 0, 0, %%DELTA%%, [UIntPtr]::Zero)
|
|
128
|
-
`;
|
|
129
|
-
|
|
130
|
-
/* Unicode güvenli yazım: SendInput yerine pano + Ctrl+V fallback'u en sağlamı.
|
|
131
|
-
Panoyu geri yüklemek yerine tek seferlik kullanıp bırakıyoruz (hız öncelik). */
|
|
253
|
+
/* Unicode güvenli yazım: pano köprüsü. Panoyu ÖNCE kaydet, yapıştırdıktan
|
|
254
|
+
SONRA geri koy — kullanıcının panosu kaybolmaz. */
|
|
132
255
|
function buildTyperScript(text) {
|
|
133
256
|
const b64 = Buffer.from(text, 'utf16le').toString('base64');
|
|
134
257
|
return `
|
|
135
258
|
$t = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("${b64}"))
|
|
136
259
|
Add-Type -AssemblyName System.Windows.Forms
|
|
260
|
+
$prev = $null
|
|
261
|
+
try { $prev = [System.Windows.Forms.Clipboard]::GetText() } catch {}
|
|
137
262
|
[System.Windows.Forms.Clipboard]::SetText($t)
|
|
138
263
|
Start-Sleep -Milliseconds 80
|
|
139
264
|
[System.Windows.Forms.SendKeys]::SendWait("^v")
|
|
265
|
+
Start-Sleep -Milliseconds 150
|
|
266
|
+
if ($prev -ne $null -and $prev -ne "") { try { [System.Windows.Forms.Clipboard]::SetText($prev) } catch {} }
|
|
140
267
|
`;
|
|
141
268
|
}
|
|
142
269
|
|
|
@@ -145,18 +272,19 @@ function buildKeyScript(combo) {
|
|
|
145
272
|
const map = {
|
|
146
273
|
enter: '{ENTER}', tab: '{TAB}', esc: '{ESC}', escape: '{ESC}',
|
|
147
274
|
backspace: '{BACKSPACE}', delete: '{DELETE}', del: '{DELETE}',
|
|
275
|
+
insert: '{INSERT}', ins: '{INSERT}', capslock: '{CAPSLOCK}',
|
|
148
276
|
home: '{HOME}', end: '{END}', pgup: '{PGUP}', pgdn: '{PGDN}',
|
|
149
277
|
up: '{UP}', down: '{DOWN}', left: '{LEFT}', right: '{RIGHT}',
|
|
150
|
-
space: ' ',
|
|
278
|
+
space: ' ',
|
|
151
279
|
};
|
|
152
280
|
let out = '';
|
|
153
281
|
const parts = String(combo).toLowerCase().split('+').map((p) => p.trim());
|
|
154
282
|
let mods = '';
|
|
155
283
|
for (const p of parts) {
|
|
156
|
-
if (p === 'ctrl') mods += '^';
|
|
284
|
+
if (p === 'ctrl' || p === 'control') mods += '^';
|
|
157
285
|
else if (p === 'alt') mods += '%';
|
|
158
286
|
else if (p === 'shift') mods += '+';
|
|
159
|
-
else if (p === 'win')
|
|
287
|
+
else if (p === 'win') mods += '^{ESC}';
|
|
160
288
|
else if (/^f\d{1,2}$/.test(p)) out += '{' + p.toUpperCase() + '}';
|
|
161
289
|
else if (map[p]) out += map[p];
|
|
162
290
|
else out += p.slice(0, 1).toUpperCase(); // tek harf/tuş
|
|
@@ -171,30 +299,50 @@ Add-Type -AssemblyName System.Windows.Forms
|
|
|
171
299
|
}
|
|
172
300
|
|
|
173
301
|
async function mouseMove(x, y) {
|
|
174
|
-
await ps(
|
|
302
|
+
await ps(mouseScript({ x: clampCoord(x), y: clampCoord(y) }));
|
|
175
303
|
}
|
|
176
304
|
|
|
177
|
-
/* down+up tek script'te
|
|
305
|
+
/* down+up tek script'te */
|
|
178
306
|
async function clickSimple(button) {
|
|
179
|
-
|
|
180
|
-
const up = button === 'right' ? 0x0010 : 0x0004;
|
|
181
|
-
await ps(`
|
|
182
|
-
Add-Type @"
|
|
183
|
-
using System;
|
|
184
|
-
using System.Runtime.InteropServices;
|
|
185
|
-
public class BC2 {
|
|
186
|
-
[DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);
|
|
307
|
+
await ps(mouseScript({ down: button === 'right' ? RMB_DOWN : LMB_DOWN, up: button === 'right' ? RMB_UP : LMB_UP }));
|
|
187
308
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
309
|
+
|
|
310
|
+
/* ---------- GÖRSEL DOĞRULAMA (verify) ----------
|
|
311
|
+
"Aksiyon işe yaradı mı?" sorusunu ucuz bir görsel parmak iziyle yanıtlar:
|
|
312
|
+
kareyi 8x6 hücreye bölüp her hücrenin ortalama parlaklığını çıkarır.
|
|
313
|
+
İki imza arasındaki fark > eşik olan hücre oranı = değişim oranı.
|
|
314
|
+
Animasyon/anti-alias gürültüsüne toleranslıdır (eşik 8/255). */
|
|
315
|
+
function bitmapSignature(bmp, w, h) {
|
|
316
|
+
try {
|
|
317
|
+
if (!bmp || !w || !h) return null;
|
|
318
|
+
const cells = [];
|
|
319
|
+
const cw = Math.max(1, Math.floor(w / 8));
|
|
320
|
+
const ch = Math.max(1, Math.floor(h / 6));
|
|
321
|
+
for (let cy = 0; cy < 6; cy++) {
|
|
322
|
+
for (let cx = 0; cx < 8; cx++) {
|
|
323
|
+
let sum = 0;
|
|
324
|
+
let n = 0;
|
|
325
|
+
for (let y = cy * ch; y < Math.min(h, (cy + 1) * ch); y += 2) {
|
|
326
|
+
for (let x = cx * cw; x < Math.min(w, (cx + 1) * cw); x += 2) {
|
|
327
|
+
const o = (y * w + x) * 4;
|
|
328
|
+
sum += 0.114 * bmp[o] + 0.587 * bmp[o + 1] + 0.299 * bmp[o + 2];
|
|
329
|
+
n++;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
cells.push(n ? sum / n : 0);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return cells;
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
193
339
|
}
|
|
194
340
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
341
|
+
function signatureDiff(a, b, thresh = 8) {
|
|
342
|
+
if (!a || !b || a.length !== b.length || !a.length) return null;
|
|
343
|
+
let changed = 0;
|
|
344
|
+
for (let i = 0; i < a.length; i++) if (Math.abs(a[i] - b[i]) > thresh) changed++;
|
|
345
|
+
return changed / a.length;
|
|
198
346
|
}
|
|
199
347
|
|
|
200
|
-
module.exports = { act, screenSize, clickSimple, mouseMove };
|
|
348
|
+
module.exports = { act, screenSize, scaleArgs, bitmapSignature, signatureDiff, clickSimple, mouseMove };
|
package/src/agent/engine.js
CHANGED
|
@@ -155,11 +155,12 @@ const PERM_TOOL_SETS = {
|
|
|
155
155
|
'web_search', 'http_fetch', 'webfetch', 'deep_search',
|
|
156
156
|
'browser_open', 'browser_read', 'browser_snapshot', 'browser_screenshot',
|
|
157
157
|
'browser_click', 'browser_type', 'browser_press', 'browser_scroll', 'browser_select',
|
|
158
|
+
'browser_wait',
|
|
158
159
|
'ocr_read', 'tool_request',
|
|
159
160
|
]),
|
|
160
161
|
read: new Set([
|
|
161
162
|
'web_search', 'http_fetch', 'webfetch', 'deep_search',
|
|
162
|
-
'browser_open', 'browser_read', 'browser_snapshot',
|
|
163
|
+
'browser_open', 'browser_read', 'browser_snapshot', 'browser_wait',
|
|
163
164
|
'list_dir', 'read_file', 'grep', 'glob',
|
|
164
165
|
'git_diff_review', 'repo_map', 'repo_symbols', 'xlsx_read',
|
|
165
166
|
'tool_request',
|
|
@@ -191,6 +192,7 @@ const CEO_EXEC_TOOLS = new Set([
|
|
|
191
192
|
'web_search', 'http_fetch', 'webfetch', 'deep_search',
|
|
192
193
|
'browser_open', 'browser_read', 'browser_screenshot', 'browser_snapshot',
|
|
193
194
|
'browser_click', 'browser_type', 'browser_press', 'browser_scroll', 'browser_select',
|
|
195
|
+
'browser_wait',
|
|
194
196
|
'computer_look', 'computer_act',
|
|
195
197
|
'ocr_read',
|
|
196
198
|
'channel_send', // dış kişilere mesaj: CEO devreder, kendisi atmaz
|
|
@@ -2125,6 +2127,7 @@ class Engine {
|
|
|
2125
2127
|
[
|
|
2126
2128
|
'Basit soruları araç kullanmadan doğrudan cevapla — hız önceliklidir.',
|
|
2127
2129
|
'Kod/dosya işlerinde: içerik araması grep (regex), dosya adı araması glob, VAR OLAN dosyayı değiştirme edit_file (write_file yalnız yeni dosya/tam yeniden yazım). read_file satır numaralı döner; büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter — okunan içerik oturum sonuna kadar bağlamda kalır, dosyayı tekrar okuma. Dosya işlemlerinde özel araçları kullan (edit_file/write_file/read_file/grep/glob); run_command terminal işlerindir (build, git, kurulum, paket) — dosya düzenlemeyi komutla değil edit_file ile yap. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA; önceki okuduğun içerik + kendi değişikliklerin üzerinden devam et.',
|
|
2130
|
+
'TERMİNAL DİSİPLİNİ: run_command komutları ETKİLEŞİMSİZDİR — REPL/editör/pager (`node`, `python`, `vi`, `notepad`) ve girdi bekleyen komutlar (mesajsız `git commit`, -y\'siz `npm init`, BatchMode\'suz `ssh`) otomatik reddedilir ya da kilitlenmeden kesilir. Etkileşimsiz bayrakları kullan (-y, -m, --yes, --accept-*), uzun betikleri dosyaya yazıp çalıştır. Uzun süreli sunucuları (`npm run dev/start`, `vite`, `uvicorn`, `flask run`...) run_command ile BAŞLATMA — 10 sn içinde kesilir; Sandbox\'ta panel_run, diğer yerlerde arka plan (`Start-Process ... -RedirectStandardOutput log.txt`) kullan, çıktıyı sonra dosyadan oku.',
|
|
2128
2131
|
'Kullanıcı bir tarihte/saatte hatırlatılmasını isterse set_reminder kullan; when değerini ORTAMdaki bugüne göre hesapla (yerel saat). "Her sabah/gün/hafta" gibi tekrarlı isteklerde repeat alanını da ver (daily/weekly/monthly/weekdays veya cron).',
|
|
2129
2132
|
'İLK MESAJ (channel_send): kullanıcı "X kişiye yaz / haber ver / duyur" derse channel_send ile SEN ilk mesajı at — karşıdan mesaj gelmesini BEKLEME. Yalnız allow listteki kişiler hedeflenebilir: isim (kısmi), telefon numarası, Telegram/Discord ID, @kullanıcı adı, "sahip" ya da "all". Kişi listede yoksa gönderme, kullanıcıya söyle. Telegram/Discord\'da karşı taraf bota daha önce hiç yazmadıysa gönderim başarısız olabilir; sonucu dürüstçe bildir.',
|
|
2130
2133
|
'Kullanıcı kalıcı bir arka plan takibi isterse (fiyat eşiği, pil seviyesi, sayfa değişikliği) watcher_add ile izleyici kur; kurduktan sonra watcher_list ile doğrula ve kullanıcıya koşulu + kontrol sıklığını kısaca bildir.',
|
|
@@ -3711,6 +3714,7 @@ class Engine {
|
|
|
3711
3714
|
`Ortam: Windows + PowerShell; çalışma klasörü: ${this.workspace}\n` +
|
|
3712
3715
|
`GENEL AJAN DİSİPLİNİ: görevi A'dan Z'ye yürüt — bağlam topla, uygula, DOĞRULA; yarım bırakma; doğrulama sonucunu rapora yaz.\n` +
|
|
3713
3716
|
`DOSYA KURALI: dosya işlemlerinde özel araçları kullan — VAR OLAN dosyayı edit_file ile düzenle, yeniyi write_file ile yaz, okuma/arama read_file/grep/glob; run_command terminal işlerindir (build, git, kurulum). Bir dosyayı BİR KEZ oku — içerik bağlamda kalır, tekrar okuma.\n` +
|
|
3717
|
+
`TERMİNAL DİSİPLİNİ: komutlar ETKİLEŞİMSİZ koşar — REPL/editör/pager ve girdi bekleyen komutlar (mesajsız git commit, -y'siz npm init) reddedilir/kesilir; -y, -m, --yes, --accept-* bayraklarını kullan. Uzun süreli sunucuları run_command ile BAŞLATMA (10 sn'de kesilir) — arka planda başlat (panel_run ya da Start-Process + çıktıyı log dosyasına yönlendir).\n` +
|
|
3714
3718
|
(proj ? `PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT — daima uy):\n${proj}\n` : '') +
|
|
3715
3719
|
`HIZ KURALLARI:\n` +
|
|
3716
3720
|
`- TOOL İSTEĞİ (CEPHANE): ihtiyacın olan araç yoksa/bozuksa tool_request ile TOOL botuna yazdır (asenkron: istek anında döner, Tool botu arka planda yazar; araç doğrulanınca tool__<ad> olarak anında çağrılır, rapor sohbete düşer); küçük aracı skill("tool-yazma") ile kendin de yaz ve run_command ile doğrula.\n` +
|
|
@@ -5139,7 +5143,8 @@ const skills = require('./skills');
|
|
|
5139
5143
|
if (!this.browser || typeof this.browser.screenshot !== 'function') {
|
|
5140
5144
|
return { ok: false, error: 'dahili tarayıcı kullanılamıyor' };
|
|
5141
5145
|
}
|
|
5142
|
-
|
|
5146
|
+
/* OCR düz görüntü ister — ref etiketleri metni kirletmesin */
|
|
5147
|
+
const shot = await this.browser.screenshot(signal, { sessionId, annotate: false });
|
|
5143
5148
|
if (!shot || !shot.ok) return { ok: false, error: (shot && shot.error) || 'görüntü alınamadı' };
|
|
5144
5149
|
image = shot.__injectImage || shot.image || null;
|
|
5145
5150
|
} else if (src === 'screen') {
|
|
@@ -5147,7 +5152,8 @@ const skills = require('./skills');
|
|
|
5147
5152
|
if (!this.computer || typeof this.computer.look !== 'function') {
|
|
5148
5153
|
return { ok: false, error: 'ekran erişimi yok' };
|
|
5149
5154
|
}
|
|
5150
|
-
|
|
5155
|
+
/* OCR düz görüntü ister — ızgara/imleç etiketleri OCR'a girmesin */
|
|
5156
|
+
const shot = await this.computer.look({ annotate: false });
|
|
5151
5157
|
image = typeof shot === 'string' ? shot : (shot && (shot.image || shot.dataUrl)) || null;
|
|
5152
5158
|
} else {
|
|
5153
5159
|
const abs = path.isAbsolute(src) ? src : path.join(this.workspace, src);
|
|
@@ -5737,12 +5743,14 @@ const skills = require('./skills');
|
|
|
5737
5743
|
return JSON.stringify({ ok: false, error: 'ekran erişimi yok' });
|
|
5738
5744
|
}
|
|
5739
5745
|
emitSafe(this, sessionId, { type: 'status', status: 'ekrana bakıyor' });
|
|
5740
|
-
const shot = await this.computer.look();
|
|
5746
|
+
const shot = await this.computer.look({ annotate: true });
|
|
5741
5747
|
if (!shot) return JSON.stringify({ ok: false, error: 'ekran görüntüsü alınamadı' });
|
|
5742
5748
|
/* görsel sonraki tura vision mesajı olarak enjekte edilir */
|
|
5743
5749
|
return JSON.stringify({
|
|
5744
5750
|
ok: true,
|
|
5745
|
-
note:
|
|
5751
|
+
note:
|
|
5752
|
+
'ekran görüntüsü alındı — görsel aşağıda; 128px ızgara + kırmızı imleç işareti çizili (etiketli koordinatlar görüntü tabanlı). ' +
|
|
5753
|
+
'computer_act koordinatları 1280px görüntü tabanlı alır ve gerçek ekrana otomatik çevirir',
|
|
5746
5754
|
__injectImage: shot,
|
|
5747
5755
|
});
|
|
5748
5756
|
}
|
|
@@ -5817,6 +5825,13 @@ const skills = require('./skills');
|
|
|
5817
5825
|
const r = await this.browser.act(name.slice(8), args, signal, { sessionId });
|
|
5818
5826
|
return JSON.stringify(r);
|
|
5819
5827
|
}
|
|
5828
|
+
if (name === 'browser_wait') {
|
|
5829
|
+
if (!this.browser || typeof this.browser.wait !== 'function') {
|
|
5830
|
+
return JSON.stringify({ ok: false, error: 'dahili tarayıcı kullanılamıyor' });
|
|
5831
|
+
}
|
|
5832
|
+
const r = await this.browser.wait(args || {}, signal, { sessionId });
|
|
5833
|
+
return JSON.stringify(r);
|
|
5834
|
+
}
|
|
5820
5835
|
/* Beast Apps: app__<id>__<tool> → apps host'a dispatch */
|
|
5821
5836
|
if (String(name).startsWith('app__')) {
|
|
5822
5837
|
return JSON.stringify(await apps.call(name, args || {}, signal));
|
|
@@ -6403,7 +6418,7 @@ const TOOLS = [
|
|
|
6403
6418
|
function: {
|
|
6404
6419
|
name: 'computer_look',
|
|
6405
6420
|
description:
|
|
6406
|
-
'Take a screenshot of the user\u2019s screen and receive it as an image. Use before computer_act to see the GUI; coordinates are on a
|
|
6421
|
+
'Take a screenshot of the user\u2019s screen and receive it as an image. Use before computer_act to see the GUI; coordinates are on a 1280px-wide image basis — computer_act converts them to real screen coordinates automatically (multi-monitor offsets included).',
|
|
6407
6422
|
parameters: { type: 'object', properties: {} },
|
|
6408
6423
|
},
|
|
6409
6424
|
},
|
|
@@ -6412,19 +6427,23 @@ const TOOLS = [
|
|
|
6412
6427
|
function: {
|
|
6413
6428
|
name: 'computer_act',
|
|
6414
6429
|
description:
|
|
6415
|
-
'Control mouse/keyboard on the user\u2019s Windows desktop (GUI automation). Ops: click{x,y}, dblclick{x,y}, rightclick{x,y}, move{x,y}, type{text}, key{combo e.g. "ctrl+s","enter","alt+tab"}, scroll{x,y,dy}. Look first with computer_look, act step by step, look again after acting.',
|
|
6430
|
+
'Control mouse/keyboard on the user\u2019s Windows desktop (GUI automation). Ops: click{x,y}, dblclick{x,y}, rightclick{x,y}, move{x,y}, hover{x,y} (opens hover menus), drag{x,y,x2,y2}, focus{title} (bring a window whose title contains the text to front), type{text}, key{combo e.g. "ctrl+s","enter","alt+tab"}, scroll{x,y,dy}. Coordinates are 1280px-wide image based (as seen in computer_look) — converted automatically. After click/type/key/drag the response reports changed/changeRatio (visual verification); if changed:false a warning means the action probably had no effect — do NOT assume success, re-check with computer_look / focus the right window. Look first with computer_look, act step by step, look again after acting.',
|
|
6416
6431
|
parameters: {
|
|
6417
6432
|
type: 'object',
|
|
6418
6433
|
properties: {
|
|
6419
6434
|
op: {
|
|
6420
6435
|
type: 'string',
|
|
6421
|
-
enum: ['click', 'dblclick', 'rightclick', 'move', 'type', 'key', 'scroll'],
|
|
6436
|
+
enum: ['click', 'dblclick', 'rightclick', 'move', 'hover', 'drag', 'focus', 'type', 'key', 'scroll'],
|
|
6422
6437
|
},
|
|
6423
6438
|
x: { type: 'number' },
|
|
6424
6439
|
y: { type: 'number' },
|
|
6440
|
+
x2: { type: 'number', description: 'for op=drag: end x' },
|
|
6441
|
+
y2: { type: 'number', description: 'for op=drag: end y' },
|
|
6442
|
+
title: { type: 'string', description: 'for op=focus: window title substring (case-insensitive)' },
|
|
6425
6443
|
text: { type: 'string', description: 'for op=type (max 2000 chars)' },
|
|
6426
6444
|
combo: { type: 'string', description: 'for op=key: "enter", "ctrl+s", "alt+tab", "win"' },
|
|
6427
6445
|
dy: { type: 'number', description: 'for op=scroll: positive = down (-10..10)' },
|
|
6446
|
+
verify: { type: 'boolean', description: 'set false to skip the visual change check' },
|
|
6428
6447
|
},
|
|
6429
6448
|
required: ['op'],
|
|
6430
6449
|
},
|
|
@@ -6537,12 +6556,13 @@ const TOOLS = [
|
|
|
6537
6556
|
function: {
|
|
6538
6557
|
name: 'browser_click',
|
|
6539
6558
|
description:
|
|
6540
|
-
'Click an element in the built-in browser. Prefer ref from the latest snapshot (e.g. {"ref":3}); CSS selector or text=X also accepted. The response includes a FRESH snapshot with new refs — continue with those directly instead of calling browser_snapshot again.',
|
|
6559
|
+
'Click an element in the built-in browser. Prefer ref from the latest snapshot (e.g. {"ref":3}); CSS selector or text=X also accepted. The response includes a FRESH snapshot with new refs — continue with those directly instead of calling browser_snapshot again. It also reports changed/changeRatio: if changed is false the click probably had no effect (stale ref/hidden element) — take a fresh snapshot and retry, do NOT assume success.',
|
|
6541
6560
|
parameters: {
|
|
6542
6561
|
type: 'object',
|
|
6543
6562
|
properties: {
|
|
6544
6563
|
ref: { type: 'number', description: 'element number from browser_snapshot' },
|
|
6545
6564
|
selector: { type: 'string', description: 'CSS selector or text=X alternative' },
|
|
6565
|
+
verify: { type: 'boolean', description: 'set false to skip the visual change check' },
|
|
6546
6566
|
},
|
|
6547
6567
|
},
|
|
6548
6568
|
},
|
|
@@ -6552,7 +6572,7 @@ const TOOLS = [
|
|
|
6552
6572
|
function: {
|
|
6553
6573
|
name: 'browser_type',
|
|
6554
6574
|
description:
|
|
6555
|
-
'Type text into an input/textarea/contenteditable in the built-in browser (prefer ref). Set submit=true to press Enter afterwards. Date/time fields (input type=date/time/month/datetime-local) are set PROGRAMMATICALLY — just send the date as text in any common format ("2026-03-15", "15.03.2026", "15 Mart 2026"); do NOT click the calendar popup. The response includes a FRESH snapshot with new refs.',
|
|
6575
|
+
'Type text into an input/textarea/contenteditable in the built-in browser (prefer ref). Set submit=true to press Enter afterwards. Date/time fields (input type=date/time/month/datetime-local) are set PROGRAMMATICALLY — just send the date as text in any common format ("2026-03-15", "15.03.2026", "15 Mart 2026"); do NOT click the calendar popup. The response includes a FRESH snapshot with new refs and changed/changeRatio (visual verification).',
|
|
6556
6576
|
parameters: {
|
|
6557
6577
|
type: 'object',
|
|
6558
6578
|
properties: {
|
|
@@ -6560,6 +6580,7 @@ const TOOLS = [
|
|
|
6560
6580
|
selector: { type: 'string' },
|
|
6561
6581
|
text: { type: 'string' },
|
|
6562
6582
|
submit: { type: 'boolean' },
|
|
6583
|
+
verify: { type: 'boolean', description: 'set false to skip the visual change check' },
|
|
6563
6584
|
},
|
|
6564
6585
|
required: ['text'],
|
|
6565
6586
|
},
|
|
@@ -6570,12 +6591,13 @@ const TOOLS = [
|
|
|
6570
6591
|
function: {
|
|
6571
6592
|
name: 'browser_press',
|
|
6572
6593
|
description:
|
|
6573
|
-
'Press a key (Enter, Tab, Escape, ArrowDown…) on the focused element in the built-in browser; optionally focus a ref first. The response includes a FRESH snapshot with new refs.',
|
|
6594
|
+
'Press a key (Enter, Tab, Escape, ArrowDown…) on the focused element in the built-in browser; optionally focus a ref first. The response includes a FRESH snapshot with new refs and changed/changeRatio (visual verification).',
|
|
6574
6595
|
parameters: {
|
|
6575
6596
|
type: 'object',
|
|
6576
6597
|
properties: {
|
|
6577
6598
|
key: { type: 'string' },
|
|
6578
6599
|
ref: { type: 'number', description: 'optional element to focus first' },
|
|
6600
|
+
verify: { type: 'boolean', description: 'set false to skip the visual change check' },
|
|
6579
6601
|
},
|
|
6580
6602
|
required: ['key'],
|
|
6581
6603
|
},
|
|
@@ -6602,18 +6624,38 @@ const TOOLS = [
|
|
|
6602
6624
|
function: {
|
|
6603
6625
|
name: 'browser_select',
|
|
6604
6626
|
description:
|
|
6605
|
-
'Pick an <option> of a dropdown (<select>) in the built-in browser by value/text (prefer ref). The response includes a FRESH snapshot with new refs. For CUSTOM (JS) dropdowns that are not <select>, click the trigger, then click the [role=option] ref from the snapshot (popups are listed first).',
|
|
6627
|
+
'Pick an <option> of a dropdown (<select>) in the built-in browser by value/text (prefer ref). The response includes a FRESH snapshot with new refs and changed/changeRatio (visual verification). For CUSTOM (JS) dropdowns that are not <select>, click the trigger, then click the [role=option] ref from the snapshot (popups are listed first).',
|
|
6606
6628
|
parameters: {
|
|
6607
6629
|
type: 'object',
|
|
6608
6630
|
properties: {
|
|
6609
6631
|
ref: { type: 'number' },
|
|
6610
6632
|
selector: { type: 'string' },
|
|
6611
6633
|
value: { type: 'string' },
|
|
6634
|
+
verify: { type: 'boolean', description: 'set false to skip the visual change check' },
|
|
6612
6635
|
},
|
|
6613
6636
|
required: ['value'],
|
|
6614
6637
|
},
|
|
6615
6638
|
},
|
|
6616
6639
|
},
|
|
6640
|
+
{
|
|
6641
|
+
type: 'function',
|
|
6642
|
+
function: {
|
|
6643
|
+
name: 'browser_wait',
|
|
6644
|
+
description:
|
|
6645
|
+
'Wait in the built-in browser until a condition is met (or timeout): selector appears (or disappears with gone:true), visible text appears, or a ref becomes valid; ms only = fixed sleep. Use this on SPAs / slow pages AFTER an action instead of blind retries — then act on the fresh snapshot. Timeout default 10000ms (max 30000).',
|
|
6646
|
+
parameters: {
|
|
6647
|
+
type: 'object',
|
|
6648
|
+
properties: {
|
|
6649
|
+
selector: { type: 'string', description: 'CSS selector to wait for' },
|
|
6650
|
+
text: { type: 'string', description: 'visible page text/substring to wait for' },
|
|
6651
|
+
ref: { type: 'number', description: 'ref from snapshot that should become valid' },
|
|
6652
|
+
gone: { type: 'boolean', description: 'wait until selector/text/ref DISAPPEARS instead' },
|
|
6653
|
+
ms: { type: 'number', description: 'fixed wait in ms (max 10000); used alone or as extra delay' },
|
|
6654
|
+
timeout_ms: { type: 'number', description: 'condition timeout, default 10000 (max 30000)' },
|
|
6655
|
+
},
|
|
6656
|
+
},
|
|
6657
|
+
},
|
|
6658
|
+
},
|
|
6617
6659
|
];
|
|
6618
6660
|
|
|
6619
6661
|
/* ---------- GÖREV (TODO) ID + GERİ ALMA SİSTEMİ ----------
|
|
@@ -60,7 +60,9 @@ After ensuring proper quoting, execute the command.
|
|
|
60
60
|
- If the commands depend on each other and must run sequentially, avoid '&&' in this shell because Windows PowerShell (5.1) does not support it. Use PowerShell conditionals such as \`cmd1; if ($?) { cmd2 }\` when later commands must depend on earlier success.
|
|
61
61
|
- DO NOT use newlines to separate commands (newlines are ok in quoted strings)
|
|
62
62
|
- The shell is PERSISTENT: working directory and environment variables are preserved between calls.
|
|
63
|
-
- Commands run in Windows PowerShell (5.1)
|
|
63
|
+
- Commands run in Windows PowerShell (5.1).
|
|
64
|
+
- Commands are NON-INTERACTIVE: no REPLs/editors/pagers (\`node\`, \`python\`, \`vi\`, \`notepad\`), no input-waiting commands (\`git commit\` without -m, \`npm init\` without -y, \`ssh\` without BatchMode). They are rejected or aborted to avoid stalling. Use non-interactive flags (-y, -m, --yes) and write scripts to files instead.
|
|
65
|
+
- NEVER start long-running servers (npm run dev/start, vite, uvicorn, flask run...) with this tool — they are aborted after 10s. Use panel_run (run panel) or a background process (Start-Process with output redirected to a log file).`;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/* opencode tool şemaları — Schema.Struct karşılığı JSON Schema'lar */
|