Skip to content

Commit 4c07c44

Browse files
authored
added fallback try to actFromObserveResult (#480)
* added fallback try to actFromObserveResult * linting fix * defaulting flags, adding comments, added selfHeal flag * adding proxy param to page * moved selfHeal flag to stagehand config from act param
1 parent 868bd6e commit 4c07c44

File tree

5 files changed

+64
-10
lines changed

5 files changed

+64
-10
lines changed

Diff for: .changeset/gentle-grapes-join.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
Adding a fallback try on actFromObserveResult to use the description from observe and call regular act.

Diff for: lib/StagehandPage.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class StagehandPage {
7171
stagehandContext: this.intContext,
7272
llmClient: llmClient,
7373
userProvidedInstructions,
74+
selfHeal: this.stagehand.selfHeal,
7475
});
7576
this.extractHandler = new StagehandExtractHandler({
7677
stagehand: this.stagehand,
@@ -305,7 +306,7 @@ export class StagehandPage {
305306
return this.actHandler.actFromObserveResult(observeResult);
306307
} else {
307308
// If it's an object but no selector/method,
308-
// check that its truly ActOptions (i.e., has an `action` field).
309+
// check that it's truly ActOptions (i.e., has an `action` field).
309310
if (!("action" in actionOrOptions)) {
310311
throw new Error(
311312
"Invalid argument. Valid arguments are: a string, an ActOptions object, " +
@@ -509,7 +510,7 @@ export class StagehandPage {
509510
modelClientOptions,
510511
useVision, // still destructure but will not pass it on
511512
domSettleTimeoutMs,
512-
returnAction = false,
513+
returnAction = true,
513514
onlyVisible = false,
514515
useAccessibilityTree,
515516
drawOverlay,

Diff for: lib/handlers/actHandler.ts

+52-8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export class StagehandActHandler {
2929
[key: string]: { result: string; action: string };
3030
};
3131
private readonly userProvidedInstructions?: string;
32+
private readonly selfHeal: boolean;
3233

3334
constructor({
3435
verbose,
@@ -37,6 +38,7 @@ export class StagehandActHandler {
3738
logger,
3839
stagehandPage,
3940
userProvidedInstructions,
41+
selfHeal,
4042
}: {
4143
verbose: 0 | 1 | 2;
4244
llmProvider: LLMProvider;
@@ -46,6 +48,7 @@ export class StagehandActHandler {
4648
stagehandPage: StagehandPage;
4749
stagehandContext: StagehandContext;
4850
userProvidedInstructions?: string;
51+
selfHeal: boolean;
4952
}) {
5053
this.verbose = verbose;
5154
this.llmProvider = llmProvider;
@@ -55,6 +58,7 @@ export class StagehandActHandler {
5558
this.actions = {};
5659
this.stagehandPage = stagehandPage;
5760
this.userProvidedInstructions = userProvidedInstructions;
61+
this.selfHeal = selfHeal;
5862
}
5963

6064
/**
@@ -90,21 +94,61 @@ export class StagehandActHandler {
9094
action: observe.description || `ObserveResult action (${method})`,
9195
};
9296
} catch (err) {
97+
if (!this.selfHeal) {
98+
this.logger({
99+
category: "action",
100+
message: "Error performing act from an ObserveResult",
101+
level: 1,
102+
auxiliary: {
103+
error: { value: err.message, type: "string" },
104+
trace: { value: err.stack, type: "string" },
105+
},
106+
});
107+
return {
108+
success: false,
109+
message: `Failed to perform act: ${err.message}`,
110+
action: observe.description || `ObserveResult action (${method})`,
111+
};
112+
}
113+
// We will try to use regular act on a failed ObserveResult-act if selfHeal is true
93114
this.logger({
94115
category: "action",
95-
message: "Error performing act from an ObserveResult",
116+
message:
117+
"Error performing act from an ObserveResult. Trying again with regular act method",
96118
level: 1,
97119
auxiliary: {
98120
error: { value: err.message, type: "string" },
99121
trace: { value: err.stack, type: "string" },
100122
observeResult: { value: JSON.stringify(observe), type: "object" },
101123
},
102124
});
103-
return {
104-
success: false,
105-
message: `Failed to perform act: ${err.message}`,
106-
action: observe.description || `ObserveResult action (${method})`,
107-
};
125+
try {
126+
// Remove redundancy from method-description
127+
const actCommand = observe.description
128+
.toLowerCase()
129+
.startsWith(method.toLowerCase())
130+
? observe.description
131+
: method
132+
? `${method} ${observe.description}`
133+
: observe.description;
134+
// Call act with the ObserveResult description
135+
await this.stagehandPage.act(actCommand);
136+
} catch (err) {
137+
this.logger({
138+
category: "action",
139+
message: "Error performing act from an ObserveResult",
140+
level: 1,
141+
auxiliary: {
142+
error: { value: err.message, type: "string" },
143+
trace: { value: err.stack, type: "string" },
144+
},
145+
});
146+
return {
147+
success: false,
148+
message: `Failed to perform act: ${err.message}`,
149+
action: observe.description || `ObserveResult action (${method})`,
150+
};
151+
}
108152
}
109153
}
110154

@@ -369,7 +413,7 @@ export class StagehandActHandler {
369413
const clickArg = args.length ? args[0] : undefined;
370414

371415
if (isRadio) {
372-
// if its a radio button, try to find a label to click
416+
// if it's a radio button, try to find a label to click
373417
const inputId = await locator.evaluate((el) => el.id);
374418
let labelLocator;
375419

@@ -380,7 +424,7 @@ export class StagehandActHandler {
380424
);
381425
}
382426
if (!labelLocator || (await labelLocator.count()) < 1) {
383-
// if no label was found or the label doesnt exist, check if
427+
// if no label was found or the label doesn't exist, check if
384428
// there is an ancestor <label>
385429
labelLocator = this.stagehandPage.page
386430
.locator(`xpath=${xpath}/ancestor::label`)

Diff for: lib/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ export class Stagehand {
327327
private contextPath?: string;
328328
private llmClient: LLMClient;
329329
private userProvidedInstructions?: string;
330+
public readonly selfHeal: boolean;
330331

331332
constructor(
332333
{
@@ -346,6 +347,7 @@ export class Stagehand {
346347
modelName,
347348
modelClientOptions,
348349
systemPrompt,
350+
selfHeal = true,
349351
}: ConstructorParams = {
350352
env: "BROWSERBASE",
351353
},
@@ -380,6 +382,7 @@ export class Stagehand {
380382
this.browserbaseSessionCreateParams = browserbaseSessionCreateParams;
381383
this.browserbaseSessionID = browserbaseSessionID;
382384
this.userProvidedInstructions = systemPrompt;
385+
this.selfHeal = selfHeal;
383386
}
384387

385388
public get logger(): (logLine: LogLine) => void {

Diff for: types/stagehand.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface ConstructorParams {
2626
* Instructions for stagehand.
2727
*/
2828
systemPrompt?: string;
29+
selfHeal?: boolean;
2930
}
3031

3132
export interface InitOptions {

0 commit comments

Comments
 (0)