-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhook_socket.go
More file actions
133 lines (116 loc) · 2.98 KB
/
Copy pathhook_socket.go
File metadata and controls
133 lines (116 loc) · 2.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package main
import (
"bufio"
"encoding/json"
"net"
"os"
"sync"
)
// HookMessage is the generic JSON format received on the hook socket.
// Extensible: new event types can be added without changing the socket protocol.
type HookMessage struct {
Event string `json:"event"`
Data json.RawMessage `json:"data"`
}
// AgentStatusData is the data payload for "agent_status" events
type AgentStatusData struct {
Status string `json:"status"` // "busy" or "idle"
}
// startHookSocket creates a Unix domain socket and listens for hook events.
// Each connection sends one JSON message per line, then closes.
func (d *Daemon) startHookSocket(socketPath string) {
// Remove stale socket file if it exists
os.Remove(socketPath)
listener, err := net.Listen("unix", socketPath)
if err != nil {
// Socket creation failed — logged silently to avoid PTY corruption
return
}
d.mu.Lock()
d.hookSocketListener = listener
d.hookSocketPath = socketPath
d.mu.Unlock()
go d.hookSocketAcceptLoop(listener)
}
// hookSocketAcceptLoop accepts connections and processes messages
func (d *Daemon) hookSocketAcceptLoop(listener net.Listener) {
var wg sync.WaitGroup
for {
conn, err := listener.Accept()
if err != nil {
// Listener closed (shutdown)
break
}
wg.Add(1)
go func(c net.Conn) {
defer wg.Done()
defer c.Close()
d.hookSocketHandleConn(c)
}(conn)
}
wg.Wait()
}
// hookSocketHandleConn reads JSON messages from a single connection
func (d *Daemon) hookSocketHandleConn(conn net.Conn) {
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) == 0 {
continue
}
var msg HookMessage
if err := json.Unmarshal(line, &msg); err != nil {
continue
}
d.handleHookMessage(msg)
}
}
// handleHookMessage dispatches a hook message by event type
func (d *Daemon) handleHookMessage(msg HookMessage) {
switch msg.Event {
case "agent_status":
d.handleHookAgentStatus(msg.Data)
default:
// Unknown event — ignore silently
}
}
// handleHookAgentStatus processes agent_status events from hooks
func (d *Daemon) handleHookAgentStatus(data json.RawMessage) {
var status AgentStatusData
if err := json.Unmarshal(data, &status); err != nil {
// Invalid agent_status data — ignore
return
}
// Mark that we're receiving status via socket — disable PTY scan
d.agentStatusViaSocket = true
switch status.Status {
case "busy":
if d.agentIdleTimer != nil {
d.agentIdleTimer.Stop()
d.agentIdleTimer = nil
}
if !d.agentBusy {
d.agentBusy = true
d.sendControlMessage("agent-status:busy")
}
case "idle":
if d.agentBusy {
d.agentBusy = false
d.sendControlMessage("agent-status:idle")
}
}
}
// stopHookSocket closes the socket listener and removes the socket file
func (d *Daemon) stopHookSocket() {
d.mu.Lock()
listener := d.hookSocketListener
socketPath := d.hookSocketPath
d.hookSocketListener = nil
d.mu.Unlock()
if listener != nil {
listener.Close()
}
if socketPath != "" {
os.Remove(socketPath)
}
}