package main import ( "strings" "time" ) const IdleThreshold = 60 * time.Second // DetectState determines the session state from the last JSONL messages. // The now parameter enables deterministic testing of the idle threshold. func DetectState(messages []JSONLMessage, now time.Time) SessionState { if len(messages) == 0 { return Unknown } last := messages[len(messages)-1] // Check idle threshold first if ts, err := time.Parse(time.RFC3339, last.Timestamp); err == nil { if now.Sub(ts) > IdleThreshold { return Idle } } if last.Type == "assistant" && last.Message != nil { switch last.Message.StopReason { case "end_turn": return NeedsInput case "tool_use": for _, block := range last.Message.Content { if block.Type == "tool_use" && block.Name == "AskUserQuestion" { return NeedsInput } } return Working } } if last.Type == "progress" { return Working } if last.Type == "user" && last.Message != nil { for _, block := range last.Message.Content { if block.Type == "tool_result" { return Working } } } return Unknown } // ExtractPreview finds the last assistant text content and returns the first // 3 lines, truncated to 200 characters. func ExtractPreview(messages []JSONLMessage) string { for i := len(messages) - 1; i >= 0; i-- { msg := messages[i] if msg.Type != "assistant" || msg.Message == nil { continue } for _, block := range msg.Message.Content { if block.Type == "text" && block.Text != "" { return truncatePreview(block.Text) } } } return "" } func truncatePreview(text string) string { lines := strings.SplitN(text, "\n", 4) if len(lines) > 3 { lines = lines[:3] } result := strings.Join(lines, "\n") if len(result) > 200 { result = result[:200] + "..." } return result }