From 85962d985d7a8064093f8643f4706d8d98888050 Mon Sep 17 00:00:00 2001 From: Erik Kotsiuba Date: Tue, 28 Apr 2026 21:53:07 +0200 Subject: [PATCH] fix: pass custom answers through elaborate output --- docs/contract.md | 3 +- src/result-format.ts | 32 +++++++++++++++--- tests/result.test.ts | 79 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/docs/contract.md b/docs/contract.md index 6605f73..5757727 100644 --- a/docs/contract.md +++ b/docs/contract.md @@ -184,7 +184,8 @@ This document defines the stable external behavior. It does not explain internal - after clarification, agents should prefer another structured follow-up over plain-text multiple choice when a decision is still unresolved - once prior answers narrow the branch, agents should bundle the next 2-3 related unresolved questions into one follow-up ask when possible, instead of using a long sequence of single-question asks - `elaboration` is only present when `mode === "elaborate"` -- elaborate `content` text and transcript rendering describe each note directly using the full question prompt and option label, instead of a generic elaboration banner +- elaborate `content` text and transcript rendering describe each note directly using the full question prompt and option label, and include the current committed answer text when available, instead of a generic elaboration banner +- when the user selects `Elaborate` without adding notes, elaborate `content` text and transcript rendering still include the committed answer text so the agent can elaborate on that answer directly ## Supported UX diff --git a/src/result-format.ts b/src/result-format.ts index d60960e..bb9140a 100644 --- a/src/result-format.ts +++ b/src/result-format.ts @@ -71,16 +71,40 @@ export function formatElaborationLines( ): string[] { const items = result.elaboration?.items ?? []; const lines = items.map((item) => { + const answerContext = formatElaborationAnswerContext(item.answer); if (item.target.kind === "question") { - return `User asked to elaborate on question ${quote(item.question.prompt)} with note ${quote(item.note)}`; + return `User asked to elaborate on question ${quote(item.question.prompt)}${answerContext} with note ${quote(item.note)}`; } if (!("option" in item)) { - return `User asked to elaborate on question ${quote(item.question.prompt)} with note ${quote(item.note)}`; + return `User asked to elaborate on question ${quote(item.question.prompt)}${answerContext} with note ${quote(item.note)}`; } - return `User asked to elaborate on question ${quote(item.question.prompt)} option ${quote(item.option.label)} with note ${quote(item.note)}`; + return `User asked to elaborate on question ${quote(item.question.prompt)} option ${quote(item.option.label)}${answerContext} with note ${quote(item.note)}`; }); - return lines.length > 0 ? lines : [ELABORATED_SUMMARY]; + if (lines.length > 0) { + return lines; + } + + const answerLines = result.questions + .map((question) => { + const answer = result.answers[question.id]; + return answer?.labels.length + ? `User asked to elaborate on question ${quote(question.prompt)} after current answer ${quote(answer.labels.join(", "))}` + : undefined; + }) + .filter((line): line is string => Boolean(line)); + + return answerLines.length > 0 ? answerLines : [ELABORATED_SUMMARY]; +} + +function formatElaborationAnswerContext( + answer: AskResult["answers"][string] | undefined +): string { + const labels = answer?.labels ?? []; + if (labels.length === 0) { + return ""; + } + return ` after current answer ${quote(labels.join(", "))}`; } function quote(value: string): string { diff --git a/tests/result.test.ts b/tests/result.test.ts index 6d42f99..bd8d90e 100644 --- a/tests/result.test.ts +++ b/tests/result.test.ts @@ -334,6 +334,85 @@ test("elaborate results keep committed answers alongside elaboration context", ( }, note: "Compare tradeoffs", }); + assert.equal( + summarizeResult(result), + 'User asked to elaborate on question "Pick a framework" after current answer "Vue" with note "Compare tradeoffs"' + ); + assert.equal( + renderResultText(result), + 'User asked to elaborate on question "Pick a framework" after current answer "Vue" with note "Compare tradeoffs"' + ); +}); + +test("elaboration summary includes custom answers in note context", () => { + let state = createInitialState({ + questions: [ + { + id: "framework", + label: "Framework", + prompt: "Pick a framework", + options: [ + { value: "react", label: "React" }, + { value: "vue", label: "Vue" }, + ], + }, + ], + }); + + state = applyNumberShortcut(state, 3); + state = submitCustomAnswer(state, "SolidStart"); + state = enterQuestionNoteMode(state, "framework"); + state = saveNote(state, "Compare tradeoffs"); + state = { ...state, mode: "elaborate" }; + const result = toAskResult(state); + + assert.equal( + summarizeResult(result), + 'User asked to elaborate on question "Pick a framework" after current answer "SolidStart" with note "Compare tradeoffs"' + ); + assert.equal( + renderResultText(result), + 'User asked to elaborate on question "Pick a framework" after current answer "SolidStart" with note "Compare tradeoffs"' + ); + assert.deepEqual(result.elaboration?.items[0]?.answer, { + values: ["SolidStart"], + labels: ["SolidStart"], + indices: [], + customText: "SolidStart", + note: "Compare tradeoffs", + optionNotes: undefined, + }); +}); + +test("elaboration summary includes answered custom text even without notes", () => { + let state = createInitialState({ + questions: [ + { + id: "demo_goal", + label: "Goal", + prompt: "What kind of ask_user showcase would you like?", + options: [ + { value: "simple-demo", label: "Simple demo" }, + { value: "code-example", label: "Code example" }, + ], + }, + ], + }); + + state = applyNumberShortcut(state, 3); + state = submitCustomAnswer(state, "teeeel meee whyyyyy"); + state = { ...state, mode: "elaborate" }; + const result = toAskResult(state); + + assert.equal( + summarizeResult(result), + 'User asked to elaborate on question "What kind of ask_user showcase would you like?" after current answer "teeeel meee whyyyyy"' + ); + assert.equal( + renderResultText(result), + 'User asked to elaborate on question "What kind of ask_user showcase would you like?" after current answer "teeeel meee whyyyyy"' + ); + assert.deepEqual(result.elaboration?.items, []); }); test("elaborate continuation preserves unrelated committed answers", () => {