beast-agent 2.6.7 → 2.7.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 +716 -79
- package/src/agent/engine.js +24 -7
- package/src/agent/watext.js +1 -1
- package/src/main.js +22 -1
- package/src/preload.js +1 -0
- package/src/renderer/renderer.js +5 -0
package/package.json
CHANGED
package/src/agent/computeruse.js
CHANGED
|
@@ -1,109 +1,597 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/* Beast Computer Use (
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/* Beast Computer Use v2 (cua-driver): Windows/macOS/Linux GUI otomasyonu.
|
|
4
|
+
Hermes SKILL.md modeli: arka planda sür (cursor oynatmaz, focus çalmaz),
|
|
5
|
+
önce capture (SOM → numaralı element index'i), element index'iyle tıkla,
|
|
6
|
+
aksiyon sonrası capture_after ile doğrula; yalnız sürücünün döndüğü
|
|
7
|
+
yapılandırılmış sinyale göre (effect/escalation) merdivenden çık.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
Sürücü: https://github.com/trycua/cua — `cua-driver mcp` stdio MCP sunucusu;
|
|
10
|
+
el yazımı minimal JSON-RPC 2.0 client'ı (mcp.js ile aynı çerçeve).
|
|
11
|
+
Binary yoksa eski PowerShell backend'ine düşer (yalnız koordinat + foreground).
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
Kurulum (Windows): irm https://cua.ai/driver/install.ps1 | iex
|
|
14
|
+
Override: BEAST_CUA_DRIVER_CMD ile sürücü yolu/başka binary verilebilir.
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Güvenlik: şüpheli shell yazma pattern'leri ve kilit/logout tuş kombinasyonları
|
|
17
|
+
modül seviyesinde blokludur; onay kapısı engine tarafında çalışır. */
|
|
18
|
+
|
|
19
|
+
const { spawn, execFile } = require('child_process');
|
|
20
|
+
const log = require('./logger');
|
|
21
|
+
const { emitInstallProgress } = require('./progressbus');
|
|
22
|
+
|
|
23
|
+
/* ---------- durum ---------- */
|
|
24
|
+
const S = {
|
|
25
|
+
proc: null,
|
|
26
|
+
buf: '',
|
|
27
|
+
nextId: 1,
|
|
28
|
+
pending: new Map(),
|
|
29
|
+
booted: false,
|
|
30
|
+
tools: null,
|
|
31
|
+
downUntil: 0,
|
|
32
|
+
starting: null,
|
|
33
|
+
target: null, /* son hedef: { pid, window_id, app_name, title } */
|
|
34
|
+
snapshot: null, /* { id, pid, window_id, map:Map(element_index→element), at } */
|
|
35
|
+
probe: null, /* { at, res } — 60 sn cache'li sürücü taraması */
|
|
36
|
+
};
|
|
37
|
+
const CALL_TIMEOUT = 120000; /* capture'ın AX yürüyüşü 20 sn'yi bulabilir */
|
|
38
|
+
const RETRY_COOLDOWN = 30000;
|
|
39
|
+
|
|
40
|
+
/* ---------- otomatik kurulum (Hermes installer deseni: best-effort, arka planda) ---------- */
|
|
41
|
+
|
|
42
|
+
const CUA_INSTALL_PS1 = 'https://cua.ai/driver/install.ps1';
|
|
43
|
+
const CUA_INSTALL_SH = 'https://cua.ai/driver/install.sh';
|
|
44
|
+
/* oturum başına tek otomatik kurulum; elle override edilen binary'ye dokunulmaz */
|
|
45
|
+
const INST = { running: null, done: false, failed: false, startedAt: 0, tick: null };
|
|
46
|
+
|
|
47
|
+
function autoInstallAllowed() {
|
|
48
|
+
/* BEAST_CUA_DRIVER_CMD verildiyse kullanıcı sürücüyü kendisi yönetiyor —
|
|
49
|
+
otomatik kurulum asla çalışmaz (tests dahil). */
|
|
50
|
+
return !String(process.env.BEAST_CUA_DRIVER_CMD || '').trim();
|
|
19
51
|
}
|
|
20
52
|
|
|
21
|
-
|
|
22
|
-
|
|
53
|
+
/* cua-driver --version: kurulu mu? 60 sn cache'li (Kurulum sekmesi 4 sn'de tarar) */
|
|
54
|
+
function probe(force) {
|
|
55
|
+
if (!force && S.probe && Date.now() - S.probe.at < 60000) return Promise.resolve(S.probe.res);
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
execFile(driverCmd(), ['--version'], { timeout: 10000, windowsHide: true }, (err, stdout) => {
|
|
58
|
+
const res = {
|
|
59
|
+
installed: !err && !!String(stdout || '').trim(),
|
|
60
|
+
version: String(stdout || '').trim().split('\n')[0] || '',
|
|
61
|
+
installing: !!INST.running,
|
|
62
|
+
failed: !!INST.failed,
|
|
63
|
+
};
|
|
64
|
+
S.probe = { at: Date.now(), res };
|
|
65
|
+
resolve(res);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function runInstaller(cb) {
|
|
71
|
+
if (process.platform === 'win32') {
|
|
23
72
|
execFile(
|
|
24
73
|
'powershell.exe',
|
|
25
|
-
['-NoProfile', '-NonInteractive', '-Command',
|
|
26
|
-
{ timeout:
|
|
27
|
-
|
|
74
|
+
['-NoProfile', '-NonInteractive', '-Command', `irm ${CUA_INSTALL_PS1} | iex`],
|
|
75
|
+
{ timeout: 15 * 60 * 1000, windowsHide: true },
|
|
76
|
+
cb
|
|
28
77
|
);
|
|
78
|
+
} else {
|
|
79
|
+
execFile('bash', ['-lc', `curl -fsSL ${CUA_INSTALL_SH} | bash`], { timeout: 15 * 60 * 1000 }, cb);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** cua-driver kurulumunu arka planda başlat (Hermes'in best-effort installer'ı gibi).
|
|
84
|
+
Yüzde üretmeyen installer için Kurulum sekmesinde yavaş ilerleme çubuğu çizilir. */
|
|
85
|
+
function autoInstall() {
|
|
86
|
+
if (!autoInstallAllowed()) {
|
|
87
|
+
return { ok: false, disabled: true, error: 'BEAST_CUA_DRIVER_CMD elle verilmiş — otomatik kurulum atlandı' };
|
|
88
|
+
}
|
|
89
|
+
if (INST.running) return { ok: true, installing: true };
|
|
90
|
+
if (INST.done) return { ok: true, installing: false, done: true };
|
|
91
|
+
if (INST.failed) return { ok: false, error: 'önceki kurulum denemesi başarısız — elle kur: irm ' + CUA_INSTALL_PS1 + ' | iex' };
|
|
92
|
+
INST.startedAt = Date.now();
|
|
93
|
+
log.info('computeruse', 'cua-driver kurulumu arka planda başlıyor (cua.ai installer)');
|
|
94
|
+
INST.tick = setInterval(() => {
|
|
95
|
+
/* yüzdeleri üretmeyen installer: 10 sn'de bir yumuşak ilerleme, tavan %95 */
|
|
96
|
+
const el = (Date.now() - INST.startedAt) / 1000;
|
|
97
|
+
emitInstallProgress('cua-driver', { pct: Math.min(95, 3 + el / 4), loaded: 0, total: 0 });
|
|
98
|
+
}, 10000);
|
|
99
|
+
INST.running = new Promise((resolve) => {
|
|
100
|
+
runInstaller((err, stdout, stderr) => {
|
|
101
|
+
if (INST.tick) { clearInterval(INST.tick); INST.tick = null; }
|
|
102
|
+
INST.running = null;
|
|
103
|
+
S.probe = null;
|
|
104
|
+
if (err) {
|
|
105
|
+
INST.failed = true;
|
|
106
|
+
log.error('computeruse', 'cua-driver kurulumu başarısız: ' + String((stderr || err.message) || '').slice(0, 200));
|
|
107
|
+
resolve({ ok: false, error: 'kurulum başarısız — elle kur: irm ' + CUA_INSTALL_PS1 + ' | iex' });
|
|
108
|
+
} else {
|
|
109
|
+
INST.done = true;
|
|
110
|
+
S.downUntil = 0; /* sonraki computer_use çağrısı taze boot dener */
|
|
111
|
+
log.info('computeruse', 'cua-driver kuruldu — sonraki computer_use çağrısında devreye girer');
|
|
112
|
+
emitInstallProgress('cua-driver', { pct: 100, loaded: 1, total: 1 });
|
|
113
|
+
resolve({ ok: true, done: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
29
116
|
});
|
|
117
|
+
return { ok: true, installing: true };
|
|
30
118
|
}
|
|
31
119
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return { w: Number(m[1]), h: Number(m[2]) };
|
|
120
|
+
/* sürücü yoksa sessizce tetikle (oturum başına bir kez) */
|
|
121
|
+
function maybeAutoInstall() {
|
|
122
|
+
if (INST.running || INST.done || INST.failed) return;
|
|
123
|
+
if (!autoInstallAllowed()) return;
|
|
124
|
+
probe(true).then((st) => {
|
|
125
|
+
if (!st.installed) autoInstall();
|
|
126
|
+
}).catch(() => {});
|
|
40
127
|
}
|
|
41
128
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
129
|
+
function driverCmd() {
|
|
130
|
+
const v = String(process.env.BEAST_CUA_DRIVER_CMD || '').trim();
|
|
131
|
+
return v || 'cua-driver';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ---------- MCP stdio istemcisi ---------- */
|
|
135
|
+
|
|
136
|
+
function teardown(reason) {
|
|
137
|
+
if (S.proc) {
|
|
138
|
+
try { S.proc.kill(); } catch {}
|
|
139
|
+
S.proc = null;
|
|
140
|
+
}
|
|
141
|
+
S.booted = false;
|
|
142
|
+
S.tools = null;
|
|
143
|
+
for (const [, p] of S.pending) {
|
|
144
|
+
try { p.reject(new Error(reason || 'cua-driver bağlantısı kapandı')); } catch {}
|
|
145
|
+
}
|
|
146
|
+
S.pending.clear();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function boot() {
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
let done = false;
|
|
152
|
+
const finish = (ok) => {
|
|
153
|
+
if (done) return;
|
|
154
|
+
done = true;
|
|
155
|
+
if (!ok) {
|
|
156
|
+
S.downUntil = Date.now() + RETRY_COOLDOWN;
|
|
157
|
+
maybeAutoInstall(); /* sürücü yoksa arka planda kurulum başlat */
|
|
158
|
+
}
|
|
159
|
+
resolve(ok);
|
|
160
|
+
};
|
|
161
|
+
let proc;
|
|
162
|
+
try {
|
|
163
|
+
proc = spawn(driverCmd(), ['mcp'], {
|
|
164
|
+
windowsHide: true,
|
|
165
|
+
env: { ...process.env, CUA_DRIVER_RS_TELEMETRY_ENABLED: '0' },
|
|
166
|
+
});
|
|
167
|
+
} catch {
|
|
168
|
+
return finish(false);
|
|
169
|
+
}
|
|
170
|
+
S.proc = proc;
|
|
171
|
+
proc.on('error', () => { teardown(); finish(false); });
|
|
172
|
+
proc.on('exit', () => { teardown(); finish(false); });
|
|
173
|
+
proc.stdout.setEncoding('utf8');
|
|
174
|
+
proc.stdout.on('data', (d) => {
|
|
175
|
+
S.buf += d;
|
|
176
|
+
let i;
|
|
177
|
+
while ((i = S.buf.indexOf('\n')) >= 0) {
|
|
178
|
+
const line = S.buf.slice(0, i).trim();
|
|
179
|
+
S.buf = S.buf.slice(i + 1);
|
|
180
|
+
if (!line) continue;
|
|
181
|
+
let m;
|
|
182
|
+
try { m = JSON.parse(line); } catch { continue; }
|
|
183
|
+
if (m.id != null && S.pending.has(m.id)) {
|
|
184
|
+
const p = S.pending.get(m.id);
|
|
185
|
+
S.pending.delete(m.id);
|
|
186
|
+
if (m.error) p.reject(new Error(String((m.error && m.error.message) || 'MCP hatası')));
|
|
187
|
+
else p.resolve(m.result);
|
|
188
|
+
}
|
|
189
|
+
/* bildirimler (agent cursor vb.) yok sayılır */
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
/* initialize → initialized → tools/list */
|
|
193
|
+
(async () => {
|
|
194
|
+
try {
|
|
195
|
+
await rpc('initialize', {
|
|
196
|
+
protocolVersion: '2024-11-05',
|
|
197
|
+
capabilities: {},
|
|
198
|
+
clientInfo: { name: 'beast-agent', version: '1.0.0' },
|
|
199
|
+
}, 15000);
|
|
200
|
+
writeLine({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
201
|
+
const list = await rpc('tools/list', {}, 15000);
|
|
202
|
+
S.tools = new Set(((list && list.tools) || []).map((t) => t.name));
|
|
203
|
+
S.booted = true;
|
|
204
|
+
finish(true);
|
|
205
|
+
} catch {
|
|
206
|
+
teardown();
|
|
207
|
+
finish(false);
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let starting = null;
|
|
214
|
+
async function ensure() {
|
|
215
|
+
if (S.proc && S.booted) return true;
|
|
216
|
+
if (Date.now() < S.downUntil) {
|
|
217
|
+
maybeAutoInstall(); /* cooldown'da da kurulum eksikse başlatılsın */
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (!starting) {
|
|
221
|
+
starting = boot().finally(() => { starting = null; });
|
|
222
|
+
}
|
|
223
|
+
return starting;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function writeLine(obj) {
|
|
227
|
+
if (!S.proc || !S.proc.stdin.writable) return;
|
|
228
|
+
try { S.proc.stdin.write(JSON.stringify(obj) + '\n'); } catch {}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function rpc(method, params, timeoutMs) {
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
const id = S.nextId++;
|
|
234
|
+
const timer = setTimeout(() => {
|
|
235
|
+
S.pending.delete(id);
|
|
236
|
+
reject(new Error('cua-driver zaman aşımı: ' + method));
|
|
237
|
+
}, timeoutMs || 30000);
|
|
238
|
+
S.pending.set(id, {
|
|
239
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
240
|
+
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
241
|
+
});
|
|
242
|
+
writeLine({ jsonrpc: '2.0', id, method, params: params || {} });
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* MCP CallTool zarfını { isError, text, structured, image } biçimine indirger */
|
|
247
|
+
function readResult(res) {
|
|
248
|
+
let text = '';
|
|
249
|
+
let image = null;
|
|
250
|
+
for (const c of Array.isArray(res && res.content) ? res.content : []) {
|
|
251
|
+
if (c && c.type === 'text') text += (text ? '\n' : '') + String(c.text || '');
|
|
252
|
+
else if (c && c.type === 'image') image = 'data:' + (c.mimeType || 'image/png') + ';base64,' + c.data;
|
|
253
|
+
}
|
|
254
|
+
text = text.replace(/^\u2705+\s*/, '').trim();
|
|
255
|
+
return { isError: !!res.isError, text, structured: (res && res.structuredContent) || null, image };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function hasTool(name) {
|
|
259
|
+
return !S.tools || S.tools.has(name); /* tools/list boş döndüyse geç */
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function call(tool, args, timeoutMs) {
|
|
263
|
+
if (!(await ensure())) throw new Error('cua-driver yok');
|
|
264
|
+
if (!hasTool(tool)) throw new Error('cua-driver bu aksiyonu desteklemiyor: ' + tool);
|
|
265
|
+
const res = await rpc('tools/call', { name: tool, arguments: args || {} }, timeoutMs || CALL_TIMEOUT);
|
|
266
|
+
return readResult(res);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* structuredContent + metin fallback'ından satır dizisi çıkarır */
|
|
270
|
+
function rowsOf(r) {
|
|
271
|
+
const s = r && r.structured;
|
|
272
|
+
if (Array.isArray(s)) return s;
|
|
273
|
+
if (s && Array.isArray(s.windows)) return s.windows;
|
|
274
|
+
if (s && Array.isArray(s.elements)) return s.elements;
|
|
275
|
+
if (s && Array.isArray(s.apps)) return s.apps;
|
|
276
|
+
if (r && r.text) {
|
|
277
|
+
try {
|
|
278
|
+
const m = r.text.match(/\[[\s\S]*\]/);
|
|
279
|
+
if (m) return JSON.parse(m[0]);
|
|
280
|
+
} catch {}
|
|
281
|
+
}
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ---------- hedef seçimi ---------- */
|
|
286
|
+
|
|
287
|
+
async function findTarget(app) {
|
|
288
|
+
const r = await call('list_windows', { on_screen_only: true });
|
|
289
|
+
let cands = rowsOf(r).filter((w) => w && w.pid != null && w.window_id != null);
|
|
290
|
+
if (app) {
|
|
291
|
+
const q = String(app).toLowerCase();
|
|
292
|
+
cands = cands.filter((w) =>
|
|
293
|
+
String(w.app_name || '').toLowerCase().includes(q) ||
|
|
294
|
+
String(w.title || '').toLowerCase().includes(q));
|
|
295
|
+
}
|
|
296
|
+
if (!cands.length) return null;
|
|
297
|
+
cands.sort((a, b) => (Number(b.z_index) || 0) - (Number(a.z_index) || 0));
|
|
298
|
+
return {
|
|
299
|
+
pid: Number(cands[0].pid),
|
|
300
|
+
window_id: Number(cands[0].window_id),
|
|
301
|
+
app_name: String(cands[0].app_name || ''),
|
|
302
|
+
title: String(cands[0].title || ''),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* capture → snapshot cache + Hermes tarzı numaralı index (+ opsiyonel görsel) */
|
|
307
|
+
async function doCapture(args = {}) {
|
|
308
|
+
const t = args.app || !S.target ? await findTarget(args.app) : S.target;
|
|
309
|
+
if (!t) {
|
|
310
|
+
return { ok: false, error: 'hedef pencere bulunamadı' + (args.app ? ': ' + args.app : '') };
|
|
311
|
+
}
|
|
312
|
+
S.target = t;
|
|
313
|
+
const mode = args.mode === 'vision' || args.mode === 'ax' ? args.mode : 'som';
|
|
314
|
+
const wargs = { pid: t.pid, window_id: t.window_id };
|
|
315
|
+
if (mode === 'vision') wargs.include_accessibility_tree = false;
|
|
316
|
+
if (mode === 'ax') wargs.include_screenshot = false;
|
|
317
|
+
const r = await call('get_window_state', wargs);
|
|
318
|
+
if (r.isError) return { ok: false, error: (r.text || 'capture başarısız').slice(0, 300) };
|
|
319
|
+
const s = r.structured || {};
|
|
320
|
+
const elements = Array.isArray(s.elements) ? s.elements : [];
|
|
321
|
+
S.snapshot = {
|
|
322
|
+
id: String(s.snapshot_id || s.snapshotId || ''),
|
|
323
|
+
pid: t.pid,
|
|
324
|
+
window_id: t.window_id,
|
|
325
|
+
map: new Map(),
|
|
326
|
+
at: Date.now(),
|
|
327
|
+
};
|
|
328
|
+
for (const el of elements) {
|
|
329
|
+
if (el && el.element_index != null) S.snapshot.map.set(Number(el.element_index), el);
|
|
330
|
+
}
|
|
331
|
+
const out = {
|
|
332
|
+
ok: true,
|
|
333
|
+
op: 'capture',
|
|
334
|
+
app: t.app_name,
|
|
335
|
+
window: { pid: t.pid, window_id: t.window_id, title: t.title },
|
|
336
|
+
elements: elements.length,
|
|
337
|
+
index: elements.slice(0, 200).map((el) => {
|
|
338
|
+
const f = el.frame || {};
|
|
339
|
+
const val = el.value != null && String(el.value).length ? " = '" + String(el.value).slice(0, 40) + "'" : '';
|
|
340
|
+
return `#${el.element_index} ${el.role || 'Element'} '${String(el.label || '').slice(0, 60)}'${val} @ (${f.x ?? 0}, ${f.y ?? 0}, ${f.w ?? 0}, ${f.h ?? 0})`;
|
|
341
|
+
}).join('\n'),
|
|
342
|
+
note: 'element index ile hedefle: {op:"click", element:N}; element bayatlarsa yeniden capture',
|
|
343
|
+
};
|
|
344
|
+
if (mode !== 'ax' && r.image) out.__injectImage = r.image;
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* snapshot element çözümü — bayat/bilinmeyen index açık hata verir */
|
|
349
|
+
function resolveEl(args) {
|
|
350
|
+
const idx = Number(args && args.element);
|
|
351
|
+
if (!idx) return {};
|
|
352
|
+
const snap = S.snapshot;
|
|
353
|
+
if (!snap || !snap.map.has(idx)) {
|
|
354
|
+
return { error: `element #${idx} bulunamadı/bayatladı — önce capture`, stale: true };
|
|
355
|
+
}
|
|
356
|
+
return { el: snap.map.get(idx) };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* aksiyon argümanlarını cua-driver hedef biçimine çevirir */
|
|
360
|
+
function address(args, t) {
|
|
361
|
+
const r = resolveEl(args);
|
|
362
|
+
if (r.error) return r;
|
|
363
|
+
const out = {};
|
|
364
|
+
if (r.el) {
|
|
365
|
+
out.pid = t.pid;
|
|
366
|
+
const tk = r.el.element_token || r.el.token;
|
|
367
|
+
if (tk) out.element_token = tk;
|
|
368
|
+
else {
|
|
369
|
+
out.element_index = Number(r.el.element_index);
|
|
370
|
+
out.snapshot_id = S.snapshot.id;
|
|
371
|
+
out.window_id = S.snapshot.window_id;
|
|
372
|
+
}
|
|
373
|
+
return { out };
|
|
374
|
+
}
|
|
375
|
+
if (args.x != null || args.y != null) {
|
|
376
|
+
out.pid = t.pid;
|
|
377
|
+
out.x = Number(args.x) || 0;
|
|
378
|
+
out.y = Number(args.y) || 0;
|
|
379
|
+
return { out };
|
|
380
|
+
}
|
|
381
|
+
/* hedef yok: yalnız uygulama hedefli (focused element) */
|
|
382
|
+
if (args.app) out.pid = t.pid;
|
|
383
|
+
return { out };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function verdict(r) {
|
|
387
|
+
if (r.isError) return { ok: false, error: (r.text || 'cua-driver hatası').slice(0, 300) };
|
|
388
|
+
const s = r.structured || {};
|
|
389
|
+
const out = { ok: true, effect: s.effect, verified: s.verified, path: s.path, code: s.code };
|
|
390
|
+
if (s.escalation) out.escalation = s.escalation;
|
|
391
|
+
if (r.text) out.note = r.text.slice(0, 300);
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* ---------- güvenlik (Hermes SKILL.md sert kuralları) ---------- */
|
|
396
|
+
|
|
397
|
+
const BLOCKED_TYPE_PATTERNS = [
|
|
398
|
+
/\b(?:curl|wget|irm|iwr|invoke-webrequest)\b[^|\n]*\|\s*(?:&\s*)?(?:bash|sh|zsh|powershell|pwsh|iex|invoke-expression)\b/i,
|
|
399
|
+
/\bsudo\s+rm\s+-rf\b/i,
|
|
400
|
+
/\brm\s+-rf\s+(?:\/|~)/,
|
|
401
|
+
/:\(\)\s*\{[^}]*\}\s*;\s*:/,
|
|
402
|
+
];
|
|
403
|
+
const BLOCKED_KEYS = [/^win\+l$/, /^ctrl\+alt\+del(?:ete)?$/, /^alt\+ctrl\+del(?:ete)?$/];
|
|
404
|
+
|
|
405
|
+
const KEY_ALIASES = {
|
|
406
|
+
enter: 'return', esc: 'escape', del: 'delete', pgup: 'pageup', pgdn: 'pagedown',
|
|
407
|
+
win: 'cmd', option: 'alt', control: 'ctrl',
|
|
408
|
+
};
|
|
409
|
+
const KEY_MODS = new Set(['ctrl', 'alt', 'shift', 'cmd', 'fn']);
|
|
410
|
+
|
|
411
|
+
function guard(op, args) {
|
|
412
|
+
if (op === 'type') {
|
|
413
|
+
const text = String(args.text ?? '');
|
|
414
|
+
if (!text.trim()) return { ok: false, error: 'boş metin' };
|
|
415
|
+
for (const re of BLOCKED_TYPE_PATTERNS) {
|
|
416
|
+
if (re.test(text)) return { ok: false, error: 'şüpheli shell pattern bloklu', blocked: true };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (op === 'key') {
|
|
420
|
+
const combo = String(args.keys || args.combo || '').trim().toLowerCase();
|
|
421
|
+
if (!combo) return { ok: false, error: 'tuş gerekli' };
|
|
422
|
+
const norm = combo.replace(/\s+/g, '');
|
|
423
|
+
for (const re of BLOCKED_KEYS) {
|
|
424
|
+
if (re.test(norm)) return { ok: false, error: 'kilit/logout kombinasyonu bloklu: ' + combo, blocked: true };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* ---------- cua-driver aksiyonları ---------- */
|
|
431
|
+
|
|
432
|
+
async function targetFor(args) {
|
|
433
|
+
if (!args || (!args.app && S.target)) return S.target || (await findTarget(null));
|
|
434
|
+
return findTarget(args && args.app);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function keyParts(combo) {
|
|
438
|
+
const parts = String(combo).toLowerCase().split('+').map((p) => p.trim()).filter(Boolean);
|
|
439
|
+
const mods = [];
|
|
440
|
+
let key = '';
|
|
441
|
+
for (const p of parts) {
|
|
442
|
+
const k = KEY_ALIASES[p] || p;
|
|
443
|
+
if (KEY_MODS.has(k)) mods.push(k);
|
|
444
|
+
else key = k;
|
|
445
|
+
}
|
|
446
|
+
return { mods, key };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function driverAct(op, args) {
|
|
450
|
+
const t = await targetFor(args);
|
|
451
|
+
if (!t && op !== 'list_apps') {
|
|
452
|
+
return { ok: false, error: 'hedef pencere bulunamadı' + (args.app ? ': ' + args.app : '') };
|
|
453
|
+
}
|
|
56
454
|
switch (op) {
|
|
57
|
-
case 'move':
|
|
58
|
-
await mouseMove(x, y);
|
|
59
|
-
return { ok: true };
|
|
60
455
|
case 'click':
|
|
61
|
-
await mouseMove(x, y);
|
|
62
|
-
await clickSimple('left');
|
|
63
|
-
return { ok: true, at: { x, y } };
|
|
64
456
|
case 'dblclick':
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
457
|
+
case 'rightclick': {
|
|
458
|
+
const a = address(args, t);
|
|
459
|
+
if (a.error) return a;
|
|
460
|
+
if (!a.out.element_token && a.out.element_index == null && a.out.x == null) {
|
|
461
|
+
return { ok: false, error: 'element veya x/y gerekli' };
|
|
462
|
+
}
|
|
463
|
+
const p = a.out;
|
|
464
|
+
if (op === 'click') {
|
|
465
|
+
p.button = ['left', 'right', 'middle'].includes(args.button) ? args.button : 'left';
|
|
466
|
+
return verdict(await call('click', p));
|
|
467
|
+
}
|
|
468
|
+
if (op === 'dblclick') return verdict(await call('double_click', p));
|
|
469
|
+
return verdict(await call('right_click', p));
|
|
470
|
+
}
|
|
74
471
|
case 'type': {
|
|
75
472
|
const text = String(args.text ?? '').slice(0, 2000);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await
|
|
79
|
-
return { ok: true, chars: text.length };
|
|
473
|
+
const a = address(args, t);
|
|
474
|
+
if (a.error) return a;
|
|
475
|
+
return verdict(await call('type_text', { text, ...a.out }));
|
|
80
476
|
}
|
|
81
477
|
case 'key': {
|
|
82
|
-
const combo = String(args.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
478
|
+
const combo = String(args.keys || args.combo || '').trim().toLowerCase();
|
|
479
|
+
const { mods, key } = keyParts(combo);
|
|
480
|
+
if (!key && !mods.length) return { ok: false, error: 'tuş gerekli' };
|
|
481
|
+
const a = address(args, t);
|
|
482
|
+
if (a.error) return a;
|
|
483
|
+
if (mods.length) {
|
|
484
|
+
return verdict(await call('hotkey', { keys: [...mods, key || ''], ...a.out }));
|
|
485
|
+
}
|
|
486
|
+
return verdict(await call('press_key', { key: key || 'return', ...a.out }));
|
|
86
487
|
}
|
|
87
488
|
case 'scroll': {
|
|
88
|
-
const
|
|
89
|
-
if (!
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
489
|
+
const direction = ['up', 'down', 'left', 'right'].includes(args.direction) ? args.direction : null;
|
|
490
|
+
if (!direction) return { ok: false, error: 'direction gerekli (up/down/left/right)' };
|
|
491
|
+
const amount = Math.max(1, Math.min(50, Math.round(Number(args.amount) || 3)));
|
|
492
|
+
const a = address(args, t);
|
|
493
|
+
if (a.error) return a;
|
|
494
|
+
return verdict(await call('scroll', { direction, amount, ...a.out }));
|
|
495
|
+
}
|
|
496
|
+
case 'drag': {
|
|
497
|
+
const pt = async (elIdx, ax, ay) => {
|
|
498
|
+
const r = elIdx ? resolveEl({ element: elIdx }) : {};
|
|
499
|
+
if (r.error) return r;
|
|
500
|
+
if (r.el && r.el.frame) {
|
|
501
|
+
return { x: r.el.frame.x + r.el.frame.w / 2, y: r.el.frame.y + r.el.frame.h / 2 };
|
|
502
|
+
}
|
|
503
|
+
if (ax != null && ay != null) return { x: Number(ax), y: Number(ay) };
|
|
504
|
+
return { error: 'drag: from/to (element veya x/y) gerekli' };
|
|
505
|
+
};
|
|
506
|
+
const from = await pt(args.from_element, args.from_x, args.from_y);
|
|
507
|
+
if (from.error) return from;
|
|
508
|
+
const to = await pt(args.to_element, args.to_x, args.to_y);
|
|
509
|
+
if (to.error) return to;
|
|
510
|
+
const p = { from_x: from.x, from_y: from.y, to_x: to.x, to_y: to.y };
|
|
511
|
+
if (args.duration_ms) p.duration_ms = Math.max(0, Math.min(10000, Math.round(Number(args.duration_ms))));
|
|
512
|
+
if (args.steps) p.steps = Math.max(1, Math.min(200, Math.round(Number(args.steps))));
|
|
513
|
+
return verdict(await call('drag', p));
|
|
514
|
+
}
|
|
515
|
+
case 'list_apps': {
|
|
516
|
+
const r = await call('list_apps');
|
|
517
|
+
if (r.isError) return { ok: false, error: (r.text || 'list_apps başarısız').slice(0, 300) };
|
|
518
|
+
const rows = rowsOf(r).slice(0, 40).map((a) => ({
|
|
519
|
+
name: a.name || a.app_name || a.bundle_id || String(a).slice(0, 40),
|
|
520
|
+
pid: a.pid || null,
|
|
521
|
+
running: a.running !== false,
|
|
522
|
+
}));
|
|
523
|
+
return { ok: true, op: 'list_apps', apps: rows, note: 'hedeflemek için op argümanlarında app=<ad> kullan' };
|
|
524
|
+
}
|
|
525
|
+
case 'focus_app': {
|
|
526
|
+
S.target = t;
|
|
527
|
+
if (args.raise_window) {
|
|
528
|
+
const r = await call('bring_to_front', { pid: t.pid });
|
|
529
|
+
if (r.isError) return { ok: false, error: (r.text || 'focus başarısız').slice(0, 300) };
|
|
94
530
|
}
|
|
95
|
-
return {
|
|
531
|
+
return {
|
|
532
|
+
ok: true,
|
|
533
|
+
op: 'focus_app',
|
|
534
|
+
app: t.app_name,
|
|
535
|
+
window: { pid: t.pid, window_id: t.window_id, title: t.title },
|
|
536
|
+
note: 'hedef seçildi — girdiler artık bu uygulamaya yönelir (raise_window olmadan odak çalınmaz)',
|
|
537
|
+
};
|
|
96
538
|
}
|
|
97
539
|
default:
|
|
98
|
-
return
|
|
540
|
+
return null; /* move vb. → legacy */
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/* capture_after: aksiyon sonrası aynı hedefte doğrulama görüntüsü */
|
|
545
|
+
async function attachCapture(out) {
|
|
546
|
+
try {
|
|
547
|
+
const c = await doCapture({});
|
|
548
|
+
if (c && c.__injectImage) {
|
|
549
|
+
out.__injectImage = c.__injectImage;
|
|
550
|
+
delete c.__injectImage;
|
|
551
|
+
}
|
|
552
|
+
out.capture = { ok: !!c.ok, elements: c.elements, index: c.index || undefined, error: c.error };
|
|
553
|
+
} catch (e) {
|
|
554
|
+
out.capture = { ok: false, error: String((e && e.message) || e) };
|
|
99
555
|
}
|
|
100
556
|
}
|
|
101
557
|
|
|
558
|
+
/* ---------- legacy PowerShell backend (fallback) ---------- */
|
|
559
|
+
|
|
560
|
+
let lastActionAt = 0;
|
|
561
|
+
function pace() {
|
|
562
|
+
const wait = 120 - (Date.now() - lastActionAt);
|
|
563
|
+
lastActionAt = Date.now();
|
|
564
|
+
if (wait > 0) return new Promise((r) => setTimeout(r, wait));
|
|
565
|
+
return Promise.resolve();
|
|
566
|
+
}
|
|
567
|
+
|
|
102
568
|
function sleep(ms) {
|
|
103
569
|
return new Promise((r) => setTimeout(r, ms));
|
|
104
570
|
}
|
|
105
571
|
|
|
106
|
-
|
|
572
|
+
function ps(script) {
|
|
573
|
+
return new Promise((resolve, reject) => {
|
|
574
|
+
execFile(
|
|
575
|
+
'powershell.exe',
|
|
576
|
+
['-NoProfile', '-NonInteractive', '-Command', script],
|
|
577
|
+
{ timeout: 20000, windowsHide: true },
|
|
578
|
+
(err, stdout) => (err ? reject(err) : resolve(String(stdout || '').trim()))
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function clamp(n, lo, hi) {
|
|
584
|
+
return Math.max(lo, Math.min(hi, n));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function legacyScreenSize() {
|
|
588
|
+
const out = await ps(
|
|
589
|
+
'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width.ToString() + "x" + [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height'
|
|
590
|
+
);
|
|
591
|
+
const m = out.match(/(\d+)x(\d+)/);
|
|
592
|
+
if (!m) throw new Error('çözünürlük alınamadı');
|
|
593
|
+
return { w: Number(m[1]), h: Number(m[2]) };
|
|
594
|
+
}
|
|
107
595
|
|
|
108
596
|
const MOUSE_MOVE_TPL = `
|
|
109
597
|
Add-Type @"
|
|
@@ -127,8 +615,7 @@ public class BW {
|
|
|
127
615
|
[BW]::mouse_event(0x0800, 0, 0, %%DELTA%%, [UIntPtr]::Zero)
|
|
128
616
|
`;
|
|
129
617
|
|
|
130
|
-
/* Unicode güvenli yazım:
|
|
131
|
-
Panoyu geri yüklemek yerine tek seferlik kullanıp bırakıyoruz (hız öncelik). */
|
|
618
|
+
/* Unicode güvenli yazım: pano + Ctrl+V en sağlamı (tek seferlik, panoyu geri yüklemez) */
|
|
132
619
|
function buildTyperScript(text) {
|
|
133
620
|
const b64 = Buffer.from(text, 'utf16le').toString('base64');
|
|
134
621
|
return `
|
|
@@ -156,10 +643,10 @@ function buildKeyScript(combo) {
|
|
|
156
643
|
if (p === 'ctrl') mods += '^';
|
|
157
644
|
else if (p === 'alt') mods += '%';
|
|
158
645
|
else if (p === 'shift') mods += '+';
|
|
159
|
-
else if (p === 'win') out += '';
|
|
646
|
+
else if (p === 'win') out += '';
|
|
160
647
|
else if (/^f\d{1,2}$/.test(p)) out += '{' + p.toUpperCase() + '}';
|
|
161
648
|
else if (map[p]) out += map[p];
|
|
162
|
-
else out += p.slice(0, 1).toUpperCase();
|
|
649
|
+
else out += p.slice(0, 1).toUpperCase();
|
|
163
650
|
}
|
|
164
651
|
const seq = mods && out ? mods + '(' + out + ')' : mods + out;
|
|
165
652
|
const b64 = Buffer.from(seq, 'utf16le').toString('base64');
|
|
@@ -174,7 +661,6 @@ async function mouseMove(x, y) {
|
|
|
174
661
|
await ps(MOUSE_MOVE_TPL.replace(/%%X%%/g, String(x)).replace(/%%Y%%/g, String(y)));
|
|
175
662
|
}
|
|
176
663
|
|
|
177
|
-
/* down+up tek script'te — karışık replace zinciri yerine bu kullanılır */
|
|
178
664
|
async function clickSimple(button) {
|
|
179
665
|
const down = button === 'right' ? 0x0008 : 0x0002;
|
|
180
666
|
const up = button === 'right' ? 0x0010 : 0x0004;
|
|
@@ -193,8 +679,159 @@ Start-Sleep -Milliseconds 45
|
|
|
193
679
|
}
|
|
194
680
|
|
|
195
681
|
async function wheel(dir) {
|
|
196
|
-
const delta = dir < 0 ? -240 : 240;
|
|
682
|
+
const delta = dir < 0 ? -240 : 240;
|
|
197
683
|
await ps(WHEEL_TPL.replace(/%%DELTA%%/g, String(delta)));
|
|
198
684
|
}
|
|
199
685
|
|
|
200
|
-
|
|
686
|
+
/* legacy op yüzeyi: koordinat tabanlı eski aksiyonlar (cua-driver'sız mod) */
|
|
687
|
+
async function legacyAct(op, args) {
|
|
688
|
+
op = String(op || '').trim().toLowerCase();
|
|
689
|
+
const noDriver = (what) => ({
|
|
690
|
+
ok: false,
|
|
691
|
+
error: `${what} için cua-driver gerekli — kurulum: irm ${CUA_INSTALL_PS1} | iex` +
|
|
692
|
+
(INST.running ? ' (kurulum arka planda sürüyor — birkaç dakika sonra tekrar dene)' : '') +
|
|
693
|
+
(INST.failed ? ' (önceki otomatik kurulum başarısız oldu)' : ''),
|
|
694
|
+
});
|
|
695
|
+
switch (op) {
|
|
696
|
+
case 'capture':
|
|
697
|
+
case 'list_apps':
|
|
698
|
+
case 'focus_app':
|
|
699
|
+
return noDriver('capture/list_apps/focus_app');
|
|
700
|
+
case 'drag':
|
|
701
|
+
return noDriver('drag');
|
|
702
|
+
case 'click':
|
|
703
|
+
case 'dblclick':
|
|
704
|
+
case 'rightclick':
|
|
705
|
+
case 'type': {
|
|
706
|
+
if (args.element != null) return noDriver('element tıklama');
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
default:
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
if (op === 'screen_size') {
|
|
713
|
+
const s = await legacyScreenSize();
|
|
714
|
+
return { ok: true, ...s };
|
|
715
|
+
}
|
|
716
|
+
await pace();
|
|
717
|
+
const x = Math.max(0, Math.round(Number(args.x) || 0));
|
|
718
|
+
const y = Math.max(0, Math.round(Number(args.y) || 0));
|
|
719
|
+
switch (op) {
|
|
720
|
+
case 'move':
|
|
721
|
+
await mouseMove(x, y);
|
|
722
|
+
return { ok: true };
|
|
723
|
+
case 'click':
|
|
724
|
+
if (args.x == null && args.y == null) return { ok: false, error: 'x/y gerekli' };
|
|
725
|
+
await mouseMove(x, y);
|
|
726
|
+
await clickSimple('left');
|
|
727
|
+
return { ok: true, at: { x, y } };
|
|
728
|
+
case 'dblclick':
|
|
729
|
+
if (args.x == null && args.y == null) return { ok: false, error: 'x/y gerekli' };
|
|
730
|
+
await mouseMove(x, y);
|
|
731
|
+
await clickSimple('left');
|
|
732
|
+
await sleep(70);
|
|
733
|
+
await clickSimple('left');
|
|
734
|
+
return { ok: true, at: { x, y } };
|
|
735
|
+
case 'rightclick':
|
|
736
|
+
if (args.x == null && args.y == null) return { ok: false, error: 'x/y gerekli' };
|
|
737
|
+
await mouseMove(x, y);
|
|
738
|
+
await clickSimple('right');
|
|
739
|
+
return { ok: true, at: { x, y } };
|
|
740
|
+
case 'type': {
|
|
741
|
+
const text = String(args.text ?? '').slice(0, 2000);
|
|
742
|
+
if (!text) return { ok: false, error: 'boş metin' };
|
|
743
|
+
await ps(buildTyperScript(text));
|
|
744
|
+
return { ok: true, chars: text.length };
|
|
745
|
+
}
|
|
746
|
+
case 'key': {
|
|
747
|
+
const combo = String(args.keys || args.combo || '').trim().slice(0, 40);
|
|
748
|
+
if (!combo) return { ok: false, error: 'tuş gerekli' };
|
|
749
|
+
await ps(buildKeyScript(combo));
|
|
750
|
+
return { ok: true, key: combo };
|
|
751
|
+
}
|
|
752
|
+
case 'scroll': {
|
|
753
|
+
if (args.direction) {
|
|
754
|
+
const dy = args.direction === 'down' ? Number(args.amount) || 3 : args.direction === 'up' ? -(Number(args.amount) || 3) : 0;
|
|
755
|
+
if (!dy) return { ok: false, error: 'dikey scroll için up/down gerekli' };
|
|
756
|
+
await mouseMove(x || 640, y || 360);
|
|
757
|
+
const clicks = Math.abs(dy) * 3;
|
|
758
|
+
for (let i = 0; i < clicks; i++) await wheel(dy > 0 ? 1 : -1);
|
|
759
|
+
return { ok: true, direction: args.direction, amount: Math.abs(dy) };
|
|
760
|
+
}
|
|
761
|
+
const dy = clamp(Math.round(Number(args.dy) || 0), -10, 10);
|
|
762
|
+
if (!dy) return { ok: false, error: 'direction veya dy gerekli (-10..10)' };
|
|
763
|
+
await mouseMove(x || 640, y || 360);
|
|
764
|
+
const clicks = Math.abs(dy) * 3;
|
|
765
|
+
for (let i = 0; i < clicks; i++) {
|
|
766
|
+
await wheel(dy > 0 ? 1 : -1);
|
|
767
|
+
}
|
|
768
|
+
return { ok: true, dy };
|
|
769
|
+
}
|
|
770
|
+
default:
|
|
771
|
+
return { ok: false, error: `bilinmeyen op: ${op}` };
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/* ---------- genel giriş ---------- */
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Hermes-SKILL.md aksiyon kümesi:
|
|
779
|
+
* capture mode=som|vision|ax app=… → numaralı element index'i (+görsel)
|
|
780
|
+
* click element=N | x,y button=left|right|middle
|
|
781
|
+
* dblclick / rightclick element=N | x,y
|
|
782
|
+
* type text="…" element=N | x,y
|
|
783
|
+
* key keys="ctrl+s" | "enter"
|
|
784
|
+
* scroll direction=up|down|left|right amount=1..50 element=N | x,y
|
|
785
|
+
* drag from_element/to_element | from_x/y → to_x/y
|
|
786
|
+
* wait seconds=0.5
|
|
787
|
+
* list_apps (argüman yok)
|
|
788
|
+
* focus_app app=… [raise_window=true]
|
|
789
|
+
* move x,y (yalnız legacy — gerçek cursor'u oynatır)
|
|
790
|
+
* State-changing aksiyonlarda capture_after=true → aynı hedefte doğrulama
|
|
791
|
+
* capture'ı tek çağrıda döndürür.
|
|
792
|
+
*/
|
|
793
|
+
async function act(op, args = {}) {
|
|
794
|
+
op = String(op || '').trim().toLowerCase();
|
|
795
|
+
args = args || {};
|
|
796
|
+
if (op === 'wait') {
|
|
797
|
+
const ms = clamp(
|
|
798
|
+
args.seconds != null ? Number(args.seconds) * 1000 : args.ms != null ? Number(args.ms) : 500,
|
|
799
|
+
0, 30000
|
|
800
|
+
);
|
|
801
|
+
await sleep(ms);
|
|
802
|
+
return { ok: true, op: 'wait', waited: Math.round(ms) };
|
|
803
|
+
}
|
|
804
|
+
const g = guard(op, args);
|
|
805
|
+
if (g) return g;
|
|
806
|
+
const stateChanging = ['click', 'dblclick', 'rightclick', 'type', 'key', 'scroll', 'drag'];
|
|
807
|
+
try {
|
|
808
|
+
if (await ensure()) {
|
|
809
|
+
const r = await driverAct(op, args);
|
|
810
|
+
if (r !== null) {
|
|
811
|
+
if (stateChanging.includes(op) && args.capture_after) await attachCapture(r);
|
|
812
|
+
return r;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} catch (e) {
|
|
816
|
+
teardown(String((e && e.message) || e));
|
|
817
|
+
S.downUntil = Date.now() + RETRY_COOLDOWN;
|
|
818
|
+
/* sürücü koptu → legacy devam */
|
|
819
|
+
}
|
|
820
|
+
return legacyAct(op, args);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/* Çözünürlük: sürücü varsa driver (logical points), yoksa PowerShell (piksel) */
|
|
824
|
+
async function screenSize() {
|
|
825
|
+
try {
|
|
826
|
+
if (await ensure()) {
|
|
827
|
+
const r = await call('get_screen_size', {}, 10000);
|
|
828
|
+
const s = r.structured || {};
|
|
829
|
+
const w = Number(s.width || s.w || 0);
|
|
830
|
+
const h = Number(s.height || s.h || 0);
|
|
831
|
+
if (w && h) return { w, h };
|
|
832
|
+
}
|
|
833
|
+
} catch {}
|
|
834
|
+
return legacyScreenSize();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
module.exports = { act, screenSize, clickSimple, mouseMove, probe, autoInstall };
|
package/src/agent/engine.js
CHANGED
|
@@ -4941,19 +4941,36 @@ const TOOLS = [
|
|
|
4941
4941
|
function: {
|
|
4942
4942
|
name: 'computer_act',
|
|
4943
4943
|
description:
|
|
4944
|
-
'
|
|
4944
|
+
'Drive the user\u2019s Windows desktop in the BACKGROUND via cua-driver: your clicks/typing do NOT move the user\u2019s cursor or steal focus. Canonical flow: 1) capture (mode=som \u2192 screenshot + numbered element index), 2) act by element index (much more reliable than pixels), 3) verify with capture_after=true. Pixel x/y are window-local pixels read from the LATEST capture of that window \u2014 re-capture before clicking. Escalate only on returned signals: effect:"suspected_noop" \u2192 retry pixel, then delivery_mode="foreground". Never type passwords/secrets; never click permission dialogs, 2FA or payment UI \u2014 ask the user instead.',
|
|
4945
4945
|
parameters: {
|
|
4946
4946
|
type: 'object',
|
|
4947
4947
|
properties: {
|
|
4948
4948
|
op: {
|
|
4949
4949
|
type: 'string',
|
|
4950
|
-
enum: ['
|
|
4950
|
+
enum: ['capture', 'click', 'dblclick', 'rightclick', 'type', 'key', 'scroll', 'drag', 'wait', 'list_apps', 'focus_app', 'move'],
|
|
4951
|
+
description: 'capture first (som/vision/ax); click/type/etc act by element or window-local pixel; wait pauses; list_apps enumerates windows; focus_app selects input target (raise_window=true steals focus \u2014 only on explicit request); move = legacy real-cursor move',
|
|
4951
4952
|
},
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4953
|
+
app: { type: 'string', description: 'target app by name/title substring (scopes capture and input)' },
|
|
4954
|
+
element: { type: 'number', description: 'element index from the latest capture \u2014 preferred over x/y' },
|
|
4955
|
+
x: { type: 'number', description: 'window-local pixel (from the latest capture of that window)' },
|
|
4956
|
+
y: { type: 'number', description: 'window-local pixel (from the latest capture of that window)' },
|
|
4957
|
+
button: { type: 'string', enum: ['left', 'right', 'middle'] },
|
|
4958
|
+
text: { type: 'string', description: 'for op=type (max 2000 chars); NEVER type passwords or secrets' },
|
|
4959
|
+
keys: { type: 'string', description: 'for op=key: "enter", "ctrl+s", "alt+tab"' },
|
|
4960
|
+
direction: { type: 'string', enum: ['up', 'down', 'left', 'right'], description: 'for op=scroll' },
|
|
4961
|
+
amount: { type: 'number', description: 'for op=scroll: wheel notches 1..50 (default 3)' },
|
|
4962
|
+
from_element: { type: 'number', description: 'for op=drag' },
|
|
4963
|
+
to_element: { type: 'number', description: 'for op=drag' },
|
|
4964
|
+
from_x: { type: 'number', description: 'for op=drag (window-local pixels)' },
|
|
4965
|
+
from_y: { type: 'number', description: 'for op=drag (window-local pixels)' },
|
|
4966
|
+
to_x: { type: 'number', description: 'for op=drag (window-local pixels)' },
|
|
4967
|
+
to_y: { type: 'number', description: 'for op=drag (window-local pixels)' },
|
|
4968
|
+
seconds: { type: 'number', description: 'for op=wait (max 30)' },
|
|
4969
|
+
mode: { type: 'string', enum: ['som', 'vision', 'ax'], description: 'for op=capture: som = screenshot + numbered elements (default), vision = screenshot only, ax = element tree only' },
|
|
4970
|
+
capture_after: { type: 'boolean', description: 're-capture the target right after this action (verify in the same call)' },
|
|
4971
|
+
delivery_mode: { type: 'string', enum: ['background', 'foreground'], description: 'background (default) never raises the window; foreground is the last-resort rung \u2014 use ONLY after effect:"suspected_noop" or background_unavailable' },
|
|
4972
|
+
dy: { type: 'number', description: 'legacy scroll fallback: positive = down (-10..10)' },
|
|
4973
|
+
combo: { type: 'string', description: 'legacy alias for keys' },
|
|
4957
4974
|
},
|
|
4958
4975
|
required: ['op'],
|
|
4959
4976
|
},
|
package/src/agent/watext.js
CHANGED
|
@@ -69,7 +69,7 @@ const TOOL_LABELS = {
|
|
|
69
69
|
kb_search: (a) => `Bilgi ara: ${String(a.query || '').slice(0, 60)}`,
|
|
70
70
|
memory_hygiene: () => 'Hafıza temizliği',
|
|
71
71
|
computer_look: () => 'Ekranı görüntülüyor',
|
|
72
|
-
computer_act: (a) => `Bilgisayar kullanıyor: ${a.op || ''}${a.combo ? ' ' + a.combo : ''}`,
|
|
72
|
+
computer_act: (a) => `Bilgisayar kullanıyor: ${a.op || ''}${a.app ? ' → ' + a.app : ''}${a.element != null ? ' #' + a.element : ''}${(a.keys || a.combo) ? ' [' + (a.keys || a.combo) + ']' : ''}${a.direction ? ' ' + a.direction : ''}`,
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
function waToolLine(name, args) {
|
package/src/main.js
CHANGED
|
@@ -6068,12 +6068,33 @@ ipcMain.handle('install:status', async () => {
|
|
|
6068
6068
|
rows.push({ id: 'python', name: 'Python — betikler / web arama', state: pyVer ? 'ok' : 'optional', detail: pyVer ? 'v' + pyVer : 'sistemde bulunamadı — opsiyonel' });
|
|
6069
6069
|
}
|
|
6070
6070
|
|
|
6071
|
-
/* 7)
|
|
6071
|
+
/* 7) cua-driver — Computer Use (arka plan GUI otomasyonu) */
|
|
6072
|
+
{
|
|
6073
|
+
const st = await require('./agent/computeruse').probe().catch(() => ({ installed: false }));
|
|
6074
|
+
rows.push({
|
|
6075
|
+
id: 'cua-driver',
|
|
6076
|
+
name: 'cua-driver — Computer Use (masaüstü otomasyonu)',
|
|
6077
|
+
state: st.installed ? 'ok' : (st.installing ? 'loading' : (st.failed ? 'failed' : 'missing')),
|
|
6078
|
+
detail: st.installed
|
|
6079
|
+
? (st.version ? 'kurulu — ' + st.version : 'kurulu')
|
|
6080
|
+
: st.installing
|
|
6081
|
+
? 'cua.ai sürücüsü arka planda kuruluyor'
|
|
6082
|
+
: (st.failed
|
|
6083
|
+
? 'kurulum başarısız — elle: irm https://cua.ai/driver/install.ps1 | iex'
|
|
6084
|
+
: 'ilk Computer Use kullanımında arka planda kurulur'),
|
|
6085
|
+
...(st.installing ? pctFields('cua-driver') : {}),
|
|
6086
|
+
});
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
/* 8) Edge TTS (bulut) */
|
|
6072
6090
|
rows.push({ id: 'edge', name: 'Edge TTS — seslendirme', state: 'cloud', detail: 'bulut — kurulum gerekmez' });
|
|
6073
6091
|
|
|
6074
6092
|
return rows;
|
|
6075
6093
|
});
|
|
6076
6094
|
|
|
6095
|
+
/* cua-driver kurulumunu şimdi başlat (Kurulum sekmesi otomatiği + elle tetikleme) */
|
|
6096
|
+
ipcMain.handle('cua:install', () => require('./agent/computeruse').autoInstall());
|
|
6097
|
+
|
|
6077
6098
|
/* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
|
|
6078
6099
|
ipcMain.handle('embed:prefetch', () => {
|
|
6079
6100
|
try {
|
package/src/preload.js
CHANGED
|
@@ -56,6 +56,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
56
56
|
sttStatus: () => ipcRenderer.invoke('stt:status'),
|
|
57
57
|
sttPrefetchNow: () => ipcRenderer.invoke('stt:prefetch'),
|
|
58
58
|
installStatus: () => ipcRenderer.invoke('install:status'),
|
|
59
|
+
cuaInstall: () => ipcRenderer.invoke('cua:install'),
|
|
59
60
|
embedPrefetch: () => ipcRenderer.invoke('embed:prefetch'),
|
|
60
61
|
sttLangSet: (lang) => ipcRenderer.invoke('stt:lang:set', lang),
|
|
61
62
|
updateStatus: () => ipcRenderer.invoke('update:status'),
|
package/src/renderer/renderer.js
CHANGED
|
@@ -2192,6 +2192,7 @@ async function renderInstallPane() {
|
|
|
2192
2192
|
loading: ['İNİYOR…', '#d9a441'],
|
|
2193
2193
|
partial: ['İNİYOR… (devam)', '#d9a441'],
|
|
2194
2194
|
missing: ['İNDİRİLİYOR…', '#d9a441'],
|
|
2195
|
+
failed: ['HATA', '#c0554d'],
|
|
2195
2196
|
cloud: ['BULUT', 'var(--muted)'],
|
|
2196
2197
|
optional: ['OPSİYONEL', 'var(--muted)'],
|
|
2197
2198
|
};
|
|
@@ -2211,6 +2212,10 @@ async function renderInstallPane() {
|
|
|
2211
2212
|
autoStarted.add('emb');
|
|
2212
2213
|
beast.embedPrefetch().catch(() => {});
|
|
2213
2214
|
}
|
|
2215
|
+
if (r.id === 'cua-driver' && r.state === 'missing') {
|
|
2216
|
+
autoStarted.add('cua-driver');
|
|
2217
|
+
beast.cuaInstall().catch(() => {});
|
|
2218
|
+
}
|
|
2214
2219
|
}
|
|
2215
2220
|
};
|
|
2216
2221
|
|