ACP / MCP

This commit is contained in:
2026-06-12 11:44:19 +08:00
parent 2a9661a6ef
commit 4ea4adfd8e
47 changed files with 2558 additions and 240 deletions
+40 -5
View File
@@ -41,6 +41,9 @@ type SupervisorConfig struct {
EventMaxPayload int
EventTruncateField int
ShutdownGrace time.Duration
// AgentHomesDir 是 agent CLI 受管 home 的根目录(<DataDir>/agent-homes)。
// 空串时跳过配置物化与重定向 env 注入(部分测试)。
AgentHomesDir string
}
// Supervisor 是 ACP 子进程总管。
@@ -154,10 +157,24 @@ func (s *Supervisor) GetRelay(sid uuid.UUID) *Relay {
// handshake 失败时调用 Kill 回滚并返回错误。调用方(SessionService)负责前置的
// worktree 占用与 acp_sessions 行 INSERT;本方法仅负责 OS 层资源与协议层握手。
func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind, extraEnv map[string]string) (*Process, error) {
envMap, err := decryptEnv(s.crypto, kind.EncryptedEnv)
// 受管 home:物化 DB 中的配置文件并注入重定向 env(优先级最低,可被
// AgentKind env / extraEnv 覆盖)。AgentHomesDir 未配置时跳过(部分测试)。
envMap := map[string]string{}
if s.cfg.AgentHomesDir != "" {
home, err := s.materializeAgentHome(ctx, kind)
if err != nil {
return nil, fmt.Errorf("materialize agent home: %w", err)
}
envMap = agentHomeEnv(home, kind.ClientType)
}
kindEnv, err := decryptEnv(s.crypto, kind.EncryptedEnv)
if err != nil {
return nil, err
}
for k, v := range kindEnv {
envMap[k] = v
}
// System override: extraEnv overrides agent_kinds same-name keys (spec §6.1 #11)
for k, v := range extraEnv {
@@ -251,7 +268,7 @@ func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind,
ToolAllowlist: kind.ToolAllowlist,
})
if err := s.handshake(ctx, sess, relay, proc); err != nil {
if err := s.handshake(ctx, sess, kind, relay, proc, extraEnv); err != nil {
s.log.Error("acp.handshake_failed", "session_id", sess.ID, "err", err.Error())
s.Kill(ctx, sess.ID, s.cfg.KillGrace)
return nil, err
@@ -261,14 +278,32 @@ func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind,
// handshake 发送 initialize → session/new,并把返回的 agent session id + pid
// 持久化到 acp_sessions 行(status → running)。SpawnTimeout 是整个握手的硬上限。
func (s *Supervisor) handshake(ctx context.Context, sess *Session, relay *Relay, proc *Process) error {
// session/new 同时下发 mcpServers:ACW 自家 MCP(extraEnv 中的 per-session
// token)+ AgentKind 第三方 MCP,按 initialize 声明的 mcpCapabilities 过滤。
func (s *Supervisor) handshake(ctx context.Context, sess *Session, kind *AgentKind,
relay *Relay, proc *Process, extraEnv map[string]string) error {
hsCtx, cancel := context.WithTimeout(ctx, s.cfg.SpawnTimeout)
defer cancel()
if _, err := relay.Call(hsCtx, "initialize", handlers.BuildInitializeParams("dev")); err != nil {
initResp, err := relay.Call(hsCtx, "initialize", handlers.BuildInitializeParams("dev"))
if err != nil {
return fmt.Errorf("initialize: %w", err)
}
resp, err := relay.Call(hsCtx, "session/new", handlers.BuildSessionNewParams(sess.CwdPath))
var ir handlers.InitializeResult
if err := json.Unmarshal(initResp.Result, &ir); err != nil {
// 能力解析失败不阻断握手:按无 http/sse 能力处理(仅基线 stdio 可下发)。
s.log.Warn("acp.handshake.parse_initialize_result", "session_id", sess.ID, "err", err.Error())
}
httpOK, sseOK := ir.McpCapabilities()
specs, err := decryptMCPServers(s.crypto, kind.EncryptedMCPServers)
if err != nil {
return err
}
mcpServers := buildMcpServers(specs, extraEnv[EnvMCPURL], extraEnv[EnvMCPToken],
httpOK, sseOK, s.log, sess.ID)
resp, err := relay.Call(hsCtx, "session/new", handlers.BuildSessionNewParams(sess.CwdPath, mcpServers))
if err != nil {
return fmt.Errorf("session/new: %w", err)
}