You've already forked agentic-coding-workflow
93 lines
3.3 KiB
Go
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 ""
|
|
}
|