fix(router,server,config,tui-go): close QA findings #2 #3 #9 #10 #12

#2: approval pauses narrate from ApprovalRequestedEvent (carries
tool/tier/preview/stage directly) instead of OrchestrationPaused +
a pending-approvals map the next event had not yet populated — the
narrator always lost that race and produced generic text.

#3: NarrationTrigger now carries the triggering event's stageId; the
narration status line and RouterNarrationEvent.stageId use it instead
of the router projection's currentStageId, which terminal events have
already cleared ('Stage: none').

#9: ExecutionPlanLocked -> plan.locked and ArtifactValidated ->
artifact.validated mapped to the TUI (Go: feed lines, plan shows the
compiled stage chain); ArtifactValidating explicitly dropped.

#10: blank router turn guard — a length-finish with empty text now
emits an explanatory turn naming the max_tokens budget instead of a
silent blank; blank steering responses no longer emit steering notes.

#12: resumeAbandoned only auto-resumes sessions whose last event is
within orchestration.resume_abandoned_max_age_minutes (default 24h,
0 disables); staler sessions stay parked for POST /resume. Knob is
TUI-editable and persisted.
This commit is contained in:
2026-06-12 13:20:04 +04:00
parent a4f0c6d0a2
commit a455762dd5
13 changed files with 171 additions and 36 deletions
+9
View File
@@ -227,6 +227,15 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
if s := m.session(msg.SessionID); s != nil {
s.addEvent(nowMillis(), "ArtifactCreated", msg.ArtifactID)
}
case protocol.TypeArtifactValid:
if s := m.session(msg.SessionID); s != nil {
s.addEvent(nowMillis(), "ArtifactValidated", msg.ArtifactID)
}
case protocol.TypePlanLocked:
if s := m.session(msg.SessionID); s != nil {
s.addEvent(nowMillis(), "PlanLocked", strings.Join(msg.StageIDs, " → "))
s.LastOutput = "plan locked: " + strings.Join(msg.StageIDs, " → ")
}
case protocol.TypeWorkspaceBound:
if s := m.session(msg.SessionID); s != nil {
s.WorkspaceRoot = msg.WorkspaceRoot