feat(tui-go): workflow-propose choice panel

Render a workflow.proposed frame as a single-select picker with a manual-answer
slot: ↑↓/jk to choose, enter to launch, e for custom, esc to peek away. Picking
a candidate sends StartSession (and focuses the launched session); the custom
slot continues the conversation via ChatInput. Reuses the modal helpers from the
clarification view.
This commit is contained in:
2026-06-14 13:29:34 +04:00
parent b62b6e09a1
commit b563d9841d
9 changed files with 420 additions and 0 deletions
+21
View File
@@ -133,12 +133,14 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
if s := m.session(msg.SessionID); s != nil {
s.Active = false
s.Clar = nil
s.Propose = nil
}
case protocol.TypeSessionFailed:
m.touch(msg.SessionID, "FAILED")
if s := m.session(msg.SessionID); s != nil {
s.Active = false
s.Clar = nil
s.Propose = nil
}
case protocol.TypeStageStarted:
if s := m.session(msg.SessionID); s != nil {
@@ -246,6 +248,8 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
m.onApprovalRequired(msg)
case protocol.TypeClarification:
m.onClarificationRequired(msg)
case protocol.TypeWorkflowProposed:
m.onWorkflowProposed(msg)
case protocol.TypeApprovalResolved:
if s := m.session(msg.SessionID); s != nil {
s.Pending = nil
@@ -424,6 +428,23 @@ func (m *Model) onClarificationRequired(msg protocol.ServerMessage) {
}
}
func (m *Model) onWorkflowProposed(msg protocol.ServerMessage) {
cands := make([]ProposeCandidate, 0, len(msg.Candidates))
for _, c := range msg.Candidates {
cands = append(cands, ProposeCandidate{WorkflowID: c.WorkflowID, Reason: c.Reason})
}
p := &Proposal{
ProposalID: msg.ProposalID, SessionID: msg.SessionID,
Prompt: msg.Prompt, OriginalRequest: msg.OriginalRequest, Candidates: cands,
}
if s := m.session(msg.SessionID); s != nil {
s.Propose = p
}
if msg.SessionID == m.selectedID {
m.proposeInitState()
}
}
func (m *Model) onSnapshot(msg protocol.ServerMessage) {
var pending *Approval
if len(msg.PendingAppr) > 0 {