beast-agent 2.66.0 → 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.66.0",
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
@@ -5370,12 +5370,14 @@ const BROWSER_OBSERVE_JS = `(function(){
5370
5370
  return null;
5371
5371
  };
5372
5372
  const actions=[];
5373
+ const added=new WeakSet();
5373
5374
  for(const e of document.querySelectorAll(selector)){
5374
5375
  if(actions.length>=300) break;
5375
5376
  if(!safe(e) || !visible(e) || e.matches(':disabled') || e.closest('[aria-disabled="true"]')) continue;
5376
5377
  const r=e.getBoundingClientRect(), x=r.x+r.width/2, y=r.y+r.height/2, rname=role(e);
5377
5378
  if(!rname || r.width<=0 || r.height<=0 || x<0 || y<0 || x>=innerWidth || y>=innerHeight) continue;
5378
5379
  if(rname==='gridcell' && e.querySelector('button,[role="button"]')) continue;
5380
+ added.add(e);
5379
5381
  const base={ref:identity(e),role:rname,label:name(e)||rname};
5380
5382
  const et=String(e.type||'').toLowerCase();
5381
5383
  if(et) base.input_type=et;
@@ -5400,6 +5402,29 @@ const BROWSER_OBSERVE_JS = `(function(){
5400
5402
  if(editable) actions.push({...base,kind:'click',value,label:'Open '+base.label});
5401
5403
  }
5402
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
+ }
5403
5428
  const words=[], walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);
5404
5429
  const range=document.createRange(); let node, length=0;
5405
5430
  while((node=walker.nextNode()) && length<6000){
@@ -5568,6 +5593,22 @@ function browserActionJs(kind, args) {
5568
5593
  res(JSON.stringify({selected:true,target:t.how,value:opt.text.trim()}));
5569
5594
  }catch(e){res(JSON.stringify({selected:false,reason:String(e)}));}});})()`;
5570
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
+ }
5571
5612
  return `JSON.stringify({ok:false,error:'bilinmeyen eylem'})`;
5572
5613
  }
5573
5614
 
@@ -5660,7 +5701,7 @@ async function browserAct(kind, args, signal) {
5660
5701
  /* JEV HIZLI YOLU: TypeSafe ajanı her adımda zaten taze observe yapar —
5661
5702
  ağır imza + taze snapshot + uzun bekleme gereksizdir. */
5662
5703
  const fast = !!a.fast;
5663
- const trusted = !!a.trusted && ['click', 'type'].includes(kind) && browser.attached;
5704
+ const trusted = !!a.trusted && ['click', 'type', 'hover'].includes(kind) && browser.attached;
5664
5705
  /* DOĞRULAMA: eylem öncesi görsel parmak izi (yalnız anlamlı eylemlerde) */
5665
5706
  const verify = ['click', 'type', 'select', 'press'].includes(kind) && !fast && !(a.verify === false);
5666
5707
  const sigBefore = verify ? await pageSignature(wc) : null;
@@ -5671,12 +5712,20 @@ async function browserAct(kind, args, signal) {
5671
5712
  let point = {};
5672
5713
  try { point = JSON.parse(pointRaw); } catch { point = { ok: false, reason: 'koordinat alinamadi' }; }
5673
5714
  if (!point.ok) {
5674
- 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 };
5675
5718
  } else if (kind === 'type' && /^(date|time|month|week|datetime-local)$/.test(String(point.input_type || ''))) {
5676
5719
  /* native tarih/saat alanı: gerçek klavye yerine programatik değer + event
5677
5720
  (tarayıcı segment segment yazmayı reddeder) */
5678
5721
  const raw = await wc.executeJavaScript(browserActionJs(kind, a), true);
5679
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 };
5680
5729
  } else {
5681
5730
  wc.sendInputEvent({ type: 'mouseMove', x: point.x, y: point.y });
5682
5731
  wc.sendInputEvent({ type: 'mouseDown', x: point.x, y: point.y, button: 'left', clickCount: 1 });
@@ -5741,6 +5790,7 @@ async function browserAct(kind, args, signal) {
5741
5790
  blog(
5742
5791
  kind,
5743
5792
  obj.clicked ? obj.target
5793
+ : obj.hovered ? ('üzerine gelindi ' + obj.target)
5744
5794
  : obj.typed ? ('"' + obj.value + '" → ' + obj.target)
5745
5795
  : obj.selected ? ('"' + obj.value + '" seçildi')
5746
5796
  : obj.pressed ? ('tuş ' + obj.pressed)