Skip to content

Commit 43c5d2e

Browse files
Harden cached replay against special and fallback throws
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 192abeb commit 43c5d2e

2 files changed

Lines changed: 99 additions & 1 deletion

File tree

src/agent/shared/run-cached-action.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,70 @@ describe("runCachedStep", () => {
282282
})
283283
);
284284
});
285+
286+
it("returns failed task output when special action execution throws", async () => {
287+
executeReplaySpecialAction.mockRejectedValue(new Error("navigation failed"));
288+
289+
const result = await runCachedStep({
290+
page: createMockPage(),
291+
instruction: "go to app",
292+
cachedAction: {
293+
actionType: "goToUrl",
294+
arguments: ["https://example.com"],
295+
},
296+
tokenLimit: 8000,
297+
llm: createMockLLM(),
298+
mcpClient: undefined,
299+
variables: [],
300+
});
301+
302+
expect(result.status).toBe(TaskStatus.FAILED);
303+
expect(result.output).toContain("Failed to execute cached special action");
304+
expect(result.output).toContain("navigation failed");
305+
expect(result.replayStepMeta).toEqual(
306+
expect.objectContaining({
307+
usedCachedAction: true,
308+
fallbackUsed: false,
309+
retries: 1,
310+
})
311+
);
312+
});
313+
314+
it("returns failed output when perform fallback throws", async () => {
315+
executeReplaySpecialAction.mockResolvedValue(null);
316+
resolveXPathWithCDP.mockRejectedValue(new Error("xpath resolution failed"));
317+
const performFallback = jest
318+
.fn()
319+
.mockRejectedValue(new Error("perform fallback crashed"));
320+
321+
const result = await runCachedStep({
322+
page: createMockPage(),
323+
instruction: "click login",
324+
cachedAction: {
325+
actionType: "actElement",
326+
xpath: "//button[1]",
327+
method: "click",
328+
frameIndex: 0,
329+
arguments: [],
330+
},
331+
maxSteps: 1,
332+
tokenLimit: 8000,
333+
llm: createMockLLM(),
334+
mcpClient: undefined,
335+
variables: [],
336+
performFallback,
337+
});
338+
339+
expect(result.status).toBe(TaskStatus.FAILED);
340+
expect(result.output).toContain("Fallback perform failed");
341+
expect(result.output).toContain("perform fallback crashed");
342+
expect(result.replayStepMeta).toEqual(
343+
expect.objectContaining({
344+
usedCachedAction: true,
345+
fallbackUsed: true,
346+
retries: 1,
347+
cachedXPath: "//button[1]",
348+
})
349+
);
350+
});
285351
});

src/agent/shared/run-cached-action.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ export async function runCachedStep(
6666
: undefined,
6767
page,
6868
retries: 1,
69+
}).catch((error) => {
70+
const message = error instanceof Error ? error.message : String(error);
71+
return {
72+
taskId,
73+
status: TaskStatus.FAILED,
74+
steps: [],
75+
output: `Failed to execute cached special action: ${message}`,
76+
replayStepMeta: {
77+
usedCachedAction: true,
78+
fallbackUsed: false,
79+
retries: 1,
80+
cachedXPath: cachedAction.xpath ?? null,
81+
fallbackXPath: null,
82+
fallbackElementId: null,
83+
},
84+
} satisfies TaskOutput;
6985
});
7086
if (specialActionResult) {
7187
return specialActionResult;
@@ -138,7 +154,23 @@ export async function runCachedStep(
138154

139155
// All cached attempts failed; optionally fall back to LLM perform
140156
if (params.performFallback) {
141-
const fb = await params.performFallback(instruction);
157+
const fb = await params.performFallback(instruction).catch((error) => {
158+
const message = error instanceof Error ? error.message : String(error);
159+
return {
160+
taskId,
161+
status: TaskStatus.FAILED,
162+
steps: [],
163+
output: `Fallback perform failed: ${message}`,
164+
replayStepMeta: {
165+
usedCachedAction: true,
166+
fallbackUsed: true,
167+
retries: maxSteps,
168+
cachedXPath: cachedAction.xpath ?? null,
169+
fallbackXPath: null,
170+
fallbackElementId: null,
171+
},
172+
} satisfies TaskOutput;
173+
});
142174
if (debug) {
143175
const cachedXPath = cachedAction.xpath || "N/A";
144176
const resolvedXPath = fb.replayStepMeta?.fallbackXPath || "N/A";

0 commit comments

Comments
 (0)