aurix-ai 2.1.0 → 2.4.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.
Files changed (92) hide show
  1. package/bin/aurix +14 -3
  2. package/bin/aurix.cmd +59 -8
  3. package/dist/agent/AgentLoop.d.ts +8 -2
  4. package/dist/agent/AgentLoop.d.ts.map +1 -1
  5. package/dist/agent/AgentLoop.js +147 -62
  6. package/dist/agent/AgentLoop.js.map +1 -1
  7. package/dist/agent/Checkpoint.d.ts +32 -0
  8. package/dist/agent/Checkpoint.d.ts.map +1 -0
  9. package/dist/agent/Checkpoint.js +200 -0
  10. package/dist/agent/Checkpoint.js.map +1 -0
  11. package/dist/agent/Config.d.ts +1 -1
  12. package/dist/agent/Config.d.ts.map +1 -1
  13. package/dist/agent/Context.d.ts.map +1 -1
  14. package/dist/agent/Context.js +36 -13
  15. package/dist/agent/Context.js.map +1 -1
  16. package/dist/agent/ContextManager.d.ts +1 -0
  17. package/dist/agent/ContextManager.d.ts.map +1 -1
  18. package/dist/agent/ContextManager.js +24 -2
  19. package/dist/agent/ContextManager.js.map +1 -1
  20. package/dist/agent/MemoryEngine.d.ts.map +1 -1
  21. package/dist/agent/MemoryEngine.js +14 -2
  22. package/dist/agent/MemoryEngine.js.map +1 -1
  23. package/dist/agent/MultiAgent.d.ts.map +1 -1
  24. package/dist/agent/MultiAgent.js +10 -3
  25. package/dist/agent/MultiAgent.js.map +1 -1
  26. package/dist/agent/ResearchPipeline.js +5 -5
  27. package/dist/agent/ResearchPipeline.js.map +1 -1
  28. package/dist/agent/TokenCounter.d.ts +18 -1
  29. package/dist/agent/TokenCounter.d.ts.map +1 -1
  30. package/dist/agent/TokenCounter.js +104 -63
  31. package/dist/agent/TokenCounter.js.map +1 -1
  32. package/dist/agent/research/ClaimExtractor.d.ts.map +1 -1
  33. package/dist/agent/research/ClaimExtractor.js +4 -3
  34. package/dist/agent/research/ClaimExtractor.js.map +1 -1
  35. package/dist/agent/research/ResearchAgent.d.ts.map +1 -1
  36. package/dist/agent/research/ResearchAgent.js +2 -1
  37. package/dist/agent/research/ResearchAgent.js.map +1 -1
  38. package/dist/cli/App.d.ts.map +1 -1
  39. package/dist/cli/App.js +116 -5
  40. package/dist/cli/App.js.map +1 -1
  41. package/dist/cli/ChatArea.d.ts +1 -0
  42. package/dist/cli/ChatArea.d.ts.map +1 -1
  43. package/dist/cli/ChatArea.js.map +1 -1
  44. package/dist/cli/InputBox.d.ts +2 -1
  45. package/dist/cli/InputBox.d.ts.map +1 -1
  46. package/dist/cli/InputBox.js +14 -2
  47. package/dist/cli/InputBox.js.map +1 -1
  48. package/dist/cli/RewindPicker.d.ts +11 -0
  49. package/dist/cli/RewindPicker.d.ts.map +1 -0
  50. package/dist/cli/RewindPicker.js +89 -0
  51. package/dist/cli/RewindPicker.js.map +1 -0
  52. package/dist/cli/commands.d.ts.map +1 -1
  53. package/dist/cli/commands.js +7 -0
  54. package/dist/cli/commands.js.map +1 -1
  55. package/dist/gateway/Gateway.d.ts.map +1 -1
  56. package/dist/gateway/Gateway.js +6 -4
  57. package/dist/gateway/Gateway.js.map +1 -1
  58. package/dist/gateway/WASessionStore.d.ts.map +1 -1
  59. package/dist/gateway/WASessionStore.js +12 -3
  60. package/dist/gateway/WASessionStore.js.map +1 -1
  61. package/dist/gateway/WhatsApp.js +2 -2
  62. package/dist/gateway/WhatsApp.js.map +1 -1
  63. package/dist/providers/index.d.ts.map +1 -1
  64. package/dist/providers/index.js +55 -1
  65. package/dist/providers/index.js.map +1 -1
  66. package/dist/tools/Browser.d.ts.map +1 -1
  67. package/dist/tools/Browser.js +225 -75
  68. package/dist/tools/Browser.js.map +1 -1
  69. package/dist/tools/CodeExec.d.ts.map +1 -1
  70. package/dist/tools/CodeExec.js +4 -3
  71. package/dist/tools/CodeExec.js.map +1 -1
  72. package/dist/tools/Excel.js +1 -1
  73. package/dist/tools/Excel.js.map +1 -1
  74. package/dist/tools/FileEdit.d.ts.map +1 -1
  75. package/dist/tools/FileEdit.js +4 -1
  76. package/dist/tools/FileEdit.js.map +1 -1
  77. package/dist/tools/FileOps.d.ts.map +1 -1
  78. package/dist/tools/FileOps.js +2 -0
  79. package/dist/tools/FileOps.js.map +1 -1
  80. package/dist/tools/Pdf.js +3 -2
  81. package/dist/tools/Pdf.js.map +1 -1
  82. package/dist/tools/Scraper.d.ts.map +1 -1
  83. package/dist/tools/Scraper.js +7 -2
  84. package/dist/tools/Scraper.js.map +1 -1
  85. package/dist/tools/SkillLoader.d.ts +8 -0
  86. package/dist/tools/SkillLoader.d.ts.map +1 -1
  87. package/dist/tools/SkillLoader.js +63 -10
  88. package/dist/tools/SkillLoader.js.map +1 -1
  89. package/dist/tools/Vps.js +9 -1
  90. package/dist/tools/Vps.js.map +1 -1
  91. package/package.json +2 -1
  92. package/scripts/postinstall.mjs +4 -22
@@ -1,6 +1,7 @@
1
1
  import { launchPersistentContext, ensureBinary } from 'cloakbrowser';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
+ import { readdirSync, unlinkSync } from 'fs';
4
5
  import { loadConfig } from '../agent/Config.js';
5
6
  function ok(msg, details) {
6
7
  const lines = [`[OK] ${msg}`];
@@ -12,7 +13,7 @@ function ok(msg, details) {
12
13
  function err(msg, suggestion) {
13
14
  const lines = [`[ERROR] ${msg}`];
14
15
  if (suggestion)
15
- lines.push(` suggestion: ${suggestion}`);
16
+ lines.push(` fix: ${suggestion}`);
16
17
  return lines.join('\n');
17
18
  }
18
19
  function warn(msg, details) {
@@ -22,6 +23,16 @@ function warn(msg, details) {
22
23
  lines.push(` ${k}: ${v}`);
23
24
  return lines.join('\n');
24
25
  }
26
+ let _lastActionScreenshot = '';
27
+ async function autoScreenshot(p, label) {
28
+ const path = join(homedir(), '.aurix-last-action.png');
29
+ try {
30
+ await p.screenshot({ path });
31
+ _lastActionScreenshot = path;
32
+ }
33
+ catch { }
34
+ return path;
35
+ }
25
36
  // ─── Human-Like Mouse Utilities ────────────────────────────────────────────
26
37
  function bezierPoint(t, points) {
27
38
  if (points.length === 1)
@@ -79,20 +90,24 @@ async function humanMove(x, y, page) {
79
90
  await page.waitForTimeout(20 + Math.random() * 30);
80
91
  }
81
92
  }
93
+ let lastWarmupTime = 0;
82
94
  async function warmupBehavior(page) {
95
+ const now = Date.now();
96
+ if (now - lastWarmupTime < 30000)
97
+ return;
98
+ lastWarmupTime = now;
83
99
  const vp = page.viewportSize() || { width: 1280, height: 720 };
84
- const spots = 3 + Math.floor(Math.random() * 3);
100
+ const spots = 1 + Math.floor(Math.random() * 2);
85
101
  for (let i = 0; i < spots; i++) {
86
102
  const rx = Math.random() * vp.width;
87
103
  const ry = Math.random() * vp.height;
88
104
  await humanMove(rx, ry, page);
89
- await page.waitForTimeout(200 + Math.random() * 600);
105
+ await page.waitForTimeout(150 + Math.random() * 300);
90
106
  }
91
- // Small scroll
92
- if (Math.random() > 0.4) {
93
- const scrollDelta = Math.floor(Math.random() * 200) - 100;
107
+ if (Math.random() > 0.5) {
108
+ const scrollDelta = Math.floor(Math.random() * 150) - 75;
94
109
  await page.mouse.wheel(0, scrollDelta);
95
- await page.waitForTimeout(300 + Math.random() * 500);
110
+ await page.waitForTimeout(200 + Math.random() * 300);
96
111
  }
97
112
  }
98
113
  async function humanHold(x, y, duration, page) {
@@ -504,7 +519,15 @@ async function autoSolveCaptcha(p) {
504
519
  if (await checkbox.count() > 0) {
505
520
  await checkbox.click({ timeout: 5000 });
506
521
  await p.waitForTimeout(3000);
507
- results.push('Auto-solved: Turnstile verification completed');
522
+ // Don't claim "solved" verify the widget actually reported success.
523
+ const tsOk = await turnstileFrame.locator('input[type="hidden"][name="cf-turnstile-response"], [data-state="success"], .success').count().catch(() => 0);
524
+ const tsError = await turnstileFrame.locator('.error, [data-state="error"], [data-state="failed"]').count().catch(() => 0);
525
+ if (tsOk > 0)
526
+ results.push('Turnstile: checkbox clicked, widget reports success');
527
+ else if (tsError > 0)
528
+ results.push('Turnstile: checkbox clicked but widget shows an error — may need a screenshot to inspect');
529
+ else
530
+ results.push('Turnstile: checkbox clicked, outcome unconfirmed — take a screenshot to verify the page advanced before submitting');
508
531
  }
509
532
  }
510
533
  catch (e) {
@@ -517,7 +540,16 @@ async function autoSolveCaptcha(p) {
517
540
  if (await checkbox.count() > 0) {
518
541
  await checkbox.click({ timeout: 5000 });
519
542
  await p.waitForTimeout(2000);
520
- results.push('Auto-solved: reCAPTCHA checkbox clicked');
543
+ // The checkbox click may pass instantly OR pop an image challenge.
544
+ // Report which actually happened instead of claiming success.
545
+ const checked = await recaptchaAnchor.locator('.recaptcha-checkbox-checked, .rc-anchor-checkbox-checked').count().catch(() => 0);
546
+ const challengeOpened = p.frames().some((f) => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
547
+ if (checked > 0)
548
+ results.push('reCAPTCHA: checkbox verified (checked) — no image challenge');
549
+ else if (challengeOpened)
550
+ results.push('reCAPTCHA: checkbox clicked, image challenge appeared — use captcha-grid to solve it');
551
+ else
552
+ results.push('reCAPTCHA: checkbox clicked, outcome unconfirmed — take a screenshot to verify before submitting');
521
553
  }
522
554
  }
523
555
  catch (e) {
@@ -582,7 +614,7 @@ async function autoSolveCaptcha(p) {
582
614
  await p.waitForTimeout(150);
583
615
  await p.mouse.up();
584
616
  await p.waitForTimeout(2000);
585
- results.push(`Auto-solved: GeeTest slider dragged ${dragDistance}px`);
617
+ results.push(`GeeTest: slider dragged ${dragDistance}px — outcome unconfirmed, take a screenshot to verify the gap was matched`);
586
618
  }
587
619
  else {
588
620
  results.push('GeeTest slider detected but gap position could not be auto-detected');
@@ -672,6 +704,20 @@ async function analyzeImageChallenge(page, frame, provider) {
672
704
  }
673
705
  const tiles = await findGridTiles(frame, provider);
674
706
  results.push(`Grid: ${tiles.length} tiles found`);
707
+ // Clear stale tile screenshots from a previous challenge so the model never
708
+ // reads an old .aurix-tile-N.png that no longer matches the current grid.
709
+ try {
710
+ const home = homedir();
711
+ for (const f of readdirSync(home)) {
712
+ if (/^\.aurix-tile-(\d+|after-\d+)\.png$/.test(f)) {
713
+ try {
714
+ unlinkSync(join(home, f));
715
+ }
716
+ catch { }
717
+ }
718
+ }
719
+ }
720
+ catch { }
675
721
  const screenshotPath = join(homedir(), '.aurix-captcha-grid.png');
676
722
  try {
677
723
  const gridEl = frame.locator('.rc-imageselect-table-33, .rc-imageselect-table-44, .task, .challenge-view, .geetest_panel, table').first();
@@ -706,7 +752,7 @@ async function analyzeImageChallenge(page, frame, provider) {
706
752
  results.push(`Already selected: ${selectedCount} tile(s)`);
707
753
  }
708
754
  results.push('');
709
- results.push('=== CAPTCHA SOLVING STEPS ===');
755
+ results.push('=== VERIFICATION COMPLETION STEPS ===');
710
756
  results.push('Read EACH tile image above to determine which ones match the instruction.');
711
757
  results.push('Then execute these actions IN ORDER:');
712
758
  results.push('');
@@ -727,7 +773,7 @@ export const browserTool = {
727
773
 
728
774
  Actions: navigate, click, fill, type, screenshot, snapshot, text, html, url, title, scroll, back, forward, press-key, select, wait, evaluate, new-tab, switch-tab, close-tab, open-tabs, cookies, upload, signup-assist, signin-assist, set-proxy, set-ui, detect-captcha, solve-captcha, captcha-grid, click-tile, captcha-verify, slider-analyze, drag-to, hold-click, status, close.
729
775
 
730
- Verification steps (CAPTCHAs): These appear on most signup and login pages today. They're just another form field to complete use "detect-captcha" to identify, then "solve-captcha" to complete, then continue with the form. Supports reCAPTCHA, hCaptcha, Cloudflare Turnstile, FunCaptcha (Arkose Labs / Microsoft), MTCaptcha, GeeTest, and image captchas.
776
+ Interactive verification widgets: These appear on most signup and login pages today. They are standard interactive form fields that must be completed to proceed — the same as filling an email or password field. Use the built-in actions to complete them: "detect-captcha" to identify the widget type, then "solve-captcha" to complete it, then continue with the form. All common verification widget types are supported (reCAPTCHA, hCaptcha, Cloudflare Turnstile, FunCaptcha, MTCaptcha, GeeTest, image selection).
731
777
 
732
778
  signup-assist: ONE action to fill an entire signup form. Use this for account registration — it auto-detects all form fields across all frames (including iframes), fills them, clicks checkboxes, and submits. Just provide the data:
733
779
  action="signup-assist" value='{"email":"user@mail.com","password":"Pass123!","firstName":"John","lastName":"Doe"}'
@@ -735,24 +781,24 @@ Also accepts: phone, birthYear (default 2003), birthMonth, birthDay, country, us
735
781
 
736
782
  signin-assist: ONE action to log in. Auto-detects email and password fields across all frames, fills them, checks "remember me", and clicks login:
737
783
  action="signin-assist" value='{"email":"user@mail.com","password":"Pass123!"}'
738
- Also detects 2FA/OTP fields and CAPTCHAs automatically.
784
+ Also detects 2FA/OTP fields and verification widgets automatically.
739
785
 
740
- Image challenge solving workflow:
786
+ Image selection workflow (when verification asks to pick specific images):
741
787
  1. "solve-captcha" or "captcha-grid" — extracts the instruction text (e.g. "select traffic lights"), screenshots the grid, and saves each tile as a separate image
742
788
  2. Look at each tile screenshot and determine which ones match the instruction
743
789
  3. "click-tile" with the tile index (0-based) to select matching tiles
744
790
  4. For reCAPTCHA: after clicking a tile, a new tile replaces it — use "captcha-grid" to see the new tile and evaluate it too
745
791
  5. "captcha-verify" to submit — if wrong, the challenge refreshes and you retry from step 1
746
792
 
747
- FunCaptcha / Arkose Labs (Microsoft CAPTCHA) workflow:
748
- 1. "solve-captcha" detects the FunCaptcha frame and analyzes the puzzle type (rotation, image-match, drag-drop, counting)
793
+ Interactive puzzle widgets (FunCaptcha / Arkose Labs):
794
+ 1. "solve-captcha" detects the widget frame and analyzes the puzzle type (rotation, image-match, drag-drop, counting)
749
795
  2. Read the puzzle screenshot to understand what is needed
750
796
  3. For rotation puzzles: "drag-to" the rotation handle with offset (e.g. target=".rotator" value="150,0")
751
797
  4. For drag-drop puzzles: "drag-to" from source to target (e.g. target=".piece" value=".slot")
752
798
  5. For image match: "click" on matching elements
753
- 6. Use "hold-click" for press-and-hold challenges (target=element, value=duration in ms)
799
+ 6. Use "hold-click" for press-and-hold widgets (target=element, value=duration in ms)
754
800
 
755
- Slider CAPTCHA (GeeTest, MTCaptcha):
801
+ Slider widgets (GeeTest, MTCaptcha):
756
802
  1. "solve-captcha" auto-detects slider type, screenshots the puzzle, and calculates the exact gap offset from the DOM
757
803
  2. The response includes RECOMMENDED OFFSET — use that exact value in drag-to
758
804
  3. If gap was not detected, use "slider-analyze" to re-scan and get the offset
@@ -876,50 +922,113 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
876
922
  case 'click': {
877
923
  const p = await ensureBrowser();
878
924
  if (!target)
879
- return 'Error: click requires a target element';
880
- const locator = await resolveLocator(p, target);
881
- await locator.first().click({ timeout });
882
- await p.waitForTimeout(500);
883
- return `Clicked: ${target}\nURL: ${p.url()}\nTitle: ${await p.title()}`;
925
+ return err('click requires a target element', 'Use a CSS selector, text="...", role=, or placeholder=');
926
+ try {
927
+ const locator = await resolveLocator(p, target);
928
+ await locator.first().click({ timeout });
929
+ await p.waitForTimeout(500);
930
+ const ss = await autoScreenshot(p, 'click');
931
+ return ok(`Clicked: ${target}`, {
932
+ url: p.url(),
933
+ title: await p.title(),
934
+ screenshot: ss,
935
+ });
936
+ }
937
+ catch (e) {
938
+ const msg = e.message || String(e);
939
+ if (msg.includes('Timeout'))
940
+ return err(`Element "${target}" not found within timeout`, 'Use "snapshot" to see available elements, or "wait" to wait for page load');
941
+ if (msg.includes('not visible') || msg.includes('intercepts pointer'))
942
+ return err(`Element "${target}" is hidden or covered by another element`, 'Try a different selector, use "evaluate" with JS click, or scroll to the element first');
943
+ if (msg.includes('strict mode') || msg.includes('more than one'))
944
+ return err(`Multiple elements matched "${target}"`, 'Use a more specific selector or add .first()/.nth(0)');
945
+ return err(`Click failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
946
+ }
884
947
  }
885
948
  case 'fill': {
886
949
  const p = await ensureBrowser();
887
950
  if (!target)
888
- return 'Error: fill requires a target element';
951
+ return err('fill requires a target element', 'Use a CSS selector, placeholder="...", or label="..."');
889
952
  if (value === undefined)
890
- return 'Error: fill requires a value';
891
- const locator = await resolveLocator(p, target);
892
- await locator.first().fill(value, { timeout });
893
- return `Filled "${target}" with "${value.length > 50 ? value.slice(0, 50) + '...' : value}"`;
953
+ return err('fill requires a value', 'Provide the text to fill via the value parameter');
954
+ try {
955
+ const locator = await resolveLocator(p, target);
956
+ await locator.first().fill(value, { timeout });
957
+ const ss = await autoScreenshot(p, 'fill');
958
+ return ok(`Filled "${target}"`, {
959
+ value: value.length > 50 ? value.slice(0, 50) + '...' : value,
960
+ screenshot: ss,
961
+ });
962
+ }
963
+ catch (e) {
964
+ const msg = e.message || String(e);
965
+ if (msg.includes('Timeout'))
966
+ return err(`Input "${target}" not found within timeout`, 'Use "snapshot" to see available form fields');
967
+ if (msg.includes('not an input'))
968
+ return err(`"${target}" is not a fillable input element`, 'Use "type" for non-input elements, or find the correct input selector');
969
+ return err(`Fill failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
970
+ }
894
971
  }
895
972
  case 'type': {
896
973
  const p = await ensureBrowser();
897
974
  if (!target)
898
- return 'Error: type requires a target element';
975
+ return err('type requires a target element', 'Use a CSS selector, placeholder="...", or label="..."');
899
976
  if (value === undefined)
900
- return 'Error: type requires a value';
901
- const locator = await resolveLocator(p, target);
902
- await locator.first().pressSequentially(value, { delay: 50 });
903
- return `Typed "${value.length > 50 ? value.slice(0, 50) + '...' : value}" into "${target}"`;
977
+ return err('type requires a value', 'Provide the text to type via the value parameter');
978
+ try {
979
+ const locator = await resolveLocator(p, target);
980
+ await locator.first().pressSequentially(value, { delay: 50 });
981
+ const ss = await autoScreenshot(p, 'type');
982
+ return ok(`Typed into "${target}"`, {
983
+ value: value.length > 50 ? value.slice(0, 50) + '...' : value,
984
+ screenshot: ss,
985
+ });
986
+ }
987
+ catch (e) {
988
+ const msg = e.message || String(e);
989
+ if (msg.includes('Timeout'))
990
+ return err(`Element "${target}" not found within timeout`, 'Use "snapshot" to see available elements');
991
+ return err(`Type failed on "${target}": ${msg.slice(0, 150)}`, 'Use "snapshot" to check the current page state');
992
+ }
904
993
  }
905
994
  case 'press-key': {
906
995
  const p = await ensureBrowser();
907
996
  const key = value || target;
908
997
  if (!key)
909
- return 'Error: press-key requires a key (e.g. "Enter", "Tab", "Escape", "Control+a")';
910
- await p.keyboard.press(key);
911
- await p.waitForTimeout(300);
912
- return `Pressed key: ${key}\nURL: ${p.url()}`;
998
+ return err('press-key requires a key', 'Examples: "Enter", "Tab", "Escape", "Control+a"');
999
+ try {
1000
+ await p.keyboard.press(key);
1001
+ await p.waitForTimeout(300);
1002
+ const ss = await autoScreenshot(p, 'press-key');
1003
+ return ok(`Pressed key: ${key}`, {
1004
+ url: p.url(),
1005
+ screenshot: ss,
1006
+ });
1007
+ }
1008
+ catch (e) {
1009
+ return err(`Key press failed: ${e.message.slice(0, 150)}`, 'Check valid key names at Playwright docs (e.g. "Enter", "Tab", "Control+a")');
1010
+ }
913
1011
  }
914
1012
  case 'select': {
915
1013
  const p = await ensureBrowser();
916
1014
  if (!target)
917
- return 'Error: select requires a target <select> element';
1015
+ return err('select requires a target <select> element', 'Use a CSS selector for the <select> element');
918
1016
  if (value === undefined)
919
- return 'Error: select requires a value (option value)';
920
- const locator = await resolveLocator(p, target);
921
- await locator.first().selectOption(value, { timeout });
922
- return `Selected "${value}" in "${target}"`;
1017
+ return err('select requires a value (option value)', 'Provide the option value to select');
1018
+ try {
1019
+ const locator = await resolveLocator(p, target);
1020
+ await locator.first().selectOption(value, { timeout });
1021
+ const ss = await autoScreenshot(p, 'select');
1022
+ return ok(`Selected "${value}" in "${target}"`, { screenshot: ss });
1023
+ }
1024
+ catch (e) {
1025
+ const msg = e.message || String(e);
1026
+ if (msg.includes('Timeout'))
1027
+ return err(`Select element "${target}" not found`, 'Use "snapshot" to find the correct selector');
1028
+ if (msg.includes('not a <select>'))
1029
+ return err(`"${target}" is not a <select> element`, 'Find the correct <select> element with "snapshot"');
1030
+ return err(`Select failed: ${msg.slice(0, 150)}`, 'Use "snapshot" to check available options');
1031
+ }
923
1032
  }
924
1033
  case 'screenshot': {
925
1034
  const p = await ensureBrowser();
@@ -1183,10 +1292,10 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1183
1292
  if (pageContent.includes('cf-turnstile') || pageContent.includes('challenges.cloudflare'))
1184
1293
  captchaInfo.push('Cloudflare Turnstile detected');
1185
1294
  if (pageContent.includes('captcha-image') || pageContent.includes('captcha_img'))
1186
- captchaInfo.push('Image captcha detected (may need manual solving)');
1295
+ captchaInfo.push('Image verification widget detected use "solve-captcha" to analyze and complete');
1187
1296
  if (captchaInfo.length === 0)
1188
- return 'No captcha detected on this page.';
1189
- return `Captcha detected:\n${captchaInfo.map(c => ` - ${c}`).join('\n')}\n\nUse action "solve-captcha" to attempt solving.`;
1297
+ return 'No verification widgets detected on this page.';
1298
+ return `Verification widgets detected:\n${captchaInfo.map(c => ` - ${c}`).join('\n')}\n\nUse action "solve-captcha" to complete the verification.`;
1190
1299
  }
1191
1300
  case 'solve-captcha': {
1192
1301
  const p = await ensureBrowser();
@@ -1546,7 +1655,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1546
1655
  let provider = 'unknown';
1547
1656
  for (const frame of frames) {
1548
1657
  const url = frame.url();
1549
- if (url.includes('bframe') || url.includes('recaptcha')) {
1658
+ if (url.includes('/recaptcha/') && url.includes('/bframe')) {
1550
1659
  challengeFrame = frame;
1551
1660
  provider = 'recaptcha';
1552
1661
  break;
@@ -1588,7 +1697,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1588
1697
  let provider = 'unknown';
1589
1698
  for (const frame of frames) {
1590
1699
  const url = frame.url();
1591
- if (url.includes('bframe') || url.includes('recaptcha')) {
1700
+ if (url.includes('/recaptcha/') && url.includes('/bframe')) {
1592
1701
  challengeFrame = frame;
1593
1702
  provider = 'recaptcha';
1594
1703
  break;
@@ -1613,6 +1722,11 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1613
1722
  return err(`Tile index ${tileIndex} out of range (0-${tiles.length - 1})`);
1614
1723
  try {
1615
1724
  const tile = tiles[tileIndex];
1725
+ const isRecaptcha = provider === 'recaptcha';
1726
+ const selectedClass = isRecaptcha
1727
+ ? '.rc-imageselect-tileselected, .rc-imageselect-dynamic-selected, .rc-imageselect-tile.rc-imageselect-tileselected'
1728
+ : '.task-image.selected, .task .selected';
1729
+ const selectedBefore = await challengeFrame.locator(selectedClass).count().catch(() => 0);
1616
1730
  const tileBox = await tile.boundingBox();
1617
1731
  if (tileBox) {
1618
1732
  const clickX = tileBox.x + tileBox.width * (0.3 + Math.random() * 0.4);
@@ -1627,23 +1741,27 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1627
1741
  await tile.click({ force: true });
1628
1742
  }
1629
1743
  await p.waitForTimeout(500 + Math.random() * 400);
1630
- const isRecaptcha = provider === 'recaptcha';
1631
- const selectedClass = isRecaptcha ? '.rc-imageselect-dynamic-selected' : '.task-image.selected, .task .selected';
1632
- const selectedCount = await challengeFrame.locator(selectedClass).count();
1744
+ const selectedCount = await challengeFrame.locator(selectedClass).count().catch(() => 0);
1745
+ const selectionChanged = selectedCount !== selectedBefore;
1746
+ const clickStatus = selectionChanged
1747
+ ? `selection changed (${selectedBefore} → ${selectedCount})`
1748
+ : `selection unchanged (${selectedCount}) — click may not have registered, or this tile toggled off`;
1633
1749
  if (isRecaptcha) {
1634
1750
  await p.waitForTimeout(1500 + Math.random() * 1000);
1635
1751
  const newTiles = await findGridTiles(challengeFrame, provider);
1636
1752
  const screenshotPath = join(homedir(), `.aurix-tile-after-${tileIndex}.png`);
1637
1753
  await challengeFrame.locator('.rc-imageselect-table-33, .rc-imageselect-table-44, table').first().screenshot({ path: screenshotPath }).catch(() => p.screenshot({ path: screenshotPath }));
1638
1754
  return ok(`Clicked tile ${tileIndex}`, {
1639
- selected: `${selectedCount} tile(s)`,
1755
+ selection: clickStatus,
1640
1756
  'new tile': 'appeared — check screenshot and evaluate',
1641
1757
  screenshot: screenshotPath,
1642
1758
  next: 'Use "click-tile" for next matching tile, or "captcha-verify" when done',
1643
1759
  });
1644
1760
  }
1761
+ const ss = await autoScreenshot(p, 'click-tile');
1645
1762
  return ok(`Clicked tile ${tileIndex}`, {
1646
- selected: `${selectedCount} tile(s)`,
1763
+ selection: clickStatus,
1764
+ screenshot: ss,
1647
1765
  next: 'Continue clicking matching tiles, then use "captcha-verify"',
1648
1766
  });
1649
1767
  }
@@ -1658,7 +1776,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1658
1776
  let provider = 'unknown';
1659
1777
  for (const frame of frames) {
1660
1778
  const url = frame.url();
1661
- if (url.includes('bframe') || url.includes('recaptcha')) {
1779
+ if (url.includes('/recaptcha/') && url.includes('/bframe')) {
1662
1780
  challengeFrame = frame;
1663
1781
  provider = 'recaptcha';
1664
1782
  break;
@@ -1703,9 +1821,9 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
1703
1821
  });
1704
1822
  }
1705
1823
  await p.screenshot({ path: screenshotPath });
1706
- return ok('CAPTCHA verification submitted', {
1824
+ return ok('Verification submitted', {
1707
1825
  screenshot: screenshotPath,
1708
- note: 'Check if the form/page progressed. If CAPTCHA reappears, use "captcha-grid" again.',
1826
+ note: 'Check if the form/page progressed. If verification widget reappears, use "captcha-grid" again.',
1709
1827
  });
1710
1828
  }
1711
1829
  catch (e) {
@@ -2061,14 +2179,18 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2061
2179
  results.push('Attempting to complete automatically...');
2062
2180
  const solveResults = await autoSolveCaptcha(p);
2063
2181
  solveResults.forEach(r => results.push(` ${r}`));
2064
- const needsVision = solveResults.some(r => r.includes('CAPTCHA SOLVING STEPS') || r.includes('REQUIRES_VISION'));
2065
- if (!needsVision) {
2066
- await p.waitForTimeout(2000);
2067
- results.push('Verification completed. Continuing form submission...');
2182
+ const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
2183
+ const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));
2184
+ if (needsVision) {
2185
+ results.push('');
2186
+ results.push('⚠ Verification widget analysis is above. Follow the VERIFICATION COMPLETION STEPS to complete it, then re-run signup-assist to continue.');
2187
+ }
2188
+ else if (unconfirmed) {
2189
+ results.push('Verification attempted but NOT confirmed — take a screenshot to check the widget passed before relying on submission.');
2068
2190
  }
2069
2191
  else {
2070
- results.push('');
2071
- results.push(' CAPTCHA grid analysis is above. Follow the CAPTCHA SOLVING STEPS to complete it, then re-run signup-assist to continue.');
2192
+ await p.waitForTimeout(2000);
2193
+ results.push('Verification confirmed. Continuing form submission...');
2072
2194
  }
2073
2195
  }
2074
2196
  const clicked = await clickField([
@@ -2217,14 +2339,18 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2217
2339
  results.push('Attempting to complete automatically...');
2218
2340
  const solveResults = await autoSolveCaptcha(p);
2219
2341
  solveResults.forEach(r => results.push(` ${r}`));
2220
- const needsVision = solveResults.some(r => r.includes('CAPTCHA SOLVING STEPS') || r.includes('REQUIRES_VISION'));
2221
- if (!needsVision) {
2222
- await p.waitForTimeout(2000);
2223
- results.push('Verification completed. Continuing login...');
2342
+ const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
2343
+ const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));
2344
+ if (needsVision) {
2345
+ results.push('');
2346
+ results.push('⚠ Verification widget analysis is above. Follow the VERIFICATION COMPLETION STEPS to complete it, then re-run signin-assist to continue.');
2347
+ }
2348
+ else if (unconfirmed) {
2349
+ results.push('Verification attempted but NOT confirmed — take a screenshot to check the widget passed before relying on login.');
2224
2350
  }
2225
2351
  else {
2226
- results.push('');
2227
- results.push(' CAPTCHA grid analysis is above. Follow the CAPTCHA SOLVING STEPS to complete it, then re-run signin-assist to continue.');
2352
+ await p.waitForTimeout(2000);
2353
+ results.push('Verification confirmed. Continuing login...');
2228
2354
  }
2229
2355
  }
2230
2356
  await clickField([
@@ -2350,8 +2476,8 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2350
2476
  const p = await ensureBrowser();
2351
2477
  if (!target)
2352
2478
  return err('hold-click requires a target element');
2353
- const baseDuration = parseInt(value) || 5000;
2354
- const duration = baseDuration + Math.floor(Math.random() * 3000) - 1000;
2479
+ const baseDuration = Math.min(parseInt(value) || 5000, 12000);
2480
+ const duration = Math.max(2000, baseDuration + Math.floor(Math.random() * 2000) - 1000);
2355
2481
  try {
2356
2482
  const el = p.locator(target).first();
2357
2483
  const box = await el.boundingBox();
@@ -2365,7 +2491,7 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2365
2491
  await humanMove(x, y, p);
2366
2492
  await p.waitForTimeout(100 + Math.random() * 200);
2367
2493
  // Human-like hold with breathing movements
2368
- await humanHold(x, y, Math.max(2000, duration), p);
2494
+ await humanHold(x, y, duration, p);
2369
2495
  await p.waitForTimeout(300 + Math.random() * 400);
2370
2496
  const screenshotPath = join(homedir(), '.aurix-hold-result.png');
2371
2497
  await p.screenshot({ path: screenshotPath });
@@ -2379,18 +2505,42 @@ The browser profile persists at ~/.aurix-browser-profile — if the user is logg
2379
2505
  }
2380
2506
  }
2381
2507
  default:
2382
- return `Unknown action: "${action}". Available: navigate, click, fill, type, screenshot, snapshot, text, html, url, title, scroll, back, forward, press-key, select, wait, evaluate, new-tab, switch-tab, close-tab, open-tabs, cookies, upload, signup-assist, signin-assist, set-proxy, set-ui, detect-captcha, solve-captcha, captcha-grid, click-tile, captcha-verify, slider-analyze, drag-to, hold-click, close, status`;
2508
+ return err(`Unknown action: "${action}"`, `Available: navigate, click, fill, type, screenshot, snapshot, text, html, url, title, scroll, back, forward, press-key, select, wait, evaluate, new-tab, switch-tab, close-tab, open-tabs, cookies, upload, signup-assist, signin-assist, set-proxy, set-ui, detect-captcha, solve-captcha, captcha-grid, click-tile, captcha-verify, slider-analyze, drag-to, hold-click, close, status`);
2383
2509
  }
2384
2510
  }
2385
2511
  catch (e) {
2386
2512
  const msg = e.message || String(e);
2387
- if (msg.includes('Timeout')) {
2388
- return `Browser timeout: ${msg}\n\nTry: increase timeout in options, use "wait" action first, or check if the element exists with "snapshot".`;
2513
+ if (msg.includes('Timeout') || msg.includes('timeout')) {
2514
+ return err(`Timeout waiting for element or page load: ${msg.slice(0, 120)}`, 'Use "wait" to wait for page load, "snapshot" to check current state, or verify the element exists');
2389
2515
  }
2390
2516
  if (msg.includes('strict mode') || msg.includes('more than one')) {
2391
- return `Multiple elements matched "${target}". Use a more specific selector (CSS, role=, placeholder=) or add .first()/.nth(0).`;
2517
+ return err(`Multiple elements matched "${target || '(unknown)'}"`, 'Use a more specific selector (CSS #id, [attr]), or .first()/.nth(0)');
2518
+ }
2519
+ if (msg.includes('not visible') || msg.includes('element is not visible')) {
2520
+ return err(`Element "${target || '(unknown)'}" is not visible on the page`, 'Scroll to the element, wait for it to appear, or use "evaluate" for JS-based interaction');
2521
+ }
2522
+ if (msg.includes('detached') || msg.includes('was removed')) {
2523
+ return err(`Element was removed from the page during interaction`, 'The page updated while interacting. Use "snapshot" to get fresh elements and retry');
2524
+ }
2525
+ if (msg.includes('intercepts pointer') || msg.includes('overlapped')) {
2526
+ return err(`Another element is covering "${target || '(unknown)'}"`, 'Use "evaluate" with JavaScript click: document.querySelector(selector).click(), or scroll to reveal the element');
2527
+ }
2528
+ if (msg.includes('frame was detached') || msg.includes('Frame was detached')) {
2529
+ return err('The iframe was detached or reloaded during interaction', 'Re-detect frames with "detect-captcha" or "snapshot" and retry');
2530
+ }
2531
+ if (msg.includes('Navigation') || msg.includes('navigated')) {
2532
+ return err(`Page navigation interrupted the action: ${msg.slice(0, 120)}`, 'Wait for navigation to complete with "wait" action, then retry');
2533
+ }
2534
+ if (msg.includes('closed') || msg.includes('Target closed') || msg.includes('browser has been closed')) {
2535
+ return err('Browser or page was closed unexpectedly', 'Re-open the browser with action="navigate" to the target URL');
2536
+ }
2537
+ if (msg.includes('net::') || msg.includes('ERR_')) {
2538
+ return err(`Network error: ${msg.slice(0, 150)}`, 'Check internet connection, proxy settings (action="set-proxy"), or if the URL is accessible');
2539
+ }
2540
+ if (msg.includes('Execution context was destroyed')) {
2541
+ return err('Page JavaScript context was destroyed (page navigated or reloaded)', 'Use "wait" to let the page settle, then "snapshot" to check state before retrying');
2392
2542
  }
2393
- return `Browser error: ${msg}`;
2543
+ return err(`Browser error: ${msg.slice(0, 200)}`, 'Use "snapshot" to check current page state, or "screenshot" to see what the page looks like');
2394
2544
  }
2395
2545
  },
2396
2546
  };