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:
@@ -149,6 +149,25 @@ func TestDecodeGoldenServerFrames(t *testing.T) {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workflow.proposed carries candidate workflows",
|
||||
json: `{"type":"workflow.proposed","sessionId":"s1","proposalId":"prop-1","prompt":"Run one?",` +
|
||||
`"candidates":[{"workflowId":"research","reason":"gather + report"},{"workflowId":"role_pipeline","reason":""}],` +
|
||||
`"originalRequest":"find papers","sequence":9,"sessionSequence":4}`,
|
||||
wantType: TypeWorkflowProposed,
|
||||
eventBear: true,
|
||||
check: func(t *testing.T, m ServerMessage) {
|
||||
if m.ProposalID != "prop-1" || m.Prompt != "Run one?" || m.OriginalRequest != "find papers" {
|
||||
t.Fatalf("workflow.proposed fields: %+v", m)
|
||||
}
|
||||
if len(m.Candidates) != 2 {
|
||||
t.Fatalf("workflow.proposed candidates: %+v", m.Candidates)
|
||||
}
|
||||
if m.Candidates[0].WorkflowID != "research" || m.Candidates[0].Reason != "gather + report" {
|
||||
t.Fatalf("workflow.proposed candidate[0]: %+v", m.Candidates[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "clarification.required carries structured questions",
|
||||
json: `{"type":"clarification.required","sessionId":"s1","requestId":"req-1","stageId":"analyst",` +
|
||||
|
||||
@@ -35,6 +35,7 @@ const (
|
||||
TypeApprovalRequired = "approval.required"
|
||||
TypeApprovalResolved = "approval.resolved"
|
||||
TypeClarification = "clarification.required"
|
||||
TypeWorkflowProposed = "workflow.proposed"
|
||||
TypeWorkspaceBound = "session.workspace_bound"
|
||||
TypeStageManifest = "stage.tool_manifest"
|
||||
TypeProviderStatus = "provider.status_changed"
|
||||
@@ -139,6 +140,19 @@ type ServerMessage struct {
|
||||
|
||||
// clarification.required — open questions a stage raised for the operator
|
||||
Questions []ClarificationQuestionDto `json:"questions"`
|
||||
|
||||
// workflow.proposed — the router's triage suggestion of candidate workflows
|
||||
ProposalID string `json:"proposalId"`
|
||||
Prompt string `json:"prompt"`
|
||||
OriginalRequest string `json:"originalRequest"`
|
||||
Candidates []ProposedWorkflowDto `json:"candidates"`
|
||||
}
|
||||
|
||||
// ProposedWorkflowDto is one candidate workflow the router suggests, with a short
|
||||
// reason. Mirrors the Kotlin ProposedWorkflow (core:events), the wire shape.
|
||||
type ProposedWorkflowDto struct {
|
||||
WorkflowID string `json:"workflowId"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// ClarificationQuestionDto is one open question from a stage. Empty Options ->
|
||||
@@ -313,6 +327,7 @@ func (m ServerMessage) IsEventBearing() bool {
|
||||
TypeToolStarted, TypeToolCompleted, TypeToolFailed, TypeToolRejected,
|
||||
TypeToolAssessed,
|
||||
TypeApprovalRequired, TypeApprovalResolved, TypeClarification,
|
||||
TypeWorkflowProposed,
|
||||
TypeWorkspaceBound, TypeArtifactCreated,
|
||||
TypeArtifactValid, TypePlanLocked,
|
||||
TypeRouterNarration:
|
||||
|
||||
Reference in New Issue
Block a user