@browserbasehq/browse-cli 0.2.0-alpha-4ff3bb831a6ef6e2d57148e7afb68ea8d23e395d → 0.2.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 (2) hide show
  1. package/dist/index.js +451 -53
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -15214,7 +15214,7 @@ var init_version = __esm({
15214
15214
  "../core/dist/esm/lib/version.js"() {
15215
15215
  "use strict";
15216
15216
  init_cjs_shims();
15217
- STAGEHAND_VERSION = "3.1.0";
15217
+ STAGEHAND_VERSION = "3.2.0";
15218
15218
  }
15219
15219
  });
15220
15220
 
@@ -103029,6 +103029,155 @@ function createAgentTools(v32, options) {
103029
103029
 
103030
103030
  // ../core/dist/esm/lib/v3/agent/prompts/agentSystemPrompt.js
103031
103031
  init_cjs_shims();
103032
+
103033
+ // ../core/dist/esm/lib/v3/agent/utils/captchaSolver.js
103034
+ init_cjs_shims();
103035
+ var SOLVING_STARTED = "browserbase-solving-started";
103036
+ var SOLVING_FINISHED = "browserbase-solving-finished";
103037
+ var SOLVING_ERRORED = "browserbase-solving-errored";
103038
+ var SOLVE_TIMEOUT_MS = 9e4;
103039
+ var CAPTCHA_SOLVED_MSG = "A captcha was automatically detected and solved \u2014 no further interaction with the captcha is needed, even if it does not visually appear solved. Do not click the captcha checkbox, widget, or challenge again. Continue with your task.";
103040
+ var CAPTCHA_ERRORED_MSG = "A captcha was detected but the automatic captcha solver failed to solve it. You may need to try a different approach or navigate around the captcha.";
103041
+ var CAPTCHA_SYSTEM_PROMPT_NOTE = "Captchas on this page are automatically detected and solved by the browser environment. Do not interact with or attempt to solve any captchas yourself \u2014 they will be handled for you. Do not click the captcha checkbox, widget, or challenge again after it has been solved, even if it still looks unresolved. Continue with your task as if the captcha does not exist.";
103042
+ var CAPTCHA_CUA_SYSTEM_PROMPT_NOTE = "\n\nCaptchas on this page are automatically detected and solved by the browser environment. Do not interact with or attempt to solve any captchas yourself \u2014 they will be handled for you. Continue with your task as if the captcha does not exist.";
103043
+ var CaptchaSolver = class {
103044
+ solving = false;
103045
+ _solvedSinceLastConsume = false;
103046
+ _erroredSinceLastConsume = false;
103047
+ listener = null;
103048
+ attachedPage = null;
103049
+ pageProvider = null;
103050
+ /** Shared promise that all concurrent waitIfSolving() callers await. */
103051
+ waitPromise = null;
103052
+ /** Resolves the shared waitPromise. */
103053
+ resolveWait = null;
103054
+ /** Timeout handle for the 90s deadline. */
103055
+ waitTimer = null;
103056
+ /**
103057
+ * Initialise with a callback that returns the current active page.
103058
+ * The listener is lazily (re-)attached whenever the active page changes.
103059
+ */
103060
+ init(pageProvider) {
103061
+ this.pageProvider = pageProvider;
103062
+ }
103063
+ /** Whether a captcha solve is currently in progress. */
103064
+ isSolving() {
103065
+ return this.solving;
103066
+ }
103067
+ /**
103068
+ * Ensure the console listener is attached to the current active page.
103069
+ * If the active page has changed since the last call, the old listener
103070
+ * is removed and a new one is installed.
103071
+ */
103072
+ async ensureAttached() {
103073
+ if (!this.pageProvider)
103074
+ return;
103075
+ const page = await this.pageProvider();
103076
+ if (page === this.attachedPage)
103077
+ return;
103078
+ this.detachListener();
103079
+ this.attachedPage = page;
103080
+ this.listener = (msg) => {
103081
+ const text2 = msg.text();
103082
+ if (text2 === SOLVING_STARTED) {
103083
+ this.solving = true;
103084
+ } else if (text2 === SOLVING_FINISHED) {
103085
+ this.solving = false;
103086
+ this._solvedSinceLastConsume = true;
103087
+ this.settle();
103088
+ } else if (text2 === SOLVING_ERRORED) {
103089
+ this.solving = false;
103090
+ this._erroredSinceLastConsume = true;
103091
+ this.settle();
103092
+ }
103093
+ };
103094
+ page.on("console", this.listener);
103095
+ }
103096
+ /**
103097
+ * Returns a promise that resolves immediately if no captcha is being
103098
+ * solved, or blocks until the solver finishes, errors, or the 90s
103099
+ * timeout is reached.
103100
+ *
103101
+ * Also re-attaches the listener to the current active page if it has
103102
+ * changed since the last call.
103103
+ *
103104
+ * All concurrent callers share the same promise, so no waiter is
103105
+ * orphaned.
103106
+ */
103107
+ async waitIfSolving() {
103108
+ await this.ensureAttached();
103109
+ if (!this.solving)
103110
+ return;
103111
+ if (this.waitPromise)
103112
+ return this.waitPromise;
103113
+ this.waitPromise = new Promise((resolve4) => {
103114
+ this.resolveWait = resolve4;
103115
+ this.waitTimer = setTimeout(() => {
103116
+ this.solving = false;
103117
+ this._erroredSinceLastConsume = true;
103118
+ this.settle();
103119
+ }, SOLVE_TIMEOUT_MS);
103120
+ });
103121
+ return this.waitPromise;
103122
+ }
103123
+ /**
103124
+ * Returns and resets the solve event flags.
103125
+ * Call after `waitIfSolving()` to check whether a captcha was solved
103126
+ * (or errored) since the last consume. This captures events even if
103127
+ * the solve completed between two `waitIfSolving()` calls.
103128
+ */
103129
+ consumeSolveResult() {
103130
+ const result = {
103131
+ solved: this._solvedSinceLastConsume,
103132
+ errored: this._erroredSinceLastConsume
103133
+ };
103134
+ this._solvedSinceLastConsume = false;
103135
+ this._erroredSinceLastConsume = false;
103136
+ return result;
103137
+ }
103138
+ /**
103139
+ * Remove the console listener and reset all state.
103140
+ */
103141
+ dispose() {
103142
+ this.detachListener();
103143
+ this.attachedPage = null;
103144
+ this.pageProvider = null;
103145
+ this.solving = false;
103146
+ this._solvedSinceLastConsume = false;
103147
+ this._erroredSinceLastConsume = false;
103148
+ this.settle();
103149
+ }
103150
+ // ------------------------------------------------------------------
103151
+ // Internal helpers
103152
+ // ------------------------------------------------------------------
103153
+ /** Remove the console listener from the currently attached page. */
103154
+ detachListener() {
103155
+ if (this.attachedPage && this.listener) {
103156
+ this.attachedPage.off("console", this.listener);
103157
+ }
103158
+ this.listener = null;
103159
+ if (this.solving) {
103160
+ this._erroredSinceLastConsume = true;
103161
+ }
103162
+ this.solving = false;
103163
+ this.settle();
103164
+ }
103165
+ /** Resolve the shared wait promise and clear the timeout. */
103166
+ settle() {
103167
+ if (this.waitTimer) {
103168
+ clearTimeout(this.waitTimer);
103169
+ this.waitTimer = null;
103170
+ }
103171
+ if (this.resolveWait) {
103172
+ const resolve4 = this.resolveWait;
103173
+ this.resolveWait = null;
103174
+ this.waitPromise = null;
103175
+ resolve4();
103176
+ }
103177
+ }
103178
+ };
103179
+
103180
+ // ../core/dist/esm/lib/v3/agent/prompts/agentSystemPrompt.js
103032
103181
  function buildToolsSection(isHybridMode, hasSearch, excludeTools) {
103033
103182
  const excludeSet = new Set(excludeTools ?? []);
103034
103183
  const hybridTools = [
@@ -103102,7 +103251,7 @@ ${toolLines}
103102
103251
  </tools>`;
103103
103252
  }
103104
103253
  function buildAgentSystemPrompt(options) {
103105
- const { url: url2, executionInstruction, mode, systemInstructions, isBrowserbase = false, excludeTools, variables, useSearch = false } = options;
103254
+ const { url: url2, executionInstruction, mode, systemInstructions, captchasAutoSolve = false, excludeTools, variables, useSearch = false } = options;
103106
103255
  const localeDate = (/* @__PURE__ */ new Date()).toLocaleDateString();
103107
103256
  const isoDate = (/* @__PURE__ */ new Date()).toISOString();
103108
103257
  const cdata = (text2) => `<![CDATA[${text2}]]>`;
@@ -103156,9 +103305,8 @@ function buildAgentSystemPrompt(options) {
103156
103305
  </secondary_tool>
103157
103306
  </step_1>
103158
103307
  </page_understanding_protocol>`;
103159
- const roadblocksSection = isBrowserbase ? `<roadblocks>
103160
- <note>captchas, popups, etc.</note>
103161
- <captcha>If you see a captcha, use the wait tool. It will automatically be solved by our internal solver.</captcha>
103308
+ const roadblocksSection = captchasAutoSolve ? `<roadblocks>
103309
+ <note>${CAPTCHA_SYSTEM_PROMPT_NOTE}</note>
103162
103310
  </roadblocks>` : "";
103163
103311
  const customInstructionsBlock = systemInstructions ? `<customInstructions>${cdata(systemInstructions)}</customInstructions>
103164
103312
  ` : "";
@@ -103541,7 +103689,8 @@ var V3AgentHandler = class {
103541
103689
  systemInstructions;
103542
103690
  mcpTools;
103543
103691
  mode;
103544
- constructor(v32, logger, llmClient, executionModel, systemInstructions, mcpTools, mode) {
103692
+ captchaAutoSolveEnabled;
103693
+ constructor(v32, logger, llmClient, executionModel, systemInstructions, mcpTools, mode, captchaAutoSolveEnabled) {
103545
103694
  this.v3 = v32;
103546
103695
  this.logger = logger;
103547
103696
  this.llmClient = llmClient;
@@ -103549,6 +103698,7 @@ var V3AgentHandler = class {
103549
103698
  this.systemInstructions = systemInstructions;
103550
103699
  this.mcpTools = mcpTools;
103551
103700
  this.mode = mode ?? "dom";
103701
+ this.captchaAutoSolveEnabled = captchaAutoSolveEnabled ?? false;
103552
103702
  }
103553
103703
  async prepareAgent(instructionOrOptions) {
103554
103704
  try {
@@ -103560,7 +103710,7 @@ var V3AgentHandler = class {
103560
103710
  executionInstruction: options.instruction,
103561
103711
  mode: this.mode,
103562
103712
  systemInstructions: this.systemInstructions,
103563
- isBrowserbase: this.v3.isBrowserbase,
103713
+ captchasAutoSolve: this.v3.isCaptchaAutoSolveEnabled,
103564
103714
  excludeTools: options.excludeTools,
103565
103715
  variables: options.variables,
103566
103716
  useSearch: options.useSearch
@@ -103609,9 +103759,42 @@ var V3AgentHandler = class {
103609
103759
  throw error46;
103610
103760
  }
103611
103761
  }
103612
- createPrepareStep(userCallback) {
103762
+ createPrepareStep(userCallback, captchaSolver) {
103613
103763
  return async (options) => {
103614
103764
  processMessages(options.messages);
103765
+ if (captchaSolver) {
103766
+ if (captchaSolver.isSolving()) {
103767
+ this.logger({
103768
+ category: "agent",
103769
+ message: "Captcha detected \u2014 waiting for Browserbase to solve it before continuing",
103770
+ level: 1
103771
+ });
103772
+ }
103773
+ await captchaSolver.waitIfSolving();
103774
+ const { solved, errored } = captchaSolver.consumeSolveResult();
103775
+ if (solved) {
103776
+ options.messages.push({
103777
+ role: "user",
103778
+ content: CAPTCHA_SOLVED_MSG
103779
+ });
103780
+ this.logger({
103781
+ category: "agent",
103782
+ message: "Captcha solved \u2014 injected notification into agent message stream",
103783
+ level: 1
103784
+ });
103785
+ }
103786
+ if (errored) {
103787
+ options.messages.push({
103788
+ role: "user",
103789
+ content: CAPTCHA_ERRORED_MSG
103790
+ });
103791
+ this.logger({
103792
+ category: "agent",
103793
+ message: "Captcha solver failed \u2014 injected error notification into agent message stream",
103794
+ level: 1
103795
+ });
103796
+ }
103797
+ }
103615
103798
  if (userCallback) {
103616
103799
  return userCallback(options);
103617
103800
  }
@@ -103678,6 +103861,7 @@ var V3AgentHandler = class {
103678
103861
  currentPageUrl: ""
103679
103862
  };
103680
103863
  let messages = [];
103864
+ let captchaSolver;
103681
103865
  try {
103682
103866
  const { options: preparedOptions, maxSteps, systemPrompt, allTools, messages: preparedMessages, wrappedModel, initialPageUrl } = await this.prepareAgent(instructionOrOptions);
103683
103867
  if (shouldHighlightCursor && this.mode === "hybrid") {
@@ -103685,6 +103869,10 @@ var V3AgentHandler = class {
103685
103869
  await page.enableCursorOverlay().catch(() => {
103686
103870
  });
103687
103871
  }
103872
+ if (this.captchaAutoSolveEnabled) {
103873
+ captchaSolver = new CaptchaSolver();
103874
+ captchaSolver.init(() => this.v3.context.awaitActivePage());
103875
+ }
103688
103876
  messages = preparedMessages;
103689
103877
  state.currentPageUrl = initialPageUrl;
103690
103878
  const callbacks = instructionOrOptions.callbacks;
@@ -103707,7 +103895,7 @@ var V3AgentHandler = class {
103707
103895
  stopWhen: (result2) => this.handleStop(result2, maxSteps),
103708
103896
  temperature: 1,
103709
103897
  toolChoice: "auto",
103710
- prepareStep: this.createPrepareStep(callbacks?.prepareStep),
103898
+ prepareStep: this.createPrepareStep(callbacks?.prepareStep, captchaSolver),
103711
103899
  onStepFinish: this.createStepHandler(state, callbacks?.onStepFinish),
103712
103900
  abortSignal: preparedOptions.signal,
103713
103901
  providerOptions: {
@@ -103739,6 +103927,8 @@ var V3AgentHandler = class {
103739
103927
  completed: false,
103740
103928
  messages
103741
103929
  };
103930
+ } finally {
103931
+ captchaSolver?.dispose();
103742
103932
  }
103743
103933
  }
103744
103934
  async stream(instructionOrOptions) {
@@ -103750,6 +103940,11 @@ var V3AgentHandler = class {
103750
103940
  await page.enableCursorOverlay().catch(() => {
103751
103941
  });
103752
103942
  }
103943
+ let captchaSolver;
103944
+ if (this.captchaAutoSolveEnabled) {
103945
+ captchaSolver = new CaptchaSolver();
103946
+ captchaSolver.init(() => this.v3.context.awaitActivePage());
103947
+ }
103753
103948
  const callbacks = instructionOrOptions.callbacks;
103754
103949
  const state = {
103755
103950
  collectedReasoning: [],
@@ -103774,45 +103969,57 @@ var V3AgentHandler = class {
103774
103969
  });
103775
103970
  rejectResult(error46);
103776
103971
  };
103777
- const streamResult = this.llmClient.streamText({
103778
- model: wrappedModel,
103779
- messages: prependSystemMessage(systemPrompt, messages),
103780
- tools: allTools,
103781
- stopWhen: (result) => this.handleStop(result, maxSteps),
103782
- temperature: 1,
103783
- toolChoice: "auto",
103784
- prepareStep: this.createPrepareStep(callbacks?.prepareStep),
103785
- onStepFinish: this.createStepHandler(state, callbacks?.onStepFinish),
103786
- onError: (event) => {
103787
- if (callbacks?.onError) {
103788
- callbacks.onError(event);
103789
- }
103790
- handleError(event.error);
103791
- },
103792
- onChunk: callbacks?.onChunk,
103793
- onFinish: (event) => {
103794
- if (callbacks?.onFinish) {
103795
- callbacks.onFinish(event);
103796
- }
103797
- const allMessages = [...messages, ...event.response?.messages || []];
103798
- this.ensureDone(state, wrappedModel, allMessages, options.instruction, options.output, this.logger).then((doneResult) => {
103799
- const result = this.consolidateMetricsAndResult(startTime, state, doneResult.messages, event, maxSteps, doneResult.output);
103800
- resolveResult(result);
103801
- });
103802
- },
103803
- onAbort: (event) => {
103804
- if (callbacks?.onAbort) {
103805
- callbacks.onAbort(event);
103972
+ let streamResult;
103973
+ try {
103974
+ streamResult = this.llmClient.streamText({
103975
+ model: wrappedModel,
103976
+ messages: prependSystemMessage(systemPrompt, messages),
103977
+ tools: allTools,
103978
+ stopWhen: (result) => this.handleStop(result, maxSteps),
103979
+ temperature: 1,
103980
+ toolChoice: "auto",
103981
+ prepareStep: this.createPrepareStep(callbacks?.prepareStep, captchaSolver),
103982
+ onStepFinish: this.createStepHandler(state, callbacks?.onStepFinish),
103983
+ onError: (event) => {
103984
+ captchaSolver?.dispose();
103985
+ if (callbacks?.onError) {
103986
+ callbacks.onError(event);
103987
+ }
103988
+ handleError(event.error);
103989
+ },
103990
+ onChunk: callbacks?.onChunk,
103991
+ onFinish: (event) => {
103992
+ captchaSolver?.dispose();
103993
+ if (callbacks?.onFinish) {
103994
+ callbacks.onFinish(event);
103995
+ }
103996
+ const allMessages = [
103997
+ ...messages,
103998
+ ...event.response?.messages || []
103999
+ ];
104000
+ this.ensureDone(state, wrappedModel, allMessages, options.instruction, options.output, this.logger).then((doneResult) => {
104001
+ const result = this.consolidateMetricsAndResult(startTime, state, doneResult.messages, event, maxSteps, doneResult.output);
104002
+ resolveResult(result);
104003
+ });
104004
+ },
104005
+ onAbort: (event) => {
104006
+ captchaSolver?.dispose();
104007
+ if (callbacks?.onAbort) {
104008
+ callbacks.onAbort(event);
104009
+ }
104010
+ const reason = options.signal?.reason ? String(options.signal.reason) : "Stream was aborted";
104011
+ rejectResult(new AgentAbortError(reason));
104012
+ },
104013
+ abortSignal: options.signal,
104014
+ providerOptions: {
104015
+ google: { mediaResolution: "MEDIA_RESOLUTION_HIGH" },
104016
+ openai: { store: false }
103806
104017
  }
103807
- const reason = options.signal?.reason ? String(options.signal.reason) : "Stream was aborted";
103808
- rejectResult(new AgentAbortError(reason));
103809
- },
103810
- abortSignal: options.signal,
103811
- providerOptions: {
103812
- google: { mediaResolution: "MEDIA_RESOLUTION_HIGH" },
103813
- openai: { store: false }
103814
- }
103815
- });
104018
+ });
104019
+ } catch (error46) {
104020
+ captchaSolver?.dispose();
104021
+ throw error46;
104022
+ }
103816
104023
  const agentStreamResult = streamResult;
103817
104024
  agentStreamResult.result = resultPromise;
103818
104025
  return agentStreamResult;
@@ -107613,6 +107820,18 @@ var AgentClient = class {
107613
107820
  this.userProvidedInstructions = userProvidedInstructions;
107614
107821
  this.clientOptions = {};
107615
107822
  }
107823
+ /** Optional hook called at the top of every step in the agent loop. */
107824
+ preStepHook;
107825
+ setPreStepHook(handler) {
107826
+ this.preStepHook = handler;
107827
+ }
107828
+ /**
107829
+ * Optional ephemeral context note that should be sent to the next model turn.
107830
+ * Clients that do not support this can ignore it.
107831
+ */
107832
+ addContextNote(note) {
107833
+ void note;
107834
+ }
107616
107835
  };
107617
107836
 
107618
107837
  // ../core/dist/esm/lib/v3/agent/utils/imageCompression.js
@@ -107800,6 +108019,7 @@ var AnthropicCUAClient = class extends AgentClient {
107800
108019
  let totalInferenceTime = 0;
107801
108020
  try {
107802
108021
  while (!completed && currentStep < maxSteps) {
108022
+ await this.preStepHook?.();
107803
108023
  logger({
107804
108024
  category: "agent",
107805
108025
  message: `Executing step ${currentStep + 1}/${maxSteps}`,
@@ -114205,7 +114425,10 @@ var openai_default = OpenAI;
114205
114425
  init_sdkErrors();
114206
114426
  init_FlowLogger();
114207
114427
  init_esm3();
114428
+ var CAPTCHA_PROCEED_TOOL = "captchaSolvedProceed";
114208
114429
  var OpenAICUAClient = class extends AgentClient {
114430
+ pendingContextNotes = [];
114431
+ captchaSolvedToolActive = false;
114209
114432
  apiKey;
114210
114433
  organization;
114211
114434
  baseURL;
@@ -114255,6 +114478,12 @@ var OpenAICUAClient = class extends AgentClient {
114255
114478
  setSafetyConfirmationHandler(handler) {
114256
114479
  this.safetyConfirmationHandler = handler;
114257
114480
  }
114481
+ addContextNote(note) {
114482
+ this.pendingContextNotes.push(note);
114483
+ if (note.toLowerCase().includes("captcha")) {
114484
+ this.captchaSolvedToolActive = true;
114485
+ }
114486
+ }
114258
114487
  /**
114259
114488
  * Execute a task with the OpenAI CUA
114260
114489
  * This is the main entry point for the agent
@@ -114277,6 +114506,7 @@ var OpenAICUAClient = class extends AgentClient {
114277
114506
  let totalInferenceTime = 0;
114278
114507
  try {
114279
114508
  while (!completed && currentStep < maxSteps) {
114509
+ await this.preStepHook?.();
114280
114510
  logger({
114281
114511
  category: "agent",
114282
114512
  message: `Executing step ${currentStep + 1}/${maxSteps}`,
@@ -114291,6 +114521,16 @@ var OpenAICUAClient = class extends AgentClient {
114291
114521
  previousResponseId = result.responseId;
114292
114522
  if (!completed) {
114293
114523
  inputItems = result.nextInputItems;
114524
+ const contextNotes = this.drainContextNotes();
114525
+ if (contextNotes.length > 0) {
114526
+ inputItems = [
114527
+ ...inputItems,
114528
+ ...contextNotes.map((note) => ({
114529
+ role: "user",
114530
+ content: note
114531
+ }))
114532
+ ];
114533
+ }
114294
114534
  }
114295
114535
  if (result.message) {
114296
114536
  messageList.push(result.message);
@@ -114358,7 +114598,7 @@ var OpenAICUAClient = class extends AgentClient {
114358
114598
  if (item.type === "computer_call" && this.isComputerCallItem(item)) {
114359
114599
  logger({
114360
114600
  category: "agent",
114361
- message: `Found computer_call: ${item.action.type}, call_id: ${item.call_id}`,
114601
+ message: `Found computer_call: ${item.action.type}, payload: ${JSON.stringify(item.action)}, call_id: ${item.call_id}`,
114362
114602
  level: 2
114363
114603
  });
114364
114604
  const action = this.convertComputerCallToAction(item);
@@ -114508,6 +114748,20 @@ var OpenAICUAClient = class extends AgentClient {
114508
114748
  ...customTools
114509
114749
  ];
114510
114750
  }
114751
+ if (this.captchaSolvedToolActive) {
114752
+ requestParams.tools = [
114753
+ ...requestParams.tools,
114754
+ {
114755
+ type: "function",
114756
+ name: CAPTCHA_PROCEED_TOOL,
114757
+ function: {
114758
+ name: CAPTCHA_PROCEED_TOOL,
114759
+ description: "The captcha on this page was solved automatically. Call this tool to confirm and continue with your task instead of asking the user for permission.",
114760
+ parameters: { type: "object", properties: {}, required: [] }
114761
+ }
114762
+ }
114763
+ ];
114764
+ }
114511
114765
  if (previousResponseId) {
114512
114766
  requestParams.previous_response_id = previousResponseId;
114513
114767
  }
@@ -114634,6 +114888,15 @@ var OpenAICUAClient = class extends AgentClient {
114634
114888
  }
114635
114889
  }
114636
114890
  } else if (item.type === "function_call" && this.isFunctionCallItem(item)) {
114891
+ if (item.name === CAPTCHA_PROCEED_TOOL) {
114892
+ this.captchaSolvedToolActive = false;
114893
+ nextInputItems.push({
114894
+ type: "function_call_output",
114895
+ call_id: item.call_id,
114896
+ output: "Confirmed. The captcha is solved. Continue completing the original task autonomously without asking for further confirmation."
114897
+ });
114898
+ continue;
114899
+ }
114637
114900
  try {
114638
114901
  const action = this.convertFunctionCallToAction(item);
114639
114902
  if (action && this.actionHandler) {
@@ -114704,6 +114967,14 @@ var OpenAICUAClient = class extends AgentClient {
114704
114967
  // Spread all properties from the action
114705
114968
  };
114706
114969
  }
114970
+ drainContextNotes() {
114971
+ if (this.pendingContextNotes.length === 0) {
114972
+ return [];
114973
+ }
114974
+ const notes = [...this.pendingContextNotes];
114975
+ this.pendingContextNotes = [];
114976
+ return notes;
114977
+ }
114707
114978
  convertFunctionCallToAction(call) {
114708
114979
  try {
114709
114980
  const args = JSON.parse(call.arguments);
@@ -115061,6 +115332,7 @@ var GoogleCUAClient = class extends AgentClient {
115061
115332
  let totalInferenceTime = 0;
115062
115333
  try {
115063
115334
  while (!completed && currentStep < maxSteps) {
115335
+ await this.preStepHook?.();
115064
115336
  logger({
115065
115337
  category: "agent",
115066
115338
  message: `Executing step ${currentStep + 1}/${maxSteps}`,
@@ -116228,6 +116500,7 @@ ${textPrompt}`;
116228
116500
  this.actionHistory = [];
116229
116501
  try {
116230
116502
  while (!completed && currentStep < maxSteps) {
116503
+ await this.preStepHook?.();
116231
116504
  logger({
116232
116505
  category: "agent",
116233
116506
  message: `Executing step ${currentStep + 1}/${maxSteps}`,
@@ -116355,6 +116628,9 @@ var V3CuaAgentHandler = class {
116355
116628
  agentClient;
116356
116629
  options;
116357
116630
  highlightCursor;
116631
+ captchaSolver = null;
116632
+ captchaClickGuardRemaining = 0;
116633
+ currentInstruction = "";
116358
116634
  constructor(v32, logger, options, tools) {
116359
116635
  this.v3 = v32;
116360
116636
  this.logger = logger;
@@ -116383,7 +116659,28 @@ var V3CuaAgentHandler = class {
116383
116659
  });
116384
116660
  this.agentClient.setActionHandler(async (action) => {
116385
116661
  this.ensureNotClosed();
116662
+ if (this.captchaSolver) {
116663
+ if (this.captchaSolver.isSolving()) {
116664
+ this.logger({
116665
+ category: "agent",
116666
+ message: "Captcha detected \u2014 waiting for Browserbase to solve it before continuing",
116667
+ level: 1
116668
+ });
116669
+ }
116670
+ await this.captchaSolver.waitIfSolving();
116671
+ this.handleCaptchaSolveResult(this.captchaSolver.consumeSolveResult());
116672
+ }
116386
116673
  action.pageUrl = (await this.v3.context.awaitActivePage()).url();
116674
+ if (await this.shouldSkipSolvedCaptchaInteraction(action)) {
116675
+ this.captchaClickGuardRemaining = Math.max(0, this.captchaClickGuardRemaining - 1);
116676
+ this.agentClient.addContextNote(`The captcha has already been solved automatically. Do not click the captcha checkbox, widget, or challenge again. Continue with the original task outside the captcha area. Original task: ${this.currentInstruction}`);
116677
+ this.logger({
116678
+ category: "agent",
116679
+ message: "Skipped click on solved captcha widget \u2014 injected follow-up guidance",
116680
+ level: 1
116681
+ });
116682
+ return;
116683
+ }
116387
116684
  const defaultDelay = 500;
116388
116685
  const waitBetween = this.options.clientOptions?.waitBetweenActions || defaultDelay;
116389
116686
  try {
@@ -116439,6 +116736,7 @@ var V3CuaAgentHandler = class {
116439
116736
  const options = typeof optionsOrInstruction === "string" ? { instruction: optionsOrInstruction } : optionsOrInstruction;
116440
116737
  this.setSafetyConfirmationHandler(options.callbacks?.onSafetyConfirmation);
116441
116738
  this.highlightCursor = options.highlightCursor !== false;
116739
+ this.currentInstruction = options.instruction;
116442
116740
  const page = await this.v3.context.awaitActivePage();
116443
116741
  const currentUrl = page.url();
116444
116742
  if (!currentUrl || currentUrl === "about:blank") {
@@ -116449,6 +116747,21 @@ var V3CuaAgentHandler = class {
116449
116747
  });
116450
116748
  await page.goto("https://www.google.com", { waitUntil: "load" });
116451
116749
  }
116750
+ if (this.v3.isCaptchaAutoSolveEnabled) {
116751
+ this.captchaSolver = new CaptchaSolver();
116752
+ this.captchaSolver.init(() => this.v3.context.awaitActivePage());
116753
+ this.agentClient.setPreStepHook(async () => {
116754
+ if (this.captchaSolver?.isSolving()) {
116755
+ this.logger({
116756
+ category: "agent",
116757
+ message: "Captcha detected \u2014 waiting for Browserbase to solve it before continuing",
116758
+ level: 1
116759
+ });
116760
+ }
116761
+ await this.captchaSolver?.waitIfSolving();
116762
+ this.handleCaptchaSolveResult(this.captchaSolver?.consumeSolveResult());
116763
+ });
116764
+ }
116452
116765
  if (this.highlightCursor) {
116453
116766
  try {
116454
116767
  await this.injectCursor();
@@ -116462,7 +116775,13 @@ var V3CuaAgentHandler = class {
116462
116775
  }
116463
116776
  }
116464
116777
  const start = Date.now();
116465
- const result = await this.agent.execute({ options, logger: this.logger });
116778
+ let result;
116779
+ try {
116780
+ result = await this.agent.execute({ options, logger: this.logger });
116781
+ } finally {
116782
+ this.captchaSolver?.dispose();
116783
+ this.captchaSolver = null;
116784
+ }
116466
116785
  const inferenceTimeMs = Date.now() - start;
116467
116786
  if (result.usage) {
116468
116787
  this.v3.updateMetrics(V3FunctionName.AGENT, result.usage.input_tokens, result.usage.output_tokens, result.usage.reasoning_tokens ?? 0, result.usage.cached_input_tokens ?? 0, inferenceTimeMs);
@@ -116797,6 +117116,78 @@ var V3CuaAgentHandler = class {
116797
117116
  return null;
116798
117117
  }
116799
117118
  }
117119
+ handleCaptchaSolveResult(result) {
117120
+ if (!result)
117121
+ return;
117122
+ if (result.solved) {
117123
+ this.captchaClickGuardRemaining = 3;
117124
+ this.agentClient.addContextNote(CAPTCHA_SOLVED_MSG);
117125
+ this.logger({
117126
+ category: "agent",
117127
+ message: "Captcha solved \u2014 continuing with task",
117128
+ level: 1
117129
+ });
117130
+ }
117131
+ if (result.errored) {
117132
+ this.captchaClickGuardRemaining = 0;
117133
+ this.agentClient.addContextNote(CAPTCHA_ERRORED_MSG);
117134
+ this.logger({
117135
+ category: "agent",
117136
+ message: "Captcha solver failed or errored",
117137
+ level: 1
117138
+ });
117139
+ }
117140
+ }
117141
+ async shouldSkipSolvedCaptchaInteraction(action) {
117142
+ if (this.captchaClickGuardRemaining <= 0) {
117143
+ return false;
117144
+ }
117145
+ if (action.type !== "click") {
117146
+ return false;
117147
+ }
117148
+ const x3 = action.x;
117149
+ const y2 = action.y;
117150
+ if (typeof x3 !== "number" || typeof y2 !== "number") {
117151
+ return false;
117152
+ }
117153
+ try {
117154
+ const page = await this.v3.context.awaitActivePage();
117155
+ const boxes = await page.evaluate(() => {
117156
+ const selectors = [
117157
+ 'iframe[title*="reCAPTCHA"]',
117158
+ 'iframe[src*="recaptcha"]',
117159
+ 'iframe[src*="hcaptcha"]',
117160
+ 'iframe[src*="turnstile"]',
117161
+ ".g-recaptcha",
117162
+ "[data-sitekey]",
117163
+ '[class*="captcha"]',
117164
+ '[id*="captcha"]'
117165
+ ];
117166
+ const seen = /* @__PURE__ */ new Set();
117167
+ const bounds = [];
117168
+ for (const selector of selectors) {
117169
+ for (const element of document.querySelectorAll(selector)) {
117170
+ if (seen.has(element))
117171
+ continue;
117172
+ seen.add(element);
117173
+ const rect = element.getBoundingClientRect();
117174
+ if (rect.width <= 0 || rect.height <= 0)
117175
+ continue;
117176
+ bounds.push({
117177
+ left: rect.left,
117178
+ top: rect.top,
117179
+ right: rect.right,
117180
+ bottom: rect.bottom
117181
+ });
117182
+ }
117183
+ }
117184
+ return bounds;
117185
+ });
117186
+ return boxes.some((box) => x3 >= box.left && x3 <= box.right && y2 >= box.top && y2 <= box.bottom);
117187
+ } catch {
117188
+ return false;
117189
+ }
117190
+ }
116800
117191
  async injectCursor() {
116801
117192
  try {
116802
117193
  const page = await this.v3.context.awaitActivePage();
@@ -162401,6 +162792,13 @@ var V3 = (() => {
162401
162792
  get isBrowserbase() {
162402
162793
  return this.state.kind === "BROWSERBASE";
162403
162794
  }
162795
+ /**
162796
+ * Returns true if captcha auto-solving is enabled on Browserbase.
162797
+ * Defaults to true when not explicitly set to false.
162798
+ */
162799
+ get isCaptchaAutoSolveEnabled() {
162800
+ return this.isBrowserbase && this.opts.browserbaseSessionCreateParams?.browserSettings?.solveCaptchas !== false;
162801
+ }
162404
162802
  /**
162405
162803
  * Returns true if advancedStealth is enabled in Browserbase settings.
162406
162804
  */
@@ -163421,7 +163819,7 @@ var V3 = (() => {
163421
163819
  const tools = options?.integrations ? await resolveTools(options.integrations, options.tools) : options?.tools ?? {};
163422
163820
  const agentLlmClient = options?.model ? this.resolveLlmClient(options.model) : this.llmClient;
163423
163821
  const resolvedExecutionModel = options?.executionModel ?? options?.model;
163424
- const handler = new V3AgentHandler(this, this.logger, agentLlmClient, resolvedExecutionModel, options?.systemPrompt, tools, options?.mode);
163822
+ const handler = new V3AgentHandler(this, this.logger, agentLlmClient, resolvedExecutionModel, options?.systemPrompt, tools, options?.mode, this.isCaptchaAutoSolveEnabled);
163425
163823
  const resolvedOptions = typeof instructionOrOptions === "string" ? {
163426
163824
  instruction: instructionOrOptions,
163427
163825
  toolTimeout: DEFAULT_AGENT_TOOL_TIMEOUT_MS
@@ -163510,8 +163908,8 @@ var V3 = (() => {
163510
163908
  const handler = new V3CuaAgentHandler(this, this.logger, {
163511
163909
  modelName,
163512
163910
  clientOptions,
163513
- userProvidedInstructions: options.systemPrompt ?? `You are a helpful assistant that can use a web browser.
163514
- Do not ask follow up questions, the user will trust your judgement.`
163911
+ userProvidedInstructions: (options.systemPrompt ?? `You are a helpful assistant that can use a web browser.
163912
+ Do not ask follow up questions, the user will trust your judgement.`) + (this.isCaptchaAutoSolveEnabled ? CAPTCHA_CUA_SYSTEM_PROMPT_NOTE : "")
163515
163913
  }, tools);
163516
163914
  const resolvedOptions = typeof instructionOrOptions === "string" ? {
163517
163915
  instruction: instructionOrOptions,
@@ -164115,7 +164513,7 @@ var import_child_process4 = require("child_process");
164115
164513
  var readline = __toESM(require("readline"));
164116
164514
 
164117
164515
  // package.json
164118
- var version3 = "0.2.0-alpha-4ff3bb831a6ef6e2d57148e7afb68ea8d23e395d";
164516
+ var version3 = "0.2.0";
164119
164517
 
164120
164518
  // src/index.ts
164121
164519
  var program = new import_commander.Command();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbasehq/browse-cli",
3
- "version": "0.2.0-alpha-4ff3bb831a6ef6e2d57148e7afb68ea8d23e395d",
3
+ "version": "0.2.0",
4
4
  "description": "Browser automation CLI for AI agents, built on Stagehand",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "pino": "^9.6.0",
48
48
  "pino-pretty": "^13.0.0",
49
49
  "ws": "^8.18.0",
50
- "@browserbasehq/stagehand": "3.2.0-alpha-4ff3bb831a6ef6e2d57148e7afb68ea8d23e395d"
50
+ "@browserbasehq/stagehand": "3.2.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^20.11.30",