fix(coding-agent/tools): normalized ask tool call args to prevent TUI render crashes
- Normalized untrusted `questions` arguments by parsing double-encoded JSON strings and skipping invalid question entries before rendering. - Added option normalization that dropped malformed option items while preserving valid entries in multi-choice rendering. - Expanded ask tool renderer tests to verify malformed or unparsable questions no longer crash and now fall back safely.
This commit is contained in:
@@ -1237,3 +1237,59 @@ describe("AskTool option markers", () => {
|
||||
expect(text).not.toContain(theme!.radio.selected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("askToolRenderer malformed call args", () => {
|
||||
it("renders double-encoded questions string instead of crashing the TUI", async () => {
|
||||
const theme = await getThemeByName("dark");
|
||||
expect(theme).toBeDefined();
|
||||
// Models occasionally JSON-encode the questions array as a string; a bare
|
||||
// string passes a truthy `.length` check but has no `.map` (TUI crash).
|
||||
const doubleEncoded = JSON.stringify([
|
||||
{ id: "q1", question: "Pick one", options: [{ label: "Alpha" }, { label: "Beta" }] },
|
||||
]);
|
||||
const rendered = askToolRenderer.renderCall(
|
||||
{ questions: doubleEncoded } as never,
|
||||
{ expanded: true, isPartial: false },
|
||||
theme!,
|
||||
);
|
||||
const text = stripAnsi(rendered.render(120).join("\n"));
|
||||
expect(text).toContain("[q1]");
|
||||
expect(text).toContain("Pick one");
|
||||
expect(text).toContain("Alpha");
|
||||
});
|
||||
|
||||
it("falls back to the error frame for unparseable questions without throwing", async () => {
|
||||
const theme = await getThemeByName("dark");
|
||||
expect(theme).toBeDefined();
|
||||
for (const questions of ["[{trunc", 42, { 0: { id: "x" } }]) {
|
||||
const rendered = askToolRenderer.renderCall(
|
||||
{ questions } as never,
|
||||
{ expanded: true, isPartial: true },
|
||||
theme!,
|
||||
);
|
||||
const text = stripAnsi(rendered.render(120).join("\n"));
|
||||
expect(text).toContain("No question provided");
|
||||
}
|
||||
});
|
||||
|
||||
it("drops malformed question entries and option items while keeping valid ones", async () => {
|
||||
const theme = await getThemeByName("dark");
|
||||
expect(theme).toBeDefined();
|
||||
const rendered = askToolRenderer.renderCall(
|
||||
{
|
||||
questions: [
|
||||
null,
|
||||
"garbage",
|
||||
{ id: "ok", question: "Real question", options: ["BareString", { label: "Proper" }, { nope: 1 }, 7] },
|
||||
],
|
||||
} as never,
|
||||
{ expanded: true, isPartial: true },
|
||||
theme!,
|
||||
);
|
||||
const text = stripAnsi(rendered.render(120).join("\n"));
|
||||
expect(text).toContain("[ok]");
|
||||
expect(text).toContain("Real question");
|
||||
expect(text).toContain("BareString");
|
||||
expect(text).toContain("Proper");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user