@tangle-network/browser-agent-driver 0.23.0 → 0.24.1

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.
@@ -282,6 +282,37 @@ export class BrowserAgent {
282
282
  this.runRegistry = options.runRegistry;
283
283
  }
284
284
  async run(scenario) {
285
+ // Gen 21: parallel tab execution for compound goals.
286
+ // Pre-flight: check if the goal should be decomposed into parallel sub-goals.
287
+ if (this.config.parallelTabs?.enabled && scenario.goal && scenario.startUrl) {
288
+ const context = this.driver.getPage?.()?.context();
289
+ if (context) {
290
+ const { decomposeGoal } = await import('./goal-decomposer.js');
291
+ const decomposition = await decomposeGoal(scenario.goal, scenario.startUrl, {
292
+ provider: this.config.provider || 'openai',
293
+ model: this.config.navModel || 'gpt-4.1-mini',
294
+ apiKey: this.config.apiKey,
295
+ });
296
+ if (decomposition.type === 'compound' && decomposition.subGoals) {
297
+ const { runParallel } = await import('./parallel-runner.js');
298
+ const result = await runParallel({
299
+ context,
300
+ config: this.config,
301
+ originalGoal: scenario.goal,
302
+ subGoals: decomposition.subGoals,
303
+ scenario,
304
+ onTurn: this.onTurn ? (_label, turn) => this.onTurn(turn) : undefined,
305
+ projectStore: this.projectStore,
306
+ });
307
+ return {
308
+ success: result.success,
309
+ reason: result.mergedResult,
310
+ turns: [],
311
+ totalMs: result.totalMs,
312
+ };
313
+ }
314
+ }
315
+ }
285
316
  // Gen 14: vision mode gets more turns — each turn takes ~15s (screenshot
286
317
  // encode + image tokens) vs ~5s for DOM-first. Without the boost, vision
287
318
  // runs out of turns before completing multi-step tasks.
@@ -322,7 +353,7 @@ export class BrowserAgent {
322
353
  phaseTimings,
323
354
  wasteMetrics: deriveWasteMetrics(turns, runState.verificationRejectionCount, runState.firstSufficientEvidenceTurn),
324
355
  };
325
- this.saveMemory(scenario, agentResult);
356
+ this.saveMemory(scenario, agentResult, turns);
326
357
  // Complete run manifest
327
358
  const lastTurn = agentResult.turns[agentResult.turns.length - 1];
328
359
  this.runRegistry?.completeRun(runId, {
@@ -395,6 +426,19 @@ export class BrowserAgent {
395
426
  await navPromise;
396
427
  phaseTimings.initialNavigateMs = Date.now() - navigateStartedAt;
397
428
  this.onPhaseTiming?.('navigate', phaseTimings.initialNavigateMs);
429
+ // Gen 24b: page warm-up — simulate human settling time after page load.
430
+ // DataDome and similar ML anti-bot systems measure time-to-first-interaction.
431
+ // Bots act within 100ms; humans take 1-3 seconds to orient on a new page.
432
+ // Also adds a small random scroll to generate natural mouse/scroll events.
433
+ const page = this.driver.getPage?.();
434
+ if (page) {
435
+ const settleMs = 1500 + Math.floor(Math.random() * 1500); // 1.5-3s
436
+ await page.waitForTimeout(settleMs);
437
+ // Small exploratory scroll — humans look around before acting
438
+ await page.mouse.move(300 + Math.random() * 400, 200 + Math.random() * 200);
439
+ await page.mouse.wheel(0, 100 + Math.random() * 200);
440
+ await page.waitForTimeout(300 + Math.floor(Math.random() * 500));
441
+ }
398
442
  }
399
443
  // Don't wait on warmup before entering the loop — it races against the
400
444
  // first observe and decode. Make sure any unhandled rejection is silenced.
@@ -410,8 +454,9 @@ export class BrowserAgent {
410
454
  });
411
455
  const supervisorConfig = {
412
456
  enabled: this.config.supervisor?.enabled ?? DEFAULT_SUPERVISOR.enabled,
413
- model: this.config.supervisor?.model || this.config.model || 'gpt-5.4',
414
- provider: this.config.supervisor?.provider || this.config.provider || 'openai',
457
+ // Gen 28: models.supervisor overrides supervisor.model, falls back to main
458
+ model: this.config.models?.supervisor?.model || this.config.supervisor?.model || this.config.model || 'gpt-5.4',
459
+ provider: (this.config.models?.supervisor?.provider || this.config.supervisor?.provider || this.config.provider || 'openai'),
415
460
  useVision: this.config.supervisor?.useVision ?? DEFAULT_SUPERVISOR.useVision,
416
461
  minTurnsBeforeInvoke: this.config.supervisor?.minTurnsBeforeInvoke ?? DEFAULT_SUPERVISOR.minTurnsBeforeInvoke,
417
462
  cooldownTurns: this.config.supervisor?.cooldownTurns ?? DEFAULT_SUPERVISOR.cooldownTurns,
@@ -1185,11 +1230,26 @@ export class BrowserAgent {
1185
1230
  }
1186
1231
  }
1187
1232
  else {
1233
+ // Gen 24b: micro-movements during LLM "thinking" — anti-bot systems
1234
+ // flag frozen cursors. Run small random mouse drifts in parallel with
1235
+ // the LLM call. Stops when the decision arrives.
1236
+ let microMoving = true;
1237
+ const microMoveLoop = (async () => {
1238
+ const p = this.driver.getPage?.();
1239
+ if (!p)
1240
+ return;
1241
+ while (microMoving) {
1242
+ await p.mouse.move(300 + Math.random() * 600, 200 + Math.random() * 400, { steps: 3 }).catch(() => { });
1243
+ await p.waitForTimeout(800 + Math.floor(Math.random() * 1200)).catch(() => { });
1244
+ }
1245
+ })();
1188
1246
  decision = await withRetry(() => this.brain.decide(scenario.goal, decisionState, finalExtraContext || undefined, { current: i, max: maxTurns }, { forceVision }), retries, retryDelayMs, (attempt, err) => {
1189
1247
  if (this.config.debug) {
1190
1248
  console.log(`[Runner] LLM retry ${attempt}: ${err.message}`);
1191
1249
  }
1192
1250
  }, scenario.signal);
1251
+ microMoving = false;
1252
+ void microMoveLoop; // ensure no unhandled rejection
1193
1253
  if (cacheKey && this.decisionCache) {
1194
1254
  // Store the fresh decision so a future identical turn replays it.
1195
1255
  this.decisionCache.set(cacheKey, decision);
@@ -1547,8 +1607,22 @@ export class BrowserAgent {
1547
1607
  runState.verificationRejectionCount++;
1548
1608
  turn.verificationFailure = goalResult.missing.join('; ') || 'Goal verification failed';
1549
1609
  runState.firstSufficientEvidenceTurn ??= i;
1610
+ // Gen 24b: checkpoint replay on 2nd rejection. Navigate back
1611
+ // to a previous page where the agent had correct data, instead
1612
+ // of continuing from the wrong-path state.
1613
+ let replayNote = '';
1614
+ if (runState.verificationRejectionCount === 2 && runState.checkpoints.length >= 2) {
1615
+ // Go back to the second-to-last checkpoint (before the wrong path)
1616
+ const target = runState.checkpoints[runState.checkpoints.length - 2];
1617
+ if (target) {
1618
+ try {
1619
+ await this.driver.execute({ action: 'navigate', url: target.url });
1620
+ replayNote = ` ROLLED BACK to ${target.url} (checkpoint from turn ${target.turn}). You went down the wrong path — try a different approach from this known-good page.`;
1621
+ }
1622
+ catch { /* rollback failed, continue from current state */ }
1623
+ }
1624
+ }
1550
1625
  // Gen 19: progressive strategy-shift escalation on rejection.
1551
- // Each rejection level suggests a MORE different approach.
1552
1626
  let escalation;
1553
1627
  if (runState.verificationRejectionCount >= 3) {
1554
1628
  escalation = ' STRATEGY SHIFT REQUIRED: Your previous approaches have failed 3 times. Try a COMPLETELY different method: use navigate to go to a different search engine or URL, try extractWithIndex instead of runScript, or scroll to look for the data in a different part of the page. Do NOT repeat what you just tried.';
@@ -1559,7 +1633,7 @@ export class BrowserAgent {
1559
1633
  else {
1560
1634
  escalation = ' Re-read the GOAL carefully — your result is missing specific data the goal asked for. Find and include it before completing.';
1561
1635
  }
1562
- this.brain.injectFeedback(`REJECTED (${goalResult.confidence.toFixed(2)}). Missing: ${goalResult.missing.join('; ')}.${escalation}`);
1636
+ this.brain.injectFeedback(`REJECTED (${goalResult.confidence.toFixed(2)}). Missing: ${goalResult.missing.join('; ')}.${escalation}${replayNote}`);
1563
1637
  turn.durationMs = Date.now() - turnStart;
1564
1638
  turns.push(turn);
1565
1639
  this.onTurn?.(turn);
@@ -1740,6 +1814,16 @@ export class BrowserAgent {
1740
1814
  console.log(`[Runner] Fill warning: ${warning}`);
1741
1815
  }
1742
1816
  }
1817
+ // Gen 24b: save checkpoint when URL changes after successful action.
1818
+ // These are rollback points for wrong-path recovery.
1819
+ const postUrl = this.driver.getPage?.()?.url() || '';
1820
+ const lastCheckpointUrl = runState.checkpoints[runState.checkpoints.length - 1]?.url;
1821
+ if (postUrl && postUrl !== 'about:blank' && postUrl !== lastCheckpointUrl) {
1822
+ runState.checkpoints.push({ url: postUrl, turn: i });
1823
+ // Keep max 5 checkpoints to avoid unbounded growth
1824
+ if (runState.checkpoints.length > 5)
1825
+ runState.checkpoints.shift();
1826
+ }
1743
1827
  // Capture element bounding box for replay overlays
1744
1828
  if (execResult.bounds) {
1745
1829
  turn.actionBounds = execResult.bounds;
@@ -1931,10 +2015,29 @@ export class BrowserAgent {
1931
2015
  return selected;
1932
2016
  }
1933
2017
  /** Persist knowledge, selector cache, and session history to disk */
1934
- saveMemory(scenario, result) {
2018
+ saveMemory(scenario, result, turns) {
1935
2019
  try {
1936
2020
  if (this.knowledge && scenario && result) {
1937
2021
  this.knowledge.recordSession(buildSession(scenario, result));
2022
+ // Gen 26b: extract reusable patterns from successful runs.
2023
+ // Patterns gain confidence with repeated observation and auto-decay
2024
+ // when contradicted. Low-confidence facts are pruned automatically.
2025
+ if (result.success && turns && turns.length > 0) {
2026
+ const domain = safeHostname(scenario.startUrl || '') || '';
2027
+ if (domain) {
2028
+ // Dynamic import to keep the module tree clean
2029
+ import('./pattern-extractor.js').then(({ extractPatterns, recordPatterns }) => {
2030
+ const patterns = extractPatterns(turns, domain, result.success);
2031
+ if (patterns.length > 0) {
2032
+ recordPatterns(this.knowledge, patterns);
2033
+ this.knowledge.save();
2034
+ if (this.config.debug) {
2035
+ console.log(`[Runner] Recorded ${patterns.length} patterns for ${domain}`);
2036
+ }
2037
+ }
2038
+ }).catch(() => { });
2039
+ }
2040
+ }
1938
2041
  }
1939
2042
  this.knowledge?.save();
1940
2043
  this.selectorCache?.save();