beast-agent 2.66.0 → 2.68.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.68.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)
@@ -217,13 +217,13 @@
217
217
  p_role_sub: 'Vision, Terminal, Coding ve Subagent için ayrı model; boş bırakırsan ana model kullanılır',
218
218
  p_role_main: '— Ana model —',
219
219
  p_custom_h2: 'Özel Provider',
220
- p_custom_sub: 'API adres + key ver, modeller otomatik çekilir',
220
+ p_custom_sub: 'API adres + key ver, modeller otomatik çekilir; olmayanları elle ekle',
221
221
  p_custom_del: 'Sil',
222
222
  p_name_opt: 'Ad (opsiyonel)',
223
223
  p_api_url: 'API adresi',
224
224
  p_api_key: 'API Key',
225
225
  p_fetch: 'Modelleri Çek',
226
- p_models_manual: 'Modelleri elle gir (her satıra bir model adı)',
226
+ p_models_manual: 'Ek modeller — elle ekle (her satıra bir ad; çekilenlere eklenir)',
227
227
  p_save: 'Kaydet',
228
228
  p_model_toast: 'Model: ',
229
229
  p_del_toast: 'Silindi: ',
@@ -979,13 +979,13 @@
979
979
  p_role_sub: 'Separate model for Vision, Terminal, Coding and Subagent; leave empty to use the main model',
980
980
  p_role_main: '— Main model —',
981
981
  p_custom_h2: 'Custom Provider',
982
- p_custom_sub: 'Provide API URL + key, models are fetched automatically',
982
+ p_custom_sub: 'Provide API URL + key, models are fetched automatically; add extra ones manually',
983
983
  p_custom_del: 'Delete',
984
984
  p_name_opt: 'Name (optional)',
985
985
  p_api_url: 'API URL',
986
986
  p_api_key: 'API Key',
987
987
  p_fetch: 'Fetch Models',
988
- p_models_manual: 'Enter models manually (one per line)',
988
+ p_models_manual: 'Extra models — add manually (one per line; added to fetched list)',
989
989
  p_save: 'Save',
990
990
  p_model_toast: 'Model: ',
991
991
  p_del_toast: 'Deleted: ',
@@ -1920,11 +1920,11 @@ async function renderProviderPane() {
1920
1920
  `</div>` +
1921
1921
  `<button id="cpFetch" class="btn ghost">${_t('p_fetch')}</button>` +
1922
1922
  `<div id="cpPicks" class="model-picks" hidden></div>` +
1923
- `<div id="cpManualWrap" hidden>` +
1923
+ `<div id="cpManualWrap">` +
1924
1924
  `<label class="mem-label">${_t('p_models_manual')}</label>` +
1925
- `<textarea id="cpManual" class="mem-area" rows="4" placeholder="model-adi-1&#10;model-adi-2"></textarea>` +
1925
+ `<textarea id="cpManual" class="mem-area" rows="3" placeholder="model-adi-1&#10;model-adi-2"></textarea>` +
1926
1926
  `</div>` +
1927
- `<button id="cpSave" class="btn" hidden>${_t('p_save')}</button>`;
1927
+ `<button id="cpSave" class="btn">${_t('p_save')}</button>`;
1928
1928
  pane.appendChild(form);
1929
1929
 
1930
1930
  let discovered = [];
@@ -1981,11 +1981,11 @@ async function renderProviderPane() {
1981
1981
 
1982
1982
  const collectedModels = () => {
1983
1983
  const picked = [...document.querySelectorAll('#cpPicks input:checked')].map((c) => c.value);
1984
- if (picked.length) return picked;
1985
- return ($('#cpManual').value || '')
1984
+ const manual = ($('#cpManual').value || '')
1986
1985
  .split(/[\n,]+/)
1987
1986
  .map((s) => s.trim())
1988
1987
  .filter(Boolean);
1988
+ return [...new Set([...picked, ...manual])];
1989
1989
  };
1990
1990
 
1991
1991
  $('#cpFetch').addEventListener('click', async () => {
@@ -2005,24 +2005,20 @@ async function renderProviderPane() {
2005
2005
  discovered = res.ok ? res.models : [];
2006
2006
  const picks = $('#cpPicks');
2007
2007
  if (discovered.length) {
2008
- $('#cpManualWrap').hidden = true;
2009
2008
  picks.hidden = false;
2010
2009
  picks.innerHTML = discovered
2011
2010
  .map((m) => `<label><input type="checkbox" value="${escapeHtml(m)}" checked> ${escapeHtml(m)}</label>`)
2012
2011
  .join('');
2013
- $('#cpSave').hidden = false;
2014
- toast(discovered.length + ' model bulundu');
2012
+ toast(discovered.length + ' model bulundu — listeden seç ya da elle ekle');
2015
2013
  } else {
2016
2014
  picks.hidden = true;
2017
2015
  picks.innerHTML = '';
2018
- $('#cpManualWrap').hidden = false;
2019
- $('#cpSave').hidden = false;
2020
2016
  toast('Model çekilemedi (' + (res.error || 'boş') + ') — elle girebilirsin');
2021
2017
  }
2022
2018
  });
2023
2019
 
2024
2020
  $('#cpSave').addEventListener('click', async () => {
2025
- const models = [...new Set(collectedModels())];
2021
+ const models = collectedModels();
2026
2022
  const url = $('#cpUrl').value.trim();
2027
2023
  if (!/^https?:\/\//i.test(url)) { toast('Geçerli API adresi gir'); return; }
2028
2024
  if (!models.length) { toast('En az bir model gir ya da seç'); return; }