Files
agentic-coding-workflow/internal/acp/agent_home.go
T
2026-06-12 11:44:19 +08:00

93 lines
3.3 KiB
Go

// agent_home.go 实现 agent CLI 受管 home 目录的物化与重定向 env 注入。
//
// 每个 AgentKind 一个受管 home:<agent_homes_dir>/<agent_kind_id>/。spawn 前把
// DB 中的配置文件解密落盘(全量覆写受管文件,不清空目录 —— agent 运行期写入的
// 缓存 / 凭据得以保留),再按 client_type 注入配置目录重定向变量。受管 home 位于
// DataDir 下,容器场景随持久卷存活。
package acp
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
// materializeAgentHome 创建受管 home 并物化该 AgentKind 的全部配置文件。
// 返回 home 绝对路径。配置文件为空时仍创建目录(agent 需要可写 home)。
func (s *Supervisor) materializeAgentHome(ctx context.Context, kind *AgentKind) (string, error) {
home, err := filepath.Abs(filepath.Join(s.cfg.AgentHomesDir, kind.ID.String()))
if err != nil {
return "", fmt.Errorf("resolve agent home: %w", err)
}
if err := os.MkdirAll(home, 0o700); err != nil {
return "", fmt.Errorf("create agent home: %w", err)
}
// client 专属配置目录预创建:CODEX_HOME 指向的目录必须已存在(codex 硬性要求),
// 其余预创建无害且省去 agent 首次启动的目录初始化分支。
if sub := clientConfigSubdir(kind.ClientType); sub != "" {
if err := os.MkdirAll(filepath.Join(home, sub), 0o700); err != nil {
return "", fmt.Errorf("create client config dir: %w", err)
}
}
files, err := s.repo.ListConfigFiles(ctx, kind.ID)
if err != nil {
return "", err
}
sep := string(filepath.Separator)
for _, f := range files {
plain, derr := s.crypto.Decrypt(f.EncryptedContent)
if derr != nil {
return "", fmt.Errorf("decrypt config file %s: %w", f.RelPath, derr)
}
dst := filepath.Clean(filepath.Join(home, filepath.FromSlash(f.RelPath)))
// rel_path 写入时已校验;此处兜底防 DB 被直接篡改后逃逸受管目录。
if !strings.HasPrefix(dst, home+sep) {
return "", fmt.Errorf("config file %s escapes agent home", f.RelPath)
}
if err := os.MkdirAll(filepath.Dir(dst), 0o700); err != nil {
return "", fmt.Errorf("create config file dir %s: %w", f.RelPath, err)
}
if err := os.WriteFile(dst, plain, 0o600); err != nil {
return "", fmt.Errorf("write config file %s: %w", f.RelPath, err)
}
}
return home, nil
}
// agentHomeEnv 返回受管 home 对应的重定向 env 默认值。优先级最低:AgentKind env
// 与系统 extraEnv 可覆盖同名 key。
func agentHomeEnv(home string, ct ClientType) map[string]string {
env := map[string]string{"HOME": home}
if runtime.GOOS == "windows" {
// Windows 上 Node/Rust 解析 home 走 USERPROFILE(开发环境)。
env["USERPROFILE"] = home
}
switch ct {
case ClientClaudeCode:
env["CLAUDE_CONFIG_DIR"] = filepath.Join(home, ".claude")
case ClientCodex:
env["CODEX_HOME"] = filepath.Join(home, ".codex")
case ClientGemini:
// GEMINI_CLI_HOME 替换的是 home 本身,.gemini 段由 CLI 拼接。
env["GEMINI_CLI_HOME"] = home
}
return env
}
// clientConfigSubdir 返回 client 专属配置子目录(相对受管 home),无则空串。
func clientConfigSubdir(ct ClientType) string {
switch ct {
case ClientClaudeCode:
return ".claude"
case ClientCodex:
return ".codex"
case ClientGemini:
return ".gemini"
}
return ""
}