Skip to content

Commit 2a4d485

Browse files
committed
Fix browser act stop reason and batch stop handling
1 parent e77e923 commit 2a4d485

3 files changed

Lines changed: 21 additions & 6 deletions

File tree

packages/agent/src/translator/browser-act.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export async function runBrowserAct(action: CuaActionBrowserAct, runtime: Browse
102102
deadline,
103103
);
104104
expectation = expectationEvidence(waitResult);
105-
if (waitResult.status === "timed_out") timeout = deadline.reason;
105+
if (waitResult.status === "timed_out" && expectation.status !== "failed") timeout = deadline.reason;
106106
}
107107
if (!timeout) {
108108
after = await beforeDeadline(() => runtime.observe(action.tab_id), deadline);

packages/agent/src/translator/translator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export class InternalComputerTranslator {
140140
const stopped = reads.some((read) =>
141141
read.type === "browser_wait_for"
142142
? read.result.status !== "satisfied"
143-
: read.type === "browser_act" && read.result.stop_reason !== undefined,
143+
: read.type === "browser_act" && read.result.stop_reason !== undefined && read.result.outcome !== "worked",
144144
);
145145
if (stopped) {
146146
const skippedActions = actions.length - index - 1;

packages/agent/test/translator-browser.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe("browser_act orchestration", () => {
113113
it.each([
114114
["newly_verified", "worked", undefined, "not_matched", "matched"],
115115
["preexisting", "unknown", "control_flow", "matched", "matched"],
116-
["failed", "didnt", "global_timeout", "not_matched", "not_matched"],
116+
["failed", "didnt", "expectation_failed", "not_matched", "not_matched"],
117117
["unverifiable", "unknown", "control_flow", "not_matched", "unknown"],
118118
] as const)("maps %s evidence to an honest %s outcome", async (evidence, outcome, stopReason, before, after) => {
119119
const states = [observation("before"), observation("before"), observation("after"), observation("after")];
@@ -170,7 +170,7 @@ describe("browser_act orchestration", () => {
170170
{ type: "click", ref: "e1", expect: { type: "url", contains: "before" } },
171171
{ type: "type", text: "no" },
172172
] }, rt);
173-
expect(result).toMatchObject({ outcome: "didnt", stopped_at: 0, stop_reason: "global_timeout", steps: [{ expectation: { status: "failed", before: "matched", after: "not_matched" } }] });
173+
expect(result).toMatchObject({ outcome: "didnt", stopped_at: 0, stop_reason: "expectation_failed", steps: [{ expectation: { status: "failed", before: "matched", after: "not_matched" } }] });
174174
expect(rt.dispatched).toEqual(["click"]);
175175
});
176176

@@ -184,7 +184,7 @@ describe("browser_act orchestration", () => {
184184
it("does not dispatch later steps after a failed expectation", async () => {
185185
const rt = runtime([observation("before"), observation("before"), observation("after"), observation("after")], [waitResult("failed")]);
186186
const result = await runBrowserAct({ type: "browser_act", steps: [{ type: "click", ref: "e1", expect: { type: "text", text: "Missing" } }, { type: "type", text: "no" }] }, rt);
187-
expect(result).toMatchObject({ outcome: "didnt", stopped_at: 0, stop_reason: "global_timeout" });
187+
expect(result).toMatchObject({ outcome: "didnt", stopped_at: 0, stop_reason: "expectation_failed" });
188188
expect(rt.dispatched).toEqual(["click"]);
189189
});
190190

@@ -433,7 +433,7 @@ describe("browser_act orchestration", () => {
433433
const result = await runBrowserAct({ type: "browser_act", steps: [{ type: "click", ref: "e1", expect: { type: "text", text: "Missing" } }] }, runtime([
434434
observation("before"), observation("before"), observation("after"),
435435
], [waitResult("failed")], { failObservationAt: [3, 4, 5] }));
436-
expect(result).toMatchObject({ outcome: "didnt", stop_reason: "global_timeout", successor: { status: "unavailable" } });
436+
expect(result).toMatchObject({ outcome: "didnt", stop_reason: "expectation_failed", successor: { status: "unavailable" } });
437437
});
438438

439439
it("preserves a verified outcome when successor collection fails", async () => {
@@ -496,6 +496,21 @@ describe("browser_act orchestration", () => {
496496
expect(executed).toEqual(["browser_act"]);
497497
expect(result.skippedActions).toBe(1);
498498
});
499+
500+
it("continues a mixed batch after a worked plan with diagnostic stop reason", async () => {
501+
const { client } = createClient();
502+
const executed: string[] = [];
503+
const executor = { execute: async (action: CuaBrowserAction) => {
504+
executed.push(action.type);
505+
return action.type === "browser_act"
506+
? [{ type: "browser_act", result: { outcome: "worked", steps: [], stopped_at: 0, stop_reason: "navigation", successor: { status: "observed", text: "done", url: "https://example.test", title: "done", diff: { changed: false, added: [], removed: [] } } } } as BatchReadResult]
507+
: [];
508+
} } as unknown as BrowserExecutor;
509+
const translator = new InternalComputerTranslator({ browser, client, createBrowserExecutor: () => executor });
510+
const result = await translator.executeBatch([{ type: "browser_act", steps: [{ type: "wait" }] }, { type: "browser_text" }]);
511+
expect(executed).toEqual(["browser_act", "browser_text"]);
512+
expect(result.skippedActions).toBeUndefined();
513+
});
499514
});
500515

501516
describe("InternalComputerTranslator computer additions", () => {

0 commit comments

Comments
 (0)