feat(tui-go): interactive clarification question view

Render stage clarification questions as an AskUserQuestion-style form:
header chip, prompt, selectable option chips, and a free-text custom slot.
Arrow/hjkl nav, space to select, e for custom, enter to submit. Submit
guards on all-answered and sends ClarificationResponse; the parked stage
re-runs with the answers in context.
This commit is contained in:
2026-06-14 13:00:59 +04:00
parent 6242b590e4
commit 218a98034e
8 changed files with 584 additions and 3 deletions
+34 -1
View File
@@ -34,6 +34,7 @@ const (
TypeToolRejected = "tool.rejected"
TypeApprovalRequired = "approval.required"
TypeApprovalResolved = "approval.resolved"
TypeClarification = "clarification.required"
TypeWorkspaceBound = "session.workspace_bound"
TypeStageManifest = "stage.tool_manifest"
TypeProviderStatus = "provider.status_changed"
@@ -135,6 +136,26 @@ type ServerMessage struct {
// session.stats — derived metrics for a session (reply to GetSessionStats)
Stats *StatsDto `json:"stats"`
// clarification.required — open questions a stage raised for the operator
Questions []ClarificationQuestionDto `json:"questions"`
}
// ClarificationQuestionDto is one open question from a stage. Empty Options ->
// free-text answer; non-empty -> selectable choices. Mirrors the Kotlin
// ClarificationQuestion (core:events), which is the wire shape.
type ClarificationQuestionDto struct {
ID string `json:"id"`
Prompt string `json:"prompt"`
Options []string `json:"options"`
MultiSelect bool `json:"multiSelect"`
Header string `json:"header"`
}
// ClarificationAnswerDto is the operator's answer to one question.
type ClarificationAnswerDto struct {
QuestionID string `json:"questionId"`
Value string `json:"value"`
}
// StatsDto mirrors the server's MetricsReport: a session's derived metrics
@@ -291,7 +312,8 @@ func (m ServerMessage) IsEventBearing() bool {
TypeInferenceFailed, TypeInferenceRetry,
TypeToolStarted, TypeToolCompleted, TypeToolFailed, TypeToolRejected,
TypeToolAssessed,
TypeApprovalRequired, TypeApprovalResolved, TypeWorkspaceBound, TypeArtifactCreated,
TypeApprovalRequired, TypeApprovalResolved, TypeClarification,
TypeWorkspaceBound, TypeArtifactCreated,
TypeArtifactValid, TypePlanLocked,
TypeRouterNarration:
return true
@@ -362,6 +384,17 @@ func ApprovalResponse(requestID, decision string, steeringNote *string) []byte {
})
}
// ClarificationResponse answers a stage's open questions; the parked stage re-runs
// with the answers in context.
func ClarificationResponse(sessionID, stageID, requestID string, answers []ClarificationAnswerDto) []byte {
return encode("ClarificationResponse", map[string]any{
"sessionId": sessionID,
"stageId": stageID,
"requestId": requestID,
"answers": answers,
})
}
// Ping is a keepalive.
func Ping(ts int64) []byte {
return encode("Ping", map[string]any{"timestamp": ts})