Files
myclaude/codeagent-wrapper/wrapper_name.go
swe-agent[bot] e1ad08fcc1 feat(codeagent-wrapper): 完整多后端支持与安全优化
修复 PR #53 中发现的问题,实现完整的多后端功能:

**多后端功能完整性**
- Claude/Gemini 后端支持 workdir (-C) 和 resume (--session-id) 参数
- 并行模式支持全局 --backend 参数和任务级 backend 配置
- 后端参数映射统一,支持 new/resume 两种模式

**安全控制**
- Claude 后端默认启用 --dangerously-skip-permissions 以支持自动化
- 通过 CODEAGENT_SKIP_PERMISSIONS 环境变量控制权限检查
- 不同后端行为区分:Claude 默认跳过,Codex/Gemini 默认启用

**并发控制**
- 新增 CODEAGENT_MAX_PARALLEL_WORKERS 环境变量限制并发数
- 实现 fail-fast context 取消机制
- Worker pool 防止资源耗尽,支持并发监控日志

**向后兼容**
- 版本号统一管理,提供 codex-wrapper 兼容脚本
- 所有默认行为保持不变
- 支持渐进式迁移

**测试覆盖**
- 总体覆盖率 93.4%(超过 90% 要求)
- 新增后端参数、并行模式、并发控制测试用例
- 核心模块覆盖率:backend.go 100%, config.go 97.8%, executor.go 96.4%

**文档更新**
- 更新 skills/codeagent/SKILL.md 反映多后端和安全控制
- 添加 CHANGELOG.md 记录重要变更
- 更新 README 版本说明和安装脚本

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 16:09:33 +08:00

127 lines
2.7 KiB
Go

package main
import (
"os"
"path/filepath"
"strings"
)
const (
defaultWrapperName = "codeagent-wrapper"
legacyWrapperName = "codex-wrapper"
)
var executablePathFn = os.Executable
func normalizeWrapperName(path string) string {
if path == "" {
return ""
}
base := filepath.Base(path)
base = strings.TrimSuffix(base, ".exe") // tolerate Windows executables
switch base {
case defaultWrapperName, legacyWrapperName:
return base
default:
return ""
}
}
// currentWrapperName resolves the wrapper name based on the invoked binary.
// Only known names are honored to avoid leaking build/test binary names into logs.
func currentWrapperName() string {
if len(os.Args) == 0 {
return defaultWrapperName
}
if name := normalizeWrapperName(os.Args[0]); name != "" {
return name
}
execPath, err := executablePathFn()
if err == nil {
if name := normalizeWrapperName(execPath); name != "" {
return name
}
if resolved, err := filepath.EvalSymlinks(execPath); err == nil {
if name := normalizeWrapperName(resolved); name != "" {
return name
}
if alias := resolveAlias(execPath, resolved); alias != "" {
return alias
}
}
if alias := resolveAlias(execPath, ""); alias != "" {
return alias
}
}
return defaultWrapperName
}
// logPrefixes returns the set of accepted log name prefixes, including the
// current wrapper name and legacy aliases.
func logPrefixes() []string {
prefixes := []string{currentWrapperName(), defaultWrapperName, legacyWrapperName}
seen := make(map[string]struct{}, len(prefixes))
var unique []string
for _, prefix := range prefixes {
if prefix == "" {
continue
}
if _, ok := seen[prefix]; ok {
continue
}
seen[prefix] = struct{}{}
unique = append(unique, prefix)
}
return unique
}
// primaryLogPrefix returns the preferred filename prefix for log files.
// Defaults to the current wrapper name when available, otherwise falls back
// to the canonical default name.
func primaryLogPrefix() string {
prefixes := logPrefixes()
if len(prefixes) == 0 {
return defaultWrapperName
}
return prefixes[0]
}
func resolveAlias(execPath string, target string) string {
if execPath == "" {
return ""
}
dir := filepath.Dir(execPath)
for _, candidate := range []string{defaultWrapperName, legacyWrapperName} {
aliasPath := filepath.Join(dir, candidate)
info, err := os.Lstat(aliasPath)
if err != nil {
continue
}
if info.Mode()&os.ModeSymlink == 0 {
continue
}
resolved, err := filepath.EvalSymlinks(aliasPath)
if err != nil {
continue
}
if target != "" && resolved != target {
continue
}
if name := normalizeWrapperName(aliasPath); name != "" {
return name
}
}
return ""
}