feat(03-02): integrate hook server into daemon with graceful degradation and dynamic poll
- Add hookPort, httpServer, lastHookTime, mu to Daemon struct - startHookServer binds HTTP on hookPort, degrades if port busy - Stop() closes httpServer before unix listener - pollLoop uses time.After(currentPollInterval()) instead of fixed ticker - currentPollInterval returns 20s when hooks active (<60s), 5s otherwise - processHookEvent records lastHookTime on each hook event Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
daemon.go
65
daemon.go
@@ -3,7 +3,9 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -155,6 +157,10 @@ type Daemon struct {
|
||||
pollInterval time.Duration
|
||||
stopCh chan struct{}
|
||||
listener net.Listener
|
||||
hookPort int
|
||||
httpServer *http.Server
|
||||
lastHookTime time.Time
|
||||
mu sync.Mutex // protects lastHookTime
|
||||
}
|
||||
|
||||
// NewDaemon creates a daemon ready to start.
|
||||
@@ -167,6 +173,7 @@ func NewDaemon(sockPath, procDir, claudeDir string, labels *LabelStore) *Daemon
|
||||
claudeDir: claudeDir,
|
||||
pollInterval: 5 * time.Second,
|
||||
stopCh: make(chan struct{}),
|
||||
hookPort: 3119,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,12 +192,60 @@ func (d *Daemon) InitWorkspaceResolver(treeProvider I3TreeProvider, x11 X11PIDRe
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs the daemon: initial scan, then listens on the Unix socket
|
||||
// startHookServer starts an HTTP server on hookPort to receive Claude Code hooks.
|
||||
// If the port is busy, logs a warning and returns nil (graceful degradation).
|
||||
func (d *Daemon) startHookServer() error {
|
||||
if d.hookPort == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", d.hookPort)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/hook", d.handleHook)
|
||||
|
||||
d.httpServer = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Printf("hook server: port %d busy, continuing without hooks: %v", d.hookPort, err)
|
||||
d.httpServer = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
go d.httpServer.Serve(ln)
|
||||
return nil
|
||||
}
|
||||
|
||||
// currentPollInterval returns 20s when hooks are active (last hook < 60s ago),
|
||||
// 5s otherwise.
|
||||
func (d *Daemon) currentPollInterval() time.Duration {
|
||||
d.mu.Lock()
|
||||
lastHook := d.lastHookTime
|
||||
d.mu.Unlock()
|
||||
|
||||
if !lastHook.IsZero() && time.Since(lastHook) < 60*time.Second {
|
||||
return 20 * time.Second
|
||||
}
|
||||
return 5 * time.Second
|
||||
}
|
||||
|
||||
// Start runs the daemon: initial scan, hook server, then listens on the Unix socket
|
||||
// and polls for sessions in the background.
|
||||
func (d *Daemon) Start() error {
|
||||
// Synchronous initial scan before accepting connections
|
||||
d.scanOnce(time.Now())
|
||||
|
||||
// Start hook server (graceful degradation if port busy)
|
||||
if err := d.startHookServer(); err != nil {
|
||||
return fmt.Errorf("start hook server: %w", err)
|
||||
}
|
||||
|
||||
if err := d.cleanStaleSocket(); err != nil {
|
||||
return fmt.Errorf("clean stale socket: %w", err)
|
||||
}
|
||||
@@ -218,6 +273,9 @@ func (d *Daemon) Stop() {
|
||||
default:
|
||||
close(d.stopCh)
|
||||
}
|
||||
if d.httpServer != nil {
|
||||
d.httpServer.Close()
|
||||
}
|
||||
if d.listener != nil {
|
||||
d.listener.Close()
|
||||
}
|
||||
@@ -244,14 +302,11 @@ func (d *Daemon) acceptLoop() {
|
||||
}
|
||||
|
||||
func (d *Daemon) pollLoop() {
|
||||
ticker := time.NewTicker(d.pollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-d.stopCh:
|
||||
return
|
||||
case t := <-ticker.C:
|
||||
case t := <-time.After(d.currentPollInterval()):
|
||||
d.scanOnce(t)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user