feat(03-01): HookEvent struct, processHookEvent mapping, UpdateFromHook, WaitType
- HookEvent struct parses Claude Code hook JSON payload - processHookEvent maps Notification/Stop/PostToolUse/PreToolUse to State+WaitType - UpdateFromHook creates new entries and manages WaitingSince transitions - SessionInfo.WaitType serialized in JSON with omitempty - 12 tests cover all event mappings, edge cases, and JSON serialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
hook.go
Normal file
83
hook.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
// HookEvent represents the JSON payload sent by Claude Code hooks.
|
||||
type HookEvent struct {
|
||||
SessionID string `json:"session_id"`
|
||||
TranscriptPath string `json:"transcript_path,omitempty"`
|
||||
Cwd string `json:"cwd,omitempty"`
|
||||
HookEventName string `json:"hook_event_name"`
|
||||
NotificationType string `json:"notification_type,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
LastAssistantMsg string `json:"last_assistant_message,omitempty"`
|
||||
StopHookActive bool `json:"stop_hook_active,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
}
|
||||
|
||||
// processHookEvent maps a Claude Code hook event to a registry update.
|
||||
// Ignores empty session IDs and unknown event types.
|
||||
func (d *Daemon) processHookEvent(event HookEvent) {
|
||||
if event.SessionID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var state, waitType string
|
||||
|
||||
switch event.HookEventName {
|
||||
case "Notification":
|
||||
state = "Needs Input"
|
||||
switch event.NotificationType {
|
||||
case "permission_prompt":
|
||||
waitType = "permission"
|
||||
case "idle_prompt":
|
||||
waitType = "idle"
|
||||
default:
|
||||
waitType = "question"
|
||||
}
|
||||
case "Stop":
|
||||
state = "Needs Input"
|
||||
waitType = "question"
|
||||
case "PostToolUse", "PreToolUse":
|
||||
state = "Working"
|
||||
waitType = ""
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
d.registry.UpdateFromHook(event.SessionID, state, waitType, event.Cwd)
|
||||
}
|
||||
|
||||
// UpdateFromHook updates a session from a hook event.
|
||||
// Creates the entry if it doesn't exist yet (hook can arrive before first poll).
|
||||
func (r *SessionRegistry) UpdateFromHook(sessionID, state, waitType, cwd string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
existing, ok := r.sessions[sessionID]
|
||||
if !ok {
|
||||
existing = &TrackedSession{}
|
||||
r.sessions[sessionID] = existing
|
||||
}
|
||||
|
||||
prevState := existing.PrevState
|
||||
existing.Info.SessionID = sessionID
|
||||
existing.Info.State = state
|
||||
existing.Info.WaitType = waitType
|
||||
if cwd != "" {
|
||||
existing.Info.Cwd = cwd
|
||||
}
|
||||
|
||||
// WaitingSince transition
|
||||
isWaiting := state == "Needs Input"
|
||||
wasWaiting := prevState == "Needs Input"
|
||||
if isWaiting && !wasWaiting {
|
||||
now := time.Now()
|
||||
existing.Info.WaitingSince = &now
|
||||
} else if !isWaiting {
|
||||
existing.Info.WaitingSince = nil
|
||||
}
|
||||
|
||||
existing.PrevState = state
|
||||
}
|
||||
Reference in New Issue
Block a user