browser-use 0.6.1 → 0.7.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 (82) hide show
  1. package/README.md +24 -18
  2. package/dist/actor/element.js +24 -3
  3. package/dist/actor/mouse.js +21 -3
  4. package/dist/actor/page.js +33 -11
  5. package/dist/agent/gif.js +28 -3
  6. package/dist/agent/message-manager/service.js +2 -22
  7. package/dist/agent/message-manager/utils.js +15 -2
  8. package/dist/agent/message-manager/views.d.ts +7 -7
  9. package/dist/agent/message-manager/views.js +1 -0
  10. package/dist/agent/prompts.d.ts +3 -0
  11. package/dist/agent/prompts.js +22 -12
  12. package/dist/agent/service.d.ts +9 -1
  13. package/dist/agent/service.js +204 -79
  14. package/dist/agent/system_prompt.md +12 -11
  15. package/dist/agent/system_prompt_anthropic_flash.md +6 -5
  16. package/dist/agent/system_prompt_no_thinking.md +12 -11
  17. package/dist/agent/views.d.ts +2 -0
  18. package/dist/agent/views.js +48 -36
  19. package/dist/browser/extensions.js +20 -10
  20. package/dist/browser/profile.d.ts +4 -0
  21. package/dist/browser/profile.js +107 -4
  22. package/dist/browser/session.d.ts +28 -1
  23. package/dist/browser/session.js +1436 -528
  24. package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
  25. package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
  26. package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
  27. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
  28. package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
  29. package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
  30. package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
  31. package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
  32. package/dist/browser/watchdogs/recording-watchdog.js +54 -2
  33. package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
  34. package/dist/browser/watchdogs/security-watchdog.js +47 -7
  35. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
  36. package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
  37. package/dist/cli.d.ts +13 -2
  38. package/dist/cli.js +187 -7
  39. package/dist/code-use/namespace.js +52 -7
  40. package/dist/code-use/notebook-export.js +18 -2
  41. package/dist/code-use/service.js +1 -0
  42. package/dist/config.js +26 -4
  43. package/dist/controller/action-timeout.d.ts +9 -0
  44. package/dist/controller/action-timeout.js +95 -0
  45. package/dist/controller/registry/service.d.ts +1 -0
  46. package/dist/controller/registry/service.js +28 -1
  47. package/dist/controller/service.d.ts +2 -1
  48. package/dist/controller/service.js +494 -329
  49. package/dist/filesystem/file-system.js +38 -8
  50. package/dist/integrations/gmail/service.js +30 -6
  51. package/dist/llm/browser-use/chat.js +2 -2
  52. package/dist/llm/codex/auth.d.ts +118 -0
  53. package/dist/llm/codex/auth.js +599 -0
  54. package/dist/llm/codex/chat.d.ts +70 -0
  55. package/dist/llm/codex/chat.js +392 -0
  56. package/dist/llm/codex/index.d.ts +2 -0
  57. package/dist/llm/codex/index.js +2 -0
  58. package/dist/llm/google/chat.js +18 -1
  59. package/dist/logging-config.js +22 -11
  60. package/dist/mcp/client.d.ts +1 -0
  61. package/dist/mcp/client.js +12 -10
  62. package/dist/mcp/redaction.d.ts +3 -0
  63. package/dist/mcp/redaction.js +132 -0
  64. package/dist/mcp/server.d.ts +2 -0
  65. package/dist/mcp/server.js +64 -22
  66. package/dist/screenshots/service.js +25 -2
  67. package/dist/skill-cli/direct.d.ts +4 -1
  68. package/dist/skill-cli/direct.js +260 -64
  69. package/dist/skill-cli/server.d.ts +1 -0
  70. package/dist/skill-cli/server.js +115 -25
  71. package/dist/skill-cli/tunnel.d.ts +1 -0
  72. package/dist/skill-cli/tunnel.js +16 -4
  73. package/dist/sync/auth.js +22 -9
  74. package/dist/telemetry/service.js +21 -2
  75. package/dist/telemetry/views.js +31 -8
  76. package/dist/tokens/custom-pricing.js +2 -2
  77. package/dist/tokens/openrouter-pricing.d.ts +11 -0
  78. package/dist/tokens/openrouter-pricing.js +102 -0
  79. package/dist/tokens/service.js +20 -16
  80. package/dist/utils.d.ts +3 -1
  81. package/dist/utils.js +3 -1
  82. package/package.json +68 -27
@@ -6,7 +6,44 @@ import net from 'node:net';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { BrowserSession, systemChrome } from '../browser/session.js';
8
8
  import { CloudBrowserClient } from '../browser/cloud/cloud.js';
9
- export const DIRECT_STATE_FILE = path.join(os.tmpdir(), 'browser-use-direct.json');
9
+ const getSafeUserSegment = () => {
10
+ if (typeof process.getuid === 'function') {
11
+ return String(process.getuid());
12
+ }
13
+ try {
14
+ const username = os.userInfo().username;
15
+ return username.replace(/[^a-zA-Z0-9_.-]/g, '_') || 'user';
16
+ }
17
+ catch {
18
+ return 'user';
19
+ }
20
+ };
21
+ const getDefaultDirectStateDir = () => {
22
+ const runtimeDir = process.env.XDG_RUNTIME_DIR?.trim();
23
+ if (runtimeDir && path.isAbsolute(runtimeDir)) {
24
+ return path.join(runtimeDir, 'browser-use');
25
+ }
26
+ const homeDir = os.homedir();
27
+ if (homeDir) {
28
+ if (process.platform === 'darwin') {
29
+ return path.join(homeDir, 'Library', 'Application Support', 'browser-use');
30
+ }
31
+ if (process.platform === 'win32') {
32
+ const localAppData = process.env.LOCALAPPDATA?.trim();
33
+ const baseDir = localAppData && path.isAbsolute(localAppData)
34
+ ? localAppData
35
+ : path.join(homeDir, 'AppData', 'Local');
36
+ return path.join(baseDir, 'browser-use');
37
+ }
38
+ const stateHome = process.env.XDG_STATE_HOME?.trim();
39
+ const baseDir = stateHome && path.isAbsolute(stateHome)
40
+ ? stateHome
41
+ : path.join(homeDir, '.local', 'state');
42
+ return path.join(baseDir, 'browser-use');
43
+ }
44
+ return path.join(os.tmpdir(), `browser-use-${getSafeUserSegment()}`);
45
+ };
46
+ export const DIRECT_STATE_FILE = path.join(getDefaultDirectStateDir(), 'direct-state.json');
10
47
  const normalizeCookieDomain = (value) => String(value ?? '')
11
48
  .trim()
12
49
  .replace(/^\./, '')
@@ -23,6 +60,9 @@ const parseCookieHostname = (url) => {
23
60
  return '';
24
61
  }
25
62
  };
63
+ const validateDirectPageAfterAction = async (session, page) => {
64
+ await session.validate_page_after_action?.(page);
65
+ };
26
66
  const parseCookieUrl = (url) => {
27
67
  const value = String(url ?? '').trim();
28
68
  if (!value) {
@@ -54,9 +94,7 @@ const cookieMatchesUrl = (cookie, url) => {
54
94
  if (!hostname || !domain) {
55
95
  return false;
56
96
  }
57
- if (!(hostname === domain ||
58
- hostname.endsWith(`.${domain}`) ||
59
- domain.endsWith(`.${hostname}`))) {
97
+ if (!(hostname === domain || hostname.endsWith(`.${domain}`))) {
60
98
  return false;
61
99
  }
62
100
  if (!cookiePathMatches(cookie.path, parsedUrl?.pathname || '/')) {
@@ -82,6 +120,33 @@ const normalizeSameSite = (value) => {
82
120
  }
83
121
  return undefined;
84
122
  };
123
+ const getDirectCookieDenialReason = (session, cookie) => {
124
+ const checker = session._get_cookie_access_denial_reason;
125
+ if (typeof checker !== 'function') {
126
+ return null;
127
+ }
128
+ return checker.call(session, cookie);
129
+ };
130
+ const filterDirectAllowedCookies = (session, cookies) => cookies.filter((cookie) => !getDirectCookieDenialReason(session, cookie));
131
+ const partitionDirectAllowedCookies = (session, cookies) => {
132
+ const allowedCookies = [];
133
+ const blockedCookies = [];
134
+ for (const cookie of cookies) {
135
+ if (getDirectCookieDenialReason(session, cookie)) {
136
+ blockedCookies.push(cookie);
137
+ }
138
+ else {
139
+ allowedCookies.push(cookie);
140
+ }
141
+ }
142
+ return { allowedCookies, blockedCookies };
143
+ };
144
+ const assertDirectCookieUrlAllowed = (session, url) => {
145
+ const denialReason = getDirectCookieDenialReason(session, { url });
146
+ if (denialReason) {
147
+ throw new Error(`Cookie URL blocked by domain policy: ${denialReason}`);
148
+ }
149
+ };
85
150
  const DEFAULT_STDOUT = process.stdout;
86
151
  const DEFAULT_STDERR = process.stderr;
87
152
  const writeLine = (stream, message) => {
@@ -99,15 +164,55 @@ export const load_direct_state = (state_file = DIRECT_STATE_FILE) => {
99
164
  }
100
165
  };
101
166
  export const save_direct_state = (state, state_file = DIRECT_STATE_FILE) => {
102
- fs.writeFileSync(state_file, JSON.stringify(state, null, 2));
167
+ fs.mkdirSync(path.dirname(state_file), { recursive: true, mode: 0o700 });
168
+ if (process.platform !== 'win32' &&
169
+ path.resolve(state_file) === path.resolve(DIRECT_STATE_FILE)) {
170
+ fs.chmodSync(path.dirname(state_file), 0o700);
171
+ }
172
+ fs.writeFileSync(state_file, JSON.stringify(state, null, 2), {
173
+ encoding: 'utf8',
174
+ mode: 0o600,
175
+ });
176
+ if (process.platform !== 'win32') {
177
+ fs.chmodSync(state_file, 0o600);
178
+ }
103
179
  };
104
180
  export const clear_direct_state = (state_file = DIRECT_STATE_FILE) => {
105
181
  fs.rmSync(state_file, { force: true });
106
182
  };
183
+ const writePrivateFile = (filePath, contents) => {
184
+ fs.writeFileSync(filePath, contents, { encoding: 'utf8', mode: 0o600 });
185
+ if (process.platform !== 'win32') {
186
+ fs.chmodSync(filePath, 0o600);
187
+ }
188
+ };
189
+ const writePrivateBufferFile = (filePath, contents) => {
190
+ fs.writeFileSync(filePath, contents, { mode: 0o600 });
191
+ if (process.platform !== 'win32') {
192
+ fs.chmodSync(filePath, 0o600);
193
+ }
194
+ };
195
+ const isOwnedDirectUserDataDir = (userDataDir) => {
196
+ if (!userDataDir || !fs.existsSync(userDataDir)) {
197
+ return false;
198
+ }
199
+ try {
200
+ const resolvedDir = fs.realpathSync(userDataDir);
201
+ const resolvedTmp = fs.realpathSync(os.tmpdir());
202
+ return (path.dirname(resolvedDir) === resolvedTmp &&
203
+ path.basename(resolvedDir).startsWith('browser-use-direct-'));
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ };
107
209
  const cleanupOwnedDirectUserDataDir = (state) => {
108
210
  if (!state.owns_user_data_dir || !state.user_data_dir) {
109
211
  return;
110
212
  }
213
+ if (!isOwnedDirectUserDataDir(state.user_data_dir)) {
214
+ return;
215
+ }
111
216
  try {
112
217
  fs.rmSync(state.user_data_dir, { recursive: true, force: true });
113
218
  }
@@ -301,40 +406,46 @@ const readDirectNodeData = async (session, node, kind) => {
301
406
  if (!page?.evaluate) {
302
407
  throw new Error('No active page available');
303
408
  }
304
- return await page.evaluate(({ xpath, dataKind }) => {
305
- const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
306
- if (!element) {
409
+ await validateDirectPageAfterAction(session, page);
410
+ try {
411
+ return await page.evaluate(({ xpath, dataKind }) => {
412
+ const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
413
+ if (!element) {
414
+ return null;
415
+ }
416
+ if (dataKind === 'text') {
417
+ return element.textContent?.trim() ?? '';
418
+ }
419
+ if (dataKind === 'value') {
420
+ return 'value' in element
421
+ ? String(element.value ?? '')
422
+ : null;
423
+ }
424
+ if (dataKind === 'attributes') {
425
+ return Object.fromEntries(Array.from(element.attributes).map((attribute) => [
426
+ attribute.name,
427
+ attribute.value,
428
+ ]));
429
+ }
430
+ if (dataKind === 'bbox') {
431
+ const rect = element.getBoundingClientRect();
432
+ return {
433
+ x: rect.x,
434
+ y: rect.y,
435
+ width: rect.width,
436
+ height: rect.height,
437
+ top: rect.top,
438
+ right: rect.right,
439
+ bottom: rect.bottom,
440
+ left: rect.left,
441
+ };
442
+ }
307
443
  return null;
308
- }
309
- if (dataKind === 'text') {
310
- return element.textContent?.trim() ?? '';
311
- }
312
- if (dataKind === 'value') {
313
- return 'value' in element
314
- ? String(element.value ?? '')
315
- : null;
316
- }
317
- if (dataKind === 'attributes') {
318
- return Object.fromEntries(Array.from(element.attributes).map((attribute) => [
319
- attribute.name,
320
- attribute.value,
321
- ]));
322
- }
323
- if (dataKind === 'bbox') {
324
- const rect = element.getBoundingClientRect();
325
- return {
326
- x: rect.x,
327
- y: rect.y,
328
- width: rect.width,
329
- height: rect.height,
330
- top: rect.top,
331
- right: rect.right,
332
- bottom: rect.bottom,
333
- left: rect.left,
334
- };
335
- }
336
- return null;
337
- }, { xpath: node.xpath, dataKind: kind });
444
+ }, { xpath: node.xpath, dataKind: kind });
445
+ }
446
+ finally {
447
+ await validateDirectPageAfterAction(session, page);
448
+ }
338
449
  };
339
450
  const takeDirectOptionValue = (args, index, option) => {
340
451
  const next = args[index + 1]?.trim();
@@ -648,7 +759,7 @@ export const run_direct_command = async (argv, options = {}) => {
648
759
  }
649
760
  const bytes = Buffer.from(screenshot, 'base64');
650
761
  if (outputPath) {
651
- fs.writeFileSync(outputPath, bytes);
762
+ writePrivateBufferFile(outputPath, bytes);
652
763
  writeLine(environment.stdout, `Screenshot saved to ${outputPath} (${bytes.length} bytes)`);
653
764
  }
654
765
  else {
@@ -737,7 +848,12 @@ export const run_direct_command = async (argv, options = {}) => {
737
848
  if (!page?.waitForFunction) {
738
849
  throw new Error('No active page available for wait text');
739
850
  }
740
- await page.waitForFunction((needle) => document.body?.innerText?.includes(needle) ?? false, text, { timeout: 5000 });
851
+ try {
852
+ await page.waitForFunction((needle) => document.body?.innerText?.includes(needle) ?? false, text, { timeout: 5000 });
853
+ }
854
+ finally {
855
+ await validateDirectPageAfterAction(session, page);
856
+ }
741
857
  writeLine(environment.stdout, `Waited for text "${text}"`);
742
858
  }
743
859
  else {
@@ -750,7 +866,13 @@ export const run_direct_command = async (argv, options = {}) => {
750
866
  if (!locator?.hover) {
751
867
  throw new Error('Hover is not available for this element');
752
868
  }
753
- await locator.hover({ timeout: 5000 });
869
+ const page = await session.get_current_page?.();
870
+ try {
871
+ await locator.hover({ timeout: 5000 });
872
+ }
873
+ finally {
874
+ await validateDirectPageAfterAction(session, page);
875
+ }
754
876
  writeLine(environment.stdout, `Hovered element [${index}]`);
755
877
  }
756
878
  else if (command === 'dblclick') {
@@ -759,7 +881,13 @@ export const run_direct_command = async (argv, options = {}) => {
759
881
  if (!locator?.dblclick) {
760
882
  throw new Error('Double-click is not available for this element');
761
883
  }
762
- await locator.dblclick({ timeout: 5000 });
884
+ const page = await session.get_current_page?.();
885
+ try {
886
+ await locator.dblclick({ timeout: 5000 });
887
+ }
888
+ finally {
889
+ await validateDirectPageAfterAction(session, page);
890
+ }
763
891
  writeLine(environment.stdout, `Double-clicked element [${index}]`);
764
892
  }
765
893
  else if (command === 'rightclick') {
@@ -768,7 +896,13 @@ export const run_direct_command = async (argv, options = {}) => {
768
896
  if (!locator?.click) {
769
897
  throw new Error('Right-click is not available for this element');
770
898
  }
771
- await locator.click({ button: 'right', timeout: 5000 });
899
+ const page = await session.get_current_page?.();
900
+ try {
901
+ await locator.click({ button: 'right', timeout: 5000 });
902
+ }
903
+ finally {
904
+ await validateDirectPageAfterAction(session, page);
905
+ }
772
906
  writeLine(environment.stdout, `Right-clicked element [${index}]`);
773
907
  }
774
908
  else if (command === 'cookies') {
@@ -776,10 +910,14 @@ export const run_direct_command = async (argv, options = {}) => {
776
910
  if (cookieCommand === 'get') {
777
911
  const parsed = parseDirectCookieOptions(args.slice(2));
778
912
  const url = parsed.url ?? parsed.positional[0] ?? null;
913
+ if (url) {
914
+ assertDirectCookieUrlAllowed(session, url);
915
+ }
779
916
  const allCookies = (await session.get_cookies?.()) ?? [];
917
+ const allowedCookies = filterDirectAllowedCookies(session, allCookies);
780
918
  const cookies = url
781
- ? allCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
782
- : allCookies;
919
+ ? allowedCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
920
+ : allowedCookies;
783
921
  writeLine(environment.stdout, JSON.stringify({ cookies, count: cookies.length }, null, 2));
784
922
  }
785
923
  else if (cookieCommand === 'set') {
@@ -815,6 +953,10 @@ export const run_direct_command = async (argv, options = {}) => {
815
953
  else {
816
954
  throw new Error('Provide cookie url/domain or open a page first');
817
955
  }
956
+ const denialReason = getDirectCookieDenialReason(session, cookie);
957
+ if (denialReason) {
958
+ throw new Error(`Cookie target blocked by domain policy: ${denialReason}`);
959
+ }
818
960
  await session.browser_context.addCookies([cookie]);
819
961
  writeLine(environment.stdout, `Set cookie ${name}`);
820
962
  }
@@ -825,16 +967,41 @@ export const run_direct_command = async (argv, options = {}) => {
825
967
  const parsed = parseDirectCookieOptions(args.slice(2));
826
968
  const url = parsed.url ?? parsed.positional[0] ?? null;
827
969
  if (!url) {
828
- await session.browser_context.clearCookies();
829
- writeLine(environment.stdout, 'Cleared cookies');
970
+ if (typeof session.get_cookies !== 'function') {
971
+ await session.browser_context.clearCookies();
972
+ writeLine(environment.stdout, 'Cleared cookies');
973
+ }
974
+ else {
975
+ const allCookies = (await session.get_cookies?.({ include_blocked: true })) ?? [];
976
+ const { allowedCookies, blockedCookies } = partitionDirectAllowedCookies(session, allCookies);
977
+ if (allowedCookies.length === 0 && blockedCookies.length > 0) {
978
+ writeLine(environment.stdout, 'Cleared 0 cookies');
979
+ }
980
+ else {
981
+ const addCookies = session.browser_context.addCookies;
982
+ if (blockedCookies.length > 0 && !addCookies) {
983
+ throw new Error('Browser context does not support preserving blocked cookies');
984
+ }
985
+ await session.browser_context.clearCookies();
986
+ if (blockedCookies.length > 0) {
987
+ await addCookies(blockedCookies);
988
+ }
989
+ writeLine(environment.stdout, `Cleared ${allowedCookies.length} cookies`);
990
+ }
991
+ }
830
992
  }
831
993
  else {
832
- const allCookies = (await session.get_cookies?.()) ?? [];
994
+ assertDirectCookieUrlAllowed(session, url);
995
+ const allCookies = (await session.get_cookies?.({ include_blocked: true })) ?? [];
833
996
  const remaining = allCookies.filter((cookie) => !cookieMatchesUrl(cookie, url));
834
997
  const removedCount = allCookies.length - remaining.length;
998
+ const addCookies = session.browser_context.addCookies;
999
+ if (remaining.length > 0 && !addCookies) {
1000
+ throw new Error('Browser context does not support preserving non-matching cookies');
1001
+ }
835
1002
  await session.browser_context.clearCookies();
836
- if (remaining.length > 0 && session.browser_context.addCookies) {
837
- await session.browser_context.addCookies(remaining);
1003
+ if (remaining.length > 0) {
1004
+ await addCookies(remaining);
838
1005
  }
839
1006
  writeLine(environment.stdout, `Cleared ${removedCount} cookies matching ${url}`);
840
1007
  }
@@ -846,12 +1013,16 @@ export const run_direct_command = async (argv, options = {}) => {
846
1013
  }
847
1014
  const parsed = parseDirectCookieOptions(args.slice(3));
848
1015
  const url = parsed.url ?? parsed.positional[0] ?? null;
1016
+ if (url) {
1017
+ assertDirectCookieUrlAllowed(session, url);
1018
+ }
849
1019
  const allCookies = (await session.get_cookies?.()) ?? [];
1020
+ const allowedCookies = filterDirectAllowedCookies(session, allCookies);
850
1021
  const cookies = url
851
- ? allCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
852
- : allCookies;
1022
+ ? allowedCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
1023
+ : allowedCookies;
853
1024
  const outputPath = path.resolve(file);
854
- fs.writeFileSync(outputPath, JSON.stringify(cookies, null, 2), 'utf8');
1025
+ writePrivateFile(outputPath, JSON.stringify(cookies, null, 2));
855
1026
  writeLine(environment.stdout, `Exported ${cookies.length} cookies to ${outputPath}`);
856
1027
  }
857
1028
  else if (cookieCommand === 'import') {
@@ -868,8 +1039,11 @@ export const run_direct_command = async (argv, options = {}) => {
868
1039
  if (!Array.isArray(cookies)) {
869
1040
  throw new Error('Cookie import file must contain a JSON array');
870
1041
  }
871
- await session.browser_context.addCookies(cookies);
872
- writeLine(environment.stdout, `Imported ${cookies.length} cookies from ${inputPath}`);
1042
+ const allowedCookies = filterDirectAllowedCookies(session, cookies);
1043
+ if (allowedCookies.length > 0) {
1044
+ await session.browser_context.addCookies(allowedCookies);
1045
+ }
1046
+ writeLine(environment.stdout, `Imported ${allowedCookies.length} cookies from ${inputPath}`);
873
1047
  }
874
1048
  else {
875
1049
  throw new Error('Usage: cookies get [url|--url <url>] | cookies set <name> <value> | cookies clear [--url <url>] | cookies export <file> [--url <url>] | cookies import <file>');
@@ -882,7 +1056,13 @@ export const run_direct_command = async (argv, options = {}) => {
882
1056
  if (!page?.title) {
883
1057
  throw new Error('No active page available for get title');
884
1058
  }
885
- writeLine(environment.stdout, await page.title());
1059
+ await validateDirectPageAfterAction(session, page);
1060
+ try {
1061
+ writeLine(environment.stdout, await page.title());
1062
+ }
1063
+ finally {
1064
+ await validateDirectPageAfterAction(session, page);
1065
+ }
886
1066
  }
887
1067
  else if (subcommand === 'html') {
888
1068
  const selector = args.slice(2).join(' ').trim();
@@ -894,10 +1074,18 @@ export const run_direct_command = async (argv, options = {}) => {
894
1074
  if (!page?.evaluate) {
895
1075
  throw new Error('No active page available for get html');
896
1076
  }
897
- const html = await page.evaluate((targetSelector) => {
898
- const element = document.querySelector(targetSelector);
899
- return element ? element.outerHTML : null;
900
- }, selector);
1077
+ await validateDirectPageAfterAction(session, page);
1078
+ const html = await (async () => {
1079
+ try {
1080
+ return await page.evaluate((targetSelector) => {
1081
+ const element = document.querySelector(targetSelector);
1082
+ return element ? element.outerHTML : null;
1083
+ }, selector);
1084
+ }
1085
+ finally {
1086
+ await validateDirectPageAfterAction(session, page);
1087
+ }
1088
+ })();
901
1089
  if (typeof html !== 'string' || html.length === 0) {
902
1090
  throw new Error(`No element found for selector: ${selector}`);
903
1091
  }
@@ -939,10 +1127,18 @@ export const run_direct_command = async (argv, options = {}) => {
939
1127
  if (!page?.evaluate) {
940
1128
  throw new Error('No active page available for html');
941
1129
  }
942
- const html = await page.evaluate((targetSelector) => {
943
- const element = document.querySelector(targetSelector);
944
- return element ? element.outerHTML : null;
945
- }, selector);
1130
+ await validateDirectPageAfterAction(session, page);
1131
+ const html = await (async () => {
1132
+ try {
1133
+ return await page.evaluate((targetSelector) => {
1134
+ const element = document.querySelector(targetSelector);
1135
+ return element ? element.outerHTML : null;
1136
+ }, selector);
1137
+ }
1138
+ finally {
1139
+ await validateDirectPageAfterAction(session, page);
1140
+ }
1141
+ })();
946
1142
  if (typeof html !== 'string' || html.length === 0) {
947
1143
  throw new Error(`No element found for selector: ${selector}`);
948
1144
  }
@@ -7,6 +7,7 @@ export declare class SkillCliServer {
7
7
  readonly registry: SessionRegistry;
8
8
  constructor(options?: SkillCliServerOptions);
9
9
  private _require_node_by_index;
10
+ private _run_with_page_validation;
10
11
  private _read_node_data;
11
12
  private _handle_browser_action;
12
13
  handle_request(request: Request | string): Promise<Response>;