diff --git a/VERSION b/VERSION index 8bd6ba8..c006218 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.5 +0.7.6 diff --git a/cmd/nomos/store.go b/cmd/nomos/store.go index ba4dfba..ab76f2b 100644 --- a/cmd/nomos/store.go +++ b/cmd/nomos/store.go @@ -931,6 +931,23 @@ func (s *store) allPlanStepsTerminal(ctx context.Context, sessionID string) bool return total > 0 && total == terminal } +// hasPendingApprovals reports whether this session has any executions in +// pending_approval state. Used by autoCompleteIfPlanDone to avoid closing a +// session that's blocked waiting for operator approval — the agent hit the +// P5 gate and can't continue until the operator responds. +func (s *store) hasPendingApprovals(ctx context.Context, sessionID string) bool { + if s == nil || sessionID == "" || sessionID == "ephemeral" { + return false + } + var count int + s.pool.QueryRow(ctx, ` + SELECT COUNT(*) FROM nomos_plan_executions pe + JOIN executions ex ON ex.entity_id = pe.execution_id + WHERE pe.session_id = $1 AND ex.status = 'pending_approval'`, + sessionID).Scan(&count) + return count > 0 +} + // planStep is a persisted plan step, as returned to the frontend for hydration // (the panel otherwise only sees steps live via plan.proposed/plan.step.*). type planStep struct { diff --git a/cmd/nomos/tasks.go b/cmd/nomos/tasks.go index 26f34bb..23c6923 100644 --- a/cmd/nomos/tasks.go +++ b/cmd/nomos/tasks.go @@ -376,6 +376,15 @@ func (a *agent) autoCompleteIfPlanDone(ctx context.Context, sessionID, responseT if err != nil || sess.Status != "executing" { return } + // Don't auto-complete if there are pending approvals — the agent is + // blocked waiting for the operator, not done. Auto-completing here + // would close the session and the operator's approval would land on a + // dead task. Confirmed in eval: agent hits P5 approval gate, turn + // ends, auto-complete fires incorrectly because the approval-queue + // `run` responses were logged as success=true in agent_activity. + if a.store.hasPendingApprovals(ctx, sessionID) { + return + } discovery := a.store.hadDiscovery(ctx, sessionID) writeback := a.store.hadEntityWriteback(ctx, sessionID) // (a) all plan steps terminal, OR (b) agent did discovery (ran `run`).