Skip to content

Commit 000b404

Browse files
Improve open-tab prompt fallback resiliency
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent ce95854 commit 000b404

3 files changed

Lines changed: 62 additions & 5 deletions

File tree

‎currentState.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ HyperAgent exposes a TypeScript SDK for browser automation with three primary pa
158158
- `getCDPClientForPage` now always clears pending init promises (including failed init paths) and tolerates trap-prone `page.once` close-listener attachment with sanitized warnings.
159159
- Removed stale inline TODO/commented dead code in markdown conversion utility to keep cleanup pass consistent.
160160
- Replaced remaining TODO-style OOPIF note in a11y DOM extraction with an accurate non-actionable implementation constraint note; `src/` now has no lingering TODO/FIXME/HACK markers.
161+
- Hardened prompt open-tab summary fallback behavior: when context/tab enumeration is unavailable or trap-prone, prompts now fall back to the current tab line rather than opaque "Open tabs unavailable" text.
161162
- Hardened A11y DOM option ingestion (`useCache`, `onFrameChunk`, `filterAdTrackingFrames`) with trap-safe reads, so malformed option objects no longer break extraction setup.
162163
- Hardened A11y DOM debug-option lookup (`getDebugOptions`) with trap-safe fallback defaults and sanitized warning diagnostics.
163164
- Hardened OpenAI/Anthropic structured-schema debug-option reads so trap-prone debug-option access no longer interrupts structured invocation paths.

‎src/agent/messages/builder.test.ts‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ describe("buildAgentStepMessages", () => {
451451
expect(joined.length).toBeLessThan(6000);
452452
});
453453

454-
it("falls back to placeholder text when open tabs cannot be listed", async () => {
454+
it("falls back to current-tab line when open tabs cannot be listed", async () => {
455455
const page = {
456456
url: () => "https://example.com/current",
457457
context: () => {
@@ -481,7 +481,46 @@ describe("buildAgentStepMessages", () => {
481481
.join("\n");
482482

483483
expect(joined).toContain("=== Open Tabs ===");
484-
expect(joined).toContain("Open tabs unavailable");
484+
expect(joined).toContain("[0] https://example.com/current (current)");
485+
expect(joined).not.toContain("Open tabs unavailable");
486+
});
487+
488+
it("falls back to current-tab line when context pages method getter traps", async () => {
489+
const context = {};
490+
Object.defineProperty(context, "pages", {
491+
get: () => {
492+
throw new Error("pages getter trap");
493+
},
494+
configurable: true,
495+
});
496+
const page = {
497+
url: () => "https://example.com/current",
498+
context: () => context as ReturnType<Page["context"]>,
499+
} as unknown as Page;
500+
501+
const messages = await buildAgentStepMessages(
502+
[{ role: "system", content: "system" }],
503+
[],
504+
"task",
505+
page,
506+
{
507+
elements: new Map(),
508+
domState: "dom",
509+
xpathMap: {},
510+
backendNodeMap: {},
511+
},
512+
undefined,
513+
[]
514+
);
515+
516+
const joined = messages
517+
.map((message) =>
518+
typeof message.content === "string" ? message.content : ""
519+
)
520+
.join("\n");
521+
522+
expect(joined).toContain("[0] https://example.com/current (current)");
523+
expect(joined).not.toContain("Open tabs unavailable");
485524
});
486525

487526
it("falls back to placeholder text when current URL cannot be read", async () => {

‎src/agent/messages/builder.ts‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,28 @@ function normalizeScrollInfo(value: unknown): [number, number] {
410410
}
411411

412412
function getOpenTabsSummary(page: Page): string {
413+
const currentTabFallback = (() => {
414+
try {
415+
return `[0] ${truncateTabUrl(page.url() || "about:blank")} (current)`;
416+
} catch {
417+
return "[0] about:blank (url unavailable) (current)";
418+
}
419+
})();
413420
try {
414-
const pages = page.context().pages();
421+
const context = page.context();
422+
if (!context || typeof context !== "object") {
423+
return currentTabFallback;
424+
}
425+
const pagesMethod = (context as { pages?: unknown }).pages;
426+
if (typeof pagesMethod !== "function") {
427+
return currentTabFallback;
428+
}
429+
const pages = pagesMethod.call(context) as ReturnType<
430+
ReturnType<Page["context"]>["pages"]
431+
>;
415432
const pageEntries = materializeSafePages(pages);
416433
if (pageEntries.length === 0) {
417-
return `[0] ${truncateTabUrl(page.url() || "about:blank")} (current)`;
434+
return currentTabFallback;
418435
}
419436
let visibleEntries = pageEntries.slice(0, MAX_OPEN_TAB_ENTRIES);
420437
const currentEntry = pageEntries.find((entry) => entry.openPage === page);
@@ -447,7 +464,7 @@ function getOpenTabsSummary(page: Page): string {
447464
}
448465
return tabLines.join("\n");
449466
} catch {
450-
return "Open tabs unavailable";
467+
return currentTabFallback;
451468
}
452469
}
453470

0 commit comments

Comments
 (0)