You've already forked agentic-coding-workflow
feat(acp): supervisor skeleton + AcpConfig wiring
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Relay field + GetRelay method added in Task D7 when Relay type is wired.
|
||||
|
||||
// Spawn / Kill / ShutdownAll / monitorProcess / handshake are implemented in subsequent tasks (E2).
|
||||
@@ -41,6 +41,7 @@ type Config struct {
|
||||
Storage Storage `mapstructure:"storage"`
|
||||
Jobs JobsConfig `mapstructure:"jobs"`
|
||||
Notify NotifyConfig `mapstructure:"notify"`
|
||||
Acp AcpConfig `mapstructure:"acp"`
|
||||
}
|
||||
|
||||
// HTTPConfig 描述 HTTP 服务监听参数。
|
||||
@@ -165,6 +166,24 @@ type WebhookCfg struct {
|
||||
Topics []string `mapstructure:"topics"`
|
||||
}
|
||||
|
||||
// AcpConfig 控制 ACP 模块的并发限流、子进程超时、WS 心跳与事件保留。
|
||||
type AcpConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
MaxActiveGlobal int `mapstructure:"max_active_global"`
|
||||
MaxActivePerUser int `mapstructure:"max_active_per_user"`
|
||||
SpawnTimeout time.Duration `mapstructure:"spawn_timeout"`
|
||||
KillGrace time.Duration `mapstructure:"kill_grace"`
|
||||
StderrBufferLines int `mapstructure:"stderr_buffer_lines"`
|
||||
StderrTailBytes int `mapstructure:"stderr_tail_bytes"`
|
||||
WSPingInterval time.Duration `mapstructure:"ws_ping_interval"`
|
||||
WSPongTimeout time.Duration `mapstructure:"ws_pong_timeout"`
|
||||
WSSendBuffer int `mapstructure:"ws_send_buffer"`
|
||||
EventMaxPayload int `mapstructure:"event_max_payload_bytes"`
|
||||
EventTruncateField int `mapstructure:"event_truncate_field_bytes"`
|
||||
EventRetentionDays int `mapstructure:"event_retention_days"`
|
||||
ShutdownGrace time.Duration `mapstructure:"shutdown_grace"`
|
||||
}
|
||||
|
||||
// Load 从(按优先级递增)默认值 → 配置文件(如有)→ 环境变量 加载配置。
|
||||
// configFile 可为空字符串,仅用环境变量。
|
||||
func Load(configFile string) (*Config, error) {
|
||||
@@ -210,6 +229,20 @@ func Load(configFile string) (*Config, error) {
|
||||
v.SetDefault("jobs.job_purge.completed_retention", "168h")
|
||||
v.SetDefault("notify.webhook.enabled", false)
|
||||
v.SetDefault("notify.webhook.timeout", "10s")
|
||||
v.SetDefault("acp.enabled", true)
|
||||
v.SetDefault("acp.max_active_global", 20)
|
||||
v.SetDefault("acp.max_active_per_user", 3)
|
||||
v.SetDefault("acp.spawn_timeout", "30s")
|
||||
v.SetDefault("acp.kill_grace", "5s")
|
||||
v.SetDefault("acp.stderr_buffer_lines", 100)
|
||||
v.SetDefault("acp.stderr_tail_bytes", 2000)
|
||||
v.SetDefault("acp.ws_ping_interval", "30s")
|
||||
v.SetDefault("acp.ws_pong_timeout", "60s")
|
||||
v.SetDefault("acp.ws_send_buffer", 256)
|
||||
v.SetDefault("acp.event_max_payload_bytes", 65536)
|
||||
v.SetDefault("acp.event_truncate_field_bytes", 4096)
|
||||
v.SetDefault("acp.event_retention_days", 7)
|
||||
v.SetDefault("acp.shutdown_grace", "30s")
|
||||
|
||||
if configFile != "" {
|
||||
v.SetConfigFile(configFile)
|
||||
|
||||
Reference in New Issue
Block a user