test(01-02): add failing tests then implement session + state
- TailReadJSONL: reverse-read JSONL without loading entire file - FindSessionForProcess: match PID to most recent JSONL via EncodePath - DetectState: heuristic based on last message stop_reason + tool name - ExtractPreview: extract first 3 lines of last assistant text - All 17 tests pass (session_test.go + state_test.go) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
state.go
Normal file
83
state.go
Normal file
@@ -0,0 +1,83 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user