package terminal import ( "io" "os" "os/exec" "strings" procgrp "github.com/yan1h/agent-coding-workflow/internal/infra/proc" ) // 终端窗口的兜底尺寸(前端尚未上报 resize 前使用)。 const ( defaultRows uint16 = 24 defaultCols uint16 = 80 ) // ptySession 持有平台相关的 shell 进程与 IO。resizeFn 由 startPTY 注入: // Unix 调 pty.Setsize,Windows 为 nil(降级实现无 resize)。这样 ptySession // 类型与生命周期方法可保持平台无关,仅 startPTY 按 build tag 特化。 type ptySession struct { cmd *exec.Cmd group procgrp.Group rw io.ReadWriteCloser // PTY master(Unix)或 stdio 桥接(Windows) resizeFn func(rows, cols uint16) error } func (p *ptySession) resize(rows, cols uint16) error { if p.resizeFn == nil { return nil } return p.resizeFn(rows, cols) } // terminate 是 Open 失败路径的尽力清理(kill 进程、释放句柄、关闭 IO)。 // 正常关闭走 Supervisor.Close → group.TerminateGroup 的整树终止。 func (p *ptySession) terminate() { if p.cmd.Process != nil { _ = p.cmd.Process.Kill() } p.group.Close() _ = p.rw.Close() } // buildEnv 从宿主环境按白名单过滤出传给 shell 的环境变量,并补上 TERM 以便 // 终端程序正确渲染。白名单确保 APP_MASTER_KEY、DB DSN、git 凭据等敏感变量 // 不会透传给交互式 shell。 func buildEnv(whitelist []string) []string { allow := make(map[string]bool, len(whitelist)) for _, k := range whitelist { allow[k] = true } out := make([]string, 0, len(whitelist)+1) hasTerm := false for _, kv := range os.Environ() { i := strings.IndexByte(kv, '=') if i < 0 { continue } name := kv[:i] if !allow[name] { continue } if name == "TERM" { hasTerm = true } out = append(out, kv) } if !hasTerm { out = append(out, "TERM=xterm-256color") } return out }