Skip to content

Commit 1c389bd

Browse files
committed
add logging of full control flow
1 parent 44bb4f5 commit 1c389bd

File tree

7 files changed

+299
-34
lines changed

7 files changed

+299
-34
lines changed

packages/core/lib/v3/handlers/actHandler.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
performUnderstudyMethod,
2323
waitForDomNetworkQuiet,
2424
} from "./handlerUtils/actHandlerUtils";
25+
import { logStepStart, logStepEnd } from "../hierarchicalLogger";
2526

2627
export class ActHandler {
2728
private readonly llmClient: LLMClient;
@@ -73,6 +74,8 @@ export class ActHandler {
7374
async act(params: ActHandlerParams): Promise<ActResult> {
7475
const { instruction, page, variables, timeout, model } = params;
7576

77+
logStepStart("act", instruction, { timeout, hasVariables: !!variables });
78+
7679
const llmClient = this.resolveLlmClient(model);
7780

7881
const doObserveAndAct = async (): Promise<ActResult> => {
@@ -313,19 +316,23 @@ export class ActHandler {
313316
};
314317

315318
// Hard timeout for entire act() call → reject on timeout (align with extract/observe)
319+
let result: ActResult;
316320
if (!timeout) {
317-
return doObserveAndAct();
321+
result = await doObserveAndAct();
322+
} else {
323+
result = await Promise.race([
324+
doObserveAndAct(),
325+
new Promise<ActResult>((_, reject) => {
326+
setTimeout(
327+
() => reject(new Error(`act() timed out after ${timeout}ms`)),
328+
timeout,
329+
);
330+
}),
331+
]);
318332
}
319333

320-
return await Promise.race([
321-
doObserveAndAct(),
322-
new Promise<ActResult>((_, reject) => {
323-
setTimeout(
324-
() => reject(new Error(`act() timed out after ${timeout}ms`)),
325-
timeout,
326-
);
327-
}),
328-
]);
334+
logStepEnd("act", result.success, result.message);
335+
return result;
329336
}
330337

331338
async actFromObserveResult(

packages/core/lib/v3/handlers/extractHandler.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ModelConfiguration,
1616
} from "../types/public/model";
1717
import { StagehandInvalidArgumentError } from "../types/public/sdkErrors";
18+
import { logStepStart, logStepEnd } from "../hierarchicalLogger";
1819

1920
/**
2021
* Scans the provided Zod schema for any `z.string().url()` fields and
@@ -94,6 +95,12 @@ export class ExtractHandler {
9495
): Promise<z.infer<T> | { pageText: string }> {
9596
const { instruction, schema, page, selector, timeout, model } = params;
9697

98+
logStepStart("extract", instruction ?? "(no instruction - page text)", {
99+
hasSchema: !!schema,
100+
selector,
101+
timeout,
102+
});
103+
97104
const llmClient = this.resolveLlmClient(model);
98105

99106
const doExtract = async (): Promise<z.infer<T> | { pageText: string }> => {
@@ -220,16 +227,23 @@ export class ExtractHandler {
220227

221228
return output as z.infer<T>;
222229
};
223-
if (!timeout) return doExtract();
224-
225-
return await Promise.race([
226-
doExtract(),
227-
new Promise<z.infer<T> | { pageText: string }>((_, reject) => {
228-
setTimeout(
229-
() => reject(new Error(`extract() timed out after ${timeout}ms`)),
230-
timeout,
231-
);
232-
}),
233-
]);
230+
231+
let result: z.infer<T> | { pageText: string };
232+
if (!timeout) {
233+
result = await doExtract();
234+
} else {
235+
result = await Promise.race([
236+
doExtract(),
237+
new Promise<z.infer<T> | { pageText: string }>((_, reject) => {
238+
setTimeout(
239+
() => reject(new Error(`extract() timed out after ${timeout}ms`)),
240+
timeout,
241+
);
242+
}),
243+
]);
244+
}
245+
246+
logStepEnd("extract", true, "extraction completed");
247+
return result;
234248
}
235249
}

packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { resolveLocatorWithHops } from "../../understudy/deepLocator";
66
import type { Page } from "../../understudy/page";
77
import { v3Logger } from "../../logger";
88
import { StagehandClickError } from "../../types/public/sdkErrors";
9+
import {
10+
logActionStart,
11+
logActionEnd,
12+
} from "../../hierarchicalLogger";
913

1014
export class UnderstudyCommandException extends Error {
1115
constructor(message: string) {
@@ -42,6 +46,9 @@ export async function performUnderstudyMethod(
4246
domSettleTimeoutMs?: number,
4347
): Promise<void> {
4448
const selectorRaw = normalizeRootXPath(rawXPath);
49+
50+
logActionStart(method, selectorRaw, args);
51+
4552
// Unified resolver: supports '>>' hops and XPath across iframes
4653
const locator: Locator = await resolveLocatorWithHops(
4754
page,
@@ -97,13 +104,16 @@ export async function performUnderstudyMethod(
97104
level: 1,
98105
auxiliary: { method: { value: method, type: "string" } },
99106
});
107+
logActionEnd(method, false, `Method ${method} not supported`);
100108
throw new UnderstudyCommandException(
101109
`Method ${method} not supported`,
102110
);
103111
}
104112
}
105113

106114
await handlePossibleNavigation("action", selectorRaw, initialUrl, frame);
115+
116+
logActionEnd(method, true);
107117
} catch (e) {
108118
const msg = e instanceof Error ? e.message : String(e);
109119
const stack = e instanceof Error ? e.stack : undefined;
@@ -119,6 +129,7 @@ export async function performUnderstudyMethod(
119129
args: { value: JSON.stringify(args), type: "object" },
120130
},
121131
});
132+
logActionEnd(method, false, msg);
122133
throw new UnderstudyCommandException(msg);
123134
}
124135
}

packages/core/lib/v3/handlers/observeHandler.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ClientOptions,
1414
ModelConfiguration,
1515
} from "../types/public/model";
16+
import { logStepStart, logStepEnd } from "../hierarchicalLogger";
1617

1718
export class ObserveHandler {
1819
private readonly llmClient: LLMClient;
@@ -67,6 +68,8 @@ export class ObserveHandler {
6768
instruction ??
6869
"Find elements that can be used for any future actions in the page. These may be navigation links, related pages, section/subsection links, buttons, or other interactive elements. Be comprehensive: if there are multiple elements that may be relevant for future actions, return all of them.";
6970

71+
logStepStart("observe", effectiveInstruction, { timeout, selector });
72+
7073
const doObserve = async (): Promise<Action[]> => {
7174
v3Logger({
7275
category: "observation",
@@ -171,16 +174,22 @@ export class ObserveHandler {
171174
return elementsWithSelectors;
172175
};
173176

174-
if (!timeout) return doObserve();
175-
176-
return await Promise.race([
177-
doObserve(),
178-
new Promise<Action[]>((_, reject) => {
179-
setTimeout(
180-
() => reject(new Error(`observe() timed out after ${timeout}ms`)),
181-
timeout,
182-
);
183-
}),
184-
]);
177+
let result: Action[];
178+
if (!timeout) {
179+
result = await doObserve();
180+
} else {
181+
result = await Promise.race([
182+
doObserve(),
183+
new Promise<Action[]>((_, reject) => {
184+
setTimeout(
185+
() => reject(new Error(`observe() timed out after ${timeout}ms`)),
186+
timeout,
187+
);
188+
}),
189+
]);
190+
}
191+
192+
logStepEnd("observe", true, `observed ${result.length} actions`);
193+
return result;
185194
}
186195
}

packages/core/lib/v3/handlers/v3AgentHandler.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { V3FunctionName } from "../types/public/methods";
1313
import { mapToolResultToActions } from "../agent/utils/actionMapping";
1414
import { MissingLLMConfigurationError } from "../types/public/sdkErrors";
15+
import { logTaskStart, logTaskEnd } from "../hierarchicalLogger";
1516

1617
export class V3AgentHandler {
1718
private v3: V3;
@@ -46,6 +47,10 @@ export class V3AgentHandler {
4647
? { instruction: instructionOrOptions }
4748
: instructionOrOptions;
4849

50+
logTaskStart(options.instruction, {
51+
maxSteps: options.maxSteps,
52+
});
53+
4954
const maxSteps = options.maxSteps || 10;
5055
const actions: AgentAction[] = [];
5156
let finalMessage = "";
@@ -155,7 +160,7 @@ export class V3AgentHandler {
155160
);
156161
}
157162

158-
return {
163+
const agentResult = {
159164
success: completed,
160165
message: finalMessage || "Task execution completed",
161166
actions,
@@ -170,19 +175,26 @@ export class V3AgentHandler {
170175
}
171176
: undefined,
172177
};
178+
179+
logTaskEnd(agentResult.success, agentResult.message);
180+
return agentResult;
173181
} catch (error) {
174182
const errorMessage = error?.message ?? String(error);
175183
this.logger({
176184
category: "agent",
177185
message: `Error executing agent task: ${errorMessage}`,
178186
level: 0,
179187
});
180-
return {
188+
189+
const errorResult = {
181190
success: false,
182191
actions,
183192
message: `Failed to execute task: ${errorMessage}`,
184193
completed: false,
185194
};
195+
196+
logTaskEnd(false, errorResult.message);
197+
return errorResult;
186198
}
187199
}
188200

0 commit comments

Comments
 (0)