first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
package agent
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"sync"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
)
// CoopTask 记录一次后台运行的 pi 协作任务。
// 任务由 run_coop 工具异步启动,完成后通过 CoopManager.Complete 通知主 agent。
type CoopTask struct {
ID string `json:"id"`
SessionID string `json:"session_id"`
ContainerID string `json:"container_id,omitempty"`
// ProcessID 是本地子进程模式的 worker 进程 PIDdocker 模式为 0)。
ProcessID int `json:"process_id,omitempty"`
Status string `json:"status"` // running | done
ExitCode int `json:"exit_code,omitempty"`
Output string `json:"output,omitempty"`
Blackboard string `json:"blackboard,omitempty"`
Error string `json:"error,omitempty"`
RoundMax int `json:"round_max"`
CreatedAt time.Time `json:"created_at"`
FinishedAt time.Time `json:"finished_at,omitempty"`
// Result 是 supervisor 写入黑板 result.json 的结构化结果(存在时优先于 Output)。
Result *CoopResult `json:"result,omitempty"`
// ChallengeCode 任务描述的题目编号(如 c-06),用于通知控制层关闭/切换靶机
ChallengeCode string `json:"challenge_code,omitempty"`
}
// CoopResult 是 pi 协作容器 supervisor 生成的标准结果($BB/result.json),
// 完成通知与主 agent 汇报轮直接使用这些结构化字段,不再依赖截断的容器 stdout。
type CoopResult struct {
Status string `json:"status"` // solved | unsolved | error | timeout
ExitCode int `json:"exit_code"`
Summary string `json:"summary"`
Flag string `json:"flag"`
Artifacts []string `json:"artifacts"`
}
// Solved 返回任务是否成功解出/交付完成。
func (r *CoopResult) Solved() bool {
return r != nil && r.Status == "solved"
}
// CoopManager 管理后台协作任务的生命周期,并在任务完成时回调 Agent,
// 由 Agent 决定是否唤醒主 agent 汇报结果。
type CoopManager struct {
mu sync.Mutex
tasks map[string]*CoopTask
notify func(task *CoopTask)
}
func NewCoopManager() *CoopManager {
return &CoopManager{tasks: make(map[string]*CoopTask)}
}
// SetNotify 注册任务完成回调。
func (m *CoopManager) SetNotify(fn func(task *CoopTask)) {
m.mu.Lock()
m.notify = fn
m.mu.Unlock()
}
// Register 登记一个正在运行的后台任务。
func (m *CoopManager) Register(task *CoopTask) {
m.mu.Lock()
m.tasks[task.ID] = task
m.mu.Unlock()
}
// Get 按任务 ID 查询任务。
func (m *CoopManager) Get(id string) (*CoopTask, bool) {
m.mu.Lock()
defer m.mu.Unlock()
task, ok := m.tasks[id]
return task, ok
}
// List 返回全部任务,按创建时间倒序;sessionID 非空时只返回该会话的任务。
func (m *CoopManager) List(sessionID string) []*CoopTask {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]*CoopTask, 0, len(m.tasks))
for _, task := range m.tasks {
if sessionID == "" || task.SessionID == sessionID {
out = append(out, task)
}
}
sort.Slice(out, func(i, j int) bool {
return out[i].CreatedAt.After(out[j].CreatedAt)
})
return out
}
// Complete 标记任务完成并触发完成回调(回调在锁外执行,避免死锁)。
func (m *CoopManager) Complete(task *CoopTask) {
m.mu.Lock()
task.Status = "done"
task.FinishedAt = time.Now()
m.tasks[task.ID] = task
notify := m.notify
m.mu.Unlock()
if notify != nil {
notify(task)
}
}
// CurrentCoopRound 统计黑板 logs 目录中已出现的最大协作轮次。
// 单 agent 版日志命名为 round-N.log;兼容旧版 a-round-N.log / b-round-N.log。
func CurrentCoopRound(blackboardDir string) int {
if blackboardDir == "" {
return 0
}
entries, err := os.ReadDir(filepath.Join(blackboardDir, "logs"))
if err != nil {
return 0
}
maxRound := 0
for _, entry := range entries {
var round int
if _, err := fmt.Sscanf(entry.Name(), "round-%d.log", &round); err == nil {
if round > maxRound {
maxRound = round
}
continue
}
if _, err := fmt.Sscanf(entry.Name(), "a-round-%d.log", &round); err == nil {
if round > maxRound {
maxRound = round
}
}
if _, err := fmt.Sscanf(entry.Name(), "b-round-%d.log", &round); err == nil {
if round > maxRound {
maxRound = round
}
}
}
return maxRound
}
// ContainerInspectStatus 查询容器当前的运行状态与人类可读状态描述
// (如 "running" + "Up 2 minutes" / "exited" + "Exited (0)"),
// 供 Web 页面实时展示协作容器状态。
func ContainerInspectStatus(socket, containerID string) (state, status string, err error) {
if containerID == "" {
return "", "", errors.New("缺少容器 ID")
}
cli, err := client.NewClientWithOpts(
client.WithHost(socket),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return "", "", err
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
list, err := cli.ContainerList(ctx, container.ListOptions{
All: true,
Filters: filters.NewArgs(filters.Arg("id", containerID)),
})
if err != nil {
return "", "", err
}
if len(list) == 0 {
return "", "", errors.New("容器不存在")
}
return list[0].State, list[0].Status, nil
}