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
+29
View File
@@ -0,0 +1,29 @@
# 单 agent 任务协议
你是独立的任务执行 agent,完成 /blackboard/task.md 中的任务。任务信息通过工作区与黑板目录交换。
## 目录结构
| 路径 | 用途 |
| --- | --- |
| /blackboard/task.md | 任务描述,每轮都要重读,勿修改 |
| /blackboard/workspace/ | 你的工作区(你的 cwd),所有产物写在这里 |
| /blackboard/AGENTS.md | 本协议(副本在你的工作区,勿修改) |
| /blackboard/DONE | 完成标记,存在即表示任务已交付完成,只用 blackboard 工具创建 |
| /blackboard/logs/ | 每轮运行日志(supervisor 维护) |
| /blackboard/sessions/ | 会话 IDsupervisor 维护,供 --resume |
| /blackboard/result.json | 任务结果(supervisor 结束时生成,勿手动修改) |
## 每轮流程
1. **读取**:读取 /blackboard/task.md,明确任务与提交规则(若为解题任务,通常含 flag 提交 API / token / unique_code)。
2. **评估**:检查工作区已有产物,判断进度与缺口,不重复已完成工作。
3. **执行**:推进任务——执行命令、读写文件、编写产物到 /blackboard/workspace/。
4. **提交**:拿到 flag 的任务,立即按 task.md 中约定的规则提交,并把提交响应与得分写入产物。
5. **判定**:全部工作完成、交付物完整后,用 blackboard 工具 action=done 创建 DONEsummary 为最终交付总结,含关键结果/flag/提交响应/产物清单)。DONE 只能创建一次;宁可多轮打磨,不要过早宣布完成;确认无法解出时同样用 done 标记并写明「未解出」与已尝试内容。
## 红线
- 不直接写 /blackboard/DONE——必须走 blackboard 工具 action=done,保证原子创建。
- 不伪造实验、命令或提交结果;如实记录。
- 不修改 /blackboard/task.md、/blackboard/AGENTS.md 与 /blackboard/result.json。
+56
View File
@@ -0,0 +1,56 @@
# pigo 协作镜像(单 agent 版)
#
# 构建(在仓库根目录执行,先本地交叉编译 pigo,再 docker build):
# $env:GOOS="linux"; $env:CGO_ENABLED="0"
# go build -trimpath -ldflags="-s -w" -o coop/tmp/pigo-linux-amd64 ./cmd/pigo
# docker build -f coop/Dockerfile -t pigo-coop .
# 或直接用 Linux 的 Go 工具链时跳过交叉编译步骤。
#
# 运行示例(模型后端由外部调用方通过环境变量传入):
# docker run --rm \
# -e MODEL=deepseek-chat \
# -e BASE_URL=https://api.deepseek.com \
# -e API_KEY=<your-key> \
# -e TASK="请为 XX 编写设计文档并实现原型" \
# -e ROUND_MAX=6 \
# pigo-coop
#
# 协作方式:单个 pigo 进程在工作区 /blackboard/workspace 内完成 task.md 的任务,
# 用 --resume 保持跨轮连续上下文;完成时通过 blackboard 工具创建 DONE
# supervisor 把结果以结构化 JSON/blackboard/result.json)落盘。
#
# 基础镜像复用本机已有的 pigo-worker:latestAlpine,已含 bash/git/wget/flock/
# timeout/CA 证书与静态 pigo 二进制),这里仅覆盖 pigo 二进制为新代码(含
# blackboard 工具)并加入 supervisor 与协作 prompt,避免重新拉取 golang/alpine。
FROM pigo-worker:latest
# solve/pentest tools: base image only ships busybox (no curl/python3), add them here.
# pigo-worker 镜像 USER=agentRUN 层以非 root 执行,apk 无法锁库,先切 root 再恢复。
USER root
RUN apk add --no-cache curl wget python3 py3-pip jq openssl
# Python 常用库:网络/加密/数据/解析等,覆盖多数协作与 CTF 场景。
# 使用清华 PyPI 镜像加速国内构建;--no-cache-dir 避免膨胀镜像。
# --break-system-packagesAlpine 3.20+ 标记 externally-managedpip 全局安装需显式放行。
RUN pip3 install --no-cache-dir --break-system-packages \
-i https://pypi.tuna.tsinghua.edu.cn/simple \
requests urllib3 certifi idna charset-normalizer \
cryptography pyOpenSSL paramiko pyjwt \
PyYAML beautifulsoup4 lxml \
python-dotenv click tqdm \
numpy pandas \
httpx aiohttp websockets dnspython \
psutil pillow
USER agent
# 覆盖 pigo 二进制:coop/tmp/pigo-linux-amd64 由本地 Go 交叉编译(GOOS=linux CGO_ENABLED=0)。
# legacy builder 不支持 COPY --chmod(需 BuildKit)。权限位由主机侧 chmod 提供(构建前先 chmod 755)。
COPY coop/tmp/pigo-linux-amd64 /usr/local/bin/pigo
COPY coop/supervisor.sh /usr/local/bin/supervisor.sh
COPY coop/prompts /prompts
COPY coop/AGENTS.md /prompts/AGENTS.md
WORKDIR /blackboard
ENTRYPOINT ["/usr/local/bin/supervisor.sh"]
+196
View File
@@ -0,0 +1,196 @@
# pigo 协作 —— 调用文档
> 在 Docker 容器内运行一个 pigo agent(单 agent)完成 /blackboard/task.md 中的任务,工作区为 /blackboard/workspace,完成时通过 blackboard 工具创建 DONEsupervisor 把结果以结构化 JSONresult.json)落盘,供外部调度方直接解析。
---
## 1. 原理与架构
```
+------------------ docker 容器 pigo-coop ----------------+
| |
TASK/MODEL/ | +---------- supervisor.sh(单 agent 编排)-------+ |
API_KEY ──────┼─▶ | 每轮运行一个 pigo 进程: | |
| | └─ agent ──▶ cwd = /blackboard/workspace | |
| | (--resume 会话 · 角色 prompt agent.md) | |
| +──────┬────────────────────────────────----------+ |
| ▼ |
| /blackboard(任务黑板:task.md + workspace + DONE |
+-----------------------------------------------------------+
```
- **单 agent**:每轮一个 pigo 进程串行推进,无评审者;工作区 `/blackboard/workspace` 是普通文件工具(read/write/bash)的根目录。
- **原子完成标记**`DONE` 只能通过 `blackboard` 工具 `done``O_CREATE|O_EXCL`)创建,supervisor 检测到 `DONE` 即收尾。
- **连续上下文**:headless 运行自动持久化会话,supervisor 从 stream-json 首事件提取 `sessionId`,下一轮用 `--resume` 恢复,agent 跨轮记住自己的思考。
- **协议注入**:任务协议(AGENTS.md)副本放入工作区,pigo 自动注入系统提示;角色 prompt 通过 `--append-system-prompt` 注入。
- **结构化结果**:任务结束(成功/未解出/超时/失败)统一写 `$BB/result.json`,字段:`status``exit_code``summary``flag``artifacts`
---
## 2. 前置条件
| 依赖 | 说明 |
| --- | --- |
| Go 工具链 | 版本与 `go.mod` 一致(当前 `go 1.27rc1`),用于交叉编译 pigo 二进制 |
| Docker | 本机已有基础镜像 `pigo-worker:latest`Alpine,含 bash/git/wget/flock/timeout/CA 证书) |
| 模型后端 | 任意 OpenAI 兼容 API(如 DeepSeek、Ollama、OpenRouter),需可访问的 base-url 与 API key |
---
## 3. 构建
### 3.1 交叉编译 pigo(含 blackboard 工具)
Windows PowerShell
```powershell
$env:GOOS="linux"; $env:CGO_ENABLED="0"
go build -trimpath -ldflags="-s -w" -o coop/tmp/pigo-linux-amd64 ./cmd/pigo
```
Linux/macOS
```bash
GOOS=linux CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o coop/tmp/pigo-linux-amd64 ./cmd/pigo
```
产物 `coop/tmp/pigo-linux-amd64` 是纯静态 Linux 二进制(约 30MB)。
### 3.2 构建镜像
```bash
docker build -f coop/Dockerfile -t pigo-coop .
```
> 镜像直接 `FROM pigo-worker:latest`(复用本机已有镜像,不拉取 golang/alpine),只覆盖 pigo 二进制并加入 supervisor 与协作 prompt,构建通常秒级完成。
---
## 4. 环境变量(运行参数)
| 变量 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `MODEL` | ✅ | — | 模型名,如 `deepseek-chat` |
| `BASE_URL` | ✅ | — | OpenAI 兼容 API base-url |
| `API_KEY` | ✅ | — | API key |
| `PROTOCOL` | 否 | 按 model 推断 | 协议 `openai` / `anthropic` |
| `TASK` | 二选一 | — | 任务描述;不填则需挂载 `$BB/task.md` |
| `BLACKBOARD` | 否 | `/blackboard` | 黑板根目录(即 `BB` |
| `PROMPTS` | 否 | `/prompts` | 协作 prompt 目录(镜像内固定,一般不用改) |
| `ROUND_MAX` | 否 | `10` | 最大轮次 |
| `TIMEOUT` | 否 | `600` | 单轮超时秒数,`0`=不超时 |
| `FAIL_MODE` | 否 | `stop` | agent 失败时:`stop`=立即退出(默认)\| `continue`=继续下一轮 |
---
## 5. 运行示例
### 5.1 最小运行(真实模型)
```bash
docker run --rm \
-e MODEL=deepseek-chat \
-e BASE_URL=https://api.deepseek.com \
-e API_KEY=<your-key> \
-e TASK="请为 XX 编写设计文档并实现原型" \
-e ROUND_MAX=6 \
pigo-coop
```
### 5.2 保留黑板产物与连续会话(推荐)
```bash
mkdir -p blackboard
docker run --rm \
-v $PWD/blackboard:/blackboard \ # 黑板产物落盘
-v $HOME/.pigo:/root/.pigo \ # 保留 agent 会话(--resume 依赖)
-e MODEL=deepseek-chat \
-e BASE_URL=https://api.deepseek.com \
-e API_KEY=<your-key> \
-e TASK="..." \
pigo-coop
```
> 会话持久化在 `~/.pigo/sessions`(容器内为 `/root/.pigo`)。**不挂载 `$HOME` 时,会话只存在于容器生命周期内**——跨 `docker run` 不保留;每次 `run` 内部跨轮仍有效。
### 5.3 结果与退出码
- 任务完成(检测到 `DONE`):退出码 `0``result.json``status=solved`
- 达到 `ROUND_MAX` 仍未完成:退出码 `1``status=unsolved`,检查黑板与日志。
- 单轮超时被强杀:`status=timeout`(退出码 124)。
- 参数缺失 / 非法:退出码 `2`
---
## 6. 黑板目录结构
```
/blackboard/
├── task.md # 任务描述(勿修改)
├── AGENTS.md # 任务协议(工作区有副本)
├── workspace/ # agent 工作区 = 其 cwd(产物全部在这里)
├── DONE # 完成标记(只用 blackboard done 创建)
├── result.json # 结构化结果(supervisor 结束时生成)
├── sessions/ # agent 的 session idsupervisor 内部维护,供 --resume
└── logs/ # 每轮运行日志:round-N.log
```
---
## 7. blackboard 工具(agent 侧 API
pigo 在 `BB` 环境变量存在时自动注册 `blackboard` 工具;普通运行不受影响。agent 通过该工具原子读写黑板:
| 操作 | 参数 | 行为 |
| --- | --- | --- |
| `read` | (可选 `path` | 无 `path`:返回黑板全局快照(task.md、workspace 清单、DONE 状态、环境);有 `path`:返回黑板内单文件内容(如 `workspace/exploit.py` |
| `post` | `file``content` | 原子追加消息到 `messages/<file>``file` 必须是裸 `*.md` 名(防目录穿越),内容 ≤ 32 KiB |
| `done` | `summary` | 原子创建 `DONE``O_CREATE|O_EXCL`);已存在则报错且不覆盖。仅交付完整时调用 |
环境变量 `BB` / `ROUND` / `NAME` 由 supervisor 注入。
---
## 8. 验证与调试
### 8.1 无模型端到端验证(mock)
`coop/tmp/mock_openai.py` 是一个 OpenAI 兼容 mock server,驱动 agent 依次调用 `blackboard done`,用于验证单 agent 编排与 DONE 原子性:
```bash
# 终端 1:启动 mock(本机)
python coop/tmp/mock_openai.py 8899
# 终端 2:运行容器,base-url 指向 mock(按环境替换宿主地址)
docker run --rm \
-v $PWD/blackboard:/blackboard \
-e MODEL=mock -e BASE_URL=http://<host-ip>:8899 -e API_KEY=test \
-e TASK="验证任务" -e ROUND_MAX=3 -e TIMEOUT=120 \
pigo-coop
```
预期:agent 完成并创建 `DONE`,容器退出 0`blackboard/result.json``status=solved`
### 8.2 查看日志
```bash
# 黑板挂载后:
cat blackboard/logs/round-1.log # 第 1 轮的 stream-json 事件
cat blackboard/sessions/agent.session # 下一轮 --resume 用的 session id
```
### 8.3 常见问题
| 现象 | 原因 / 处理 |
| --- | --- |
| 退出码 `2` 且提示缺 MODEL/BASE_URL/API_KEY | 未传必填环境变量 |
| 容器内报 `DONE already exists` | 正常:DONE 幂等,上一次运行已留标记 |
| 跨 run 后 agent 不记得之前轮次 | 未挂载 `$HOME/.pigo`;会话在容器内 `~/.pigo/sessions``--rm` 后丢失 |
| `docker build` 报找不到 `pigo-worker:latest` | 先 `docker pull` 或在目标机导入该基础镜像 |
| 构建期 `chmod` 被拒 | 已用 `COPY --chmod=755` 规避(NTFS 挂载构建上下文会丢可执行位) |
---
## 9. 任务协议要点(详见 `/blackboard/AGENTS.md`
每轮:**读取 task.md → 检查已有产物 → 推进任务 → 完成后用 blackboard done 创建 DONE**。红线:不直接写 DONE;不伪造命令/提交结果;不修改 task.md 与 AGENTS.md。
+107
View File
@@ -0,0 +1,107 @@
# pigo 协作运行指南
> 只讲怎么跑。原理、架构、完整参数见 [README.md](./README.md)。
---
## 1. 前置检查(1 分钟)
```bash
go version # 与 go.mod 一致(当前 go 1.27rc1
docker images # 确认已有 pigo-worker:latest
```
没有 `pigo-worker:latest` 时先导入它,否则构建会失败。
---
## 2. 构建
```bash
# Windows PowerShell
$env:GOOS="linux"; $env:CGO_ENABLED="0"
go build -trimpath -ldflags="-s -w" -o coop/tmp/pigo-linux-amd64 ./cmd/pigo
# Linux/macOS
GOOS=linux CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o coop/tmp/pigo-linux-amd64 ./cmd/pigo
```
```bash
docker build -f coop/Dockerfile -t pigo-coop .
```
---
## 3. 运行
### 3.1 直接运行(不保留任何数据)
```bash
docker run --rm \
-e MODEL=deepseek-chat \
-e BASE_URL=https://api.deepseek.com \
-e API_KEY=<your-key> \
-e TASK="请为 XX 编写设计文档并实现原型" \
-e ROUND_MAX=6 \
pigo-coop
```
### 3.2 推荐运行(保留黑板产物 + 跨 run 会话)
```bash
mkdir -p blackboard
docker run --rm \
-v $PWD/blackboard:/blackboard \
-v $HOME/.pigo:/root/.pigo \
-e MODEL=deepseek-chat \
-e BASE_URL=https://api.deepseek.com \
-e API_KEY=<your-key> \
-e TASK="请为 XX 编写设计文档并实现原型" \
-e ROUND_MAX=6 \
pigo-coop
```
> 不挂载 `$HOME/.pigo` 时,agent 会话只在容器生命周期内有效(单次 run 内跨轮不受影响)。
### 3.3 常用参数速查
| 参数 | 必填 | 默认 | 用途 |
| --- | --- | --- | --- |
| `MODEL` | ✅ | — | 模型名 |
| `BASE_URL` | ✅ | — | OpenAI 兼容 base-url |
| `API_KEY` | ✅ | — | API key |
| `TASK` | ✅(或用 `task.md` | — | 任务描述 |
| `ROUND_MAX` | 否 | `10` | 最大轮次 |
| `TIMEOUT` | 否 | `600` | 单轮超时秒数(`0`=不限) |
| `PROTOCOL` | 否 | 按模型推断 | `openai` / `anthropic` |
---
## 4. 结果怎么看
- **完成**stdout 打印 `[supervisor] status=solved`,退出码 `0`
- **未完成**:达到 `ROUND_MAX``status=unsolved`,退出码 `1`
- **参数错**:退出码 `2`
保留黑板后查看:
```bash
ls blackboard/workspace/ # agent 产物
cat blackboard/DONE # 最终交付总结
cat blackboard/result.json # 结构化结果(status/exit_code/summary/flag/artifacts
cat blackboard/logs/round-1.log # 第 1 轮运行日志
```
`result.json` 是给外部调度方(如 agent-web 的 run_coop 工具)直接解析的标准结果文件,字段固定:`status``exit_code``summary``flag``artifacts`
---
## 5. 常见运行问题
| 报错 / 现象 | 处理 |
| --- | --- |
| 退出 2`必须提供 MODEL / BASE_URL / API_KEY` | 补传环境变量 |
| `DONE already exists` | 正常,上一次运行已留标记(幂等) |
| 找不到 `pigo-worker:latest` | 先导入基础镜像再 build |
| 想从头重跑 | 删除挂载目录里的 `DONE``result.json` 后重新 run |
+19
View File
@@ -0,0 +1,19 @@
# 构建 pigo 协作镜像(单 agent 版:一个 pigo 进程完成任务,blackboard 工具负责原子创建 DONE
# 用法(仓库根目录执行): powershell -File coop/build.ps1
$ErrorActionPreference = "Stop"
docker build -f coop/Dockerfile -t pigo-coop .
Write-Host ""
Write-Host "构建完成: pigo-coop"
Write-Host ""
Write-Host "运行示例(外部调用方传入模型/API 信息与任务):"
Write-Host ' docker run --rm \'
Write-Host ' -e MODEL=deepseek-chat \'
Write-Host ' -e BASE_URL=https://api.deepseek.com \'
Write-Host ' -e API_KEY=<your-key> \'
Write-Host ' -e TASK="请为 XX 编写设计文档并实现原型" \'
Write-Host ' -e ROUND_MAX=6 \'
Write-Host ' pigo-coop'
Write-Host ""
Write-Host "黑板产物默认在容器内 /blackboard,如需保留可挂载卷: -v $PWD/blackboard:/blackboard"
+18
View File
@@ -0,0 +1,18 @@
# 角色:agent(主执行者)
你是 pigo 单 agent 任务执行者,独立完成 /blackboard/task.md 中的任务。
## 你的职责
- 理解任务、制定方案、完成核心产出。
- 所有产物写入你的工作区(你的 cwd,即 /blackboard/workspace/),按需命名。
- 需要提交 flag 的任务:拿到 flag 后立即按任务中的提交规则提交,并把提交响应与得分记录在产物中。
## 工作方式
- 每轮:读取 /blackboard/task.md 与工作区已有产物 → 推进任务 → 将进展与产物写入工作区。
- 环境变量 ROUND、NAME、BB 由 supervisor 提供;需要记录进展或创建完成标记时用 blackboard 工具(BB 指向黑板根目录)。
- 不要伪造命令结果、文件内容或提交响应;如实记录。
- 全部工作完成、交付物完整时,用 blackboard 工具 action=done 创建完成标记(summary 写最终交付总结,含关键结果、flag、提交响应、产物清单)。
## 注意
- 若本轮未完成,下一轮会用 --resume 恢复你的会话继续推进,跨轮保持上下文。
- 宁可多轮打磨,不要过早宣布完成;确认无法解出时同样用 action=done 标记,summary 写明「未解出」与已尝试内容,让调度方及时切换下一题。
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# pigo 协作 supervisor(单 agent 版)
#
# 在容器内运行一个 pigo headless 进程完成 /blackboard/task.md 中的任务。
# agent 的工作区为 /blackboard/workspace,产物与轮次日志保留在黑板上;
# 任务完成(agent 用 blackboard 工具创建 DONE)后,supervisor 把结果
# 以结构化 JSONresult.json)落盘并打印一行摘要,供外部调度方直接解析。
#
# 与旧双 agent 版的关键差异:
# 1. 单 agent:每轮只运行一个 pigo 进程,无评审者,串行推进直至 DONE。
# 2. 结构化结果:无论成功/未解出/超时/失败,统一写 $BB/result.json
# status / exit_code / summary / flag / artifacts),主 agent 无需再
# 漫游黑板文件逐段猜测结果。
# 3. 日志精简:supervisor 只输出带 [supervisor] 前缀的状态行;agent 的
# stream-json 事件只写日志文件,不再混入 stdout。
#
# 环境变量(均由外部调用方传入):
# MODEL 模型名,如 deepseek-chat (必填)
# BASE_URL OpenAI 兼容 API base-url (必填)
# API_KEY API key (必填)
# PROTOCOL 协议 openai|anthropic (可选,默认按 model 推断)
# TASK 任务描述 (必填;或挂载 $BB/task.md
# BLACKBOARD 黑板目录 (默认 /blackboard
# PROMPTS 协作 prompt 目录 (默认 /prompts
# ROUND_MAX 最大轮次 (默认 10)
# TIMEOUT 单轮超时秒数,0=不超时 (默认 600)
# FAIL_MODE agent 失败时:stop=立即退出(默认)| continue=继续下一轮
set -u
BB="${BLACKBOARD:-/blackboard}"
PROMPTS="${PROMPTS:-/prompts}"
ROUND_MAX="${ROUND_MAX:-10}"
TIMEOUT="${TIMEOUT:-600}"
FAIL_MODE="${FAIL_MODE:-stop}"
log() { echo "[supervisor] $*"; }
# emit_result 把任务结果以结构化 JSON 写入 $BB/result.json 并打印一行摘要。
# status: solved | unsolved | error | timeout
emit_result() {
local status="$1" summary="$2" code="$3"
local flag="" artifacts_json="[]" summary_json='""' flag_json='""'
# flag 优先从 summary 提取,其次在 workspace 产物中全量检索
if [ -n "$summary" ]; then
flag=$(printf '%s' "$summary" | grep -oE 'flag\{[^}]+\}' | head -1)
fi
if [ -z "$flag" ] && [ -d "$BB/workspace" ]; then
flag=$(grep -rhoE 'flag\{[^}]+\}' "$BB/workspace" 2>/dev/null | head -1)
fi
# 产物清单:workspace 下的全部文件(最多 50 个),经 python3 转义为 JSON 数组
if [ -d "$BB/workspace" ]; then
artifacts_json=$(
cd "$BB/workspace" && find . -type f 2>/dev/null | sed 's|^\./||' | head -50 |
python3 -c 'import json,sys; print(json.dumps([l.rstrip("\n") for l in sys.stdin]))' 2>/dev/null
)
[ -z "$artifacts_json" ] && artifacts_json="[]"
fi
# summary 截断到 4000 字符并经 python3 转义,防止引号/换行破坏 JSON
summary_json=$(printf '%s' "$summary" | head -c 4000 | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
[ -z "$summary_json" ] && summary_json='""'
flag_json=$(printf '%s' "$flag" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
[ -z "$flag_json" ] && flag_json='""'
cat > "$BB/result.json" <<EOF
{
"status": "$status",
"exit_code": $code,
"summary": $summary_json,
"flag": $flag_json,
"artifacts": $artifacts_json
}
EOF
log "===== 协作结束 ====="
log "status=$status exit_code=$code"
[ -n "$flag" ] && log "flag=$flag"
log "结构化结果已写入:$BB/result.json"
}
# ---- 前置校验 ----
if [ -z "${MODEL:-}" ] || [ -z "${BASE_URL:-}" ] || [ -z "${API_KEY:-}" ]; then
echo "错误:必须提供 MODEL / BASE_URL / API_KEY 环境变量" >&2
exit 2
fi
if [ -z "${TASK:-}" ] && [ ! -f "$BB/task.md" ]; then
echo "错误:请通过 TASK 环境变量或挂载 $BB/task.md 提供任务" >&2
exit 2
fi
if [ -f "$BB/DONE" ]; then
log "黑板已有完成标记,如需重新开始请删除 $BB/DONE"
exit 0
fi
# ---- 初始化黑板 ----
mkdir -p "$BB/workspace" "$BB/logs" "$BB/sessions"
if [ -n "${TASK:-}" ]; then
printf '%s\n' "$TASK" > "$BB/task.md"
fi
# 协议注入:agent 的 cwd 是 $BB/workspaceAGENTS.md 注入链从 cwd 起,
# 因此把协议副本放进工作区(勿修改,它是每轮系统提示的一部分)
cp "$PROMPTS/AGENTS.md" "$BB/workspace/AGENTS.md"
log "黑板初始化完成:$BB"
log "任务:$(head -c 200 "$BB/task.md")"
# ---- 单轮运行:一个 pigo 进程,输出 stream-json(首事件携带 sessionId)供 --resume ----
run_agent() {
local round="$1"
local session_file="$BB/sessions/agent.session"
local task_prompt="轮次 $round 开始。先读取 /blackboard/task.md 中的任务,检查工作区已有产物与 DONE 状态,然后继续推进任务。全部工作完成、交付物完整时,用 blackboard 工具 action=done 创建完成标记(summary 写最终交付总结,含关键结果/flag/提交响应/产物清单)。"
local args=(-p "$task_prompt" -a -C "$BB/workspace" -o stream-json \
--model "$MODEL" --base-url "$BASE_URL" --api-key "$API_KEY" \
--append-system-prompt "$PROMPTS/agent.md")
[ -n "${PROTOCOL:-}" ] && args+=(--protocol "$PROTOCOL")
if [ -s "$session_file" ]; then
args+=(--resume "$(cat "$session_file")")
fi
export ROUND="$round" NAME="agent" BB="$BB"
local logfile="$BB/logs/round-$round.log"
log "$round 轮开始运行 agent(日志:$logfile"
if [ "$TIMEOUT" -gt 0 ] 2>/dev/null; then
timeout "$TIMEOUT" pigo "${args[@]}" > "$logfile" 2>&1
else
pigo "${args[@]}" > "$logfile" 2>&1
fi
local rc=$?
# 从 stream-json 首事件提取 sessionId 供下一轮 --resumeagent_start 事件带 env.sessionId
local sid
sid=$(grep -o '"sessionId":"[^"]*"' "$logfile" 2>/dev/null | head -1 | cut -d'"' -f4)
if [ -n "$sid" ]; then
printf '%s' "$sid" > "$session_file"
fi
return $rc
}
# ---- 主循环:逐轮运行 agent,直到 DONE 或达到轮次上限 ----
for round in $(seq 1 "$ROUND_MAX"); do
[ -f "$BB/DONE" ] && break
log "===== 第 $round 轮开始 ====="
run_agent "$round"
rc=$?
if [ "$rc" -eq 0 ]; then
log "$round 轮完成"
else
log "$round 轮失败(exit=$rc),日志见 $BB/logs/round-$round.log"
if [ "$FAIL_MODE" = "stop" ]; then
# 兜底:agent 虽超时/失败,但黑板已有提交成功证据(workspace 内 *.md 含 correct:true
# → 视为完成正常退出,避免"flag 已提交却因未写 DONE 被强杀(exit 143"。
_done=0
for _f in "$BB"/workspace/*.md "$BB"/messages/*.md; do
[ -f "$_f" ] && grep -q '"correct":true' "$_f" && _done=1 && break
done
if [ "$_done" = "1" ]; then
log "检测到提交成功证据,自动标记完成"
{ echo "Task finished (auto-detected submit success)"; } > "$BB/DONE"
emit_result solved "$(cat "$BB/DONE")" 0
exit 0
fi
if [ "$rc" -eq 124 ]; then
emit_result timeout "" "$rc"
else
emit_result error "agent 退出码 $rc,日志见 logs/round-$round.log" "$rc"
fi
exit "$rc"
fi
fi
done
if [ -f "$BB/DONE" ]; then
log "检测到完成标记,任务结束"
emit_result solved "$(cat "$BB/DONE")" 0
exit 0
fi
log "达到最大轮次 $ROUND_MAX 仍未完成,请检查黑板产物与日志"
emit_result unsolved "" 1
exit 1