fix(opencode): show the question text and typed answer in v2 question cards (#5679)

OpenCode v2 sends each question as a form field whose title is the short
header, whose description is the question, and whose custom flag allows a
typed answer. The mapper used the field key as the header and the title as
the question, and dropped custom, so the card showed only the header and
had no answer field.

Closes #5667

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
(cherry picked from commit 2838922ade)
This commit is contained in:
paseo-bot[bot]
2026-09-30 00:33:59 +02:00
committed by Mohamed Boudra
parent e26eb483e2
commit 86df0587ba
2 changed files with 68 additions and 2 deletions
@@ -66,6 +66,71 @@ describe("OpenCode v2 questions", () => {
});
});
describe("OpenCode v2 question tool", () => {
test("shows the question text and accepts a typed answer", async () => {
const harness = new V2Harness();
harness.api.session.form.list = async () => [
{
id: "question",
sessionID: "session",
title: "Questions",
fields: [
{
key: "q0",
title: "Next task",
description: "What would you like me to work on next?",
type: "string",
options: [
{ value: "Explore", label: "Explore", description: "Map the codebase" },
{ value: "Fix a bug", label: "Fix a bug", description: "Track down a bug" },
],
custom: true,
},
],
},
];
const answers: Parameters<V2Api["session"]["form"]["reply"]>[0][] = [];
harness.api.session.form.reply = async (input) => {
answers.push(input);
};
const client = new OpenCodeV2AgentClient({
logger: createTestLogger(),
runtime: harness.runtime,
});
const session = await client.createSession({ provider: "opencode", cwd: "/tmp/project" });
try {
const [request] = session.getPendingPermissions();
expect(request.input).toEqual({
questions: [
{
header: "Next task",
question: "What would you like me to work on next?",
options: [
{ value: "Explore", label: "Explore", description: "Map the codebase" },
{ value: "Fix a bug", label: "Fix a bug", description: "Track down a bug" },
],
multiple: false,
allowOther: true,
},
],
});
await session.respondToPermission("question", {
behavior: "allow",
updatedInput: { answers: { "Next task": "Write the release notes" } },
});
expect(answers).toEqual([
{
sessionID: "session",
formID: "question",
answer: { q0: "Write the release notes" },
},
]);
} finally {
await session.close();
}
});
});
describe("OpenCode v2 permission routing", () => {
test("routes a child approval back to its owning session", async () => {
const harness = new V2Harness();
@@ -100,10 +100,11 @@ export class SessionPermissions {
title: form.title,
input: {
questions: form.fields.map((field) => ({
header: field.key,
question: field.title ?? field.key,
header: field.title ?? field.key,
question: field.description ?? field.title ?? field.key,
options: "options" in field ? field.options : undefined,
multiple: field.type === "multiselect",
allowOther: "custom" in field && field.custom === true,
})),
},
};