Skip to content

Commit 7b3a032

Browse files
Harden perform-action diagnostic formatting
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent fbd2c36 commit 7b3a032

2 files changed

Lines changed: 58 additions & 7 deletions

File tree

src/agent/actions/shared/perform-action.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ describe("performAction variable interpolation", () => {
152152
expect(result.success).toBe(false);
153153
expect(result.message).not.toContain("\u0000");
154154
expect(result.message).not.toContain("\n");
155-
expect(result.message).toContain("");
155+
expect(result.message).toContain("[truncated");
156156
expect(result.message.length).toBeLessThan(1_200);
157157
});
158158

@@ -226,6 +226,41 @@ describe("performAction variable interpolation", () => {
226226
expect(result.message).toContain("current DOM elements are unavailable");
227227
});
228228

229+
it("sanitizes and truncates DOM lookup trap diagnostics", async () => {
230+
const trappedElements = new Proxy(new Map(), {
231+
get: (target, prop, receiver) => {
232+
if (prop === "get") {
233+
return () => {
234+
throw new Error(`lookup\u0000\n${"x".repeat(10_000)}`);
235+
};
236+
}
237+
return Reflect.get(target, prop, receiver);
238+
},
239+
}) as unknown as Map<string, unknown>;
240+
const baseContext = createContext();
241+
const context = createContext({
242+
domState: {
243+
...baseContext.domState,
244+
elements:
245+
trappedElements as unknown as ActionContext["domState"]["elements"],
246+
} as ActionContext["domState"],
247+
});
248+
249+
const result = await performAction(context, {
250+
elementId: "0-1",
251+
method: "click",
252+
arguments: [],
253+
instruction: "Click submit",
254+
});
255+
256+
expect(result.success).toBe(false);
257+
expect(result.message).toContain("DOM element lookup failed");
258+
expect(result.message).toContain("[truncated");
259+
expect(result.message).not.toContain("\u0000");
260+
expect(result.message).not.toContain("\n");
261+
expect(result.message.length).toBeLessThan(700);
262+
});
263+
229264
it("falls back to Playwright when CDP hooks are invalid", async () => {
230265
const invalidCdp = {
231266
client: {} as unknown,

src/agent/actions/shared/perform-action.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const MAX_ACTION_ARGS = 50;
1919
const MAX_ACTION_ARG_CHARS = 20_000;
2020
const MAX_ACTION_METHOD_CHARS = 128;
2121
const MAX_ACTION_TEXT_CHARS = 1_000;
22+
const MAX_ACTION_DIAGNOSTIC_CHARS = 400;
2223

2324
function sanitizeActionText(value: string): string {
2425
if (value.length === 0) {
@@ -66,6 +67,18 @@ function normalizeMethodInput(value: unknown): string {
6667
return normalizeTextInput(value, "click", MAX_ACTION_METHOD_CHARS);
6768
}
6869

70+
function formatPerformActionDiagnostic(value: unknown): string {
71+
const normalized = sanitizeActionText(formatUnknownError(value));
72+
if (normalized.length === 0) {
73+
return "unknown error";
74+
}
75+
if (normalized.length <= MAX_ACTION_DIAGNOSTIC_CHARS) {
76+
return normalized;
77+
}
78+
const omittedChars = normalized.length - MAX_ACTION_DIAGNOSTIC_CHARS;
79+
return `${normalized.slice(0, MAX_ACTION_DIAGNOSTIC_CHARS)}... [truncated ${omittedChars} chars]`;
80+
}
81+
6982
function normalizeActionArguments(value: unknown): string[] {
7083
if (!Array.isArray(value)) return [];
7184
return value.slice(0, MAX_ACTION_ARGS).map((arg) =>
@@ -94,12 +107,18 @@ function readVariables(ctx: ActionContext): Array<{ key: string; value: string }
94107
return normalized;
95108
}
96109

97-
function buildFailureMessage(instruction: string, error: unknown): string {
110+
function buildFailureMessage(
111+
instruction: string,
112+
error: unknown,
113+
details?: string
114+
): string {
115+
const diagnostic = formatPerformActionDiagnostic(error);
116+
const suffix = details ? `${details}: ${diagnostic}` : diagnostic;
98117
return `Failed to execute "${normalizeTextInput(
99118
instruction,
100119
"task",
101120
MAX_ACTION_TEXT_CHARS
102-
)}": ${normalizeTextInput(formatUnknownError(error), "unknown error", MAX_ACTION_TEXT_CHARS)}`;
121+
)}": ${suffix}`;
103122
}
104123

105124
function interpolateVariables(value: string, ctx: ActionContext): string {
@@ -158,10 +177,7 @@ export async function performAction(
158177
} catch (error) {
159178
return {
160179
success: false,
161-
message: buildFailureMessage(
162-
resolvedInstruction,
163-
`DOM element lookup failed: ${formatUnknownError(error)}`
164-
),
180+
message: buildFailureMessage(resolvedInstruction, error, "DOM element lookup failed"),
165181
};
166182
}
167183
if (!elementMetadata) {

0 commit comments

Comments
 (0)