This commit is contained in:
2026-06-20 19:16:44 +08:00
parent db3d030169
commit dbb87823e8
26 changed files with 676 additions and 115 deletions
+23 -1
View File
@@ -108,6 +108,12 @@ type Process struct {
StartedAt time.Time
KilledByUs atomic.Bool // true → exited,false → crashed
done chan struct{} // monitor 退出时 close
// relayCancel 取消 relay 的会话级 context。该 context 由 Spawn 用
// context.Background() 派生,独立于调用方的 spawn ctx(spawn ctx 在 Spawn
// 返回后立即取消),故 relay 的 reader/writer/persist/permission Decide 在整个
// 会话生命周期内都使用未取消的 ctx。onExit 调用 relayCancel 完成 relay teardown。
relayCancel context.CancelFunc
}
// stderrRing 是固定行数 ring buffer,monitor 退出时取 tail 写 last_error。
@@ -237,6 +243,12 @@ func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind,
s.log,
)
// relay 的会话级 context:独立于调用方的 spawn ctx(生产调用方在 Spawn 返回后
// 立即 cancel spawn ctx)。relay 的 reader/writer/persist/permission Decide 必须
// 在整个会话生命周期内使用未取消的 ctx,否则 prompt 被丢、事件停止持久化、权限
// 审批被强制拒绝。teardown 由 onExit 调 relayCancel + Relay.Close(关 r.done)完成。
relayCtx, relayCancel := context.WithCancel(context.Background())
proc := &Process{
SessionID: sess.ID,
WorkspaceID: sess.WorkspaceID,
@@ -251,6 +263,7 @@ func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind,
StderrBuf: newStderrRing(s.cfg.StderrBufferLines),
StartedAt: time.Now(),
done: make(chan struct{}),
relayCancel: relayCancel,
}
s.mu.Lock()
@@ -259,7 +272,7 @@ func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind,
go s.drainStderr(proc)
go s.monitorProcess(proc)
go relay.Run(ctx, handlers.SessionContext{
go relay.Run(relayCtx, handlers.SessionContext{
SessionID: sess.ID,
WorkspaceID: sess.WorkspaceID,
UserID: sess.UserID,
@@ -268,6 +281,7 @@ func (s *Supervisor) Spawn(ctx context.Context, sess *Session, kind *AgentKind,
ToolAllowlist: kind.ToolAllowlist,
})
// 握手仍走调用方的 spawn ctx,保留 SpawnTimeout 截止语义。
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)
@@ -516,6 +530,14 @@ func (s *Supervisor) onExit(ctx context.Context, proc *Process, status SessionSt
}
}
// 取消 relay 的会话级 context:停止 reader/writer,并让仍阻塞在 Decide / 已派生
// 的 handleAgentRequest goroutine 收到 ctx.Done 后退出。在 Relay.Close 之前调,
// 二者共同完成 teardown(relayCancel 停 ctx 派生的 goroutine,Close 关 r.done +
// 广播 session_terminated)。relayCancel 在早期 spawn 失败路径也可能为 nil。
if proc.relayCancel != nil {
proc.relayCancel()
}
// 最后关 relay:广播 session_terminated 给 WS subscribers。
proc.Relay.Close(string(status), &exitCode)
}