feat: improve ask flow elaboration and validation

This commit is contained in:
Erik Kotsiuba
2026-04-24 19:13:10 +02:00
parent 0ee2d11ec9
commit b7c7301f97
16 changed files with 1270 additions and 134 deletions
+109 -2
View File
@@ -37,7 +37,7 @@ This document defines the stable external behavior. It does not explain internal
- `type` defaults to `single`
- `required` defaults to `false`
- `required` is metadata only; it never blocks submission
- preview questions require preview text for every option
- preview questions require preview text for every option; option descriptions do not satisfy this requirement, and invalid preview payloads report a fix hint to add preview text or switch to `type: "single"`
- non-`preview` questions get an internal `Type your own` option
## Output
@@ -48,6 +48,14 @@ This document defines the stable external behavior. It does not explain internal
details: {
title?: string;
cancelled: boolean;
error?: {
kind: "invalid_input";
issues: Array<{
path: string;
message: string;
}>;
};
mode: "submit" | "elaborate";
questions: Array<{
id: string;
label: string;
@@ -65,6 +73,84 @@ This document defines the stable external behavior. It does not explain internal
optionNotes?: Record<string, string>;
}
>;
continuation?: {
strategy: "refine_only" | "resume";
affectedQuestionIds: string[];
preservedAnswers: Record<string, {
values: string[];
labels: string[];
indices: number[];
customText?: string;
note?: string;
optionNotes?: Record<string, string>;
}>;
questionStates: Record<string, {
status: "answered" | "needs_clarification" | "unanswered";
}>;
};
elaboration?: {
instruction: string;
nextAction: "clarify" | "clarify_then_reask";
items: Array<
| {
target: { kind: "question" };
question: {
id: string;
label: string;
prompt: string;
type: "single" | "multi" | "preview";
options: Array<{
value: string;
label: string;
description?: string;
preview?: string;
}>;
};
answered: boolean;
answer?: {
values: string[];
labels: string[];
indices: number[];
customText?: string;
note?: string;
optionNotes?: Record<string, string>;
};
note: string;
}
| {
target: { kind: "option"; optionValue: string };
question: {
id: string;
label: string;
prompt: string;
type: "single" | "multi" | "preview";
options: Array<{
value: string;
label: string;
description?: string;
preview?: string;
}>;
};
option: {
value: string;
label: string;
description?: string;
preview?: string;
};
selected: boolean;
answered: boolean;
answer?: {
values: string[];
labels: string[];
indices: number[];
customText?: string;
note?: string;
optionNotes?: Record<string, string>;
};
note: string;
}
>;
};
};
}
```
@@ -72,7 +158,14 @@ This document defines the stable external behavior. It does not explain internal
## Output rules
- `cancelled: true` means the user dismissed the flow, UI was unavailable, or the payload was invalid before UI opened
- invalid payloads return `error.kind === "invalid_input"` with structured `issues` and a transcript-friendly `Invalid ask_user payload:` message
- `mode: "submit"` is normal completion; `mode: "elaborate"` means the user asked the agent to continue with follow-up clarification based on notes
- unanswered questions are omitted from `answers`
- in `mode: "elaborate"`, `answers` contains only committed answers; note-only entries move to `elaboration.items`
- `continuation.strategy === "refine_only"` means the next ask should refine the current flow rather than restart it
- `continuation.preservedAnswers` contains previously committed answers that should be kept as context and not re-asked
- `continuation.affectedQuestionIds` lists the only questions that should be revisited
- `continuation.questionStates` marks each question as `answered`, `needs_clarification`, or `unanswered`
- single-select answers still use arrays
- `indices` are 1-based rendered option positions
- `customText` stores the free-form answer
@@ -83,6 +176,15 @@ This document defines the stable external behavior. It does not explain internal
- `note` stores a question-level note
- `optionNotes` includes only notes for selected options
- question notes may exist without a selected answer
- `elaboration.items` includes all question notes and all option notes, even for unselected options
- every elaboration item includes the full normalized question and option list for that question so referential notes like `above` remain understandable to the agent
- option-targeted elaboration items include the specific noted option plus whether it is currently selected
- question-targeted elaboration items include whether the question already has a committed answer
- `elaboration.instruction` tells the agent to answer the clarification directly first, then re-ask only the affected questions if a choice is still needed
- 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
## Supported UX
@@ -93,8 +195,11 @@ This document defines the stable external behavior. It does not explain internal
- question notes via `Shift+N`
- option notes via `n`
- number-key quick selection
- submit/cancel review tab
- submit/elaborate/cancel review tab
- on the review tab, `Submit` and `Cancel` preview notes only for answered questions
- on the review tab, `Elaborate` preview expands to all question notes and all option notes, including notes on unselected options
- transcript-friendly call and result rendering
- elaborate results are phrased as direct follow-up instructions, for example: `User asked to elaborate on question "Which option would you like to select?" option "Option A" with note "why this one?"`
## Keyboard behavior
@@ -123,6 +228,8 @@ Editing flow:
If `ctx.hasUI === false`, the tool returns a `Needs user input` message in `content` and a cancelled result in `details`.
Validation is handled inside the tool so malformed calls produce the same structured error shape as other invalid payloads instead of relying on pre-execution schema failures.
The fallback message includes normalized pending questions and options so the caller can re-ask them manually. `details.questions` still contains normalized question metadata, while `details.answers` stays empty until a user responds.
## Source of truth