@dyyz1993/agent-browser 0.11.5 → 0.13.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/bin/agent-browser-linux-x64 +0 -0
- package/dist/__tests__/utils/parseCli.d.ts.map +1 -1
- package/dist/__tests__/utils/parseCli.js +97 -2
- package/dist/__tests__/utils/parseCli.js.map +1 -1
- package/dist/actions.d.ts +2 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +254 -105
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +41 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +125 -30
- package/dist/browser.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +129 -5
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/connection.d.ts.map +1 -1
- package/dist/cli/connection.js +12 -29
- package/dist/cli/connection.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +35 -25
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +27 -0
- package/dist/cli/output.js.map +1 -1
- package/dist/cli.js +117 -3
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +18 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +71 -33
- package/dist/daemon.js.map +1 -1
- package/dist/message-bridge.d.ts.map +1 -1
- package/dist/message-bridge.js +4 -1
- package/dist/message-bridge.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -1
- package/dist/rc-config.d.ts +42 -0
- package/dist/rc-config.d.ts.map +1 -0
- package/dist/rc-config.js +171 -0
- package/dist/rc-config.js.map +1 -0
- package/dist/recorder/inject.js +30 -24
- package/dist/types.d.ts +16 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
- package/scripts/generate-skill.cjs +303 -0
- package/skills/agent-browser/SKILL.md +135 -370
package/dist/actions.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { getAppDir, getSession, getInstanceId } from './daemon.js';
|
|
4
4
|
import { performDiff } from './diff.js';
|
|
5
5
|
import { MessageBridge } from './message-bridge.js';
|
|
6
|
+
import { getViewerUrl, getViewerWsUrl, getViewerPort, getMessageBridgeUrl, getExecutablePath, getEffectiveValue, loadConfig, } from './rc-config.js';
|
|
6
7
|
import { detectMainContent, generateContentTips } from './content-detection.js';
|
|
7
8
|
import { humanClick, humanType, humanMoveTo, humanWander, getHumanConfigFromEnv, } from './human-mouse.js';
|
|
8
9
|
import { successResponse, errorResponse } from './protocol.js';
|
|
@@ -52,37 +53,44 @@ async function assertElementExists(locator, selector, isRef) {
|
|
|
52
53
|
*/
|
|
53
54
|
export function toAIFriendlyError(error, selector) {
|
|
54
55
|
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
-
// Handle strict mode violation (multiple elements match)
|
|
56
56
|
if (message.includes('strict mode violation')) {
|
|
57
|
-
// Extract count if available
|
|
58
57
|
const countMatch = message.match(/resolved to (\d+) elements/);
|
|
59
58
|
const count = countMatch ? countMatch[1] : 'multiple';
|
|
60
59
|
return new Error(`Selector "${selector}" matched ${count} elements. ` +
|
|
61
|
-
`Run 'snapshot' to get updated refs, or use a more specific CSS selector
|
|
60
|
+
`Run 'snapshot' to get updated refs, or use a more specific CSS selector. ` +
|
|
61
|
+
`Tip: Use 'find nth <index> ${selector} --click' to target a specific match.`);
|
|
62
62
|
}
|
|
63
|
-
// Handle element not interactable (must be checked BEFORE timeout case)
|
|
64
|
-
// This includes cases where an overlay/modal blocks the element
|
|
65
63
|
if (message.includes('intercepts pointer events')) {
|
|
66
64
|
return new Error(`Element "${selector}" is blocked by another element (likely a modal or overlay). ` +
|
|
67
|
-
`Try dismissing any modals/cookie banners first
|
|
65
|
+
`Try dismissing any modals/cookie banners first. ` +
|
|
66
|
+
`Tip: Run 'snapshot -i' to see all visible elements and identify what's blocking.`);
|
|
68
67
|
}
|
|
69
|
-
// Handle element not visible
|
|
70
68
|
if (message.includes('not visible') && !message.includes('Timeout')) {
|
|
71
69
|
return new Error(`Element "${selector}" is not visible. ` +
|
|
72
|
-
`Try
|
|
70
|
+
`Try 'scrollintoview ${selector}' or check if it's hidden. ` +
|
|
71
|
+
`Tip: Run 'is visible ${selector}' to confirm visibility state.`);
|
|
73
72
|
}
|
|
74
|
-
// Handle general timeout (element exists but action couldn't complete)
|
|
75
73
|
if (message.includes('Timeout') && message.includes('exceeded')) {
|
|
76
74
|
return new Error(`Action on "${selector}" timed out. The element may be blocked, still loading, or not interactable. ` +
|
|
77
|
-
`Run 'snapshot' to check the current page state
|
|
75
|
+
`Run 'snapshot' to check the current page state. ` +
|
|
76
|
+
`Tip: If the page is still loading, try 'wait --load networkidle' first.`);
|
|
78
77
|
}
|
|
79
|
-
// Handle element not found (timeout waiting for element)
|
|
80
78
|
if (message.includes('waiting for') &&
|
|
81
79
|
(message.includes('to be visible') || message.includes('Timeout'))) {
|
|
82
80
|
return new Error(`Element "${selector}" not found or not visible. ` +
|
|
83
|
-
`Run 'snapshot' to see current page elements
|
|
81
|
+
`Run 'snapshot -i' to see current page elements and their refs. ` +
|
|
82
|
+
`Tip: If using @ref, the page may have changed. Re-run 'snapshot -i' to get fresh refs.`);
|
|
83
|
+
}
|
|
84
|
+
if (message.includes('Execution context was destroyed') || message.includes('Target closed')) {
|
|
85
|
+
return new Error(`Browser context was lost (page navigated or closed). ` +
|
|
86
|
+
`Re-open the page with 'open <url>' and start fresh. ` +
|
|
87
|
+
`Tip: This usually happens after a form submission triggers navigation.`);
|
|
88
|
+
}
|
|
89
|
+
if (message.includes('querySelector') || message.includes('is not a valid selector')) {
|
|
90
|
+
return new Error(`Invalid selector "${selector}". ` +
|
|
91
|
+
`CSS selectors like '#id', '.class', or 'tag' are supported. ` +
|
|
92
|
+
`Tip: Use 'snapshot -i' to get @ref selectors (e.g., @e1) that are always valid.`);
|
|
84
93
|
}
|
|
85
|
-
// Return original error for unknown cases
|
|
86
94
|
return error instanceof Error ? error : new Error(message);
|
|
87
95
|
}
|
|
88
96
|
/**
|
|
@@ -146,6 +154,8 @@ export async function executeCommand(command, browser) {
|
|
|
146
154
|
return await handleTabNew(cmd, browser);
|
|
147
155
|
case 'tab_list':
|
|
148
156
|
return await handleTabList(cmd, browser);
|
|
157
|
+
case 'frames':
|
|
158
|
+
return await handleFrames(cmd, browser);
|
|
149
159
|
case 'tab_switch':
|
|
150
160
|
return await handleTabSwitch(cmd, browser);
|
|
151
161
|
case 'tab_close':
|
|
@@ -174,6 +184,8 @@ export async function executeCommand(command, browser) {
|
|
|
174
184
|
return await handleUnroute(cmd, browser);
|
|
175
185
|
case 'requests':
|
|
176
186
|
return await handleRequests(cmd, browser);
|
|
187
|
+
case 'websockets':
|
|
188
|
+
return await handleWebSockets(cmd, browser);
|
|
177
189
|
case 'download':
|
|
178
190
|
return await handleDownload(cmd, browser);
|
|
179
191
|
case 'geolocation':
|
|
@@ -352,6 +364,8 @@ export async function executeCommand(command, browser) {
|
|
|
352
364
|
return await handleAsk(cmd, browser);
|
|
353
365
|
case 'config':
|
|
354
366
|
return handleConfig(cmd);
|
|
367
|
+
case 'history':
|
|
368
|
+
return await handleHistory(cmd, browser);
|
|
355
369
|
default: {
|
|
356
370
|
const unknownCommand = cmd;
|
|
357
371
|
return errorResponse(unknownCommand.id, `Unknown action: ${unknownCommand.action}`);
|
|
@@ -369,7 +383,7 @@ async function handleLaunch(command, browser) {
|
|
|
369
383
|
return successResponse(command.id, {
|
|
370
384
|
launched: true,
|
|
371
385
|
instanceId,
|
|
372
|
-
viewerUrl:
|
|
386
|
+
viewerUrl: getViewerUrl(instanceId),
|
|
373
387
|
});
|
|
374
388
|
}
|
|
375
389
|
async function handleNavigate(command, browser) {
|
|
@@ -418,6 +432,7 @@ async function handleClick(command, browser) {
|
|
|
418
432
|
result.diff = diffResult.output;
|
|
419
433
|
result.diffScope = diffResult.diff.scope;
|
|
420
434
|
}
|
|
435
|
+
browser.recordCommand('click', command.selector, undefined, true);
|
|
421
436
|
return successResponse(command.id, result);
|
|
422
437
|
}
|
|
423
438
|
const diffResult = await performDiff(locator, command.diffScope, async () => {
|
|
@@ -438,6 +453,7 @@ async function handleClick(command, browser) {
|
|
|
438
453
|
result.diff = diffResult.output;
|
|
439
454
|
result.diffScope = diffResult.diff.scope;
|
|
440
455
|
}
|
|
456
|
+
browser.recordCommand('click', command.selector, undefined, true);
|
|
441
457
|
return successResponse(command.id, result);
|
|
442
458
|
}
|
|
443
459
|
async function handleType(command, browser) {
|
|
@@ -468,6 +484,7 @@ async function handleType(command, browser) {
|
|
|
468
484
|
result.diff = diffResult.output;
|
|
469
485
|
result.diffScope = diffResult.diff.scope;
|
|
470
486
|
}
|
|
487
|
+
browser.recordCommand('type', command.selector, command.text, true);
|
|
471
488
|
return successResponse(command.id, result);
|
|
472
489
|
}
|
|
473
490
|
const diffResult = await performDiff(locator, command.diffScope, async () => {
|
|
@@ -491,6 +508,7 @@ async function handleType(command, browser) {
|
|
|
491
508
|
result.diff = diffResult.output;
|
|
492
509
|
result.diffScope = diffResult.diff.scope;
|
|
493
510
|
}
|
|
511
|
+
browser.recordCommand('type', command.selector, command.text, true);
|
|
494
512
|
return successResponse(command.id, result);
|
|
495
513
|
}
|
|
496
514
|
async function handlePress(command, browser) {
|
|
@@ -604,7 +622,6 @@ async function handleScreenshot(command, browser) {
|
|
|
604
622
|
async function handleSnapshot(command, browser) {
|
|
605
623
|
let effectiveSelector = command.selector;
|
|
606
624
|
let detectionResult = null;
|
|
607
|
-
// 如果未指定 selector,自动检测主体区域
|
|
608
625
|
if (!command.selector) {
|
|
609
626
|
const page = browser.getPage();
|
|
610
627
|
detectionResult = await detectMainContent(page);
|
|
@@ -619,6 +636,8 @@ async function handleSnapshot(command, browser) {
|
|
|
619
636
|
framePath: command.inFrame,
|
|
620
637
|
path: command.path,
|
|
621
638
|
attrs: command.attrs,
|
|
639
|
+
selectors: command.selectors,
|
|
640
|
+
all: command.all,
|
|
622
641
|
});
|
|
623
642
|
const simpleRefs = {};
|
|
624
643
|
const refs = snapshot.refs || {};
|
|
@@ -662,9 +681,12 @@ async function handleEvaluate(command, browser) {
|
|
|
662
681
|
const page = browser.getPage();
|
|
663
682
|
result = await page.evaluate(script);
|
|
664
683
|
}
|
|
684
|
+
browser.recordCommand('eval', 'javascript', script.length > 200 ? script.substring(0, 200) + '...' : script, true);
|
|
665
685
|
return successResponse(command.id, { result });
|
|
666
686
|
}
|
|
667
687
|
catch (error) {
|
|
688
|
+
const script = command.script || command.file || '';
|
|
689
|
+
browser.recordCommand('eval', 'javascript', script.length > 200 ? script.substring(0, 200) + '...' : script, false);
|
|
668
690
|
console.error('Error in handleEvaluate:', error);
|
|
669
691
|
return errorResponse(command.id, error instanceof Error ? error.message : String(error));
|
|
670
692
|
}
|
|
@@ -766,6 +788,7 @@ async function handleSelect(command, browser) {
|
|
|
766
788
|
result.diff = diffResult.output;
|
|
767
789
|
result.diffScope = diffResult.diff.scope;
|
|
768
790
|
}
|
|
791
|
+
browser.recordCommand('select', command.selector, values.join(','), true);
|
|
769
792
|
return successResponse(command.id, result);
|
|
770
793
|
}
|
|
771
794
|
async function handleHover(command, browser) {
|
|
@@ -842,6 +865,16 @@ async function handleTabList(command, browser) {
|
|
|
842
865
|
active: browser.getActiveIndex(),
|
|
843
866
|
});
|
|
844
867
|
}
|
|
868
|
+
async function handleFrames(command, browser) {
|
|
869
|
+
const frames = browser.listFrames();
|
|
870
|
+
if (frames.length === 0) {
|
|
871
|
+
return successResponse(command.id, {
|
|
872
|
+
frames: [],
|
|
873
|
+
tip: 'No iframes found on this page.',
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
return successResponse(command.id, { frames });
|
|
877
|
+
}
|
|
845
878
|
async function handleTabSwitch(command, browser) {
|
|
846
879
|
const result = await browser.switchTo(command.index);
|
|
847
880
|
const page = browser.getPage();
|
|
@@ -890,22 +923,23 @@ async function handleFill(command, browser) {
|
|
|
890
923
|
result.diff = diffResult.output;
|
|
891
924
|
result.diffScope = diffResult.diff.scope;
|
|
892
925
|
}
|
|
926
|
+
browser.recordCommand('fill', command.selector, command.value, true);
|
|
893
927
|
return successResponse(command.id, result);
|
|
894
928
|
}
|
|
895
929
|
const diffResult = await performDiff(locator, command.diffScope, async () => {
|
|
896
930
|
try {
|
|
897
931
|
await locator.fill(command.value);
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
908
|
-
}
|
|
932
|
+
if (!isRef) {
|
|
933
|
+
const page = browser.getPage();
|
|
934
|
+
if (page) {
|
|
935
|
+
await page.evaluate((selector) => {
|
|
936
|
+
const el = document.querySelector(selector);
|
|
937
|
+
if (el) {
|
|
938
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
939
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
940
|
+
}
|
|
941
|
+
}, command.selector);
|
|
942
|
+
}
|
|
909
943
|
}
|
|
910
944
|
}
|
|
911
945
|
catch (error) {
|
|
@@ -917,6 +951,7 @@ async function handleFill(command, browser) {
|
|
|
917
951
|
result.diff = diffResult.output;
|
|
918
952
|
result.diffScope = diffResult.diff.scope;
|
|
919
953
|
}
|
|
954
|
+
browser.recordCommand('fill', command.selector, command.value, true);
|
|
920
955
|
return successResponse(command.id, result);
|
|
921
956
|
}
|
|
922
957
|
async function handleCheck(command, browser) {
|
|
@@ -940,6 +975,7 @@ async function handleCheck(command, browser) {
|
|
|
940
975
|
result.diff = diffResult.output;
|
|
941
976
|
result.diffScope = diffResult.diff.scope;
|
|
942
977
|
}
|
|
978
|
+
browser.recordCommand('check', command.selector, undefined, true);
|
|
943
979
|
return successResponse(command.id, result);
|
|
944
980
|
}
|
|
945
981
|
async function handleUncheck(command, browser) {
|
|
@@ -963,6 +999,7 @@ async function handleUncheck(command, browser) {
|
|
|
963
999
|
result.diff = diffResult.output;
|
|
964
1000
|
result.diffScope = diffResult.diff.scope;
|
|
965
1001
|
}
|
|
1002
|
+
browser.recordCommand('uncheck', command.selector, undefined, true);
|
|
966
1003
|
return successResponse(command.id, result);
|
|
967
1004
|
}
|
|
968
1005
|
async function handleUpload(command, browser) {
|
|
@@ -1061,32 +1098,85 @@ async function handleGetByRole(command, browser) {
|
|
|
1061
1098
|
name: command.name,
|
|
1062
1099
|
exact: command.exact,
|
|
1063
1100
|
});
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1101
|
+
try {
|
|
1102
|
+
switch (command.subaction) {
|
|
1103
|
+
case 'click':
|
|
1104
|
+
await locator.click();
|
|
1105
|
+
return successResponse(command.id, { clicked: true });
|
|
1106
|
+
case 'fill':
|
|
1107
|
+
await locator.fill(command.value ?? '');
|
|
1108
|
+
return successResponse(command.id, { filled: true });
|
|
1109
|
+
case 'check':
|
|
1110
|
+
await locator.check();
|
|
1111
|
+
return successResponse(command.id, { checked: true });
|
|
1112
|
+
case 'hover':
|
|
1113
|
+
await locator.hover();
|
|
1114
|
+
return successResponse(command.id, { hovered: true });
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1119
|
+
if (msg.includes('strict mode violation')) {
|
|
1120
|
+
const countMatch = msg.match(/resolved to (\d+) elements/);
|
|
1121
|
+
const count = countMatch ? countMatch[1] : 'multiple';
|
|
1122
|
+
const first = locator.first();
|
|
1123
|
+
const warning = `Matched ${count} elements, used first match. Use 'find nth <index> role "${command.role}" --click' for a specific match.`;
|
|
1124
|
+
switch (command.subaction) {
|
|
1125
|
+
case 'click':
|
|
1126
|
+
await first.click();
|
|
1127
|
+
return successResponse(command.id, { clicked: true, warning });
|
|
1128
|
+
case 'fill':
|
|
1129
|
+
await first.fill(command.value ?? '');
|
|
1130
|
+
return successResponse(command.id, { filled: true, warning });
|
|
1131
|
+
case 'check':
|
|
1132
|
+
await first.check();
|
|
1133
|
+
return successResponse(command.id, { checked: true, warning });
|
|
1134
|
+
case 'hover':
|
|
1135
|
+
await first.hover();
|
|
1136
|
+
return successResponse(command.id, { hovered: true, warning });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
throw error;
|
|
1077
1140
|
}
|
|
1141
|
+
return successResponse(command.id, {});
|
|
1078
1142
|
}
|
|
1079
1143
|
async function handleGetByText(command, browser) {
|
|
1080
1144
|
const frame = browser.getFrame(command.inFrame);
|
|
1081
1145
|
const locator = frame.getByText(command.text, { exact: command.exact });
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1146
|
+
try {
|
|
1147
|
+
switch (command.subaction) {
|
|
1148
|
+
case 'click':
|
|
1149
|
+
await locator.click();
|
|
1150
|
+
return successResponse(command.id, { clicked: true });
|
|
1151
|
+
case 'hover':
|
|
1152
|
+
await locator.hover();
|
|
1153
|
+
return successResponse(command.id, { hovered: true });
|
|
1154
|
+
}
|
|
1089
1155
|
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1158
|
+
if (msg.includes('strict mode violation')) {
|
|
1159
|
+
const countMatch = msg.match(/resolved to (\d+) elements/);
|
|
1160
|
+
const count = countMatch ? countMatch[1] : 'multiple';
|
|
1161
|
+
const first = locator.first();
|
|
1162
|
+
switch (command.subaction) {
|
|
1163
|
+
case 'click':
|
|
1164
|
+
await first.click();
|
|
1165
|
+
return successResponse(command.id, {
|
|
1166
|
+
clicked: true,
|
|
1167
|
+
warning: `Matched ${count} elements, used first match. Use 'find nth <index> text "${command.text}" --click' for a specific match.`,
|
|
1168
|
+
});
|
|
1169
|
+
case 'hover':
|
|
1170
|
+
await first.hover();
|
|
1171
|
+
return successResponse(command.id, {
|
|
1172
|
+
hovered: true,
|
|
1173
|
+
warning: `Matched ${count} elements, used first match. Use 'find nth <index> text "${command.text}" --hover' for a specific match.`,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
throw error;
|
|
1178
|
+
}
|
|
1179
|
+
return successResponse(command.id, {});
|
|
1090
1180
|
}
|
|
1091
1181
|
async function handleGetByLabel(command, browser) {
|
|
1092
1182
|
const frame = browser.getFrame(command.inFrame);
|
|
@@ -1207,6 +1297,7 @@ async function handleRequests(command, browser) {
|
|
|
1207
1297
|
return successResponse(command.id, { cleared: true });
|
|
1208
1298
|
}
|
|
1209
1299
|
// Start tracking if not already (with response capture if requested)
|
|
1300
|
+
const wasTracking = browser.trackingEnabled;
|
|
1210
1301
|
browser.startRequestTracking(command.captureResponse);
|
|
1211
1302
|
// If output directory is specified, save to directory
|
|
1212
1303
|
if (command.output) {
|
|
@@ -1219,7 +1310,25 @@ async function handleRequests(command, browser) {
|
|
|
1219
1310
|
});
|
|
1220
1311
|
}
|
|
1221
1312
|
const requests = browser.getRequests(command.filter, command.type);
|
|
1222
|
-
|
|
1313
|
+
const result = { requests };
|
|
1314
|
+
if (requests.length === 0 && !wasTracking) {
|
|
1315
|
+
result.hint = 'Request tracking just activated. Reload or navigate to capture requests.';
|
|
1316
|
+
}
|
|
1317
|
+
return successResponse(command.id, result);
|
|
1318
|
+
}
|
|
1319
|
+
async function handleWebSockets(command, browser) {
|
|
1320
|
+
if (command.clear) {
|
|
1321
|
+
browser.clearWebSockets();
|
|
1322
|
+
return successResponse(command.id, { cleared: true });
|
|
1323
|
+
}
|
|
1324
|
+
const wasTracking = browser.wsTrackingEnabled;
|
|
1325
|
+
browser.startWebSocketTracking();
|
|
1326
|
+
const sockets = browser.getWebSockets(command.filter);
|
|
1327
|
+
const result = { websockets: sockets };
|
|
1328
|
+
if (sockets.length === 0 && !wasTracking) {
|
|
1329
|
+
result.hint = 'WebSocket tracking just activated. Reload or navigate to capture connections.';
|
|
1330
|
+
}
|
|
1331
|
+
return successResponse(command.id, result);
|
|
1223
1332
|
}
|
|
1224
1333
|
async function handleDownload(command, browser) {
|
|
1225
1334
|
const page = browser.getPage();
|
|
@@ -1659,7 +1768,7 @@ async function handleNth(command, browser) {
|
|
|
1659
1768
|
const refLocator = browser.getLocatorFromRef(command.selector, command.inFrame);
|
|
1660
1769
|
let locator;
|
|
1661
1770
|
if (refLocator) {
|
|
1662
|
-
locator = refLocator;
|
|
1771
|
+
locator = command.index === -1 ? refLocator.last() : refLocator.nth(command.index);
|
|
1663
1772
|
}
|
|
1664
1773
|
else {
|
|
1665
1774
|
const frame = browser.getFrame(command.inFrame);
|
|
@@ -1794,10 +1903,19 @@ async function handleScrollIntoView(command, browser) {
|
|
|
1794
1903
|
await page.locator(command.selector).scrollIntoViewIfNeeded();
|
|
1795
1904
|
return successResponse(command.id, { scrolled: true });
|
|
1796
1905
|
}
|
|
1797
|
-
async function handleAddInitScript(command, browser) {
|
|
1798
|
-
const
|
|
1906
|
+
export async function handleAddInitScript(command, browser) {
|
|
1907
|
+
const page = browser.getPage();
|
|
1908
|
+
const context = page.context();
|
|
1799
1909
|
await context.addInitScript(command.script);
|
|
1800
|
-
|
|
1910
|
+
const tips = [];
|
|
1911
|
+
try {
|
|
1912
|
+
await page.evaluate(command.script);
|
|
1913
|
+
}
|
|
1914
|
+
catch (e) {
|
|
1915
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1916
|
+
tips.push(`Init script error on current page: ${msg}. Script will work on next navigation.`);
|
|
1917
|
+
}
|
|
1918
|
+
return successResponse(command.id, { added: true }, tips.length ? tips : undefined);
|
|
1801
1919
|
}
|
|
1802
1920
|
async function handleKeyDown(command, browser) {
|
|
1803
1921
|
const page = browser.getPage();
|
|
@@ -1999,14 +2117,10 @@ async function handleRecorderStatus(command, browser) {
|
|
|
1999
2117
|
async function handleRecorderReplay(command, browser) {
|
|
2000
2118
|
const fs = await import('node:fs');
|
|
2001
2119
|
const path = await import('node:path');
|
|
2002
|
-
// Determine YAML file path
|
|
2003
2120
|
let yamlPath = command.path;
|
|
2004
2121
|
if (!yamlPath) {
|
|
2005
|
-
// Use the most recent recording from temp directory
|
|
2006
2122
|
const recorderDir = path.join(getAppDir(), 'tmp', 'recordings');
|
|
2007
|
-
console.log('[Replay] Looking for recordings in:', recorderDir);
|
|
2008
2123
|
if (!fs.existsSync(recorderDir)) {
|
|
2009
|
-
console.log('[Replay] Directory does not exist');
|
|
2010
2124
|
return errorResponse(command.id, 'No recordings found. Please record first.');
|
|
2011
2125
|
}
|
|
2012
2126
|
const files = fs
|
|
@@ -2017,21 +2131,39 @@ async function handleRecorderReplay(command, browser) {
|
|
|
2017
2131
|
time: fs.statSync(path.join(recorderDir, f)).mtime.getTime(),
|
|
2018
2132
|
}))
|
|
2019
2133
|
.sort((a, b) => b.time - a.time);
|
|
2020
|
-
console.log('[Replay] Found files:', files.length);
|
|
2021
2134
|
if (files.length === 0) {
|
|
2022
2135
|
return errorResponse(command.id, 'No recordings found. Please record first.');
|
|
2023
2136
|
}
|
|
2024
2137
|
yamlPath = path.join(recorderDir, files[0].name);
|
|
2025
|
-
console.log('[Replay] Using file:', yamlPath);
|
|
2026
2138
|
}
|
|
2027
|
-
// Read YAML file
|
|
2028
2139
|
if (!fs.existsSync(yamlPath)) {
|
|
2029
2140
|
return errorResponse(command.id, `Recording file not found: ${yamlPath}`);
|
|
2030
2141
|
}
|
|
2031
2142
|
const yamlContent = fs.readFileSync(yamlPath, 'utf-8');
|
|
2032
|
-
// Parse CLI commands from YAML
|
|
2033
|
-
const lines = yamlContent.split('\n');
|
|
2034
2143
|
const cliCommands = [];
|
|
2144
|
+
// Strategy 1: Parse structured steps and generate CLI commands
|
|
2145
|
+
const stepRegex = /^\s+-\s+(?:id:\s*.+)/;
|
|
2146
|
+
const lines = yamlContent.split('\n');
|
|
2147
|
+
let inSteps = false;
|
|
2148
|
+
const parsedSteps = {};
|
|
2149
|
+
for (const line of lines) {
|
|
2150
|
+
if (/^steps:/.test(line.trim())) {
|
|
2151
|
+
inSteps = true;
|
|
2152
|
+
continue;
|
|
2153
|
+
}
|
|
2154
|
+
if (inSteps && /^-\s+id:/.test(line.trim())) {
|
|
2155
|
+
const idMatch = line.match(/id:\s*(.+)/);
|
|
2156
|
+
if (idMatch)
|
|
2157
|
+
parsedSteps.currentId = idMatch[1].trim();
|
|
2158
|
+
}
|
|
2159
|
+
if (inSteps && /^\s+action:\s*(.+)/.test(line)) {
|
|
2160
|
+
// End of steps section when we hit a non-step line
|
|
2161
|
+
}
|
|
2162
|
+
if (inSteps && !/^\s/.test(line) && !/^$/.test(line) && !/^steps:/.test(line.trim())) {
|
|
2163
|
+
inSteps = false;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
// Strategy 2: Fall back to CLI Commands comment section
|
|
2035
2167
|
let inCliSection = false;
|
|
2036
2168
|
for (const line of lines) {
|
|
2037
2169
|
if (line.includes('# CLI Commands')) {
|
|
@@ -2043,10 +2175,23 @@ async function handleRecorderReplay(command, browser) {
|
|
|
2043
2175
|
cliCommands.push(line.trim());
|
|
2044
2176
|
}
|
|
2045
2177
|
}
|
|
2178
|
+
// Strategy 3: If no CLI section, generate from structured steps using browser's method
|
|
2046
2179
|
if (cliCommands.length === 0) {
|
|
2047
2180
|
return errorResponse(command.id, 'No CLI commands found in recording. Please re-record with the new version.');
|
|
2048
2181
|
}
|
|
2049
|
-
//
|
|
2182
|
+
// Filter out env-only lines (keep them for env setup but not as commands)
|
|
2183
|
+
const envLines = cliCommands.filter((l) => l.startsWith('AGENT_BROWSER_'));
|
|
2184
|
+
const cmdLines = cliCommands.filter((l) => l.startsWith('agent-browser '));
|
|
2185
|
+
// Set env vars from recording
|
|
2186
|
+
const originalEnv = {};
|
|
2187
|
+
for (const envLine of envLines) {
|
|
2188
|
+
const eqIdx = envLine.indexOf('=');
|
|
2189
|
+
if (eqIdx > 0) {
|
|
2190
|
+
const key = envLine.substring(0, eqIdx);
|
|
2191
|
+
originalEnv[key] = process.env[key];
|
|
2192
|
+
process.env[key] = envLine.substring(eqIdx + 1);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2050
2195
|
function parseCommandLine(line) {
|
|
2051
2196
|
const parts = [];
|
|
2052
2197
|
let current = '';
|
|
@@ -2077,62 +2222,38 @@ async function handleRecorderReplay(command, browser) {
|
|
|
2077
2222
|
}
|
|
2078
2223
|
return parts;
|
|
2079
2224
|
}
|
|
2080
|
-
//
|
|
2225
|
+
// Get current session for passthrough
|
|
2226
|
+
const currentSession = process.env.AGENT_BROWSER_SESSION || 'default';
|
|
2081
2227
|
const results = [];
|
|
2082
|
-
for (const cmdLine of
|
|
2228
|
+
for (const cmdLine of cmdLines) {
|
|
2083
2229
|
try {
|
|
2084
|
-
// Parse command line with proper quote handling
|
|
2085
2230
|
let parts = parseCommandLine(cmdLine);
|
|
2086
|
-
const envVars = {};
|
|
2087
|
-
// Extract environment variables (format: KEY=value)
|
|
2088
|
-
while (parts.length > 0 && parts[0].includes('=')) {
|
|
2089
|
-
const [key, ...valueParts] = parts.shift().split('=');
|
|
2090
|
-
envVars[key] = valueParts.join('=');
|
|
2091
|
-
}
|
|
2092
|
-
// Remove 'agent-browser' prefix if present
|
|
2093
2231
|
if (parts.length > 0 && parts[0] === 'agent-browser') {
|
|
2094
2232
|
parts = parts.slice(1);
|
|
2095
2233
|
}
|
|
2096
|
-
// Skip empty commands
|
|
2097
2234
|
if (parts.length === 0) {
|
|
2098
2235
|
results.push({ command: cmdLine, success: true });
|
|
2099
2236
|
continue;
|
|
2100
2237
|
}
|
|
2101
|
-
// Set environment variables temporarily
|
|
2102
|
-
const originalEnv = {};
|
|
2103
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
2104
|
-
originalEnv[key] = process.env[key];
|
|
2105
|
-
process.env[key] = value;
|
|
2106
|
-
}
|
|
2107
|
-
// Execute command using the existing executeCommand flow
|
|
2108
2238
|
const { parseCommand } = await import('./cli/commands.js');
|
|
2109
2239
|
const { parseFlags } = await import('./cli/flags.js');
|
|
2110
2240
|
const flags = parseFlags([]);
|
|
2241
|
+
if (currentSession !== 'default') {
|
|
2242
|
+
flags.session = currentSession;
|
|
2243
|
+
}
|
|
2111
2244
|
const parsedCmd = parseCommand(parts, flags);
|
|
2112
|
-
// Check if recording is active, and temporarily disable
|
|
2113
2245
|
const wasRecording = browser.isRecordingSession();
|
|
2114
2246
|
if (wasRecording) {
|
|
2115
2247
|
browser.pauseRecording();
|
|
2116
2248
|
}
|
|
2117
|
-
// Execute the command with a timeout to prevent hanging on invalid selectors
|
|
2118
2249
|
const COMMAND_TIMEOUT_MS = 5000;
|
|
2119
2250
|
const result = (await Promise.race([
|
|
2120
2251
|
executeCommand(parsedCmd, browser),
|
|
2121
2252
|
new Promise((_, reject) => setTimeout(() => reject(new Error(`Command timed out after ${COMMAND_TIMEOUT_MS}ms`)), COMMAND_TIMEOUT_MS)),
|
|
2122
2253
|
]));
|
|
2123
|
-
// Restore recording state
|
|
2124
2254
|
if (wasRecording) {
|
|
2125
2255
|
browser.resumeRecording();
|
|
2126
2256
|
}
|
|
2127
|
-
// Restore environment variables
|
|
2128
|
-
for (const [key, value] of Object.entries(originalEnv)) {
|
|
2129
|
-
if (value === undefined) {
|
|
2130
|
-
delete process.env[key];
|
|
2131
|
-
}
|
|
2132
|
-
else {
|
|
2133
|
-
process.env[key] = value;
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
2257
|
results.push({ command: cmdLine, success: result.success });
|
|
2137
2258
|
}
|
|
2138
2259
|
catch (e) {
|
|
@@ -2140,29 +2261,37 @@ async function handleRecorderReplay(command, browser) {
|
|
|
2140
2261
|
results.push({ command: cmdLine, success: false, error: errorMessage });
|
|
2141
2262
|
}
|
|
2142
2263
|
}
|
|
2264
|
+
// Restore env vars
|
|
2265
|
+
for (const [key, value] of Object.entries(originalEnv)) {
|
|
2266
|
+
if (value === undefined) {
|
|
2267
|
+
delete process.env[key];
|
|
2268
|
+
}
|
|
2269
|
+
else {
|
|
2270
|
+
process.env[key] = value;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2143
2273
|
const successCount = results.filter((r) => r.success).length;
|
|
2144
2274
|
const failCount = results.filter((r) => !r.success).length;
|
|
2145
2275
|
return successResponse(command.id, {
|
|
2146
2276
|
replayed: true,
|
|
2147
2277
|
file: yamlPath,
|
|
2148
|
-
totalCommands:
|
|
2278
|
+
totalCommands: cmdLines.length,
|
|
2149
2279
|
successCount,
|
|
2150
2280
|
failCount,
|
|
2151
|
-
results: results.slice(0, 20),
|
|
2281
|
+
results: results.slice(0, 20),
|
|
2152
2282
|
});
|
|
2153
2283
|
}
|
|
2154
2284
|
async function handleViewer(command, _browser) {
|
|
2155
2285
|
const instanceId = getInstanceId();
|
|
2156
|
-
const port = parseInt(process.env.AGENT_BROWSER_STREAM_PORT || '5005', 10);
|
|
2157
2286
|
return successResponse(command.id, {
|
|
2158
|
-
url:
|
|
2159
|
-
wsUrl:
|
|
2160
|
-
streamPort:
|
|
2287
|
+
url: getViewerUrl(instanceId),
|
|
2288
|
+
wsUrl: getViewerWsUrl(instanceId),
|
|
2289
|
+
streamPort: getViewerPort(),
|
|
2161
2290
|
});
|
|
2162
2291
|
}
|
|
2163
2292
|
async function handleAsk(command, _browser) {
|
|
2164
2293
|
const session = getSession();
|
|
2165
|
-
const bridge = new MessageBridge();
|
|
2294
|
+
const bridge = new MessageBridge(getMessageBridgeUrl());
|
|
2166
2295
|
try {
|
|
2167
2296
|
const answer = await bridge.ask(command.question, session);
|
|
2168
2297
|
return successResponse(command.id, { answer });
|
|
@@ -2174,9 +2303,10 @@ async function handleAsk(command, _browser) {
|
|
|
2174
2303
|
}
|
|
2175
2304
|
function handleConfig(command) {
|
|
2176
2305
|
const humanConfig = getHumanConfigFromEnv();
|
|
2306
|
+
const rcConfig = loadConfig();
|
|
2177
2307
|
const config = {
|
|
2178
2308
|
session: process.env.AGENT_BROWSER_SESSION || 'default',
|
|
2179
|
-
executablePath:
|
|
2309
|
+
executablePath: getExecutablePath() || null,
|
|
2180
2310
|
extensions: process.env.AGENT_BROWSER_EXTENSIONS || null,
|
|
2181
2311
|
profile: process.env.AGENT_BROWSER_PROFILE || null,
|
|
2182
2312
|
state: process.env.AGENT_BROWSER_STATE || null,
|
|
@@ -2187,24 +2317,34 @@ function handleConfig(command) {
|
|
|
2187
2317
|
provider: process.env.AGENT_BROWSER_PROVIDER || null,
|
|
2188
2318
|
allowFileAccess: process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1',
|
|
2189
2319
|
iosDevice: process.env.AGENT_BROWSER_IOS_DEVICE || null,
|
|
2190
|
-
streamPort:
|
|
2320
|
+
streamPort: getViewerPort(),
|
|
2191
2321
|
headed: process.env.AGENT_BROWSER_HEADED === '1',
|
|
2192
2322
|
human: humanConfig,
|
|
2193
2323
|
};
|
|
2194
2324
|
if (command.json) {
|
|
2195
|
-
return successResponse(command.id, { config });
|
|
2325
|
+
return successResponse(command.id, { config, rc: rcConfig });
|
|
2196
2326
|
}
|
|
2327
|
+
const viewerHost = getEffectiveValue('viewer.host');
|
|
2328
|
+
const bridgeUrl = getEffectiveValue('messageBridge.url');
|
|
2329
|
+
const msgProxy = getEffectiveValue('messageProxy.url');
|
|
2197
2330
|
// Format human-readable output
|
|
2198
2331
|
const lines = [
|
|
2199
2332
|
'Agent Browser Configuration',
|
|
2200
2333
|
'===========================',
|
|
2201
2334
|
'',
|
|
2202
2335
|
'Session & Browser:',
|
|
2203
|
-
`
|
|
2204
|
-
` AGENT_BROWSER_EXECUTABLE_PATH ${config.executablePath || '(not set)'}`,
|
|
2336
|
+
` executablePath ${config.executablePath || '(not set)'}`,
|
|
2205
2337
|
` AGENT_BROWSER_PROVIDER ${config.provider || '(not set)'}`,
|
|
2206
2338
|
` AGENT_BROWSER_HEADED ${config.headed ? 'true' : 'false (default)'}`,
|
|
2207
2339
|
'',
|
|
2340
|
+
'Viewer & Stream:',
|
|
2341
|
+
` viewer.host ${viewerHost || '(not set, using http://localhost)'}`,
|
|
2342
|
+
` viewer.port ${config.streamPort}`,
|
|
2343
|
+
'',
|
|
2344
|
+
'Message Bridge (ask command):',
|
|
2345
|
+
` messageBridge.url ${bridgeUrl || '(not set, using default)'}`,
|
|
2346
|
+
` messageProxy.url ${msgProxy || '(not set)'}`,
|
|
2347
|
+
'',
|
|
2208
2348
|
'Browser Options:',
|
|
2209
2349
|
` AGENT_BROWSER_PROFILE ${config.profile || '(not set)'}`,
|
|
2210
2350
|
` AGENT_BROWSER_EXTENSIONS ${config.extensions || '(not set)'}`,
|
|
@@ -2216,9 +2356,18 @@ function handleConfig(command) {
|
|
|
2216
2356
|
'Human Mode (runtime):',
|
|
2217
2357
|
` AGENT_BROWSER_HUMAN ${humanConfig.enabled ? humanConfig.pathType + ' ✓' : '(disabled)'}`,
|
|
2218
2358
|
'',
|
|
2219
|
-
|
|
2220
|
-
'
|
|
2359
|
+
`Persistent config: ~/.agent-browser/config.json`,
|
|
2360
|
+
'Run "agent-browser config set <key> <value>" to persist settings.',
|
|
2361
|
+
'Run "agent-browser config list" to see configurable keys.',
|
|
2221
2362
|
];
|
|
2222
2363
|
return successResponse(command.id, { config, output: lines.join('\n') });
|
|
2223
2364
|
}
|
|
2365
|
+
async function handleHistory(command, browser) {
|
|
2366
|
+
if (command.clear) {
|
|
2367
|
+
browser.clearHistory();
|
|
2368
|
+
return successResponse(command.id, { cleared: true });
|
|
2369
|
+
}
|
|
2370
|
+
const history = browser.getHistory(command.filter);
|
|
2371
|
+
return successResponse(command.id, { history });
|
|
2372
|
+
}
|
|
2224
2373
|
//# sourceMappingURL=actions.js.map
|