mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-05 02:30:26 +08:00
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>
This commit is contained in:
@@ -2,11 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -319,3 +321,106 @@ func TestLoggerOrderPreservation(t *testing.T) {
|
||||
|
||||
t.Logf("Order preservation test: all %d goroutines maintained sequence order", len(sequences))
|
||||
}
|
||||
|
||||
func TestConcurrentWorkerPoolLimit(t *testing.T) {
|
||||
orig := runCodexTaskFn
|
||||
defer func() { runCodexTaskFn = orig }()
|
||||
|
||||
logger, err := NewLoggerWithSuffix("pool-limit")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setLogger(logger)
|
||||
t.Cleanup(func() {
|
||||
_ = closeLogger()
|
||||
_ = logger.RemoveLogFile()
|
||||
})
|
||||
|
||||
var active int64
|
||||
var maxSeen int64
|
||||
runCodexTaskFn = func(task TaskSpec, timeout int) TaskResult {
|
||||
if task.Context == nil {
|
||||
t.Fatalf("context not propagated for task %s", task.ID)
|
||||
}
|
||||
cur := atomic.AddInt64(&active, 1)
|
||||
for {
|
||||
prev := atomic.LoadInt64(&maxSeen)
|
||||
if cur <= prev || atomic.CompareAndSwapInt64(&maxSeen, prev, cur) {
|
||||
break
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-task.Context.Done():
|
||||
atomic.AddInt64(&active, -1)
|
||||
return TaskResult{TaskID: task.ID, ExitCode: 130, Error: "context cancelled"}
|
||||
case <-time.After(30 * time.Millisecond):
|
||||
}
|
||||
atomic.AddInt64(&active, -1)
|
||||
return TaskResult{TaskID: task.ID}
|
||||
}
|
||||
|
||||
layers := [][]TaskSpec{{{ID: "t1"}, {ID: "t2"}, {ID: "t3"}, {ID: "t4"}, {ID: "t5"}}}
|
||||
results := executeConcurrentWithContext(context.Background(), layers, 5, 2)
|
||||
|
||||
if len(results) != 5 {
|
||||
t.Fatalf("unexpected result count: got %d", len(results))
|
||||
}
|
||||
if maxSeen > 2 {
|
||||
t.Fatalf("worker pool exceeded limit: saw %d active workers", maxSeen)
|
||||
}
|
||||
|
||||
logger.Flush()
|
||||
data, err := os.ReadFile(logger.Path())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read log file: %v", err)
|
||||
}
|
||||
content := string(data)
|
||||
if !strings.Contains(content, "worker_limit=2") {
|
||||
t.Fatalf("concurrency planning log missing, content: %s", content)
|
||||
}
|
||||
if !strings.Contains(content, "parallel: start") {
|
||||
t.Fatalf("concurrency start logs missing, content: %s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentCancellationPropagation(t *testing.T) {
|
||||
orig := runCodexTaskFn
|
||||
defer func() { runCodexTaskFn = orig }()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
runCodexTaskFn = func(task TaskSpec, timeout int) TaskResult {
|
||||
if task.Context == nil {
|
||||
t.Fatalf("context not propagated for task %s", task.ID)
|
||||
}
|
||||
select {
|
||||
case <-task.Context.Done():
|
||||
return TaskResult{TaskID: task.ID, ExitCode: 130, Error: "context cancelled"}
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
return TaskResult{TaskID: task.ID}
|
||||
}
|
||||
}
|
||||
|
||||
layers := [][]TaskSpec{{{ID: "a"}, {ID: "b"}, {ID: "c"}}}
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
results := executeConcurrentWithContext(ctx, layers, 1, 2)
|
||||
if len(results) != 3 {
|
||||
t.Fatalf("unexpected result count: got %d", len(results))
|
||||
}
|
||||
|
||||
cancelled := 0
|
||||
for _, res := range results {
|
||||
if res.ExitCode != 0 {
|
||||
cancelled++
|
||||
}
|
||||
}
|
||||
|
||||
if cancelled == 0 {
|
||||
t.Fatalf("expected cancellation to propagate, got results: %+v", results)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user