beast-agent 2.65.1 → 2.67.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.65.1",
4
+ "version": "2.67.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -20,6 +20,8 @@ Submit populated search fields before opening a result; a populated field alone
20
20
  WAIT only when the needed control is absent/disabled, or submitted results are still loading.
21
21
  If Search/Submit is visible and the required fields are ready, CLICK it immediately.
22
22
  Recent WAIT actions are not evidence of loading. Prefer a useful visible control over WAIT.
23
+ Category/nav items that reveal a submenu only on hover: choose HOVER on the category first,
24
+ then CLICK the submenu item that becomes visible. HOVER never navigates by itself.
23
25
  DONE requires visible evidence that ALL requirements are satisfied. If asked to open a result,
24
26
  a matching link is not enough. BLOCKED means no supported operation can make progress.
25
27
  Before the goal itself: if a modal, cookie or consent band, campaign popup,
@@ -50,6 +52,7 @@ const OPERATION_LABELS = {
50
52
  TYPE_TEXT: 'Enter or replace text in an editable field. A small LLM will supply the value from the goal.',
51
53
  SELECT: 'Select an observed dropdown value.',
52
54
  PRESS: 'Press a keyboard key (Escape closes modals and popups).',
55
+ HOVER: 'Move the mouse over an element WITHOUT clicking (opens hover menus / mega menus).',
53
56
  DONE: 'Every requirement is visibly satisfied.',
54
57
  BLOCKED: 'No supported operation can progress.',
55
58
  };
@@ -142,6 +145,7 @@ function operationsFor(targets, controls) {
142
145
  for (const key of Object.keys(targets)) operations[key] = OPERATION_LABELS[key] || key;
143
146
  for (const [key, value] of Object.entries(controls)) operations[key] = value.label || key;
144
147
  operations.PRESS = OPERATION_LABELS.PRESS;
148
+ operations.HOVER = OPERATION_LABELS.HOVER;
145
149
  for (const key of ['DONE', 'BLOCKED']) operations[key] = OPERATION_LABELS[key];
146
150
  return operations;
147
151
  }
@@ -165,10 +169,12 @@ function targetCriteria(candidates) {
165
169
  istekte gider. Yalnız seçilen operation'ın target başlığı doğrulanır. */
166
170
  async function choose(deps, page, goal, history, space) {
167
171
  const { elements, targets, controls } = space;
172
+ /* HOVER, CLICK hedeflerini kullanır: kategori/mega menüler önce hover ister */
173
+ const targetOps = targets.CLICK ? Object.assign({ HOVER: targets.CLICK }, targets) : targets;
168
174
  const questions = {
169
175
  operation: {
170
176
  type: 'choice',
171
- criteria: operationsFor(targets, controls),
177
+ criteria: operationsFor(targetOps, controls),
172
178
  instructions: { goal, rules: NEXT_ACTION },
173
179
  },
174
180
  verification: {
@@ -176,7 +182,7 @@ async function choose(deps, page, goal, history, space) {
176
182
  instructions: { goal, rules: [NEXT_ACTION, VERIFY] },
177
183
  },
178
184
  };
179
- for (const [operation, candidates] of Object.entries(targets)) {
185
+ for (const [operation, candidates] of Object.entries(targetOps)) {
180
186
  questions[operation.toLowerCase() + '_target'] = {
181
187
  type: 'choice',
182
188
  criteria: targetCriteria(candidates),
@@ -199,21 +205,21 @@ async function choose(deps, page, goal, history, space) {
199
205
  const started = Date.now();
200
206
  const result = await (deps.systemOne || systemOne)({ state, questions });
201
207
  const answers = (result && result.answers) || {};
202
- const operationAnswer = validateChoice(answers.operation || {}, Object.keys(operationsFor(targets, controls)));
208
+ const operationAnswer = validateChoice(answers.operation || {}, Object.keys(operationsFor(targetOps, controls)));
203
209
  const operation = operationAnswer.choice;
204
210
  const verifyAnswer = answers.verification && typeof answers.verification === 'object' ? answers.verification : null;
205
211
  const verification =
206
212
  verifyAnswer && Number.isFinite(Number(verifyAnswer.noul)) ? Number(verifyAnswer.noul) : null;
207
213
  let target = null;
208
214
  let targetAnswer = null;
209
- if (targets[operation]) {
210
- targetAnswer = validateChoice(answers[operation.toLowerCase() + '_target'] || {}, Object.keys(targets[operation]));
215
+ if (targetOps[operation]) {
216
+ targetAnswer = validateChoice(answers[operation.toLowerCase() + '_target'] || {}, Object.keys(targetOps[operation]));
211
217
  target = targetAnswer.choice;
212
218
  }
213
219
  return {
214
220
  operation,
215
221
  target,
216
- action: target != null ? targets[operation][target] : controls[operation] || { id: operation, kind: operation.toLowerCase(), label: operation },
222
+ action: target != null ? targetOps[operation][target] : controls[operation] || { id: operation, kind: operation.toLowerCase(), label: operation },
217
223
  confidence: targetAnswer ? targetAnswer.confidence : operationAnswer.confidence,
218
224
  target_probability: target != null && targetAnswer ? targetAnswer.probabilities[target] : null,
219
225
  verification,
@@ -322,7 +328,7 @@ async function run(deps, options) {
322
328
  Değişmeyen sayfada 3. eylemde döngü kendini bloke ilan eder. */
323
329
  const rev = page.rev != null ? String(page.rev) : String(page.url || '') + '|' + String(page.title || '');
324
330
  const last = history.length ? history[history.length - 1] : null;
325
- if (last && last.kind !== 'wait' && last.kind !== 'scroll' && !last.stale) {
331
+ if (last && last.kind !== 'wait' && last.kind !== 'scroll' && last.kind !== 'hover' && !last.stale) {
326
332
  if (last.page_changed == null) last.page_changed = prevRev != null ? rev !== prevRev : null;
327
333
  if (last.page_changed === false) {
328
334
  if (!last.covered) noChangeStreak++;
@@ -374,7 +380,7 @@ async function run(deps, options) {
374
380
  if (operation === 'BLOCKED') return finish('blocked');
375
381
 
376
382
  const action = decision.action || {};
377
- const kind = String(action.kind || '');
383
+ const kind = operation === 'HOVER' ? 'hover' : String(action.kind || '');
378
384
  const expect = page.url ? { url: page.url } : null;
379
385
  let result = null;
380
386
  let text = null;
@@ -408,6 +414,10 @@ async function run(deps, options) {
408
414
  } else if (kind === 'click') {
409
415
  result = await deps.act('click', { ref: Number(action.ref), fast: true, trusted: true, expect });
410
416
  await deps.wait({ ms: 60 });
417
+ } else if (kind === 'hover') {
418
+ /* alt menü/mega menü: tıkla değil, üzerine gel — menü açılsın */
419
+ result = await deps.act('hover', { ref: Number(action.ref), fast: true, trusted: true, expect });
420
+ await deps.wait({ ms: 180 });
411
421
  } else if (kind === 'select') {
412
422
  const value = action.value != null && String(action.value) !== '' ? action.value : baseLabel(action.label);
413
423
  result = await deps.act('select', { ref: Number(action.ref), value: String(value), fast: true, expect });
package/src/main.js CHANGED
@@ -3818,6 +3818,17 @@ function ensureShortcuts() {
3818
3818
  /* TTS otomatik seslendirme: kullanıcı jesti olmadan Audio.play() çalışsın */
3819
3819
  app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
3820
3820
 
3821
+ /* DAHİLİ TARAYICI CDP (HER AÇILIŞTA AÇIK): Jev (browser_agent) ve toollar
3822
+ (browser_js) daima DAHİLİ panelde çalışsın diye remote debug portu otomatik
3823
+ açılır — bat/argüman gerekmez. Chromium gerçek portu %APPDATA%\Beast Agent\
3824
+ DevToolsActivePort dosyasına yazar; browser_js önce o dosyayı okur, paneli
3825
+ bulamazsa zaten hata verir (yardımcı Chrome'a düşmez). Kullanıcı kendi
3826
+ --remote-debugging-port argümanını verdiyse ona dokunulmaz. */
3827
+ try {
3828
+ const hasPort = process.argv.some((a) => /^--remote-debugging-port(=|$)/.test(String(a)));
3829
+ if (!hasPort) app.commandLine.appendSwitch('remote-debugging-port', '9234');
3830
+ } catch {}
3831
+
3821
3832
  app.whenReady().then(() => {
3822
3833
  // Tailscale modu: paketli uygulamada Windows ile otomatik başlat (sessiz, tepside)
3823
3834
  if (app.isPackaged) {
@@ -5359,12 +5370,14 @@ const BROWSER_OBSERVE_JS = `(function(){
5359
5370
  return null;
5360
5371
  };
5361
5372
  const actions=[];
5373
+ const added=new WeakSet();
5362
5374
  for(const e of document.querySelectorAll(selector)){
5363
5375
  if(actions.length>=300) break;
5364
5376
  if(!safe(e) || !visible(e) || e.matches(':disabled') || e.closest('[aria-disabled="true"]')) continue;
5365
5377
  const r=e.getBoundingClientRect(), x=r.x+r.width/2, y=r.y+r.height/2, rname=role(e);
5366
5378
  if(!rname || r.width<=0 || r.height<=0 || x<0 || y<0 || x>=innerWidth || y>=innerHeight) continue;
5367
5379
  if(rname==='gridcell' && e.querySelector('button,[role="button"]')) continue;
5380
+ added.add(e);
5368
5381
  const base={ref:identity(e),role:rname,label:name(e)||rname};
5369
5382
  const et=String(e.type||'').toLowerCase();
5370
5383
  if(et) base.input_type=et;
@@ -5389,6 +5402,29 @@ const BROWSER_OBSERVE_JS = `(function(){
5389
5402
  if(editable) actions.push({...base,kind:'click',value,label:'Open '+base.label});
5390
5403
  }
5391
5404
  }
5405
+ /* İLAVE TIKLANABİLİRLER: <a>/<button> dışı gerçek hedef olan kategori/menü
5406
+ kutuları (li, custom div/span butonlar). Kısıtlı aday seti + cursor:pointer
5407
+ (ya da onclick) + metin + makul kutu şartı. İçinde gerçek link/buton olan
5408
+ kapsayıcılar atlanır (onlar zaten ana listede). */
5409
+ const extraSel='li,[onclick],[class*="categor" i],[class*="kategor" i],[class*="menu" i],[class*="nav" i],[class*="tab" i],[role="treeitem"],[role="menuitem"]';
5410
+ let extra=0;
5411
+ for(const e of document.querySelectorAll(extraSel)){
5412
+ if(extra>=80 || actions.length>=300) break;
5413
+ if(added.has(e) || e.closest('a[href],button,[role="button"]')) continue;
5414
+ if(e.querySelector('a[href],button,[role="button"]')) continue;
5415
+ if(!visible(e) || e.matches(':disabled') || e.closest('[aria-disabled="true"]')) continue;
5416
+ let pointer=false;
5417
+ try{ pointer=getComputedStyle(e).cursor==='pointer'||e.hasAttribute('onclick'); }catch(err){}
5418
+ if(!pointer) continue;
5419
+ const r=e.getBoundingClientRect(), x=r.x+r.width/2, y=r.y+r.height/2;
5420
+ if(r.width<16||r.height<12||r.width>innerWidth*0.85||r.height>innerHeight*0.4) continue;
5421
+ if(x<0||y<0||x>=innerWidth||y>=innerHeight) continue;
5422
+ const label=String(name(e)||'').replace(/\\s+/g,' ').trim();
5423
+ if(!label||label.length>80) continue;
5424
+ added.add(e);
5425
+ actions.push({ref:identity(e),role:'button',label:label,kind:'click',value:''});
5426
+ extra++;
5427
+ }
5392
5428
  const words=[], walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);
5393
5429
  const range=document.createRange(); let node, length=0;
5394
5430
  while((node=walker.nextNode()) && length<6000){
@@ -5557,6 +5593,22 @@ function browserActionJs(kind, args) {
5557
5593
  res(JSON.stringify({selected:true,target:t.how,value:opt.text.trim()}));
5558
5594
  }catch(e){res(JSON.stringify({selected:false,reason:String(e)}));}});})()`;
5559
5595
  }
5596
+ if (kind === 'hover') {
5597
+ return `(function(){${BROWSER_JS_HELPERS}${resolveTarget}
5598
+ try{
5599
+ const t=__target();
5600
+ if(!t) return JSON.stringify({hovered:false,reason:'eleman bulunamadi (ref eski olabilir)'});
5601
+ const el=t.el;
5602
+ el.scrollIntoView({block:'center'});
5603
+ const r=el.getBoundingClientRect(), x=Math.round(r.x+r.width/2), y=Math.round(r.y+r.height/2);
5604
+ const opt={bubbles:true,cancelable:true,clientX:x,clientY:y};
5605
+ ['pointerover','pointerenter','mouseover','mouseenter','mousemove'].forEach(function(type){
5606
+ try{ el.dispatchEvent(new MouseEvent(type,opt)); }catch(e){}
5607
+ });
5608
+ return JSON.stringify({hovered:true,target:t.how});
5609
+ }catch(e){return JSON.stringify({hovered:false,reason:String(e)});}
5610
+ })()`;
5611
+ }
5560
5612
  return `JSON.stringify({ok:false,error:'bilinmeyen eylem'})`;
5561
5613
  }
5562
5614
 
@@ -5649,7 +5701,7 @@ async function browserAct(kind, args, signal) {
5649
5701
  /* JEV HIZLI YOLU: TypeSafe ajanı her adımda zaten taze observe yapar —
5650
5702
  ağır imza + taze snapshot + uzun bekleme gereksizdir. */
5651
5703
  const fast = !!a.fast;
5652
- const trusted = !!a.trusted && ['click', 'type'].includes(kind) && browser.attached;
5704
+ const trusted = !!a.trusted && ['click', 'type', 'hover'].includes(kind) && browser.attached;
5653
5705
  /* DOĞRULAMA: eylem öncesi görsel parmak izi (yalnız anlamlı eylemlerde) */
5654
5706
  const verify = ['click', 'type', 'select', 'press'].includes(kind) && !fast && !(a.verify === false);
5655
5707
  const sigBefore = verify ? await pageSignature(wc) : null;
@@ -5660,12 +5712,20 @@ async function browserAct(kind, args, signal) {
5660
5712
  let point = {};
5661
5713
  try { point = JSON.parse(pointRaw); } catch { point = { ok: false, reason: 'koordinat alinamadi' }; }
5662
5714
  if (!point.ok) {
5663
- obj = kind === 'click' ? { clicked: false, ...point } : { typed: false, ...point };
5715
+ obj = kind === 'click' ? { clicked: false, ...point }
5716
+ : kind === 'hover' ? { hovered: false, ...point }
5717
+ : { typed: false, ...point };
5664
5718
  } else if (kind === 'type' && /^(date|time|month|week|datetime-local)$/.test(String(point.input_type || ''))) {
5665
5719
  /* native tarih/saat alanı: gerçek klavye yerine programatik değer + event
5666
5720
  (tarayıcı segment segment yazmayı reddeder) */
5667
5721
  const raw = await wc.executeJavaScript(browserActionJs(kind, a), true);
5668
5722
  try { obj = JSON.parse(raw); } catch { obj = { result: String(raw).slice(0, 300) }; }
5723
+ } else if (kind === 'hover') {
5724
+ /* GERÇEK fare: yalnız mouseMove — tıklama YOK; kategori/mega menü
5725
+ hover ile açılır, sonraki gözlemde alt menü hedefleri görünür olur. */
5726
+ wc.sendInputEvent({ type: 'mouseMove', x: point.x, y: point.y });
5727
+ await new Promise((r) => setTimeout(r, 260));
5728
+ obj = { hovered: true, trusted: true, target: point.how };
5669
5729
  } else {
5670
5730
  wc.sendInputEvent({ type: 'mouseMove', x: point.x, y: point.y });
5671
5731
  wc.sendInputEvent({ type: 'mouseDown', x: point.x, y: point.y, button: 'left', clickCount: 1 });
@@ -5730,6 +5790,7 @@ async function browserAct(kind, args, signal) {
5730
5790
  blog(
5731
5791
  kind,
5732
5792
  obj.clicked ? obj.target
5793
+ : obj.hovered ? ('üzerine gelindi ' + obj.target)
5733
5794
  : obj.typed ? ('"' + obj.value + '" → ' + obj.target)
5734
5795
  : obj.selected ? ('"' + obj.value + '" seçildi')
5735
5796
  : obj.pressed ? ('tuş ' + obj.pressed)