TUI: Closed column, legacy 30m stale window, filtered-view fix, header shown-count, toggle tests

This commit is contained in:
2026-08-04 11:13:36 +10:00
parent 19b39de473
commit a0d25aa108
3 changed files with 141 additions and 52 deletions

View File

@@ -12,34 +12,34 @@ import (
// AgentState matches the TypeScript interface from dashboard.ts
type AgentState struct {
Session string `json:"session"`
Machine string `json:"machine"`
Cwd string `json:"cwd"`
AgentType string `json:"agent_type"`
Model string `json:"model"`
AgentStatus string `json:"agent_status"`
Status string `json:"status"` // legacy alias of agent_status
SessionState string `json:"session_state"`
SessionStartedAt *string `json:"session_started_at"`
ClosedAt *string `json:"closed_at"`
LastSeenAt *string `json:"last_seen_at"`
StartedAt *string `json:"started_at"`
CompletedAt *string `json:"completed_at"`
DurationSeconds int `json:"duration_seconds"`
ToolCalls int `json:"tool_calls"`
CurrentTask string `json:"current_task"`
LastTool *string `json:"last_tool"`
LastToolAt *string `json:"last_tool_at"`
TerminalType string `json:"terminal_type"`
ZellijSession string `json:"zellij_session"`
TmuxSession string `json:"tmux_session"`
PID int `json:"pid"`
IsSubagent bool `json:"is_subagent"`
ConnectionType string `json:"connection_type"`
ConnectionName string `json:"connection_name"`
Blocked bool `json:"blocked"`
BlockedPrompt *string `json:"blocked_prompt"`
CostEstimate float64 `json:"cost_estimate"`
Session string `json:"session"`
Machine string `json:"machine"`
Cwd string `json:"cwd"`
AgentType string `json:"agent_type"`
Model string `json:"model"`
AgentStatus string `json:"agent_status"`
Status string `json:"status"` // legacy alias of agent_status
SessionState string `json:"session_state"`
SessionStartedAt *string `json:"session_started_at"`
ClosedAt *string `json:"closed_at"`
LastSeenAt *string `json:"last_seen_at"`
StartedAt *string `json:"started_at"`
CompletedAt *string `json:"completed_at"`
DurationSeconds int `json:"duration_seconds"`
ToolCalls int `json:"tool_calls"`
CurrentTask string `json:"current_task"`
LastTool *string `json:"last_tool"`
LastToolAt *string `json:"last_tool_at"`
TerminalType string `json:"terminal_type"`
ZellijSession string `json:"zellij_session"`
TmuxSession string `json:"tmux_session"`
PID int `json:"pid"`
IsSubagent bool `json:"is_subagent"`
ConnectionType string `json:"connection_type"`
ConnectionName string `json:"connection_name"`
Blocked bool `json:"blocked"`
BlockedPrompt *string `json:"blocked_prompt"`
CostEstimate float64 `json:"cost_estimate"`
}
// agentStatus returns the agent-turn status, falling back to the legacy
@@ -51,6 +51,17 @@ func (a AgentState) agentStatus() string {
return a.Status
}
// legacyStale is the staleness window applied to legacy state files (written
// by the pre-heartbeat extension). Those files are only rewritten on events,
// so an idle-but-alive session can go minutes without a write — be generous.
const legacyStale = 30 * time.Minute
// isLegacy reports whether the state file predates the heartbeat extension
// (new files always carry pid + session_state; legacy ones have neither).
func (a AgentState) isLegacy() bool {
return a.PID == 0 && a.SessionState == ""
}
// IsClosed reports whether the session is no longer alive:
// - the extension marked it closed (session_state == "closed"), or
// - the heartbeat (last_seen_at) is older than staleAfter (killed
@@ -68,7 +79,11 @@ func (a AgentState) IsClosed(staleAfter time.Duration) bool {
if err != nil {
return true
}
return time.Since(t) > staleAfter
window := staleAfter
if a.isLegacy() {
window = legacyStale
}
return time.Since(t) > window
}
// WasKilled is true when the session died without a graceful shutdown