// supervisor.go 管理 ACP agent 子进程池。每 session 一个 Process,spawn / kill / // monitor 三类操作通过 mu 保护 procs map。 // // 生命周期 invariants(spec §6.8): // - procs 写锁仅在 spawn / Kill / monitorProcess delete 时持有 // - done channel 严格 close-once(monitor 唯一持有) // - KilledByUs atomic 防 race(Kill / monitor 并发) // - onExit 不能再 Lock supervisor.mu(避免死锁) package acp import ( "io" "log/slog" "os/exec" "sync" "sync/atomic" "time" "github.com/google/uuid" "github.com/yan1h/agent-coding-workflow/internal/audit" "github.com/yan1h/agent-coding-workflow/internal/infra/crypto" "github.com/yan1h/agent-coding-workflow/internal/infra/notify" "github.com/yan1h/agent-coding-workflow/internal/workspace" ) // SupervisorConfig 是从 config.AcpConfig 派生的 supervisor 子集。 type SupervisorConfig struct { SpawnTimeout time.Duration KillGrace time.Duration StderrBufferLines int StderrTailBytes int EventMaxPayload int EventTruncateField int ShutdownGrace time.Duration } // Supervisor 是 ACP 子进程总管。 type Supervisor struct { mu sync.RWMutex procs map[uuid.UUID]*Process repo Repository audit audit.Recorder notify *notify.Dispatcher wtSvc workspace.WorktreeService wsRepo workspace.Repository crypto *crypto.Encryptor cfg SupervisorConfig log *slog.Logger } // NewSupervisor 构造空 Supervisor。Start 不需要 — supervisor 是被动的(spawn 时 // 起 goroutine,无主循环)。Stop 在 ShutdownAll 中实现。 func NewSupervisor(repo Repository, rec audit.Recorder, disp *notify.Dispatcher, wtSvc workspace.WorktreeService, wsRepo workspace.Repository, enc *crypto.Encryptor, cfg SupervisorConfig, log *slog.Logger) *Supervisor { if log == nil { log = slog.Default() } return &Supervisor{ procs: map[uuid.UUID]*Process{}, repo: repo, audit: rec, notify: disp, wtSvc: wtSvc, wsRepo: wsRepo, crypto: enc, cfg: cfg, log: log, } } // Process 是单个 agent 子进程的运行时句柄。 type Process struct { SessionID uuid.UUID WorkspaceID uuid.UUID UserID uuid.UUID IsMain bool // 主 worktree 占用 → release 走不同路径 WorktreeID *uuid.UUID // 子 worktree 时填,用于 release Cmd *exec.Cmd Stdin io.WriteCloser Stdout io.ReadCloser Stderr io.ReadCloser Relay *Relay StderrBuf *stderrRing // 排障 ring buffer StartedAt time.Time KilledByUs atomic.Bool // true → exited,false → crashed done chan struct{} // monitor 退出时 close } // stderrRing 是固定行数 ring buffer,monitor 退出时取 tail 写 last_error。 type stderrRing struct { mu sync.Mutex lines []string cap int } func newStderrRing(cap int) *stderrRing { return &stderrRing{cap: cap} } func (r *stderrRing) Append(line string) { r.mu.Lock() defer r.mu.Unlock() if len(r.lines) >= r.cap { r.lines = r.lines[1:] } r.lines = append(r.lines, line) } func (r *stderrRing) Tail(maxBytes int) string { r.mu.Lock() defer r.mu.Unlock() out := "" for i := len(r.lines) - 1; i >= 0 && len(out) < maxBytes; i-- { out = r.lines[i] + "\n" + out } if len(out) > maxBytes { out = out[len(out)-maxBytes:] } return out } // GetRelay returns the relay for an active session, or nil if not running. // WS handler 用此判断是否进 live 模式。 func (s *Supervisor) GetRelay(sid uuid.UUID) *Relay { s.mu.RLock() defer s.mu.RUnlock() p, ok := s.procs[sid] if !ok { return nil } return p.Relay } // Spawn / Kill / ShutdownAll / monitorProcess / handshake are implemented in subsequent tasks (E2).