ai-battle 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/ai-battle.sh +1728 -0
- package/package.json +29 -0
package/ai-battle.sh
ADDED
|
@@ -0,0 +1,1728 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================
|
|
3
|
+
# ai-battle.sh — AI 圆桌讨论工具
|
|
4
|
+
#
|
|
5
|
+
# 让多个 AI Agent(Claude/Codex/Gemini…)对同一问题进行
|
|
6
|
+
# 结构化讨论,自动管理轮次、检测共识、保存全部讨论记录。
|
|
7
|
+
#
|
|
8
|
+
# 用法:
|
|
9
|
+
# ai-battle [--agents claude,codex] [--rounds 10] [--god] [--referee]
|
|
10
|
+
# ai-battle --help
|
|
11
|
+
#
|
|
12
|
+
# 依赖: jq, bash 4+, (可选) codex, claude, gemini
|
|
13
|
+
# ============================================================
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# ======================== 加载 .env ========================
|
|
17
|
+
# 如果执行目录存在 .env 文件,自动加载环境变量
|
|
18
|
+
if [ -f ".env" ]; then
|
|
19
|
+
set -a
|
|
20
|
+
# shellcheck source=/dev/null
|
|
21
|
+
source .env
|
|
22
|
+
set +a
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# ======================== 版本 ========================
|
|
26
|
+
VERSION="0.1.0"
|
|
27
|
+
|
|
28
|
+
# ======================== 颜色 ========================
|
|
29
|
+
BLUE='\033[1;34m'
|
|
30
|
+
GREEN='\033[1;32m'
|
|
31
|
+
YELLOW='\033[1;33m'
|
|
32
|
+
RED='\033[1;31m'
|
|
33
|
+
CYAN='\033[0;36m'
|
|
34
|
+
BOLD='\033[1m'
|
|
35
|
+
NC='\033[0m'
|
|
36
|
+
|
|
37
|
+
# ======================== 默认配置 ========================
|
|
38
|
+
DEFAULT_AGENTS="claude,codex"
|
|
39
|
+
DEFAULT_MAX_ROUNDS=10
|
|
40
|
+
PROBLEM_FILE="problem.md"
|
|
41
|
+
ROUNDS_DIR="rounds"
|
|
42
|
+
CONSENSUS_FILE="consensus.md"
|
|
43
|
+
LOG_FILE=".debate.log"
|
|
44
|
+
CONFIG_FILE=".debate.json"
|
|
45
|
+
REFEREE_PROMPT_FILE="referee.md"
|
|
46
|
+
SESSIONS_DIR=".sessions"
|
|
47
|
+
|
|
48
|
+
# ======================== Codex 配置 ========================
|
|
49
|
+
CODEX_MODEL="${CODEX_MODEL:-gpt-5.3-codex}"
|
|
50
|
+
|
|
51
|
+
# ======================== Agent 注册表 ========================
|
|
52
|
+
# 已注册的 agent 名称列表
|
|
53
|
+
REGISTERED_AGENTS=()
|
|
54
|
+
|
|
55
|
+
# 注册 agent
|
|
56
|
+
# 用法: register_agent <name>
|
|
57
|
+
# 要求: 必须实现 check_<name>() 和 call_<name>() 两个函数
|
|
58
|
+
register_agent() {
|
|
59
|
+
REGISTERED_AGENTS+=("$1")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# ======================== Agent: Claude (CLI) ========================
|
|
63
|
+
# 前置条件: 用户需自行设置 claude 所需的环境变量, 例如:
|
|
64
|
+
# export ANTHROPIC_BASE_URL="https://open.bigmodel.cn/api/anthropic"
|
|
65
|
+
# export ANTHROPIC_AUTH_TOKEN="your-token"
|
|
66
|
+
# export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
|
67
|
+
# export API_TIMEOUT_MS=600000
|
|
68
|
+
# export ANTHROPIC_DEFAULT_SONNET_MODEL="glm-4.7"
|
|
69
|
+
|
|
70
|
+
# 检查 Claude CLI 是否可用
|
|
71
|
+
# 返回: 0=可用, 1=不可用
|
|
72
|
+
check_claude() {
|
|
73
|
+
if ! command -v claude &>/dev/null; then
|
|
74
|
+
echo -e " ${RED}✗ claude 命令不在 PATH 中${NC}" >&2
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
# 调用测试:分离 stdout/stderr,检查退出码
|
|
78
|
+
local resp errmsg
|
|
79
|
+
local tmperr
|
|
80
|
+
tmperr=$(mktemp)
|
|
81
|
+
resp=$(claude -p "hello" --output-format text 2>"$tmperr") && local rc=0 || local rc=$?
|
|
82
|
+
errmsg=$(cat "$tmperr" 2>/dev/null)
|
|
83
|
+
rm -f "$tmperr"
|
|
84
|
+
|
|
85
|
+
if [ $rc -ne 0 ] || [ -z "$resp" ]; then
|
|
86
|
+
# 输出具体错误信息帮助诊断
|
|
87
|
+
if [ -n "$errmsg" ]; then
|
|
88
|
+
echo -e " ${RED}✗ claude 调用失败: ${errmsg}${NC}" >&2
|
|
89
|
+
elif [ -n "$resp" ]; then
|
|
90
|
+
# 退出码非零但有 stdout(可能是错误信息输出到了 stdout)
|
|
91
|
+
echo -e " ${RED}✗ claude 调用失败: ${resp}${NC}" >&2
|
|
92
|
+
else
|
|
93
|
+
echo -e " ${RED}✗ claude 命令无响应 (请检查环境变量配置)${NC}" >&2
|
|
94
|
+
fi
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
return 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# 调用 Claude CLI(支持原生 --system-prompt)
|
|
101
|
+
# 参数: $1=system_prompt $2=user_message
|
|
102
|
+
# 输出: stdout 回复文本
|
|
103
|
+
call_claude() {
|
|
104
|
+
local system_prompt="$1"
|
|
105
|
+
local user_msg="$2"
|
|
106
|
+
local session_tag="${3:-}" # 可选: session 文件名标识
|
|
107
|
+
local max_retries=3
|
|
108
|
+
local retry_delay=10
|
|
109
|
+
|
|
110
|
+
local attempt=1
|
|
111
|
+
while [ $attempt -le $max_retries ]; do
|
|
112
|
+
local raw_out
|
|
113
|
+
raw_out=$(mktemp)
|
|
114
|
+
|
|
115
|
+
# 捕获原始输出(stream-json 格式)
|
|
116
|
+
claude -p "$user_msg" --system-prompt "$system_prompt" --output-format stream-json \
|
|
117
|
+
> "$raw_out" 2>&1 || true
|
|
118
|
+
|
|
119
|
+
# 保存原始 session 记录
|
|
120
|
+
if [ -n "$session_tag" ] && [ -d "$SESSIONS_DIR" ]; then
|
|
121
|
+
cp "$raw_out" "$SESSIONS_DIR/${session_tag}_claude.jsonl"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# 从 stream-json 提取文本内容 (Claude CLI 格式: type=result 的 .result 字段)
|
|
125
|
+
local text
|
|
126
|
+
text=$(jq -r 'select(.type=="result") | .result // empty' "$raw_out" 2>/dev/null || true)
|
|
127
|
+
|
|
128
|
+
# 备选: 从 type=assistant 的 message.content[].text 提取
|
|
129
|
+
if [ -z "$text" ]; then
|
|
130
|
+
text=$(jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text // empty' "$raw_out" 2>/dev/null || true)
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# 如果 stream-json 提取失败,回退到 text 格式
|
|
134
|
+
if [ -z "$text" ]; then
|
|
135
|
+
text=$(claude -p "$user_msg" --system-prompt "$system_prompt" --output-format text 2>/dev/null || true)
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
rm -f "$raw_out"
|
|
139
|
+
|
|
140
|
+
if [ -n "$text" ]; then
|
|
141
|
+
echo "$text"
|
|
142
|
+
return 0
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
echo -e "${YELLOW}WARN: Claude CLI 第 $attempt 次失败,${retry_delay}s 后重试...${NC}" >&2
|
|
146
|
+
sleep $retry_delay
|
|
147
|
+
attempt=$((attempt + 1))
|
|
148
|
+
retry_delay=$((retry_delay * 2))
|
|
149
|
+
done
|
|
150
|
+
|
|
151
|
+
echo -e "${RED}ERROR: Claude CLI $max_retries 次重试均失败${NC}" >&2
|
|
152
|
+
return 1
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# 生成 Claude 指令文件
|
|
156
|
+
# 参数: $1=max_rounds $2=problem_text
|
|
157
|
+
generate_claude_md() {
|
|
158
|
+
local max_rounds="$1"
|
|
159
|
+
local problem="$2"
|
|
160
|
+
cat << CLAUDE_EOF
|
|
161
|
+
# AI 圆桌讨论 — Claude 参与者
|
|
162
|
+
|
|
163
|
+
## 身份
|
|
164
|
+
你是 **Claude**,正在与其他 AI 进行结构化技术讨论。
|
|
165
|
+
|
|
166
|
+
## 讨论规则
|
|
167
|
+
1. 阅读 \`$PROBLEM_FILE\` 了解讨论主题
|
|
168
|
+
2. 查看 \`$ROUNDS_DIR/\` 目录确定当前轮次 N
|
|
169
|
+
3. 如果对方的回复已存在,先仔细阅读
|
|
170
|
+
4. 将你的回复写入 \`$ROUNDS_DIR/round_N_claude.md\`
|
|
171
|
+
|
|
172
|
+
## 当前配置
|
|
173
|
+
- 最大轮次: **$max_rounds**
|
|
174
|
+
- 讨论问题: $problem
|
|
175
|
+
|
|
176
|
+
## 回复要求
|
|
177
|
+
- 深入分析,不要敷衍;如不同意对方,明确反驳
|
|
178
|
+
- 达成共识时在回复最后一行写: \`AGREED: <结论>\`
|
|
179
|
+
- 不要过早同意,确保分析完整
|
|
180
|
+
|
|
181
|
+
## 约束
|
|
182
|
+
- 每次只写一轮,写完等待下一轮指示
|
|
183
|
+
- 不要修改其他 Agent 的文件
|
|
184
|
+
CLAUDE_EOF
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
register_agent "claude"
|
|
188
|
+
|
|
189
|
+
# ======================== Agent: Codex ========================
|
|
190
|
+
|
|
191
|
+
# 检查 Codex 是否可用
|
|
192
|
+
check_codex() {
|
|
193
|
+
if ! command -v codex &>/dev/null; then
|
|
194
|
+
echo -e " ${RED}✗ codex 命令不在 PATH 中${NC}" >&2
|
|
195
|
+
return 1
|
|
196
|
+
fi
|
|
197
|
+
# 实际调用验证认证和可用性
|
|
198
|
+
local resp errmsg
|
|
199
|
+
local tmperr
|
|
200
|
+
tmperr=$(mktemp)
|
|
201
|
+
resp=$(codex exec "hello" 2>"$tmperr") && local rc=0 || local rc=$?
|
|
202
|
+
errmsg=$(cat "$tmperr" 2>/dev/null)
|
|
203
|
+
rm -f "$tmperr"
|
|
204
|
+
|
|
205
|
+
if [ $rc -ne 0 ] || [ -z "$resp" ]; then
|
|
206
|
+
if [ -n "$errmsg" ]; then
|
|
207
|
+
echo -e " ${RED}✗ codex 调用失败: ${errmsg}${NC}" >&2
|
|
208
|
+
elif [ -n "$resp" ]; then
|
|
209
|
+
echo -e " ${RED}✗ codex 调用失败: ${resp}${NC}" >&2
|
|
210
|
+
else
|
|
211
|
+
echo -e " ${RED}✗ codex 命令无响应 (请检查认证配置)${NC}" >&2
|
|
212
|
+
fi
|
|
213
|
+
return 1
|
|
214
|
+
fi
|
|
215
|
+
return 0
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# 调用 Codex
|
|
219
|
+
# 参数: $1=system_prompt $2=user_message $3=session_tag(可选)
|
|
220
|
+
# 输出: stdout 回复文本
|
|
221
|
+
call_codex() {
|
|
222
|
+
local system_prompt="$1"
|
|
223
|
+
local user_msg="$2"
|
|
224
|
+
local session_tag="${3:-}" # 可选: session 文件名标识
|
|
225
|
+
local max_retries=3
|
|
226
|
+
local retry_delay=10
|
|
227
|
+
|
|
228
|
+
local full_prompt="$system_prompt
|
|
229
|
+
|
|
230
|
+
$user_msg
|
|
231
|
+
|
|
232
|
+
重要:直接输出你的分析内容,不要写任何文件,不要输出方案图或伪代码,直接给出你的讨论观点。"
|
|
233
|
+
|
|
234
|
+
# 确保 codex 有 git 仓库
|
|
235
|
+
if [ ! -d ".git" ]; then
|
|
236
|
+
git init -q
|
|
237
|
+
git add -A 2>/dev/null || true
|
|
238
|
+
git commit -q -m "debate-init" 2>/dev/null || true
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
local attempt=1
|
|
242
|
+
while [ $attempt -le $max_retries ]; do
|
|
243
|
+
local tmpout
|
|
244
|
+
tmpout=$(mktemp)
|
|
245
|
+
|
|
246
|
+
codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox \
|
|
247
|
+
-m "$CODEX_MODEL" "$full_prompt" \
|
|
248
|
+
2>&1 | tee "$tmpout" > /dev/null || true
|
|
249
|
+
|
|
250
|
+
# 保存原始 session 记录
|
|
251
|
+
if [ -n "$session_tag" ] && [ -d "$SESSIONS_DIR" ]; then
|
|
252
|
+
cp "$tmpout" "$SESSIONS_DIR/${session_tag}_codex.raw"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
local text=""
|
|
256
|
+
|
|
257
|
+
# 从输出中提取有效内容(跳过 codex 元数据行)
|
|
258
|
+
text=$(grep -v '^\(thinking\|exec\|mcp startup\|OpenAI Codex\|--------\|workdir:\|model:\|provider:\|approval:\|sandbox:\|reasoning\|session id:\|user$\|tokens used\|Exit code:\|^$\)' "$tmpout" 2>/dev/null | sed '/^$/d')
|
|
259
|
+
|
|
260
|
+
rm -f "$tmpout"
|
|
261
|
+
|
|
262
|
+
if [ -n "$text" ]; then
|
|
263
|
+
echo "$text"
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
echo -e "${YELLOW}WARN: Codex 第 $attempt 次失败,${retry_delay}s 后重试...${NC}" >&2
|
|
268
|
+
sleep $retry_delay
|
|
269
|
+
attempt=$((attempt + 1))
|
|
270
|
+
retry_delay=$((retry_delay * 2))
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
echo -e "${RED}ERROR: Codex $max_retries 次重试均失败${NC}" >&2
|
|
274
|
+
return 1
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# 生成 Codex 指令文件
|
|
278
|
+
generate_codex_md() {
|
|
279
|
+
local max_rounds="$1"
|
|
280
|
+
local problem="$2"
|
|
281
|
+
cat << CODEX_EOF
|
|
282
|
+
# AI 圆桌讨论 — Codex 参与者
|
|
283
|
+
|
|
284
|
+
## 身份
|
|
285
|
+
你是 **Codex**,正在与其他 AI 进行结构化技术讨论。
|
|
286
|
+
|
|
287
|
+
## 讨论规则
|
|
288
|
+
1. 阅读 \`$PROBLEM_FILE\` 了解讨论主题
|
|
289
|
+
2. 查看 \`$ROUNDS_DIR/\` 目录确定当前轮次 N
|
|
290
|
+
3. 如果对方的回复已存在,先仔细阅读
|
|
291
|
+
4. 将你的回复写入 \`$ROUNDS_DIR/round_N_codex.md\`
|
|
292
|
+
|
|
293
|
+
## 当前配置
|
|
294
|
+
- 最大轮次: **$max_rounds**
|
|
295
|
+
- 讨论问题: $problem
|
|
296
|
+
|
|
297
|
+
## 回复要求
|
|
298
|
+
- 深入分析,不要敷衍;如不同意对方,明确反驳
|
|
299
|
+
- 达成共识时在回复最后一行写: \`AGREED: <结论>\`
|
|
300
|
+
- 不要过早同意,确保分析完整
|
|
301
|
+
|
|
302
|
+
## 约束
|
|
303
|
+
- 每次只写一轮,写完等待下一轮指示
|
|
304
|
+
- 不要修改其他 Agent 的文件
|
|
305
|
+
CODEX_EOF
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
register_agent "codex"
|
|
309
|
+
|
|
310
|
+
# ======================== Agent: Gemini (CLI) ========================
|
|
311
|
+
# 前置条件: 安装 Gemini CLI (npm install -g @anthropic-ai/gemini-cli 或参考官方文档)
|
|
312
|
+
# 可选环境变量:
|
|
313
|
+
# export GEMINI_API_KEY="your-key"
|
|
314
|
+
|
|
315
|
+
# 检查 Gemini CLI 是否可用
|
|
316
|
+
# 返回: 0=可用, 1=不可用
|
|
317
|
+
check_gemini() {
|
|
318
|
+
if ! command -v gemini &>/dev/null; then
|
|
319
|
+
echo -e " ${RED}✗ gemini 命令不在 PATH 中${NC}" >&2
|
|
320
|
+
return 1
|
|
321
|
+
fi
|
|
322
|
+
# 调用测试:分离 stdout/stderr,检查退出码
|
|
323
|
+
local resp errmsg tmperr
|
|
324
|
+
tmperr=$(mktemp)
|
|
325
|
+
resp=$(gemini -p "hello" --output-format text 2>"$tmperr") && local rc=0 || local rc=$?
|
|
326
|
+
errmsg=$(cat "$tmperr" 2>/dev/null)
|
|
327
|
+
rm -f "$tmperr"
|
|
328
|
+
|
|
329
|
+
if [ $rc -ne 0 ] || [ -z "$resp" ]; then
|
|
330
|
+
if [ -n "$errmsg" ]; then
|
|
331
|
+
echo -e " ${RED}✗ gemini 调用失败: ${errmsg}${NC}" >&2
|
|
332
|
+
elif [ -n "$resp" ]; then
|
|
333
|
+
echo -e " ${RED}✗ gemini 调用失败: ${resp}${NC}" >&2
|
|
334
|
+
else
|
|
335
|
+
echo -e " ${RED}✗ gemini 命令无响应 (请检查 API Key 配置)${NC}" >&2
|
|
336
|
+
fi
|
|
337
|
+
return 1
|
|
338
|
+
fi
|
|
339
|
+
return 0
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# 调用 Gemini CLI
|
|
343
|
+
# 参数: $1=system_prompt $2=user_message
|
|
344
|
+
# 输出: stdout 回复文本
|
|
345
|
+
call_gemini() {
|
|
346
|
+
local system_prompt="$1"
|
|
347
|
+
local user_msg="$2"
|
|
348
|
+
local session_tag="${3:-}" # 可选: session 文件名标识
|
|
349
|
+
local max_retries=3
|
|
350
|
+
local retry_delay=10
|
|
351
|
+
|
|
352
|
+
local full_msg="${system_prompt}
|
|
353
|
+
|
|
354
|
+
${user_msg}"
|
|
355
|
+
|
|
356
|
+
local attempt=1
|
|
357
|
+
while [ $attempt -le $max_retries ]; do
|
|
358
|
+
local raw_out
|
|
359
|
+
raw_out=$(mktemp)
|
|
360
|
+
|
|
361
|
+
# 捕获原始输出
|
|
362
|
+
gemini -p "$full_msg" --output-format json \
|
|
363
|
+
> "$raw_out" 2>&1 || true
|
|
364
|
+
|
|
365
|
+
# 保存原始 session 记录
|
|
366
|
+
if [ -n "$session_tag" ] && [ -d "$SESSIONS_DIR" ]; then
|
|
367
|
+
cp "$raw_out" "$SESSIONS_DIR/${session_tag}_gemini.json"
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
# 从 json 提取文本内容 (Gemini CLI 格式: type=result 的 .result 字段)
|
|
371
|
+
local text
|
|
372
|
+
text=$(jq -r 'select(.type=="result") | .result // empty' "$raw_out" 2>/dev/null || true)
|
|
373
|
+
|
|
374
|
+
# 备选: 尝试数组格式
|
|
375
|
+
if [ -z "$text" ]; then
|
|
376
|
+
text=$(jq -r '.[] | select(.type=="text") | .text // empty' "$raw_out" 2>/dev/null || true)
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
# 如果 json 提取失败,回退到 text 格式
|
|
380
|
+
if [ -z "$text" ]; then
|
|
381
|
+
text=$(gemini -p "$full_msg" --output-format text 2>/dev/null || true)
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
rm -f "$raw_out"
|
|
385
|
+
|
|
386
|
+
if [ -n "$text" ]; then
|
|
387
|
+
echo "$text"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
echo -e "${YELLOW}WARN: Gemini CLI 第 $attempt 次失败,${retry_delay}s 后重试...${NC}" >&2
|
|
392
|
+
sleep $retry_delay
|
|
393
|
+
attempt=$((attempt + 1))
|
|
394
|
+
retry_delay=$((retry_delay * 2))
|
|
395
|
+
done
|
|
396
|
+
|
|
397
|
+
echo -e "${RED}ERROR: Gemini CLI $max_retries 次重试均失败${NC}" >&2
|
|
398
|
+
return 1
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# 生成 Gemini 指令文件
|
|
402
|
+
# 参数: $1=max_rounds $2=problem_text
|
|
403
|
+
generate_gemini_md() {
|
|
404
|
+
local max_rounds="$1"
|
|
405
|
+
local problem="$2"
|
|
406
|
+
cat << GEMINI_EOF
|
|
407
|
+
# AI 圆桌讨论 — Gemini 参与者
|
|
408
|
+
|
|
409
|
+
## 身份
|
|
410
|
+
你是 **Gemini**,正在与其他 AI 进行结构化技术讨论。
|
|
411
|
+
|
|
412
|
+
## 讨论规则
|
|
413
|
+
1. 阅读 \`$PROBLEM_FILE\` 了解讨论主题
|
|
414
|
+
2. 查看 \`$ROUNDS_DIR/\` 目录确定当前轮次 N
|
|
415
|
+
3. 如果对方的回复已存在,先仔细阅读
|
|
416
|
+
4. 将你的回复写入 \`$ROUNDS_DIR/round_N_gemini.md\`
|
|
417
|
+
|
|
418
|
+
## 当前配置
|
|
419
|
+
- 最大轮次: **$max_rounds**
|
|
420
|
+
- 讨论问题: $problem
|
|
421
|
+
|
|
422
|
+
## 回复要求
|
|
423
|
+
- 深入分析,不要敷衍;如不同意对方,明确反驳
|
|
424
|
+
- 达成共识时在回复最后一行写: \`AGREED: <结论>\`
|
|
425
|
+
- 不要过早同意,确保分析完整
|
|
426
|
+
|
|
427
|
+
## 约束
|
|
428
|
+
- 每次只写一轮,写完等待下一轮指示
|
|
429
|
+
- 不要修改其他 Agent 的文件
|
|
430
|
+
GEMINI_EOF
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
register_agent "gemini"
|
|
434
|
+
|
|
435
|
+
# ======================== 工具函数 ========================
|
|
436
|
+
|
|
437
|
+
# Round 层级重试包装器
|
|
438
|
+
# 当 agent 内部重试耗尽后,询问用户是否继续重试
|
|
439
|
+
# 参数: $1=agent名称(display) $2+=要执行的命令
|
|
440
|
+
# 输出: stdout 命令结果
|
|
441
|
+
# 返回: 0=成功, 1=用户选择退出
|
|
442
|
+
retry_call_agent() {
|
|
443
|
+
local agent_display="$1"
|
|
444
|
+
shift
|
|
445
|
+
|
|
446
|
+
while true; do
|
|
447
|
+
local result
|
|
448
|
+
result=$("$@" 2>/dev/null) && local rc=0 || local rc=$?
|
|
449
|
+
|
|
450
|
+
if [ $rc -eq 0 ] && [ -n "$result" ]; then
|
|
451
|
+
echo "$result"
|
|
452
|
+
return 0
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
echo "" >&2
|
|
456
|
+
echo -e "${RED}━━━ ⚠️ ${agent_display} 调用失败 ━━━${NC}" >&2
|
|
457
|
+
echo -e " ${BOLD}r)${NC} 重试" >&2
|
|
458
|
+
echo -e " ${BOLD}s)${NC} 跳过该 agent 本轮发言" >&2
|
|
459
|
+
echo -e " ${BOLD}q)${NC} 退出讨论" >&2
|
|
460
|
+
echo -ne "${BOLD}请选择 [r/s/q]: ${NC}" >&2
|
|
461
|
+
local choice
|
|
462
|
+
read -er choice
|
|
463
|
+
case "$choice" in
|
|
464
|
+
s|S)
|
|
465
|
+
echo -e "${YELLOW} 跳过 ${agent_display}${NC}" >&2
|
|
466
|
+
echo "" # 返回空字符串
|
|
467
|
+
return 0
|
|
468
|
+
;;
|
|
469
|
+
q|Q)
|
|
470
|
+
echo -e "${CYAN}已退出。${NC}" >&2
|
|
471
|
+
exit 1
|
|
472
|
+
;;
|
|
473
|
+
*) # 默认重试
|
|
474
|
+
echo -e "${CYAN} 重试 ${agent_display}...${NC}" >&2
|
|
475
|
+
;;
|
|
476
|
+
esac
|
|
477
|
+
done
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
# 从实例名提取基础 agent 类型
|
|
481
|
+
# 用法: agent_base "claude_1" → "claude",agent_base "claude" → "claude"
|
|
482
|
+
agent_base() {
|
|
483
|
+
echo "$1" | sed 's/_[0-9]*$//'
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# 日志输出(同时写终端和日志文件)
|
|
487
|
+
log_and_print() {
|
|
488
|
+
echo -e "$1"
|
|
489
|
+
echo -e "$1" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
# 检查 AGREED 关键字(支持行内匹配,不要求行首)
|
|
493
|
+
has_agreed() {
|
|
494
|
+
echo "$1" | grep -qiE '\*{0,2}AGREED:'
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
# 提取 AGREED 后的结论
|
|
498
|
+
extract_agreed() {
|
|
499
|
+
echo "$1" | grep -ioE '\*{0,2}AGREED:[[:space:]]*.*' | head -1 \
|
|
500
|
+
| sed 's/^\*\{0,2\}[Aa][Gg][Rr][Ee][Ee][Dd]:[[:space:]]*//' | sed 's/[[:space:]]*$//' | sed 's/\*\{1,2\}$//'
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
# Agent 颜色映射(自动提取基础类型)
|
|
504
|
+
agent_color() {
|
|
505
|
+
local base
|
|
506
|
+
base=$(agent_base "$1")
|
|
507
|
+
case "$base" in
|
|
508
|
+
claude) echo "$BLUE" ;;
|
|
509
|
+
codex) echo "$GREEN" ;;
|
|
510
|
+
gemini) echo "\033[1;35m" ;; # 紫色
|
|
511
|
+
*) echo "$YELLOW" ;;
|
|
512
|
+
esac
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
# Agent 指令文件名映射(支持实例名,如 claude_1 → CLAUDE_1.md)
|
|
516
|
+
agent_md_file() {
|
|
517
|
+
local base
|
|
518
|
+
base=$(agent_base "$1")
|
|
519
|
+
local suffix=""
|
|
520
|
+
if [ "$1" != "$base" ]; then
|
|
521
|
+
suffix="_${1##*_}" # 提取 _N 后缀
|
|
522
|
+
fi
|
|
523
|
+
case "$base" in
|
|
524
|
+
claude) echo "CLAUDE${suffix}.md" ;;
|
|
525
|
+
codex) echo "AGENTS${suffix}.md" ;;
|
|
526
|
+
gemini) echo "GEMINI${suffix}.md" ;;
|
|
527
|
+
*) echo "${1^^}.md" ;;
|
|
528
|
+
esac
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
# 上帝视角: 等待用户输入补充信息
|
|
532
|
+
# 参数: $1=当前轮次
|
|
533
|
+
# 输出: stdout 用户输入的补充信息(可为空)
|
|
534
|
+
god_input() {
|
|
535
|
+
local round="$1"
|
|
536
|
+
echo "" >&2
|
|
537
|
+
echo -e "${CYAN}━━━ 👁️ 上帝视角 [Round ${round} 结束] ━━━${NC}" >&2
|
|
538
|
+
echo -e "${CYAN}输入补充信息(直接回车跳过,多行输入以 END 结束):${NC}" >&2
|
|
539
|
+
echo -ne "${CYAN}> ${NC}" >&2
|
|
540
|
+
|
|
541
|
+
local first_line
|
|
542
|
+
read -er first_line
|
|
543
|
+
|
|
544
|
+
# 直接回车 = 跳过
|
|
545
|
+
if [ -z "$first_line" ]; then
|
|
546
|
+
echo -e "${CYAN} (跳过)${NC}" >&2
|
|
547
|
+
echo ""
|
|
548
|
+
return
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
local input="$first_line"
|
|
552
|
+
|
|
553
|
+
# 如果第一行不是 END,继续读取多行
|
|
554
|
+
if [ "$first_line" != "END" ]; then
|
|
555
|
+
while true; do
|
|
556
|
+
echo -ne "${CYAN}> ${NC}" >&2
|
|
557
|
+
local line
|
|
558
|
+
read -er line
|
|
559
|
+
if [ "$line" = "END" ] || [ -z "$line" ]; then
|
|
560
|
+
break
|
|
561
|
+
fi
|
|
562
|
+
input+=$'\n'"$line"
|
|
563
|
+
done
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
# 保存上帝视角输入
|
|
567
|
+
echo "$input" > "$ROUNDS_DIR/god_round_${round}.md"
|
|
568
|
+
log_and_print "${CYAN}👁️ 上帝视角注入: $(echo "$input" | head -1)...${NC}"
|
|
569
|
+
echo "$input"
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
# 系统提示(通用)
|
|
573
|
+
build_system_prompt() {
|
|
574
|
+
local max_rounds="$1"
|
|
575
|
+
cat << SYSPROMPT
|
|
576
|
+
你正在参与一场 AI 圆桌讨论。规则:
|
|
577
|
+
1. 深入分析问题,给出有价值的观点
|
|
578
|
+
2. 积极回应对方——认同有道理的部分,挑战有漏洞的部分
|
|
579
|
+
3. 每轮回复应增加新信息或新见解,不要重复
|
|
580
|
+
4. 确信分析完整且正确时,在回复最后一行写 AGREED: <共识结论>
|
|
581
|
+
5. 不要过早同意。最多 $max_rounds 轮讨论。
|
|
582
|
+
|
|
583
|
+
重要格式要求:
|
|
584
|
+
- 当你要表示同意时,必须在回复中直接输出独立一行 AGREED: <结论>
|
|
585
|
+
- 不要描述你会写什么,直接写出来。错误示例:"我在文件中写入 AGREED:" — 正确示例:直接输出 AGREED: xxx
|
|
586
|
+
- AGREED 行必须独占一行,不要嵌套在其他句子中
|
|
587
|
+
SYSPROMPT
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
# ======================== 裁判功能 ========================
|
|
591
|
+
|
|
592
|
+
# 裁判 system prompt(自由辩论模式)
|
|
593
|
+
# 裁判独立于参与者,客观总结并判断是否实质达成一致
|
|
594
|
+
build_referee_free_prompt() {
|
|
595
|
+
cat << 'REFEREE_FREE'
|
|
596
|
+
你是一位公正的讨论裁判。你的职责是:
|
|
597
|
+
1. 客观总结各参与者本轮的核心观点
|
|
598
|
+
2. 识别各方的共识和分歧
|
|
599
|
+
3. 判断是否已实质达成一致(即使没有明确说 AGREED)
|
|
600
|
+
|
|
601
|
+
请严格按以下格式输出:
|
|
602
|
+
|
|
603
|
+
## 本轮总结
|
|
604
|
+
(简要概括各参与者的核心论点)
|
|
605
|
+
|
|
606
|
+
## 共识与分歧
|
|
607
|
+
(列出已达成一致的要点和仍有分歧的要点)
|
|
608
|
+
|
|
609
|
+
## 裁判判定
|
|
610
|
+
CONSENSUS: YES 或 CONSENSUS: NO
|
|
611
|
+
(如果 YES,必须紧接着下一行输出)
|
|
612
|
+
AGREED: <最终结论的简要概括>
|
|
613
|
+
|
|
614
|
+
注意:只有在各方观点确实在关键议题上没有实质分歧时才判定 YES。
|
|
615
|
+
不要过早判定共识,除非分歧已经真正解决。
|
|
616
|
+
REFEREE_FREE
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
# 裁判 system prompt(上帝视角模式)
|
|
620
|
+
# 格式化输出差异点,辅助上帝做出判断
|
|
621
|
+
build_referee_god_prompt() {
|
|
622
|
+
cat << 'REFEREE_GOD'
|
|
623
|
+
你是一位公正的讨论裁判,你需要为「上帝视角」的操控者提供决策辅助。
|
|
624
|
+
|
|
625
|
+
请严格按以下格式输出:
|
|
626
|
+
|
|
627
|
+
## 本轮总结
|
|
628
|
+
(简要概括各参与者的核心论点,2-3 句话)
|
|
629
|
+
|
|
630
|
+
## 差异对比表
|
|
631
|
+
| 议题 | 参与者A | 参与者B | ... |
|
|
632
|
+
|------|---------|---------|-----|
|
|
633
|
+
| 议题1 | 立场/方案 | 立场/方案 | ... |
|
|
634
|
+
| 议题2 | 立场/方案 | 立场/方案 | ... |
|
|
635
|
+
|
|
636
|
+
(用实际参与者名称替换表头)
|
|
637
|
+
|
|
638
|
+
## 关键分歧点
|
|
639
|
+
(列出 2-3 个最重要的分歧,简要说明各方理由)
|
|
640
|
+
|
|
641
|
+
## 建议关注
|
|
642
|
+
(给上帝视角操控者的建议:哪些分歧值得介入引导?)
|
|
643
|
+
|
|
644
|
+
注意:保持客观,不要偏向任何一方。
|
|
645
|
+
REFEREE_GOD
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
# 裁判最终总结 prompt(讨论结束后生成综合总结)
|
|
649
|
+
build_referee_final_prompt() {
|
|
650
|
+
cat << 'REFEREE_FINAL'
|
|
651
|
+
你是讨论裁判。请综合全部讨论记录,输出一份完整的总结文档。
|
|
652
|
+
|
|
653
|
+
输出格式要求(Markdown):
|
|
654
|
+
# 总结:<主题标题>
|
|
655
|
+
|
|
656
|
+
## 1. 问题概述
|
|
657
|
+
简明扼要地总结讨论主题和核心问题。
|
|
658
|
+
|
|
659
|
+
## 2. 主要共识
|
|
660
|
+
列出讨论中各方达成一致的关键点。
|
|
661
|
+
|
|
662
|
+
## 3. 主要分歧
|
|
663
|
+
列出讨论中各方未达成一致的点,并说明各方理由。
|
|
664
|
+
|
|
665
|
+
## 4. 关键结论
|
|
666
|
+
列出讨论中确定的重要结论及其依据。
|
|
667
|
+
|
|
668
|
+
## 5. 待解决问题
|
|
669
|
+
列出仍需进一步讨论或行动的问题(如果有)。
|
|
670
|
+
|
|
671
|
+
规则:
|
|
672
|
+
- 直接输出 Markdown 内容,不要输出任何解释性前缀
|
|
673
|
+
- 使用中文
|
|
674
|
+
- 内容必须基于讨论记录中的实际观点,不要编造
|
|
675
|
+
- 保持结构清晰、客观中立
|
|
676
|
+
REFEREE_FINAL
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
# 调用裁判
|
|
680
|
+
# 参数: $1=裁判 agent 基础类型, $2=裁判 prompt, $3=用户消息, $4=session tag(可选)
|
|
681
|
+
# 输出: stdout 裁判总结文本
|
|
682
|
+
call_referee() {
|
|
683
|
+
local referee_base="$1"
|
|
684
|
+
local referee_prompt="$2"
|
|
685
|
+
local user_msg="$3"
|
|
686
|
+
local session_tag="${4:-}"
|
|
687
|
+
"call_${referee_base}" "$referee_prompt" "$user_msg" "$session_tag"
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
# 裁判生成最终设计方案
|
|
691
|
+
# 参数: $1=裁判 base 类型, $2=自定义提示词(可空)
|
|
692
|
+
# 依赖全局: ROUNDS_DIR, PROBLEM_FILE, CONSENSUS_FILE, CONFIG_FILE
|
|
693
|
+
generate_final_summary() {
|
|
694
|
+
local ref_base="$1"
|
|
695
|
+
local custom_prompt="${2:-}"
|
|
696
|
+
|
|
697
|
+
log_and_print ""
|
|
698
|
+
log_and_print "${BOLD}📝 裁判正在生成最终总结...${NC}"
|
|
699
|
+
|
|
700
|
+
# 收集所有轮次内容
|
|
701
|
+
local all_rounds=""
|
|
702
|
+
for f in $(ls -1 "$ROUNDS_DIR"/*.md 2>/dev/null | sort); do
|
|
703
|
+
local fname
|
|
704
|
+
fname=$(basename "$f")
|
|
705
|
+
all_rounds+="\n--- ${fname} ---\n"
|
|
706
|
+
all_rounds+=$(cat "$f")
|
|
707
|
+
all_rounds+="\n"
|
|
708
|
+
done
|
|
709
|
+
|
|
710
|
+
local problem
|
|
711
|
+
problem=$(cat "$PROBLEM_FILE")
|
|
712
|
+
|
|
713
|
+
local consensus=""
|
|
714
|
+
if [ -f "$CONSENSUS_FILE" ]; then
|
|
715
|
+
consensus=$(cat "$CONSENSUS_FILE")
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
local final_prompt
|
|
719
|
+
final_prompt=$(build_referee_final_prompt)
|
|
720
|
+
if [ -n "$custom_prompt" ]; then
|
|
721
|
+
final_prompt+=$'\n\n[额外指示]\n'"$custom_prompt"
|
|
722
|
+
fi
|
|
723
|
+
|
|
724
|
+
local user_content="<discussion_topic>
|
|
725
|
+
${problem}
|
|
726
|
+
</discussion_topic>
|
|
727
|
+
|
|
728
|
+
<consensus>
|
|
729
|
+
${consensus:-未达成明确共识}
|
|
730
|
+
</consensus>
|
|
731
|
+
|
|
732
|
+
<discussion_records>
|
|
733
|
+
${all_rounds}
|
|
734
|
+
</discussion_records>"
|
|
735
|
+
|
|
736
|
+
local summary_text
|
|
737
|
+
summary_text=$(call_referee "$ref_base" "$final_prompt" "$user_content" "final_summary")
|
|
738
|
+
|
|
739
|
+
if [ -z "$summary_text" ]; then
|
|
740
|
+
log_and_print "${RED}❌ 生成最终总结失败${NC}"
|
|
741
|
+
return 1
|
|
742
|
+
fi
|
|
743
|
+
|
|
744
|
+
local summary_file="SUMMARY.md"
|
|
745
|
+
echo "$summary_text" > "$summary_file"
|
|
746
|
+
|
|
747
|
+
log_and_print ""
|
|
748
|
+
log_and_print "${BOLD}╔══════════════════════════════════════════╗${NC}"
|
|
749
|
+
log_and_print "${BOLD}║ 📋 最终总结已生成 ║${NC}"
|
|
750
|
+
log_and_print "${BOLD}╚══════════════════════════════════════════╝${NC}"
|
|
751
|
+
log_and_print " 📄 文件: ${BOLD}${summary_file}${NC}"
|
|
752
|
+
log_and_print " 🔨 裁判: ${ref_base}"
|
|
753
|
+
log_and_print ""
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
# ======================== 命令: run ========================
|
|
757
|
+
cmd_run() {
|
|
758
|
+
local agents="$DEFAULT_AGENTS"
|
|
759
|
+
local max_rounds="$DEFAULT_MAX_ROUNDS"
|
|
760
|
+
local god_mode=false
|
|
761
|
+
local referee_mode=false
|
|
762
|
+
local referee_agent="" # 空=使用第一个 agent
|
|
763
|
+
|
|
764
|
+
# 解析参数
|
|
765
|
+
while [[ $# -gt 0 ]]; do
|
|
766
|
+
case "$1" in
|
|
767
|
+
--agents|-a) agents="$2"; shift 2 ;;
|
|
768
|
+
--rounds|-r) max_rounds="$2"; shift 2 ;;
|
|
769
|
+
--god|-g) god_mode=true; shift ;;
|
|
770
|
+
--referee) referee_mode=true
|
|
771
|
+
# 可选参数: --referee [agent_name]
|
|
772
|
+
if [[ $# -gt 1 && ! "$2" =~ ^-- ]]; then
|
|
773
|
+
referee_agent="$2"; shift
|
|
774
|
+
fi
|
|
775
|
+
shift ;;
|
|
776
|
+
*) echo -e "${RED}未知参数: $1${NC}"; cmd_help; exit 1 ;;
|
|
777
|
+
esac
|
|
778
|
+
done
|
|
779
|
+
|
|
780
|
+
# ---- 阶段 0: 裁判提示词检查 ----
|
|
781
|
+
local referee_custom_prompt=""
|
|
782
|
+
if $referee_mode; then
|
|
783
|
+
if [ -f "$REFEREE_PROMPT_FILE" ] && [ -s "$REFEREE_PROMPT_FILE" ]; then
|
|
784
|
+
referee_custom_prompt=$(cat "$REFEREE_PROMPT_FILE")
|
|
785
|
+
echo -e "${CYAN}📋 已加载裁判提示词: ${REFEREE_PROMPT_FILE}${NC}"
|
|
786
|
+
else
|
|
787
|
+
echo -e "${YELLOW}⚠️ 未找到裁判提示词文件: ${REFEREE_PROMPT_FILE}${NC}"
|
|
788
|
+
echo -e " 你可以创建该文件来自定义裁判的行为,例如:"
|
|
789
|
+
echo -e " ${CYAN}echo '重点关注架构设计的可行性和成本' > ${REFEREE_PROMPT_FILE}${NC}"
|
|
790
|
+
echo ""
|
|
791
|
+
echo -ne "${BOLD}是否跳过,使用默认裁判提示词?[Y/n]: ${NC}"
|
|
792
|
+
local ref_choice
|
|
793
|
+
read -er ref_choice
|
|
794
|
+
case "$ref_choice" in
|
|
795
|
+
n|N|no|NO)
|
|
796
|
+
echo -e "${CYAN}请创建 ${REFEREE_PROMPT_FILE} 后重新运行。${NC}"
|
|
797
|
+
exit 0
|
|
798
|
+
;;
|
|
799
|
+
*)
|
|
800
|
+
echo -e "${CYAN}使用默认裁判提示词。${NC}"
|
|
801
|
+
;;
|
|
802
|
+
esac
|
|
803
|
+
echo ""
|
|
804
|
+
fi
|
|
805
|
+
fi
|
|
806
|
+
|
|
807
|
+
# ---- 阶段 1: 初始化 ----
|
|
808
|
+
|
|
809
|
+
# 检查是否存在历史讨论记录
|
|
810
|
+
local resume_mode=false
|
|
811
|
+
if [ -f "$CONFIG_FILE" ] || [ -d "$ROUNDS_DIR" ] && [ "$(ls -A "$ROUNDS_DIR" 2>/dev/null)" ]; then
|
|
812
|
+
local prev_status=""
|
|
813
|
+
local prev_round=0
|
|
814
|
+
local prev_agents=""
|
|
815
|
+
local prev_max_rounds=0
|
|
816
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
817
|
+
prev_status=$(jq -r '.status // "unknown"' "$CONFIG_FILE" 2>/dev/null)
|
|
818
|
+
prev_round=$(jq -r '.current_round // 0' "$CONFIG_FILE" 2>/dev/null)
|
|
819
|
+
prev_agents=$(jq -r '.agents // ""' "$CONFIG_FILE" 2>/dev/null)
|
|
820
|
+
prev_max_rounds=$(jq -r '.max_rounds // 0' "$CONFIG_FILE" 2>/dev/null)
|
|
821
|
+
fi
|
|
822
|
+
local round_count=0
|
|
823
|
+
if [ -d "$ROUNDS_DIR" ]; then
|
|
824
|
+
round_count=$(ls -1 "$ROUNDS_DIR"/*.md 2>/dev/null | wc -l | xargs)
|
|
825
|
+
fi
|
|
826
|
+
|
|
827
|
+
echo -e "${YELLOW}⚠️ 检测到历史讨论记录${NC}"
|
|
828
|
+
echo -e " 状态: ${BOLD}${prev_status}${NC}"
|
|
829
|
+
echo -e " 轮次: ${BOLD}${prev_round}/${prev_max_rounds}${NC}"
|
|
830
|
+
echo -e " 参与: ${BOLD}${prev_agents}${NC}"
|
|
831
|
+
echo -e " 文件: ${round_count} 个回复记录"
|
|
832
|
+
echo ""
|
|
833
|
+
echo -e " ${BOLD}1)${NC} 清理历史,重新开始"
|
|
834
|
+
# 仅当配置完整且有历史轮次时,才提供继续选项
|
|
835
|
+
if [ -n "$prev_agents" ] && [ "$prev_round" -gt 0 ] 2>/dev/null; then
|
|
836
|
+
echo -e " ${BOLD}2)${NC} 从 Round $((prev_round + 1)) 继续讨论"
|
|
837
|
+
echo -e " ${BOLD}3)${NC} 取消执行"
|
|
838
|
+
echo ""
|
|
839
|
+
echo -ne "${BOLD}请选择 [1/2/3]: ${NC}"
|
|
840
|
+
else
|
|
841
|
+
echo -e " ${BOLD}2)${NC} 取消执行"
|
|
842
|
+
echo ""
|
|
843
|
+
echo -ne "${BOLD}请选择 [1/2]: ${NC}"
|
|
844
|
+
fi
|
|
845
|
+
local choice
|
|
846
|
+
read -er choice
|
|
847
|
+
|
|
848
|
+
case "$choice" in
|
|
849
|
+
1)
|
|
850
|
+
echo -e "${CYAN}清理历史记录...${NC}"
|
|
851
|
+
rm -rf "$ROUNDS_DIR" "$SESSIONS_DIR" "$CONFIG_FILE" "$CONSENSUS_FILE" "$LOG_FILE" SUMMARY.md
|
|
852
|
+
;;
|
|
853
|
+
2)
|
|
854
|
+
# 仅当有完整配置时 2=继续,否则 2=取消
|
|
855
|
+
if [ -n "$prev_agents" ] && [ "$prev_round" -gt 0 ] 2>/dev/null; then
|
|
856
|
+
resume_mode=true
|
|
857
|
+
# 恢复历史 agents(必须一致),max_rounds 使用命令行参数
|
|
858
|
+
agents="$prev_agents"
|
|
859
|
+
echo -e "${CYAN}从 Round $((prev_round + 1)) 继续讨论 (最大轮次: ${max_rounds})...${NC}"
|
|
860
|
+
else
|
|
861
|
+
echo -e "${CYAN}已取消。${NC}"
|
|
862
|
+
exit 0
|
|
863
|
+
fi
|
|
864
|
+
;;
|
|
865
|
+
*)
|
|
866
|
+
echo -e "${CYAN}已取消。${NC}"
|
|
867
|
+
exit 0
|
|
868
|
+
;;
|
|
869
|
+
esac
|
|
870
|
+
echo ""
|
|
871
|
+
fi
|
|
872
|
+
|
|
873
|
+
# 检查 problem.md
|
|
874
|
+
if [ ! -f "$PROBLEM_FILE" ]; then
|
|
875
|
+
echo -e "${RED}❌ 当前目录缺少 $PROBLEM_FILE${NC}"
|
|
876
|
+
echo -e "${CYAN}请先创建讨论问题:${NC}"
|
|
877
|
+
echo -e " echo \"你的问题\" > $PROBLEM_FILE"
|
|
878
|
+
exit 1
|
|
879
|
+
fi
|
|
880
|
+
|
|
881
|
+
if [ ! -s "$PROBLEM_FILE" ]; then
|
|
882
|
+
echo -e "${RED}❌ $PROBLEM_FILE 为空${NC}"
|
|
883
|
+
exit 1
|
|
884
|
+
fi
|
|
885
|
+
|
|
886
|
+
local problem
|
|
887
|
+
problem=$(cat "$PROBLEM_FILE")
|
|
888
|
+
|
|
889
|
+
echo -e "${BOLD}🔍 检查 Agent 可用性...${NC}"
|
|
890
|
+
echo ""
|
|
891
|
+
|
|
892
|
+
# 解析 agent 列表,支持重复 agent(如 claude,claude)
|
|
893
|
+
IFS=',' read -ra RAW_AGENT_LIST <<< "$agents"
|
|
894
|
+
local available_agents=()
|
|
895
|
+
|
|
896
|
+
# 统计每个 agent 类型出现次数(bash 3.x 兼容,用字符串模拟)
|
|
897
|
+
# 格式: "|name:count|name:count|"
|
|
898
|
+
local _agent_total="|"
|
|
899
|
+
for agent in "${RAW_AGENT_LIST[@]}"; do
|
|
900
|
+
agent=$(echo "$agent" | xargs) # trim
|
|
901
|
+
local _cur_total
|
|
902
|
+
_cur_total=$(echo "$_agent_total" | sed -n "s/.*|${agent}:\([0-9]*\)|.*/\1/p")
|
|
903
|
+
_cur_total=$(( ${_cur_total:-0} + 1 ))
|
|
904
|
+
if echo "$_agent_total" | grep -q "|${agent}:"; then
|
|
905
|
+
_agent_total=$(echo "$_agent_total" | sed "s/|${agent}:[0-9]*|/|${agent}:${_cur_total}|/")
|
|
906
|
+
else
|
|
907
|
+
_agent_total="${_agent_total}${agent}:${_cur_total}|"
|
|
908
|
+
fi
|
|
909
|
+
done
|
|
910
|
+
|
|
911
|
+
# ---- Phase 1: 去重收集 agent 类型 + 注册验证 ----
|
|
912
|
+
local unique_agents="|" # 需要检查的不同 agent 类型(去重)
|
|
913
|
+
|
|
914
|
+
for agent in "${RAW_AGENT_LIST[@]}"; do
|
|
915
|
+
agent=$(echo "$agent" | xargs) # trim
|
|
916
|
+
|
|
917
|
+
# 检查是否已注册
|
|
918
|
+
local registered=false
|
|
919
|
+
for ra in "${REGISTERED_AGENTS[@]}"; do
|
|
920
|
+
if [ "$ra" == "$agent" ]; then
|
|
921
|
+
registered=true
|
|
922
|
+
break
|
|
923
|
+
fi
|
|
924
|
+
done
|
|
925
|
+
|
|
926
|
+
if ! $registered; then
|
|
927
|
+
echo -e " ${RED}✗ $agent — 未注册的 Agent${NC}"
|
|
928
|
+
echo -e " 已注册: ${REGISTERED_AGENTS[*]}"
|
|
929
|
+
exit 1
|
|
930
|
+
fi
|
|
931
|
+
|
|
932
|
+
# 收集不重复的 agent 类型
|
|
933
|
+
if ! echo "$unique_agents" | grep -q "|${agent}|"; then
|
|
934
|
+
unique_agents="${unique_agents}${agent}|"
|
|
935
|
+
fi
|
|
936
|
+
done
|
|
937
|
+
|
|
938
|
+
# ---- Phase 2: 并发检查所有不同类型的可用性 ----
|
|
939
|
+
# 为每个 agent 类型创建状态文件,后台并发执行
|
|
940
|
+
local check_pids=()
|
|
941
|
+
local check_agents=()
|
|
942
|
+
local check_status_dir
|
|
943
|
+
check_status_dir=$(mktemp -d)
|
|
944
|
+
|
|
945
|
+
# 从 unique_agents 字符串解析出 agent 列表
|
|
946
|
+
local _ua_list
|
|
947
|
+
_ua_list=$(echo "$unique_agents" | tr '|' '\n' | sed '/^$/d')
|
|
948
|
+
|
|
949
|
+
while IFS= read -r agent; do
|
|
950
|
+
[ -z "$agent" ] && continue
|
|
951
|
+
check_agents+=("$agent")
|
|
952
|
+
# 后台执行可用性检查,结果写入状态文件
|
|
953
|
+
(
|
|
954
|
+
if "check_$agent" 2>"${check_status_dir}/${agent}.err"; then
|
|
955
|
+
echo "ok" > "${check_status_dir}/${agent}.status"
|
|
956
|
+
else
|
|
957
|
+
echo "fail" > "${check_status_dir}/${agent}.status"
|
|
958
|
+
fi
|
|
959
|
+
) &
|
|
960
|
+
check_pids+=($!)
|
|
961
|
+
done <<< "$_ua_list"
|
|
962
|
+
|
|
963
|
+
echo -ne " ${CYAN}并发检查 ${#check_agents[@]} 个 Agent...${NC}"
|
|
964
|
+
|
|
965
|
+
# 等待所有检查完成
|
|
966
|
+
for pid in "${check_pids[@]}"; do
|
|
967
|
+
wait "$pid" 2>/dev/null || true
|
|
968
|
+
done
|
|
969
|
+
echo "" # 检查完成后换行
|
|
970
|
+
|
|
971
|
+
# 汇总检查结果
|
|
972
|
+
for agent in "${check_agents[@]}"; do
|
|
973
|
+
local status
|
|
974
|
+
status=$(cat "${check_status_dir}/${agent}.status" 2>/dev/null || echo "fail")
|
|
975
|
+
if [ "$status" = "ok" ]; then
|
|
976
|
+
echo -e " ${BOLD}$agent${NC} ${GREEN}✓ 可用${NC}"
|
|
977
|
+
else
|
|
978
|
+
echo -e " ${BOLD}$agent${NC} ${RED}✗ 不可用${NC}"
|
|
979
|
+
# 显示错误信息
|
|
980
|
+
if [ -f "${check_status_dir}/${agent}.err" ] && [ -s "${check_status_dir}/${agent}.err" ]; then
|
|
981
|
+
cat "${check_status_dir}/${agent}.err" >&2
|
|
982
|
+
fi
|
|
983
|
+
rm -rf "$check_status_dir"
|
|
984
|
+
exit 1
|
|
985
|
+
fi
|
|
986
|
+
done
|
|
987
|
+
rm -rf "$check_status_dir"
|
|
988
|
+
|
|
989
|
+
# ---- Phase 3: 生成实例名 ----
|
|
990
|
+
local _agent_count="|"
|
|
991
|
+
for agent in "${RAW_AGENT_LIST[@]}"; do
|
|
992
|
+
agent=$(echo "$agent" | xargs) # trim
|
|
993
|
+
|
|
994
|
+
# 生成实例名:有重复则添加 _1, _2 后缀
|
|
995
|
+
local _cur_count
|
|
996
|
+
_cur_count=$(echo "$_agent_count" | sed -n "s/.*|${agent}:\([0-9]*\)|.*/\1/p")
|
|
997
|
+
_cur_count=$(( ${_cur_count:-0} + 1 ))
|
|
998
|
+
if echo "$_agent_count" | grep -q "|${agent}:"; then
|
|
999
|
+
_agent_count=$(echo "$_agent_count" | sed "s/|${agent}:[0-9]*|/|${agent}:${_cur_count}|/")
|
|
1000
|
+
else
|
|
1001
|
+
_agent_count="${_agent_count}${agent}:${_cur_count}|"
|
|
1002
|
+
fi
|
|
1003
|
+
|
|
1004
|
+
local instance_name="$agent"
|
|
1005
|
+
local _total
|
|
1006
|
+
_total=$(echo "$_agent_total" | sed -n "s/.*|${agent}:\([0-9]*\)|.*/\1/p")
|
|
1007
|
+
if [ "${_total:-1}" -gt 1 ]; then
|
|
1008
|
+
instance_name="${agent}_${_cur_count}"
|
|
1009
|
+
fi
|
|
1010
|
+
|
|
1011
|
+
available_agents+=("$instance_name")
|
|
1012
|
+
done
|
|
1013
|
+
|
|
1014
|
+
echo ""
|
|
1015
|
+
|
|
1016
|
+
if [ ${#available_agents[@]} -lt 2 ]; then
|
|
1017
|
+
echo -e "${RED}❌ 至少需要 2 个可用 Agent 才能进行讨论${NC}"
|
|
1018
|
+
exit 1
|
|
1019
|
+
fi
|
|
1020
|
+
|
|
1021
|
+
# 创建 rounds 目录
|
|
1022
|
+
mkdir -p "$ROUNDS_DIR"
|
|
1023
|
+
mkdir -p "$SESSIONS_DIR"
|
|
1024
|
+
|
|
1025
|
+
# 动态生成各 Agent 的指令文件(新建和恢复模式都需要更新)
|
|
1026
|
+
for agent in "${available_agents[@]}"; do
|
|
1027
|
+
local md_file base
|
|
1028
|
+
md_file=$(agent_md_file "$agent")
|
|
1029
|
+
base=$(agent_base "$agent")
|
|
1030
|
+
"generate_${base}_md" "$max_rounds" "$problem" > "$md_file"
|
|
1031
|
+
if $resume_mode; then
|
|
1032
|
+
echo -e " ${CYAN}🔄 更新 $md_file (轮次: $max_rounds)${NC}"
|
|
1033
|
+
else
|
|
1034
|
+
echo -e " ${CYAN}📝 生成 $md_file${NC}"
|
|
1035
|
+
fi
|
|
1036
|
+
done
|
|
1037
|
+
|
|
1038
|
+
# 初始化 git(codex 需要)
|
|
1039
|
+
if [ ! -d ".git" ]; then
|
|
1040
|
+
git init -q
|
|
1041
|
+
echo ".debate.log" >> .gitignore 2>/dev/null || true
|
|
1042
|
+
echo ".debate.json" >> .gitignore 2>/dev/null || true
|
|
1043
|
+
git add -A 2>/dev/null || true
|
|
1044
|
+
git commit -q -m "ai-battle: init" 2>/dev/null || true
|
|
1045
|
+
fi
|
|
1046
|
+
|
|
1047
|
+
# ---- 阶段 2: 开始讨论 ----
|
|
1048
|
+
|
|
1049
|
+
local agent_count=${#available_agents[@]}
|
|
1050
|
+
|
|
1051
|
+
local sys_prompt
|
|
1052
|
+
sys_prompt=$(build_system_prompt "$max_rounds")
|
|
1053
|
+
|
|
1054
|
+
# 上帝视角累积信息
|
|
1055
|
+
local god_context=""
|
|
1056
|
+
|
|
1057
|
+
# 构建 agents 显示标题
|
|
1058
|
+
local agents_display="${available_agents[0]}"
|
|
1059
|
+
for ((idx=1; idx<agent_count; idx++)); do
|
|
1060
|
+
agents_display+=" vs ${available_agents[$idx]}"
|
|
1061
|
+
done
|
|
1062
|
+
|
|
1063
|
+
echo ""
|
|
1064
|
+
log_and_print "${BOLD}╔══════════════════════════════════════════╗${NC}"
|
|
1065
|
+
log_and_print "${BOLD}║ AI 圆桌讨论: ${agents_display}${NC}"
|
|
1066
|
+
log_and_print "${BOLD}╚══════════════════════════════════════════╝${NC}"
|
|
1067
|
+
echo ""
|
|
1068
|
+
log_and_print " 📝 问题: $(head -1 "$PROBLEM_FILE")"
|
|
1069
|
+
log_and_print " 🤖 Agent: ${available_agents[*]}"
|
|
1070
|
+
log_and_print " 🔄 最大轮次: $max_rounds"
|
|
1071
|
+
if $god_mode; then
|
|
1072
|
+
log_and_print "${CYAN} 👁️ 上帝视角: 开启${NC}"
|
|
1073
|
+
fi
|
|
1074
|
+
|
|
1075
|
+
# 裁判 agent 解析: 默认使用第一个 available agent 的基础类型
|
|
1076
|
+
local referee_base=""
|
|
1077
|
+
if $referee_mode; then
|
|
1078
|
+
if [ -n "$referee_agent" ]; then
|
|
1079
|
+
# 验证指定的裁判 agent 是否已注册
|
|
1080
|
+
local ref_valid=false
|
|
1081
|
+
for ra in "${REGISTERED_AGENTS[@]}"; do
|
|
1082
|
+
if [ "$ra" == "$referee_agent" ]; then
|
|
1083
|
+
ref_valid=true
|
|
1084
|
+
break
|
|
1085
|
+
fi
|
|
1086
|
+
done
|
|
1087
|
+
if ! $ref_valid; then
|
|
1088
|
+
echo -e "${RED}❌ 裁判 agent '$referee_agent' 未注册${NC}"
|
|
1089
|
+
exit 1
|
|
1090
|
+
fi
|
|
1091
|
+
referee_base="$referee_agent"
|
|
1092
|
+
else
|
|
1093
|
+
referee_base=$(agent_base "${available_agents[0]}")
|
|
1094
|
+
fi
|
|
1095
|
+
log_and_print " 🔨 裁判: ${BOLD}${referee_base}${NC}"
|
|
1096
|
+
fi
|
|
1097
|
+
|
|
1098
|
+
log_and_print "${CYAN} 日志: tail -f $LOG_FILE${NC}"
|
|
1099
|
+
echo ""
|
|
1100
|
+
|
|
1101
|
+
# 使用普通数组存储每个 agent 的最新回复(索引与 available_agents 对应)
|
|
1102
|
+
local responses=()
|
|
1103
|
+
|
|
1104
|
+
# 恢复模式起始轮次(默认从 Round 2 开始)
|
|
1105
|
+
local round=2
|
|
1106
|
+
|
|
1107
|
+
if $resume_mode; then
|
|
1108
|
+
# ---- 恢复模式: 从历史记录恢复状态 ----
|
|
1109
|
+
local prev_round
|
|
1110
|
+
prev_round=$(jq -r '.current_round // 0' "$CONFIG_FILE" 2>/dev/null)
|
|
1111
|
+
round=$((prev_round + 1))
|
|
1112
|
+
|
|
1113
|
+
# 更新配置: 状态为 running,max_rounds 使用命令行参数
|
|
1114
|
+
jq --argjson m "$max_rounds" '.status = "running" | .max_rounds = $m' \
|
|
1115
|
+
"$CONFIG_FILE" > "${CONFIG_FILE}.tmp" \
|
|
1116
|
+
&& mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
|
1117
|
+
|
|
1118
|
+
log_and_print "${CYAN}🔄 恢复模式: 从 Round ${round} 继续${NC}"
|
|
1119
|
+
log_and_print "${CYAN} 加载历史回复 (Round ${prev_round})...${NC}"
|
|
1120
|
+
|
|
1121
|
+
# 从最近一轮的回复文件恢复 responses[] 数组
|
|
1122
|
+
for ((i=0; i<agent_count; i++)); do
|
|
1123
|
+
local agent="${available_agents[$i]}"
|
|
1124
|
+
local reply_file="$ROUNDS_DIR/round_${prev_round}_${agent}.md"
|
|
1125
|
+
if [ -f "$reply_file" ]; then
|
|
1126
|
+
responses[$i]=$(cat "$reply_file")
|
|
1127
|
+
log_and_print " ${GREEN}✓${NC} ${agent} Round ${prev_round} 回复已加载"
|
|
1128
|
+
else
|
|
1129
|
+
log_and_print "${RED}❌ 缺少 ${reply_file},无法恢复${NC}"
|
|
1130
|
+
exit 1
|
|
1131
|
+
fi
|
|
1132
|
+
done
|
|
1133
|
+
|
|
1134
|
+
# 恢复上帝视角历史信息
|
|
1135
|
+
local god_file="$ROUNDS_DIR/god_round_${prev_round}.md"
|
|
1136
|
+
if [ -f "$god_file" ]; then
|
|
1137
|
+
god_context=$(cat "$god_file")
|
|
1138
|
+
log_and_print " ${GREEN}✓${NC} 上帝视角 Round ${prev_round} 信息已加载"
|
|
1139
|
+
fi
|
|
1140
|
+
|
|
1141
|
+
echo ""
|
|
1142
|
+
else
|
|
1143
|
+
# ---- 正常模式: 写入配置并执行 Round 1 ----
|
|
1144
|
+
|
|
1145
|
+
# 写入配置(供恢复模式使用)
|
|
1146
|
+
jq -n \
|
|
1147
|
+
--arg agents "$(IFS=,; echo "${available_agents[*]}")" \
|
|
1148
|
+
--argjson max_rounds "$max_rounds" \
|
|
1149
|
+
--arg problem "$problem" \
|
|
1150
|
+
'{agents: $agents, max_rounds: $max_rounds, problem: $problem,
|
|
1151
|
+
status: "running", current_round: 0}' \
|
|
1152
|
+
> "$CONFIG_FILE"
|
|
1153
|
+
|
|
1154
|
+
# 清空日志
|
|
1155
|
+
: > "$LOG_FILE"
|
|
1156
|
+
|
|
1157
|
+
# ---- Round 1: 所有 agent 并行独立思考 ----
|
|
1158
|
+
log_and_print "${CYAN}⏳ Round 1: ${agent_count} 位参与者独立思考中...${NC}"
|
|
1159
|
+
|
|
1160
|
+
local tmp_files=()
|
|
1161
|
+
local pids=()
|
|
1162
|
+
for ((i=0; i<agent_count; i++)); do
|
|
1163
|
+
local agent="${available_agents[$i]}"
|
|
1164
|
+
local base
|
|
1165
|
+
base=$(agent_base "$agent")
|
|
1166
|
+
local tmp
|
|
1167
|
+
tmp=$(mktemp)
|
|
1168
|
+
tmp_files+=("$tmp")
|
|
1169
|
+
(
|
|
1170
|
+
"call_${base}" "$sys_prompt" "请对以下问题给出你的深入分析:
|
|
1171
|
+
|
|
1172
|
+
$problem" "round_1_${agent}" > "$tmp" 2>/dev/null
|
|
1173
|
+
) &
|
|
1174
|
+
pids+=($!)
|
|
1175
|
+
done
|
|
1176
|
+
|
|
1177
|
+
# 等待全部完成
|
|
1178
|
+
for pid in "${pids[@]}"; do
|
|
1179
|
+
wait "$pid" 2>/dev/null || true
|
|
1180
|
+
done
|
|
1181
|
+
|
|
1182
|
+
# 读取所有回复,失败的 agent 交互式重试
|
|
1183
|
+
for ((i=0; i<agent_count; i++)); do
|
|
1184
|
+
local agent="${available_agents[$i]}"
|
|
1185
|
+
local base
|
|
1186
|
+
base=$(agent_base "$agent")
|
|
1187
|
+
local color
|
|
1188
|
+
color=$(agent_color "$agent")
|
|
1189
|
+
if [ -s "${tmp_files[$i]}" ]; then
|
|
1190
|
+
responses[$i]=$(cat "${tmp_files[$i]}")
|
|
1191
|
+
echo "${responses[$i]}" > "$ROUNDS_DIR/round_1_${agent}.md"
|
|
1192
|
+
log_and_print ""
|
|
1193
|
+
log_and_print "${color}━━━ ${agent} [Round 1/${max_rounds}] ━━━${NC}"
|
|
1194
|
+
log_and_print "${color}${responses[$i]}${NC}"
|
|
1195
|
+
else
|
|
1196
|
+
log_and_print "${YELLOW}⚠️ ${agent} Round 1 并发调用失败,进入交互式重试...${NC}"
|
|
1197
|
+
responses[$i]=$(retry_call_agent "${agent} [Round 1]" "call_${base}" "$sys_prompt" "请对以下问题给出你的深入分析:
|
|
1198
|
+
|
|
1199
|
+
$problem" "round_1_${agent}")
|
|
1200
|
+
if [ -n "${responses[$i]}" ]; then
|
|
1201
|
+
echo "${responses[$i]}" > "$ROUNDS_DIR/round_1_${agent}.md"
|
|
1202
|
+
log_and_print ""
|
|
1203
|
+
log_and_print "${color}━━━ ${agent} [Round 1/${max_rounds}] ━━━${NC}"
|
|
1204
|
+
log_and_print "${color}${responses[$i]}${NC}"
|
|
1205
|
+
else
|
|
1206
|
+
log_and_print "${YELLOW}⏭️ ${agent} Round 1 已跳过${NC}"
|
|
1207
|
+
fi
|
|
1208
|
+
fi
|
|
1209
|
+
rm -f "${tmp_files[$i]}"
|
|
1210
|
+
done
|
|
1211
|
+
|
|
1212
|
+
log_and_print ""
|
|
1213
|
+
log_and_print "${CYAN}✅ Round 1 完成${NC}"
|
|
1214
|
+
|
|
1215
|
+
# 检查 Round 1 共识(所有人都 AGREED 才算)
|
|
1216
|
+
local all_agreed_r1=true
|
|
1217
|
+
for ((i=0; i<agent_count; i++)); do
|
|
1218
|
+
if ! has_agreed "${responses[$i]}"; then
|
|
1219
|
+
all_agreed_r1=false
|
|
1220
|
+
break
|
|
1221
|
+
fi
|
|
1222
|
+
done
|
|
1223
|
+
if $all_agreed_r1; then
|
|
1224
|
+
local conclusion
|
|
1225
|
+
conclusion=$(extract_agreed "${responses[0]}")
|
|
1226
|
+
finish_consensus "$conclusion" 1 "$max_rounds"
|
|
1227
|
+
$referee_mode && generate_final_summary "$referee_base" "$referee_custom_prompt"
|
|
1228
|
+
exit 0
|
|
1229
|
+
fi
|
|
1230
|
+
|
|
1231
|
+
# 上帝视角: Round 1 后注入
|
|
1232
|
+
if $god_mode; then
|
|
1233
|
+
god_context=$(god_input 1)
|
|
1234
|
+
fi
|
|
1235
|
+
fi
|
|
1236
|
+
|
|
1237
|
+
# 恢复模式 + 上帝视角: 判断上次是否在上帝视角阶段退出
|
|
1238
|
+
if $resume_mode && $god_mode; then
|
|
1239
|
+
local prev_round_num=$((round - 1))
|
|
1240
|
+
local god_file="$ROUNDS_DIR/god_round_${prev_round_num}.md"
|
|
1241
|
+
if [ ! -f "$god_file" ]; then
|
|
1242
|
+
# 上次在上帝视角阶段退出,重新触发
|
|
1243
|
+
log_and_print "${CYAN}👁️ 恢复上帝视角 (Round ${prev_round_num})${NC}"
|
|
1244
|
+
god_context=$(god_input "$prev_round_num")
|
|
1245
|
+
fi
|
|
1246
|
+
# 文件已存在的情况,god_context 在上面恢复阶段已加载
|
|
1247
|
+
fi
|
|
1248
|
+
|
|
1249
|
+
# ---- Round 2+: N 方顺序对话 ----
|
|
1250
|
+
# 提取为函数逻辑,供主循环和追加轮次复用
|
|
1251
|
+
while [ "$round" -le "$max_rounds" ]; do
|
|
1252
|
+
local remaining=$((max_rounds - round))
|
|
1253
|
+
|
|
1254
|
+
# 每个 agent 依次发言,看到所有其他 agent 的上一轮回复
|
|
1255
|
+
for ((i=0; i<agent_count; i++)); do
|
|
1256
|
+
local agent="${available_agents[$i]}"
|
|
1257
|
+
local base
|
|
1258
|
+
base=$(agent_base "$agent")
|
|
1259
|
+
local color
|
|
1260
|
+
color=$(agent_color "$agent")
|
|
1261
|
+
|
|
1262
|
+
log_and_print ""
|
|
1263
|
+
log_and_print "${CYAN}⏳ Round $round: ${agent} 思考中...${NC}"
|
|
1264
|
+
|
|
1265
|
+
# 构建其他 agent 回复的 XML 块
|
|
1266
|
+
local others_responses=""
|
|
1267
|
+
for ((j=0; j<agent_count; j++)); do
|
|
1268
|
+
if [ "$j" != "$i" ]; then
|
|
1269
|
+
local other="${available_agents[$j]}"
|
|
1270
|
+
others_responses+="<${other}_response>
|
|
1271
|
+
${responses[$j]}
|
|
1272
|
+
</${other}_response>
|
|
1273
|
+
|
|
1274
|
+
"
|
|
1275
|
+
fi
|
|
1276
|
+
done
|
|
1277
|
+
|
|
1278
|
+
local prompt="以下是其他参与者的上一轮回复:\n\n${others_responses}请回应以上观点。剩余 $remaining 轮。"
|
|
1279
|
+
if [ -n "$god_context" ]; then
|
|
1280
|
+
prompt+="\n\n[上帝视角 - 额外补充信息]\n${god_context}"
|
|
1281
|
+
fi
|
|
1282
|
+
|
|
1283
|
+
responses[$i]=$(retry_call_agent "${agent} [Round ${round}]" "call_${base}" "$sys_prompt" "$prompt" "round_${round}_${agent}")
|
|
1284
|
+
if [ -z "${responses[$i]}" ]; then
|
|
1285
|
+
log_and_print "${YELLOW}⏭️ ${agent} Round ${round} 已跳过${NC}"
|
|
1286
|
+
continue
|
|
1287
|
+
fi
|
|
1288
|
+
echo "${responses[$i]}" > "$ROUNDS_DIR/round_${round}_${agent}.md"
|
|
1289
|
+
log_and_print ""
|
|
1290
|
+
log_and_print "${color}━━━ ${agent} [Round ${round}/${max_rounds}] ━━━${NC}"
|
|
1291
|
+
log_and_print "${color}${responses[$i]}${NC}"
|
|
1292
|
+
|
|
1293
|
+
# 检查该 agent 是否提出 AGREED
|
|
1294
|
+
if has_agreed "${responses[$i]}"; then
|
|
1295
|
+
local conclusion
|
|
1296
|
+
conclusion=$(extract_agreed "${responses[$i]}")
|
|
1297
|
+
log_and_print "${CYAN}${agent} 提出共识,等待其他 agent 确认...${NC}"
|
|
1298
|
+
|
|
1299
|
+
# 向所有其他 agent 发送确认请求
|
|
1300
|
+
local all_confirmed=true
|
|
1301
|
+
for ((k=0; k<agent_count; k++)); do
|
|
1302
|
+
if [ "$k" != "$i" ]; then
|
|
1303
|
+
local other="${available_agents[$k]}"
|
|
1304
|
+
local other_base
|
|
1305
|
+
other_base=$(agent_base "$other")
|
|
1306
|
+
local other_color
|
|
1307
|
+
other_color=$(agent_color "$other")
|
|
1308
|
+
|
|
1309
|
+
local confirm
|
|
1310
|
+
confirm=$(retry_call_agent "${other} [确认]" "call_${other_base}" "$sys_prompt" "${agent} 提出了共识:
|
|
1311
|
+
|
|
1312
|
+
<proposed_consensus>
|
|
1313
|
+
$conclusion
|
|
1314
|
+
</proposed_consensus>
|
|
1315
|
+
|
|
1316
|
+
请审查这个结论是否完整正确。如果同意,在回复末尾写 AGREED: <结论>。如果不同意,说明原因。" "round_${round}_${other}_confirm")
|
|
1317
|
+
if [ -z "$confirm" ]; then
|
|
1318
|
+
log_and_print "${YELLOW}⏭️ ${other} 确认已跳过,视为不同意${NC}"
|
|
1319
|
+
all_confirmed=false
|
|
1320
|
+
break
|
|
1321
|
+
fi
|
|
1322
|
+
echo "$confirm" > "$ROUNDS_DIR/round_${round}_${other}_confirm.md"
|
|
1323
|
+
log_and_print "${other_color}━━━ ${other} [确认] ━━━${NC}"
|
|
1324
|
+
log_and_print "${other_color}${confirm}${NC}"
|
|
1325
|
+
|
|
1326
|
+
if has_agreed "$confirm"; then
|
|
1327
|
+
log_and_print "${CYAN} ✓ ${other} 同意${NC}"
|
|
1328
|
+
else
|
|
1329
|
+
log_and_print "${YELLOW} ✗ ${other} 不同意,讨论继续${NC}"
|
|
1330
|
+
responses[$k]="$confirm" # 更新该 agent 的回复供后续轮次使用
|
|
1331
|
+
all_confirmed=false
|
|
1332
|
+
break
|
|
1333
|
+
fi
|
|
1334
|
+
fi
|
|
1335
|
+
done
|
|
1336
|
+
|
|
1337
|
+
if $all_confirmed; then
|
|
1338
|
+
local final
|
|
1339
|
+
final=$(extract_agreed "${responses[$i]}")
|
|
1340
|
+
finish_consensus "$final" "$round" "$max_rounds"
|
|
1341
|
+
$referee_mode && generate_final_summary "$referee_base" "$referee_custom_prompt"
|
|
1342
|
+
exit 0
|
|
1343
|
+
fi
|
|
1344
|
+
fi
|
|
1345
|
+
done
|
|
1346
|
+
|
|
1347
|
+
# ---- 裁判总结: 所有 agent 发言完毕后 ----
|
|
1348
|
+
if $referee_mode; then
|
|
1349
|
+
log_and_print ""
|
|
1350
|
+
log_and_print "${BOLD}\033[1;37m━━━ 🔨 裁判总结 [Round ${round}/${max_rounds}] ━━━${NC}"
|
|
1351
|
+
log_and_print "${CYAN}⏳ 裁判分析中...${NC}"
|
|
1352
|
+
|
|
1353
|
+
# 构建所有 agent 回复的汇总
|
|
1354
|
+
local all_responses=""
|
|
1355
|
+
for ((ri=0; ri<agent_count; ri++)); do
|
|
1356
|
+
local ra="${available_agents[$ri]}"
|
|
1357
|
+
all_responses+="<${ra}_response>
|
|
1358
|
+
${responses[$ri]}
|
|
1359
|
+
</${ra}_response>
|
|
1360
|
+
|
|
1361
|
+
"
|
|
1362
|
+
done
|
|
1363
|
+
|
|
1364
|
+
# 根据模式选择裁判 prompt
|
|
1365
|
+
local ref_prompt
|
|
1366
|
+
if $god_mode; then
|
|
1367
|
+
ref_prompt=$(build_referee_god_prompt)
|
|
1368
|
+
else
|
|
1369
|
+
ref_prompt=$(build_referee_free_prompt)
|
|
1370
|
+
fi
|
|
1371
|
+
# 追加自定义裁判提示词
|
|
1372
|
+
if [ -n "$referee_custom_prompt" ]; then
|
|
1373
|
+
ref_prompt+=$'\n\n[额外指示]\n'"$referee_custom_prompt"
|
|
1374
|
+
fi
|
|
1375
|
+
|
|
1376
|
+
local referee_result
|
|
1377
|
+
referee_result=$(call_referee "$referee_base" "$ref_prompt" "以下是 Round ${round} 各参与者的回复:
|
|
1378
|
+
|
|
1379
|
+
${all_responses}请进行裁判总结。" "referee_round_${round}")
|
|
1380
|
+
|
|
1381
|
+
# 保存裁判结果
|
|
1382
|
+
echo "$referee_result" > "$ROUNDS_DIR/referee_round_${round}.md"
|
|
1383
|
+
log_and_print "${BOLD}\033[1;37m${referee_result}${NC}"
|
|
1384
|
+
|
|
1385
|
+
# 自由辩论模式: 裁判检测共识
|
|
1386
|
+
if ! $god_mode; then
|
|
1387
|
+
if echo "$referee_result" | grep -qiE 'CONSENSUS:[[:space:]]*YES'; then
|
|
1388
|
+
local ref_conclusion
|
|
1389
|
+
ref_conclusion=$(echo "$referee_result" | grep -ioE '\*{0,2}AGREED:[[:space:]]*.*' | head -1 \
|
|
1390
|
+
| sed 's/^\*\{0,2\}[Aa][Gg][Rr][Ee][Ee][Dd]:[[:space:]]*//' | sed 's/[[:space:]]*$//' | sed 's/\*\{1,2\}$//')
|
|
1391
|
+
if [ -n "$ref_conclusion" ]; then
|
|
1392
|
+
log_and_print "${CYAN}🔨 裁判判定: 已达成共识${NC}"
|
|
1393
|
+
finish_consensus "$ref_conclusion" "$round" "$max_rounds"
|
|
1394
|
+
generate_final_summary "$referee_base" "$referee_custom_prompt"
|
|
1395
|
+
exit 0
|
|
1396
|
+
fi
|
|
1397
|
+
fi
|
|
1398
|
+
fi
|
|
1399
|
+
fi
|
|
1400
|
+
|
|
1401
|
+
# 更新配置
|
|
1402
|
+
jq --argjson r "$round" '.current_round = $r | .status = "running"' \
|
|
1403
|
+
"$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
|
1404
|
+
|
|
1405
|
+
# 上帝视角: 每轮结束后注入(裁判总结后再让 god 输入)
|
|
1406
|
+
if $god_mode && [ "$round" -lt "$max_rounds" ]; then
|
|
1407
|
+
god_context=$(god_input "$round")
|
|
1408
|
+
fi
|
|
1409
|
+
|
|
1410
|
+
round=$((round + 1))
|
|
1411
|
+
done
|
|
1412
|
+
|
|
1413
|
+
# 超过最大轮次,询问是否追加
|
|
1414
|
+
while true; do
|
|
1415
|
+
log_and_print ""
|
|
1416
|
+
log_and_print "${YELLOW}╔════════════════════════════════════════╗${NC}"
|
|
1417
|
+
log_and_print "${YELLOW}║ 讨论结束,未达成共识 ║${NC}"
|
|
1418
|
+
log_and_print "${YELLOW}╚════════════════════════════════════════╝${NC}"
|
|
1419
|
+
log_and_print "${CYAN}已完成 $((round - 1)) 轮,所有回复保存在: $ROUNDS_DIR/${NC}"
|
|
1420
|
+
|
|
1421
|
+
# 交互式询问是否追加轮次
|
|
1422
|
+
local extra_rounds=""
|
|
1423
|
+
echo ""
|
|
1424
|
+
echo -ne "${BOLD}是否追加讨论轮次?输入轮次数(默认 ${max_rounds},0 或回车结束): ${NC}"
|
|
1425
|
+
read -er extra_rounds
|
|
1426
|
+
|
|
1427
|
+
# 空输入或 0 表示结束
|
|
1428
|
+
if [ -z "$extra_rounds" ] || [ "$extra_rounds" = "0" ]; then
|
|
1429
|
+
log_and_print "${CYAN}讨论结束。${NC}"
|
|
1430
|
+
jq '.status = "no_consensus"' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" \
|
|
1431
|
+
&& mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
|
1432
|
+
$referee_mode && generate_final_summary "$referee_base" "$referee_custom_prompt"
|
|
1433
|
+
return
|
|
1434
|
+
fi
|
|
1435
|
+
|
|
1436
|
+
# 验证输入为正整数
|
|
1437
|
+
if ! [[ "$extra_rounds" =~ ^[1-9][0-9]*$ ]]; then
|
|
1438
|
+
echo -e "${RED}请输入正整数${NC}"
|
|
1439
|
+
continue
|
|
1440
|
+
fi
|
|
1441
|
+
|
|
1442
|
+
local new_max=$((round - 1 + extra_rounds))
|
|
1443
|
+
max_rounds="$new_max"
|
|
1444
|
+
log_and_print "${CYAN}追加 $extra_rounds 轮,总轮次上限: $max_rounds${NC}"
|
|
1445
|
+
echo ""
|
|
1446
|
+
|
|
1447
|
+
# 更新配置
|
|
1448
|
+
jq --argjson m "$max_rounds" '.max_rounds = $m' \
|
|
1449
|
+
"$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
|
1450
|
+
|
|
1451
|
+
# 继续讨论循环(逻辑与上方 Round 2+ 完全相同)
|
|
1452
|
+
while [ "$round" -le "$max_rounds" ]; do
|
|
1453
|
+
local remaining=$((max_rounds - round))
|
|
1454
|
+
|
|
1455
|
+
for ((i=0; i<agent_count; i++)); do
|
|
1456
|
+
local agent="${available_agents[$i]}"
|
|
1457
|
+
local base
|
|
1458
|
+
base=$(agent_base "$agent")
|
|
1459
|
+
local color
|
|
1460
|
+
color=$(agent_color "$agent")
|
|
1461
|
+
|
|
1462
|
+
log_and_print ""
|
|
1463
|
+
log_and_print "${CYAN}⏳ Round $round: ${agent} 思考中...${NC}"
|
|
1464
|
+
|
|
1465
|
+
# 构建其他 agent 回复的 XML 块
|
|
1466
|
+
local others_responses=""
|
|
1467
|
+
for ((j=0; j<agent_count; j++)); do
|
|
1468
|
+
if [ "$j" != "$i" ]; then
|
|
1469
|
+
local other="${available_agents[$j]}"
|
|
1470
|
+
others_responses+="<${other}_response>
|
|
1471
|
+
${responses[$j]}
|
|
1472
|
+
</${other}_response>
|
|
1473
|
+
|
|
1474
|
+
"
|
|
1475
|
+
fi
|
|
1476
|
+
done
|
|
1477
|
+
|
|
1478
|
+
local prompt="以下是其他参与者的上一轮回复:\n\n${others_responses}请回应以上观点。剩余 $remaining 轮。"
|
|
1479
|
+
if [ -n "$god_context" ]; then
|
|
1480
|
+
prompt+="\n\n[上帝视角 - 额外补充信息]\n${god_context}"
|
|
1481
|
+
fi
|
|
1482
|
+
|
|
1483
|
+
responses[$i]=$(retry_call_agent "${agent} [Round ${round}]" "call_${base}" "$sys_prompt" "$prompt" "round_${round}_${agent}")
|
|
1484
|
+
if [ -z "${responses[$i]}" ]; then
|
|
1485
|
+
log_and_print "${YELLOW}⏭️ ${agent} Round ${round} 已跳过${NC}"
|
|
1486
|
+
continue
|
|
1487
|
+
fi
|
|
1488
|
+
echo "${responses[$i]}" > "$ROUNDS_DIR/round_${round}_${agent}.md"
|
|
1489
|
+
log_and_print ""
|
|
1490
|
+
log_and_print "${color}━━━ ${agent} [Round ${round}/${max_rounds}] ━━━${NC}"
|
|
1491
|
+
log_and_print "${color}${responses[$i]}${NC}"
|
|
1492
|
+
|
|
1493
|
+
# 检查该 agent 是否提出 AGREED
|
|
1494
|
+
if has_agreed "${responses[$i]}"; then
|
|
1495
|
+
local conclusion
|
|
1496
|
+
conclusion=$(extract_agreed "${responses[$i]}")
|
|
1497
|
+
log_and_print "${CYAN}${agent} 提出共识,等待其他 agent 确认...${NC}"
|
|
1498
|
+
|
|
1499
|
+
local all_confirmed=true
|
|
1500
|
+
for ((k=0; k<agent_count; k++)); do
|
|
1501
|
+
if [ "$k" != "$i" ]; then
|
|
1502
|
+
local other="${available_agents[$k]}"
|
|
1503
|
+
local other_base
|
|
1504
|
+
other_base=$(agent_base "$other")
|
|
1505
|
+
local other_color
|
|
1506
|
+
other_color=$(agent_color "$other")
|
|
1507
|
+
|
|
1508
|
+
local confirm
|
|
1509
|
+
confirm=$(retry_call_agent "${other} [确认]" "call_${other_base}" "$sys_prompt" "${agent} 提出了共识:
|
|
1510
|
+
|
|
1511
|
+
<proposed_consensus>
|
|
1512
|
+
$conclusion
|
|
1513
|
+
</proposed_consensus>
|
|
1514
|
+
|
|
1515
|
+
请审查这个结论是否完整正确。如果同意,在回复末尾写 AGREED: <结论>。如果不同意,说明原因。" "round_${round}_${other}_confirm")
|
|
1516
|
+
if [ -z "$confirm" ]; then
|
|
1517
|
+
log_and_print "${YELLOW}⏭️ ${other} 确认已跳过,视为不同意${NC}"
|
|
1518
|
+
all_confirmed=false
|
|
1519
|
+
break
|
|
1520
|
+
fi
|
|
1521
|
+
echo "$confirm" > "$ROUNDS_DIR/round_${round}_${other}_confirm.md"
|
|
1522
|
+
log_and_print "${other_color}━━━ ${other} [确认] ━━━${NC}"
|
|
1523
|
+
log_and_print "${other_color}${confirm}${NC}"
|
|
1524
|
+
|
|
1525
|
+
if has_agreed "$confirm"; then
|
|
1526
|
+
log_and_print "${CYAN} ✓ ${other} 同意${NC}"
|
|
1527
|
+
else
|
|
1528
|
+
log_and_print "${YELLOW} ✗ ${other} 不同意,讨论继续${NC}"
|
|
1529
|
+
responses[$k]="$confirm"
|
|
1530
|
+
all_confirmed=false
|
|
1531
|
+
break
|
|
1532
|
+
fi
|
|
1533
|
+
fi
|
|
1534
|
+
done
|
|
1535
|
+
|
|
1536
|
+
if $all_confirmed; then
|
|
1537
|
+
local final
|
|
1538
|
+
final=$(extract_agreed "${responses[$i]}")
|
|
1539
|
+
finish_consensus "$final" "$round" "$max_rounds"
|
|
1540
|
+
$referee_mode && generate_final_summary "$referee_base" "$referee_custom_prompt"
|
|
1541
|
+
exit 0
|
|
1542
|
+
fi
|
|
1543
|
+
fi
|
|
1544
|
+
done
|
|
1545
|
+
|
|
1546
|
+
# ---- 裁判总结: 追加轮次中同样执行 ----
|
|
1547
|
+
if $referee_mode; then
|
|
1548
|
+
log_and_print ""
|
|
1549
|
+
log_and_print "${BOLD}\033[1;37m━━━ 🔨 裁判总结 [Round ${round}/${max_rounds}] ━━━${NC}"
|
|
1550
|
+
log_and_print "${CYAN}⏳ 裁判分析中...${NC}"
|
|
1551
|
+
|
|
1552
|
+
local all_responses=""
|
|
1553
|
+
for ((ri=0; ri<agent_count; ri++)); do
|
|
1554
|
+
local ra="${available_agents[$ri]}"
|
|
1555
|
+
all_responses+="<${ra}_response>
|
|
1556
|
+
${responses[$ri]}
|
|
1557
|
+
</${ra}_response>
|
|
1558
|
+
|
|
1559
|
+
"
|
|
1560
|
+
done
|
|
1561
|
+
|
|
1562
|
+
local ref_prompt
|
|
1563
|
+
if $god_mode; then
|
|
1564
|
+
ref_prompt=$(build_referee_god_prompt)
|
|
1565
|
+
else
|
|
1566
|
+
ref_prompt=$(build_referee_free_prompt)
|
|
1567
|
+
fi
|
|
1568
|
+
# 追加自定义裁判提示词
|
|
1569
|
+
if [ -n "$referee_custom_prompt" ]; then
|
|
1570
|
+
ref_prompt+=$'\n\n[额外指示]\n'"$referee_custom_prompt"
|
|
1571
|
+
fi
|
|
1572
|
+
|
|
1573
|
+
local referee_result
|
|
1574
|
+
referee_result=$(call_referee "$referee_base" "$ref_prompt" "以下是 Round ${round} 各参与者的回复:
|
|
1575
|
+
|
|
1576
|
+
${all_responses}请进行裁判总结。" "referee_round_${round}")
|
|
1577
|
+
|
|
1578
|
+
echo "$referee_result" > "$ROUNDS_DIR/referee_round_${round}.md"
|
|
1579
|
+
log_and_print "${BOLD}\033[1;37m${referee_result}${NC}"
|
|
1580
|
+
|
|
1581
|
+
if ! $god_mode; then
|
|
1582
|
+
if echo "$referee_result" | grep -qiE 'CONSENSUS:[[:space:]]*YES'; then
|
|
1583
|
+
local ref_conclusion
|
|
1584
|
+
ref_conclusion=$(echo "$referee_result" | grep -ioE '\*{0,2}AGREED:[[:space:]]*.*' | head -1 \
|
|
1585
|
+
| sed 's/^\*\{0,2\}[Aa][Gg][Rr][Ee][Ee][Dd]:[[:space:]]*//' | sed 's/[[:space:]]*$//' | sed 's/\*\{1,2\}$//')
|
|
1586
|
+
if [ -n "$ref_conclusion" ]; then
|
|
1587
|
+
log_and_print "${CYAN}🔨 裁判判定: 已达成共识${NC}"
|
|
1588
|
+
finish_consensus "$ref_conclusion" "$round" "$max_rounds"
|
|
1589
|
+
generate_final_summary "$referee_base" "$referee_custom_prompt"
|
|
1590
|
+
exit 0
|
|
1591
|
+
fi
|
|
1592
|
+
fi
|
|
1593
|
+
fi
|
|
1594
|
+
fi
|
|
1595
|
+
|
|
1596
|
+
# 更新配置
|
|
1597
|
+
jq --argjson r "$round" '.current_round = $r | .status = "running"' \
|
|
1598
|
+
"$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
|
1599
|
+
|
|
1600
|
+
# 上帝视角: 每轮结束后注入(裁判总结后再让 god 输入)
|
|
1601
|
+
if $god_mode && [ "$round" -lt "$max_rounds" ]; then
|
|
1602
|
+
god_context=$(god_input "$round")
|
|
1603
|
+
fi
|
|
1604
|
+
|
|
1605
|
+
round=$((round + 1))
|
|
1606
|
+
done
|
|
1607
|
+
done
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
# 共识达成处理
|
|
1611
|
+
finish_consensus() {
|
|
1612
|
+
local conclusion="$1" round="$2" max_rounds="$3"
|
|
1613
|
+
echo "$conclusion" > "$CONSENSUS_FILE"
|
|
1614
|
+
|
|
1615
|
+
log_and_print ""
|
|
1616
|
+
log_and_print "${YELLOW}╔════════════════════════════════════════╗${NC}"
|
|
1617
|
+
log_and_print "${YELLOW}║ 🎉 达成共识! ║${NC}"
|
|
1618
|
+
log_and_print "${YELLOW}╚════════════════════════════════════════╝${NC}"
|
|
1619
|
+
log_and_print "${BOLD}结论: ${conclusion}${NC}"
|
|
1620
|
+
log_and_print "${CYAN}轮次: ${round}/${max_rounds}${NC}"
|
|
1621
|
+
|
|
1622
|
+
jq --arg c "$conclusion" '.status = "consensus" | .conclusion = $c' \
|
|
1623
|
+
"$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
|
|
1627
|
+
# ======================== 命令: help ========================
|
|
1628
|
+
cmd_help() {
|
|
1629
|
+
cat << HELP
|
|
1630
|
+
|
|
1631
|
+
ai-battle — AI 圆桌讨论工具 (v${VERSION})
|
|
1632
|
+
|
|
1633
|
+
让多个 AI Agent 对同一问题进行结构化讨论,自动管理轮次、
|
|
1634
|
+
检测共识、保存全部讨论记录。
|
|
1635
|
+
|
|
1636
|
+
前置条件:
|
|
1637
|
+
1. 在当前目录创建 problem.md,写入讨论问题
|
|
1638
|
+
2. 确保参与讨论的 Agent CLI 工具已安装并可用
|
|
1639
|
+
|
|
1640
|
+
依赖: jq, bash 4+, (可选) claude, codex, gemini
|
|
1641
|
+
|
|
1642
|
+
用法:
|
|
1643
|
+
ai-battle [options]
|
|
1644
|
+
ai-battle help
|
|
1645
|
+
|
|
1646
|
+
参数:
|
|
1647
|
+
--agents, -a <a1,a2> 选择参与的 Agent(默认: claude,codex)
|
|
1648
|
+
支持同类 Agent: --agents gemini,gemini
|
|
1649
|
+
--rounds, -r <N> 最大讨论轮次(默认: 10)
|
|
1650
|
+
--god, -g 开启上帝视角(每轮结束后可注入补充信息)
|
|
1651
|
+
--referee [agent] 开启裁判模式(每轮总结差异/检测共识,结束时生成最终总结)
|
|
1652
|
+
可选指定裁判使用的 Agent(默认: 第一个参与者)
|
|
1653
|
+
|
|
1654
|
+
已注册 Agent:
|
|
1655
|
+
claude 通过 Claude CLI 调用 (需 claude 命令)
|
|
1656
|
+
codex 通过 codex exec 调用 (需 codex 命令)
|
|
1657
|
+
gemini 通过 Gemini CLI 调用 (需 gemini 命令)
|
|
1658
|
+
|
|
1659
|
+
使用示例:
|
|
1660
|
+
# 快速开始
|
|
1661
|
+
mkdir my-topic && cd my-topic
|
|
1662
|
+
echo "微服务 vs 单体架构的优缺点?" > problem.md
|
|
1663
|
+
ai-battle --agents claude,gemini --rounds 8
|
|
1664
|
+
|
|
1665
|
+
# 同类 Agent 自我辩论
|
|
1666
|
+
ai-battle --agents gemini,gemini
|
|
1667
|
+
|
|
1668
|
+
# 三方圆桌讨论
|
|
1669
|
+
ai-battle --agents claude,codex,gemini --rounds 5
|
|
1670
|
+
|
|
1671
|
+
# 裁判模式(每轮总结 + 结束时生成 SUMMARY.md)
|
|
1672
|
+
ai-battle --agents claude,codex,gemini --referee --rounds 5
|
|
1673
|
+
|
|
1674
|
+
# 指定 claude 做裁判
|
|
1675
|
+
ai-battle --agents codex,gemini --referee claude --rounds 5
|
|
1676
|
+
|
|
1677
|
+
# 上帝视角(每轮可人工补充信息)
|
|
1678
|
+
ai-battle --agents claude,codex --god
|
|
1679
|
+
|
|
1680
|
+
# 裁判 + 上帝视角
|
|
1681
|
+
ai-battle --agents claude,codex --referee --god
|
|
1682
|
+
|
|
1683
|
+
产出文件:
|
|
1684
|
+
rounds/ 每轮讨论记录 (round_N_<agent>.md)
|
|
1685
|
+
rounds/referee_*.md 裁判总结记录 (开启 --referee 时)
|
|
1686
|
+
rounds/god_*.md 上帝视角注入记录 (开启 --god 时)
|
|
1687
|
+
.sessions/ Agent CLI 原始输出记录 (stream-json/json/raw)
|
|
1688
|
+
consensus.md 共识结论(如达成)
|
|
1689
|
+
SUMMARY.md 最终总结 (裁判自动生成)
|
|
1690
|
+
.debate.log 完整日志 (可用 tail -f 实时查看)
|
|
1691
|
+
|
|
1692
|
+
扩展 Agent:
|
|
1693
|
+
在脚本中实现 check_<name>()、call_<name>()、generate_<name>_md()
|
|
1694
|
+
然后调用 register_agent "<name>"
|
|
1695
|
+
|
|
1696
|
+
配置文件:
|
|
1697
|
+
.env 自动加载环境变量 (启动时)
|
|
1698
|
+
referee.md 裁判自定义提示词 (开启 --referee 时)
|
|
1699
|
+
|
|
1700
|
+
环境变量:
|
|
1701
|
+
Claude:
|
|
1702
|
+
ANTHROPIC_BASE_URL API 地址
|
|
1703
|
+
ANTHROPIC_AUTH_TOKEN 认证 Token
|
|
1704
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL 模型名称
|
|
1705
|
+
API_TIMEOUT_MS 超时时间 (毫秒)
|
|
1706
|
+
|
|
1707
|
+
Codex:
|
|
1708
|
+
CODEX_MODEL 模型名称 (默认: gpt-5.3-codex)
|
|
1709
|
+
|
|
1710
|
+
Gemini:
|
|
1711
|
+
GEMINI_API_KEY API Key (如需自定义)
|
|
1712
|
+
|
|
1713
|
+
HELP
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
# ======================== 主入口 ========================
|
|
1717
|
+
main() {
|
|
1718
|
+
# 处理 help/version 等无参数命令
|
|
1719
|
+
case "${1:-}" in
|
|
1720
|
+
help|--help|-h) cmd_help; exit 0 ;;
|
|
1721
|
+
--version|-v) echo "ai-battle v$VERSION"; exit 0 ;;
|
|
1722
|
+
esac
|
|
1723
|
+
|
|
1724
|
+
# 默认行为: 启动讨论
|
|
1725
|
+
cmd_run "$@"
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
main "$@"
|