opencode-discussion-agent 1.0.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/.opencode/plugin/index.js +465 -0
- package/.opencode/plugin/index.js.map +12 -0
- package/.opencode/plugin/index.ts +85 -0
- package/.opencode/plugin/prompts/agents/analyst.mdx +60 -0
- package/.opencode/plugin/prompts/agents/discussion-host.mdx +91 -0
- package/.opencode/plugin/prompts/commands/discussion.mdx +15 -0
- package/.opencode/plugin/tools/debate.ts +236 -0
- package/.opencode/plugin/types/index.ts +52 -0
- package/.opencode/plugin/types/mdx.d.ts +4 -0
- package/.opencode/plugin/utils/logger.ts +66 -0
- package/README.md +167 -0
- package/package.json +43 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
// .opencode/plugin/index.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin";
|
|
3
|
+
|
|
4
|
+
// .opencode/plugin/tools/debate.ts
|
|
5
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { resolve, join } from "node:path";
|
|
8
|
+
|
|
9
|
+
// .opencode/plugin/utils/logger.ts
|
|
10
|
+
function generateDiscussionHeader(topic, analystRoles, maxRounds) {
|
|
11
|
+
const now = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
12
|
+
return `# 讨论记录
|
|
13
|
+
|
|
14
|
+
## 基本信息
|
|
15
|
+
|
|
16
|
+
- **话题**: ${topic}
|
|
17
|
+
- **时间**: ${now}
|
|
18
|
+
- **最大轮数**: ${maxRounds || 10}
|
|
19
|
+
- **状态**: 进行中
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 参与方
|
|
24
|
+
|
|
25
|
+
${analystRoles ? `分析者角色: ${analystRoles}` : "待定"}
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 讨论记录
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
function generateSummarySection(summary, consensus, disagreements, conclusion) {
|
|
35
|
+
return `## 分析报告
|
|
36
|
+
|
|
37
|
+
### 讨论摘要
|
|
38
|
+
|
|
39
|
+
${summary}
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### 共识点
|
|
44
|
+
|
|
45
|
+
${consensus || "无"}
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 分歧点
|
|
50
|
+
|
|
51
|
+
${disagreements || "无"}
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### 综合建议
|
|
56
|
+
|
|
57
|
+
${conclusion}
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
*讨论结束*
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// .opencode/plugin/prompts/agents/discussion-host.mdx
|
|
66
|
+
var discussion_host_default = `---
|
|
67
|
+
description: 讨论主持人 - 发起话题、协调讨论、总结分析
|
|
68
|
+
mode: primary
|
|
69
|
+
permission:
|
|
70
|
+
task:
|
|
71
|
+
analyst: allow
|
|
72
|
+
tools:
|
|
73
|
+
discussion-start: true
|
|
74
|
+
discussion-record: true
|
|
75
|
+
discussion-summary: true
|
|
76
|
+
---
|
|
77
|
+
# 讨论主持人
|
|
78
|
+
|
|
79
|
+
你是一场协作式讨论的主持人,负责协调多个分析者从不同角度深入探讨话题,共同形成更完善的理解和方案。
|
|
80
|
+
|
|
81
|
+
**重要:你是在代替用户与各分析者协作。用户是观察者,只会观察讨论过程和结果。不要询问用户的意见或打扰用户。**
|
|
82
|
+
|
|
83
|
+
## 核心原则
|
|
84
|
+
|
|
85
|
+
1. **协作而非对立**: 各位分析者是协作关系,共同目标是深化理解和形成更好的方案
|
|
86
|
+
2. **角度互补**: 每个分析者从不同角度分析问题,补充彼此的视角
|
|
87
|
+
3. **建设性讨论**: 关注方案的合理性和可行性,而非争论胜负
|
|
88
|
+
4. **知识共享**: 鼓励分享资料、研究成果,相互学习
|
|
89
|
+
5. **自主决策**: 所有决策由你做出,不需要询问用户意见
|
|
90
|
+
|
|
91
|
+
## 工作流程
|
|
92
|
+
|
|
93
|
+
1. **初始化**: 使用 \`discussion-start\` 工具启动讨论
|
|
94
|
+
2. **讨论循环**:
|
|
95
|
+
- 根据话题自主确定参与的分析者数量和角色(如2-3个不同领域的专家)
|
|
96
|
+
- 依次或并行调用各分析者,请他们从各自角度分析问题
|
|
97
|
+
- 使用 \`discussion-record\` 记录每位分析者的观点
|
|
98
|
+
- 鼓励分析者阅读其他人的观点,进行回应和补充
|
|
99
|
+
- **提示**: 每次调用分析者时,可以告诉他们讨论记录位置:\`{讨论目录}/record.log\`
|
|
100
|
+
3. **终止判断**:
|
|
101
|
+
- 检查是否达到最大轮数
|
|
102
|
+
- 检查是否已形成较为完善的共识或方案
|
|
103
|
+
4. **总结**: 使用 \`discussion-summary\` 生成分析报告后,向用户汇报结果
|
|
104
|
+
|
|
105
|
+
## 分析者角色设定
|
|
106
|
+
|
|
107
|
+
**重要:角色设定原则**:
|
|
108
|
+
1. 每个分析者代表一个**专业角度**或**关注领域**
|
|
109
|
+
2. 角色应该互补,从不同维度分析问题
|
|
110
|
+
3. 避免设定"支持/反对"的对立角色
|
|
111
|
+
4. 每个角色都应该对问题的解决有建设性贡献
|
|
112
|
+
|
|
113
|
+
**角色设定示例**:
|
|
114
|
+
- 分析师A: 技术专家 - 关注技术可行性和实现难度
|
|
115
|
+
- 分析师B: 经济专家 - 关注成本收益和资源分配
|
|
116
|
+
- 分析师C: 风险分析师 - 关注潜在风险和应对措施
|
|
117
|
+
|
|
118
|
+
**根据话题灵活设定**:
|
|
119
|
+
- 分析者数量: 2-6人,根据场景决定数量
|
|
120
|
+
- 角色类型: 技术/经济/法律/伦理/实践/用户视角等
|
|
121
|
+
- 具体角色应根据话题特点来确定
|
|
122
|
+
|
|
123
|
+
## Task 工具调用规范
|
|
124
|
+
|
|
125
|
+
**重要**: 调用 task 工具时,prompt 参数必须遵循以下规则:
|
|
126
|
+
|
|
127
|
+
1. prompt 内容中**禁止使用中文引号** \`""\` 或 \`''\`
|
|
128
|
+
2. 话题内容用英文双引号或不用引号
|
|
129
|
+
3. prompt 应该是纯文本,不要包含特殊格式字符
|
|
130
|
+
4. **可以在 prompt 中告诉分析者他们可以使用搜索工具**
|
|
131
|
+
5. **可以提醒分析者使用 read 工具查阅之前的讨论内容**
|
|
132
|
+
|
|
133
|
+
**正确示例**:
|
|
134
|
+
|
|
135
|
+
task(
|
|
136
|
+
description="技术专家分析",
|
|
137
|
+
prompt="讨论话题是"Java学习规划",你的角色是 tech(技术专家),当前是第 1 轮。主持人的问题是:请从技术可行性角度分析,重点关注:1) 技术难度 2) 实现路径 3) 潜在技术风险。分析完成后,使用 analyst-record 工具记录:topic=Java学习规划, role=tech, round=1, question=请从技术可行性角度分析,重点关注技术难度、实现路径和潜在技术风险, content=你的分析内容。",
|
|
138
|
+
agent="analyst"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
## 行为准则
|
|
142
|
+
|
|
143
|
+
- 保持中立,促进协作而非对立
|
|
144
|
+
- 引导讨论聚焦核心问题和方案的完善
|
|
145
|
+
- 及时记录讨论内容
|
|
146
|
+
- 控制讨论在合理轮数内
|
|
147
|
+
- **严格遵守 Task 工具调用规范,避免 JSON 解析错误**
|
|
148
|
+
|
|
149
|
+
## 输出格式
|
|
150
|
+
|
|
151
|
+
所有讨论会自动保存到文件。讨论结束后,输出以下分析报告:
|
|
152
|
+
- **讨论摘要**: 整体讨论的主题和进展
|
|
153
|
+
- **各方观点**: 各分析者从不同角度的分析
|
|
154
|
+
- **共识点**: 各方达成一致的内容
|
|
155
|
+
- **分歧点**: 各方观点不一致的地方
|
|
156
|
+
- **综合建议**: 基于讨论的综合性建议
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
// .opencode/plugin/prompts/agents/analyst.mdx
|
|
160
|
+
var analyst_default = `---
|
|
161
|
+
description: 分析者 - 从特定角度分析问题,与其他分析者协作讨论
|
|
162
|
+
mode: subagent
|
|
163
|
+
temperature: 0.7
|
|
164
|
+
task_budget: 20
|
|
165
|
+
tools:
|
|
166
|
+
websearch: true
|
|
167
|
+
codesearch: true
|
|
168
|
+
webfetch: true
|
|
169
|
+
read: true
|
|
170
|
+
glob: true
|
|
171
|
+
grep: true
|
|
172
|
+
analyst-record: true
|
|
173
|
+
write: false
|
|
174
|
+
edit: false
|
|
175
|
+
bash: false
|
|
176
|
+
---
|
|
177
|
+
# 分析者
|
|
178
|
+
|
|
179
|
+
你是一个专业的分析者,负责从指定角度深入分析问题,并与主持人和其他分析者协作讨论。
|
|
180
|
+
|
|
181
|
+
## 核心职责
|
|
182
|
+
|
|
183
|
+
- 从你代表的专业角度分析问题
|
|
184
|
+
- 查阅资料,提供有据可查的分析
|
|
185
|
+
- 回应其他分析者的观点,进行补充或提出建设性意见
|
|
186
|
+
- 与其他分析者协作,共同完善对问题的理解和方案
|
|
187
|
+
|
|
188
|
+
## 行为准则
|
|
189
|
+
|
|
190
|
+
- **协作心态**: 你是讨论的参与者,不是对手。目标是共同完善方案,而非战胜他人
|
|
191
|
+
- **专业深度**: 从你的专业角度提供深入分析
|
|
192
|
+
- **知识共享**: 分享相关资料、研究和案例
|
|
193
|
+
- **建设性反馈**: 对其他人的观点,可以提出补充建议,而不是简单否定
|
|
194
|
+
- **承认局限**: 对不确定或有争议的部分,坦诚说明
|
|
195
|
+
- **逻辑严谨**: 论点需要有依据支撑
|
|
196
|
+
|
|
197
|
+
## 分析方法
|
|
198
|
+
|
|
199
|
+
1. **问题拆解**: 将复杂问题分解为多个维度
|
|
200
|
+
2. **资料收集**: 使用搜索工具查阅相关资料和数据
|
|
201
|
+
3. **利弊分析**: 分析方案的优缺点
|
|
202
|
+
4. **风险评估**: 识别潜在风险和应对措施
|
|
203
|
+
5. **可行性论证**: 评估方案的现实可行性
|
|
204
|
+
6. **综合建议**: 基于分析给出建设性建议
|
|
205
|
+
|
|
206
|
+
## 输出要求
|
|
207
|
+
|
|
208
|
+
直接输出你的分析内容,不需要额外解释。内容应该清晰、有条理、有依据。
|
|
209
|
+
|
|
210
|
+
## 记录要求
|
|
211
|
+
|
|
212
|
+
完成分析后,使用 analyst-record 工具记录:
|
|
213
|
+
- topic: 讨论话题(主持人在任务中已告知)
|
|
214
|
+
- role: 你的角色名称(主持人在任务中已告知)
|
|
215
|
+
- round: 当前轮次(主持人在任务中已告知)
|
|
216
|
+
- question: 主持人的问题(主持人在任务中已告知)
|
|
217
|
+
- content: 你的完整分析内容
|
|
218
|
+
|
|
219
|
+
分析会自动保存到你的个人日志文件中。
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
// .opencode/plugin/prompts/commands/discussion.mdx
|
|
223
|
+
var discussion_default = `---
|
|
224
|
+
description: 启动一场协作式讨论
|
|
225
|
+
agent: discussion-host
|
|
226
|
+
---
|
|
227
|
+
请作为讨论主持人启动一场协作式讨论。
|
|
228
|
+
|
|
229
|
+
讨论话题是: {{input}}
|
|
230
|
+
|
|
231
|
+
请按照以下步骤进行:
|
|
232
|
+
1. 使用 discussion-start 工具初始化讨论
|
|
233
|
+
2. 确定参与的分析者数量和角色(如技术专家、经济专家等)
|
|
234
|
+
3. 协调讨论循环,每轮调用各分析者
|
|
235
|
+
4. 使用 discussion-record 记录每轮分析
|
|
236
|
+
5. 判断是否形成共识或达到最大轮数
|
|
237
|
+
6. 使用 discussion-summary 生成分析报告
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
// .opencode/plugin/tools/debate.ts
|
|
241
|
+
var DEFAULT_MAX_ROUNDS = 10;
|
|
242
|
+
var DEFAULT_DISCUSSION_LOG_DIR = "discussion-logs";
|
|
243
|
+
var RECORD_FILE = "record.log";
|
|
244
|
+
var SUMMARY_FILE = "summarize.log";
|
|
245
|
+
function getConfigDir() {
|
|
246
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
247
|
+
return join(home, ".config", "opencode");
|
|
248
|
+
}
|
|
249
|
+
function createDiscussionStartHandler(ctx) {
|
|
250
|
+
return async function handleDiscussionStart(args) {
|
|
251
|
+
try {
|
|
252
|
+
const { directory } = ctx;
|
|
253
|
+
const { topic, analystRoles, maxRounds = DEFAULT_MAX_ROUNDS } = args;
|
|
254
|
+
const safeTopic = topic.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_").slice(0, 50);
|
|
255
|
+
const topicFolder = safeTopic;
|
|
256
|
+
const session = {
|
|
257
|
+
id: `discussion-${safeTopic}`,
|
|
258
|
+
topic,
|
|
259
|
+
questionerRole: analystRoles || "待定",
|
|
260
|
+
answererRole: "",
|
|
261
|
+
maxRounds,
|
|
262
|
+
currentRound: 0,
|
|
263
|
+
status: "pending",
|
|
264
|
+
logFile: topicFolder,
|
|
265
|
+
createdAt: new Date().toISOString()
|
|
266
|
+
};
|
|
267
|
+
const header = generateDiscussionHeader(topic, analystRoles, maxRounds);
|
|
268
|
+
const logsDir = resolve(directory, DEFAULT_DISCUSSION_LOG_DIR);
|
|
269
|
+
const topicDir = join(logsDir, topicFolder);
|
|
270
|
+
if (!existsSync(logsDir)) {
|
|
271
|
+
await mkdir(logsDir, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
if (existsSync(topicDir)) {
|
|
274
|
+
return `讨论主题文件夹已存在: ${DEFAULT_DISCUSSION_LOG_DIR}/${topicFolder}
|
|
275
|
+
|
|
276
|
+
请使用新的讨论话题,或删除现有文件夹后重试。`;
|
|
277
|
+
}
|
|
278
|
+
await mkdir(topicDir, { recursive: true });
|
|
279
|
+
await writeFile(join(topicDir, RECORD_FILE), header, "utf-8");
|
|
280
|
+
await writeFile(join(topicDir, SUMMARY_FILE), "", "utf-8");
|
|
281
|
+
return `讨论已启动!
|
|
282
|
+
|
|
283
|
+
话题: ${topic}
|
|
284
|
+
最大轮数: ${maxRounds}
|
|
285
|
+
${analystRoles ? `分析者角色: ${analystRoles}` : ""}
|
|
286
|
+
|
|
287
|
+
记录文件: ${DEFAULT_DISCUSSION_LOG_DIR}/${topicFolder}/${RECORD_FILE}
|
|
288
|
+
|
|
289
|
+
请确定参与的分析者数量和角色(如技术专家、经济专家等),然后开始讨论循环。`;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
292
|
+
return `启动讨论失败: ${errMsg}`;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function createDiscussionRecordHandler(ctx) {
|
|
297
|
+
return async function handleDiscussionRecord(args) {
|
|
298
|
+
try {
|
|
299
|
+
const { directory } = ctx;
|
|
300
|
+
const { topic, round, analystName, content } = args;
|
|
301
|
+
const safeTopic = topic.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_").slice(0, 50);
|
|
302
|
+
const entry = `### 第 ${round} 轮 - ${analystName}
|
|
303
|
+
|
|
304
|
+
${content}
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
`;
|
|
308
|
+
const filePath = resolve(directory, DEFAULT_DISCUSSION_LOG_DIR, safeTopic, RECORD_FILE);
|
|
309
|
+
const existingContent = await readFile(filePath, "utf-8").catch(() => "");
|
|
310
|
+
await writeFile(filePath, existingContent + entry, "utf-8");
|
|
311
|
+
return `${analystName} 第 ${round} 轮分析已记录到 record.log`;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
314
|
+
return `记录失败: ${errMsg}`;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function createAnalystRecordHandler(ctx) {
|
|
319
|
+
return async function handleAnalystRecord(args) {
|
|
320
|
+
try {
|
|
321
|
+
const { directory } = ctx;
|
|
322
|
+
const { topic, role, round, question, content } = args;
|
|
323
|
+
const safeTopic = topic.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_").slice(0, 50);
|
|
324
|
+
const safeRole = role.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
|
|
325
|
+
const analystLogFile = `analyst-${safeRole}_${round}.log`;
|
|
326
|
+
const entry = `### 第 ${round} 轮 - ${role}
|
|
327
|
+
|
|
328
|
+
**主持人的问题**: ${question || ""}
|
|
329
|
+
|
|
330
|
+
**分析内容**:
|
|
331
|
+
${content}
|
|
332
|
+
---
|
|
333
|
+
`;
|
|
334
|
+
const filePath = resolve(directory, DEFAULT_DISCUSSION_LOG_DIR, safeTopic, analystLogFile);
|
|
335
|
+
const existingContent = await readFile(filePath, "utf-8").catch(() => "");
|
|
336
|
+
await writeFile(filePath, existingContent + entry, "utf-8");
|
|
337
|
+
return `${role} 第 ${round} 轮分析已记录到 ${analystLogFile}`;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
340
|
+
return `记录失败: ${errMsg}`;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function createDiscussionSummaryHandler(ctx) {
|
|
345
|
+
return async function handleDiscussionSummary(args) {
|
|
346
|
+
try {
|
|
347
|
+
const { directory } = ctx;
|
|
348
|
+
const { topic, summary, consensus, disagreements, conclusion } = args;
|
|
349
|
+
const safeTopic = topic.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_").slice(0, 50);
|
|
350
|
+
const report = generateSummarySection(summary, consensus, disagreements, conclusion);
|
|
351
|
+
const filePath = resolve(directory, DEFAULT_DISCUSSION_LOG_DIR, safeTopic, SUMMARY_FILE);
|
|
352
|
+
const existingContent = await readFile(filePath, "utf-8").catch(() => "");
|
|
353
|
+
await writeFile(filePath, existingContent + report, "utf-8");
|
|
354
|
+
return `分析报告已生成并追加到 ${safeTopic}/${SUMMARY_FILE}`;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
357
|
+
return `生成报告失败: ${errMsg}`;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function createDiscussionSetupHandler() {
|
|
362
|
+
return async function handleDiscussionSetup() {
|
|
363
|
+
try {
|
|
364
|
+
const configDir = getConfigDir();
|
|
365
|
+
const agentsDir = join(configDir, "agents");
|
|
366
|
+
const commandsDir = join(configDir, "commands");
|
|
367
|
+
const results = [];
|
|
368
|
+
if (!existsSync(agentsDir)) {
|
|
369
|
+
await mkdir(agentsDir, { recursive: true });
|
|
370
|
+
results.push(`Created: ${agentsDir}`);
|
|
371
|
+
}
|
|
372
|
+
if (!existsSync(commandsDir)) {
|
|
373
|
+
await mkdir(commandsDir, { recursive: true });
|
|
374
|
+
results.push(`Created: ${commandsDir}`);
|
|
375
|
+
}
|
|
376
|
+
await writeFile(join(agentsDir, "discussion-host.md"), discussion_host_default, "utf-8");
|
|
377
|
+
results.push("Created: discussion-host.md");
|
|
378
|
+
await writeFile(join(agentsDir, "analyst.md"), analyst_default, "utf-8");
|
|
379
|
+
results.push("Created: analyst.md");
|
|
380
|
+
await writeFile(join(commandsDir, "discussion.md"), discussion_default, "utf-8");
|
|
381
|
+
results.push("Created: discussion.md");
|
|
382
|
+
return `✅ 讨论插件配置完成!重启 opencode 即可使用 /discussion 命令启动讨论!`;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
385
|
+
return `配置失败: ${errMsg}`;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// .opencode/plugin/index.ts
|
|
391
|
+
var discussionAgent = async (ctx) => {
|
|
392
|
+
const handleDiscussionStart = createDiscussionStartHandler(ctx);
|
|
393
|
+
const handleDiscussionRecord = createDiscussionRecordHandler(ctx);
|
|
394
|
+
const handleDiscussionSummary = createDiscussionSummaryHandler(ctx);
|
|
395
|
+
const handleDiscussionSetup = createDiscussionSetupHandler();
|
|
396
|
+
const handleAnalystRecord = createAnalystRecordHandler(ctx);
|
|
397
|
+
return {
|
|
398
|
+
tool: {
|
|
399
|
+
"discussion-setup": tool({
|
|
400
|
+
description: "自动配置讨论插件 - 创建agent和command配置文件",
|
|
401
|
+
args: {},
|
|
402
|
+
async execute() {
|
|
403
|
+
return await handleDiscussionSetup();
|
|
404
|
+
}
|
|
405
|
+
}),
|
|
406
|
+
"discussion-start": tool({
|
|
407
|
+
description: "启动一场协作式讨论",
|
|
408
|
+
args: {
|
|
409
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
410
|
+
analystRoles: tool.schema.string().optional().describe("分析者角色描述,多个角色用分号分隔"),
|
|
411
|
+
maxRounds: tool.schema.number().default(10).describe("最大讨论轮数")
|
|
412
|
+
},
|
|
413
|
+
async execute(args, context) {
|
|
414
|
+
return await handleDiscussionStart(args);
|
|
415
|
+
}
|
|
416
|
+
}),
|
|
417
|
+
"discussion-record": tool({
|
|
418
|
+
description: "汇总记录 - 主持人使用,记录每轮各分析者的观点摘要到record.log",
|
|
419
|
+
args: {
|
|
420
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
421
|
+
round: tool.schema.number().describe("当前轮次"),
|
|
422
|
+
analystName: tool.schema.string().describe("分析者名称"),
|
|
423
|
+
content: tool.schema.string().describe("分析内容摘要")
|
|
424
|
+
},
|
|
425
|
+
async execute(args, context) {
|
|
426
|
+
return await handleDiscussionRecord(args);
|
|
427
|
+
}
|
|
428
|
+
}),
|
|
429
|
+
"analyst-record": tool({
|
|
430
|
+
description: "分析者记录 - 分析者使用,将自己的分析记录到个人日志",
|
|
431
|
+
args: {
|
|
432
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
433
|
+
role: tool.schema.string().describe("分析者角色名,如 tech, economic, risk"),
|
|
434
|
+
round: tool.schema.number().describe("当前轮次"),
|
|
435
|
+
question: tool.schema.string().optional().describe("主持人的问题"),
|
|
436
|
+
content: tool.schema.string().describe("分析内容")
|
|
437
|
+
},
|
|
438
|
+
async execute(args, context) {
|
|
439
|
+
return await handleAnalystRecord(args);
|
|
440
|
+
}
|
|
441
|
+
}),
|
|
442
|
+
"discussion-summary": tool({
|
|
443
|
+
description: "生成讨论分析报告",
|
|
444
|
+
args: {
|
|
445
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
446
|
+
summary: tool.schema.string().describe("讨论摘要"),
|
|
447
|
+
consensus: tool.schema.string().describe("共识点"),
|
|
448
|
+
disagreements: tool.schema.string().describe("分歧点"),
|
|
449
|
+
conclusion: tool.schema.string().describe("综合建议")
|
|
450
|
+
},
|
|
451
|
+
async execute(args, context) {
|
|
452
|
+
return await handleDiscussionSummary(args);
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
var plugin_default = discussionAgent;
|
|
459
|
+
export {
|
|
460
|
+
discussionAgent,
|
|
461
|
+
plugin_default as default
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
//# debugId=4876201D84658B3264756E2164756E21
|
|
465
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["index.ts", "tools/debate.ts", "utils/logger.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { type Plugin, tool } from \"@opencode-ai/plugin\"\nimport {\n createDiscussionStartHandler,\n createDiscussionRecordHandler,\n createDiscussionSummaryHandler,\n createDiscussionSetupHandler,\n createAnalystRecordHandler,\n} from \"./tools/debate\"\n\nexport const discussionAgent: Plugin = async (ctx) => {\n const handleDiscussionStart = createDiscussionStartHandler(ctx)\n const handleDiscussionRecord = createDiscussionRecordHandler(ctx)\n const handleDiscussionSummary = createDiscussionSummaryHandler(ctx)\n const handleDiscussionSetup = createDiscussionSetupHandler()\n const handleAnalystRecord = createAnalystRecordHandler(ctx)\n\n return {\n tool: {\n \"discussion-setup\": tool({\n description: \"自动配置讨论插件 - 创建agent和command配置文件\",\n args: {},\n async execute() {\n return await handleDiscussionSetup()\n },\n }),\n \"discussion-start\": tool({\n description: \"启动一场协作式讨论\",\n args: {\n topic: tool.schema.string().describe(\"讨论话题\"),\n analystRoles: tool.schema\n .string()\n .optional()\n .describe(\"分析者角色描述,多个角色用分号分隔\"),\n maxRounds: tool.schema\n .number()\n .default(10)\n .describe(\"最大讨论轮数\"),\n },\n async execute(args, context) {\n return await handleDiscussionStart(args)\n },\n }),\n \"discussion-record\": tool({\n description: \"汇总记录 - 主持人使用,记录每轮各分析者的观点摘要到record.log\",\n args: {\n topic: tool.schema.string().describe(\"讨论话题\"),\n round: tool.schema.number().describe(\"当前轮次\"),\n analystName: tool.schema.string().describe(\"分析者名称\"),\n content: tool.schema.string().describe(\"分析内容摘要\"),\n },\n async execute(args, context) {\n return await handleDiscussionRecord(args)\n },\n }),\n \"analyst-record\": tool({\n description: \"分析者记录 - 分析者使用,将自己的分析记录到个人日志\",\n args: {\n topic: tool.schema.string().describe(\"讨论话题\"),\n role: tool.schema.string().describe(\"分析者角色名,如 tech, economic, risk\"),\n round: tool.schema.number().describe(\"当前轮次\"),\n question: tool.schema.string().optional().describe(\"主持人的问题\"),\n content: tool.schema.string().describe(\"分析内容\"),\n },\n async execute(args, context) {\n return await handleAnalystRecord(args)\n },\n }),\n \"discussion-summary\": tool({\n description: \"生成讨论分析报告\",\n args: {\n topic: tool.schema.string().describe(\"讨论话题\"),\n summary: tool.schema.string().describe(\"讨论摘要\"),\n consensus: tool.schema.string().describe(\"共识点\"),\n disagreements: tool.schema.string().describe(\"分歧点\"),\n conclusion: tool.schema.string().describe(\"综合建议\"),\n },\n async execute(args, context) {\n return await handleDiscussionSummary(args)\n },\n }),\n },\n }\n}\n\nexport default discussionAgent\n",
|
|
6
|
+
"import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { resolve, dirname, join } from \"node:path\";\nimport type { PluginInput } from \"@opencode-ai/plugin\";\nimport type { DebateSession } from \"../types\";\nimport {\n generateDiscussionHeader,\n generateSummarySection,\n} from \"../utils/logger\";\nimport AGENT_DISCUSSION_HOST from \"../prompts/agents/discussion-host.mdx\";\nimport AGENT_ANALYST from \"../prompts/agents/analyst.mdx\";\nimport COMMAND_DISCUSSION from \"../prompts/commands/discussion.mdx\";\n\nconst DEFAULT_MAX_ROUNDS = 10;\nconst DEFAULT_DISCUSSION_LOG_DIR = \"discussion-logs\";\nconst RECORD_FILE = \"record.log\";\nconst SUMMARY_FILE = \"summarize.log\";\n\nfunction getConfigDir(): string {\n const home = process.env.HOME || process.env.USERPROFILE || \"\";\n return join(home, \".config\", \"opencode\");\n}\n\nexport function createDiscussionStartHandler(ctx: PluginInput) {\n return async function handleDiscussionStart(args: {\n topic: string;\n analystRoles?: string;\n maxRounds?: number;\n }): Promise<string> {\n try {\n const { directory } = ctx;\n const { topic, analystRoles, maxRounds = DEFAULT_MAX_ROUNDS } = args;\n\n const safeTopic = topic\n .replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5]/g, \"_\")\n .slice(0, 50);\n const topicFolder = safeTopic;\n\n const session: DebateSession = {\n id: `discussion-${safeTopic}`,\n topic,\n questionerRole: analystRoles || \"待定\",\n answererRole: \"\",\n maxRounds,\n currentRound: 0,\n status: \"pending\",\n logFile: topicFolder,\n createdAt: new Date().toISOString(),\n };\n\n const header = generateDiscussionHeader(topic, analystRoles, maxRounds);\n const logsDir = resolve(directory, DEFAULT_DISCUSSION_LOG_DIR);\n const topicDir = join(logsDir, topicFolder);\n\n if (!existsSync(logsDir)) {\n await mkdir(logsDir, { recursive: true });\n }\n\n if (existsSync(topicDir)) {\n return `讨论主题文件夹已存在: ${DEFAULT_DISCUSSION_LOG_DIR}/${topicFolder}\\n\\n请使用新的讨论话题,或删除现有文件夹后重试。`;\n }\n\n await mkdir(topicDir, { recursive: true });\n\n await writeFile(join(topicDir, RECORD_FILE), header, \"utf-8\");\n await writeFile(join(topicDir, SUMMARY_FILE), \"\", \"utf-8\");\n\n return `讨论已启动!\\n\\n话题: ${topic}\\n最大轮数: ${maxRounds}\\n${\n analystRoles ? `分析者角色: ${analystRoles}` : \"\"\n }\\n\\n记录文件: ${DEFAULT_DISCUSSION_LOG_DIR}/${topicFolder}/${RECORD_FILE}\\n\\n请确定参与的分析者数量和角色(如技术专家、经济专家等),然后开始讨论循环。`;\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n return `启动讨论失败: ${errMsg}`;\n }\n };\n}\n\nexport function createDiscussionRecordHandler(ctx: PluginInput) {\n return async function handleDiscussionRecord(args: {\n topic: string;\n round: number;\n analystName: string;\n content: string;\n }): Promise<string> {\n try {\n const { directory } = ctx;\n const { topic, round, analystName, content } = args;\n\n const safeTopic = topic\n .replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5]/g, \"_\")\n .slice(0, 50);\n\n const entry = `### 第 ${round} 轮 - ${analystName}\\n\\n${content}\\n\\n---\\n`;\n const filePath = resolve(\n directory,\n DEFAULT_DISCUSSION_LOG_DIR,\n safeTopic,\n RECORD_FILE,\n );\n const existingContent = await readFile(filePath, \"utf-8\").catch(() => \"\");\n await writeFile(filePath, existingContent + entry, \"utf-8\");\n\n return `${analystName} 第 ${round} 轮分析已记录到 record.log`;\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n return `记录失败: ${errMsg}`;\n }\n };\n}\n\nexport function createAnalystRecordHandler(ctx: PluginInput) {\n return async function handleAnalystRecord(args: {\n topic: string;\n role: string;\n round: number;\n question?: string;\n content: string;\n }): Promise<string> {\n try {\n const { directory } = ctx;\n const { topic, role, round, question, content } = args;\n\n const safeTopic = topic\n .replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5]/g, \"_\")\n .slice(0, 50);\n const safeRole = role.replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5]/g, \"_\");\n const analystLogFile = `analyst-${safeRole}_${round}.log`;\n\n const entry = `### 第 ${round} 轮 - ${role}\n\n **主持人的问题**: ${question || \"\"}\n\n **分析内容**:\n ${content}\n ---\n `;\n\n const filePath = resolve(\n directory,\n DEFAULT_DISCUSSION_LOG_DIR,\n safeTopic,\n analystLogFile,\n );\n const existingContent = await readFile(filePath, \"utf-8\").catch(() => \"\");\n await writeFile(filePath, existingContent + entry, \"utf-8\");\n\n return `${role} 第 ${round} 轮分析已记录到 ${analystLogFile}`;\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n return `记录失败: ${errMsg}`;\n }\n };\n}\n\nexport function createDiscussionSummaryHandler(ctx: PluginInput) {\n return async function handleDiscussionSummary(args: {\n topic: string;\n summary: string;\n consensus: string;\n disagreements: string;\n conclusion: string;\n }): Promise<string> {\n try {\n const { directory } = ctx;\n const { topic, summary, consensus, disagreements, conclusion } = args;\n\n const safeTopic = topic\n .replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5]/g, \"_\")\n .slice(0, 50);\n const report = generateSummarySection(\n summary,\n consensus,\n disagreements,\n conclusion,\n );\n const filePath = resolve(\n directory,\n DEFAULT_DISCUSSION_LOG_DIR,\n safeTopic,\n SUMMARY_FILE,\n );\n\n const existingContent = await readFile(filePath, \"utf-8\").catch(() => \"\");\n await writeFile(filePath, existingContent + report, \"utf-8\");\n\n return `分析报告已生成并追加到 ${safeTopic}/${SUMMARY_FILE}`;\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n return `生成报告失败: ${errMsg}`;\n }\n };\n}\n\nexport function createDiscussionSetupHandler() {\n return async function handleDiscussionSetup(): Promise<string> {\n try {\n const configDir = getConfigDir();\n const agentsDir = join(configDir, \"agents\");\n const commandsDir = join(configDir, \"commands\");\n\n const results: string[] = [];\n\n if (!existsSync(agentsDir)) {\n await mkdir(agentsDir, { recursive: true });\n results.push(`Created: ${agentsDir}`);\n }\n\n if (!existsSync(commandsDir)) {\n await mkdir(commandsDir, { recursive: true });\n results.push(`Created: ${commandsDir}`);\n }\n\n await writeFile(\n join(agentsDir, \"discussion-host.md\"),\n AGENT_DISCUSSION_HOST,\n \"utf-8\",\n );\n results.push(\"Created: discussion-host.md\");\n\n await writeFile(join(agentsDir, \"analyst.md\"), AGENT_ANALYST, \"utf-8\");\n results.push(\"Created: analyst.md\");\n\n await writeFile(\n join(commandsDir, \"discussion.md\"),\n COMMAND_DISCUSSION,\n \"utf-8\",\n );\n results.push(\"Created: discussion.md\");\n\n return `✅ 讨论插件配置完成!重启 opencode 即可使用 /discussion 命令启动讨论!`;\n } catch (error) {\n const errMsg = error instanceof Error ? error.message : String(error);\n return `配置失败: ${errMsg}`;\n }\n };\n}\n",
|
|
7
|
+
"export function generateDiscussionHeader(\n topic: string,\n analystRoles?: string,\n maxRounds?: number,\n): string {\n const now = new Date().toLocaleString(\"zh-CN\", { timeZone: \"Asia/Shanghai\" });\n return `# 讨论记录\n\n ## 基本信息\n\n - **话题**: ${topic}\n - **时间**: ${now}\n - **最大轮数**: ${maxRounds || 10}\n - **状态**: 进行中\n\n ---\n\n ## 参与方\n\n ${analystRoles ? `分析者角色: ${analystRoles}` : \"待定\"}\n\n ---\n\n ## 讨论记录\n\n ---\n `\n ;\n}\n\nexport function generateSummarySection(\n summary: string,\n consensus: string,\n disagreements: string,\n conclusion: string,\n): string {\n return `## 分析报告\n\n ### 讨论摘要\n\n ${summary}\n\n ---\n\n ### 共识点\n\n ${consensus || \"无\"}\n\n ---\n\n ### 分歧点\n\n ${disagreements || \"无\"}\n\n ---\n\n ### 综合建议\n\n ${conclusion}\n\n ---\n\n *讨论结束*\n `\n ;\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "4876201D84658B3264756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type Plugin, tool } from "@opencode-ai/plugin"
|
|
2
|
+
import {
|
|
3
|
+
createDiscussionStartHandler,
|
|
4
|
+
createDiscussionRecordHandler,
|
|
5
|
+
createDiscussionSummaryHandler,
|
|
6
|
+
createDiscussionSetupHandler,
|
|
7
|
+
createAnalystRecordHandler,
|
|
8
|
+
} from "./tools/debate"
|
|
9
|
+
|
|
10
|
+
export const discussionAgent: Plugin = async (ctx) => {
|
|
11
|
+
const handleDiscussionStart = createDiscussionStartHandler(ctx)
|
|
12
|
+
const handleDiscussionRecord = createDiscussionRecordHandler(ctx)
|
|
13
|
+
const handleDiscussionSummary = createDiscussionSummaryHandler(ctx)
|
|
14
|
+
const handleDiscussionSetup = createDiscussionSetupHandler()
|
|
15
|
+
const handleAnalystRecord = createAnalystRecordHandler(ctx)
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
tool: {
|
|
19
|
+
"discussion-setup": tool({
|
|
20
|
+
description: "自动配置讨论插件 - 创建agent和command配置文件",
|
|
21
|
+
args: {},
|
|
22
|
+
async execute() {
|
|
23
|
+
return await handleDiscussionSetup()
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
"discussion-start": tool({
|
|
27
|
+
description: "启动一场协作式讨论",
|
|
28
|
+
args: {
|
|
29
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
30
|
+
analystRoles: tool.schema
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("分析者角色描述,多个角色用分号分隔"),
|
|
34
|
+
maxRounds: tool.schema
|
|
35
|
+
.number()
|
|
36
|
+
.default(10)
|
|
37
|
+
.describe("最大讨论轮数"),
|
|
38
|
+
},
|
|
39
|
+
async execute(args, context) {
|
|
40
|
+
return await handleDiscussionStart(args)
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
"discussion-record": tool({
|
|
44
|
+
description: "汇总记录 - 主持人使用,记录每轮各分析者的观点摘要到record.log",
|
|
45
|
+
args: {
|
|
46
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
47
|
+
round: tool.schema.number().describe("当前轮次"),
|
|
48
|
+
analystName: tool.schema.string().describe("分析者名称"),
|
|
49
|
+
content: tool.schema.string().describe("分析内容摘要"),
|
|
50
|
+
},
|
|
51
|
+
async execute(args, context) {
|
|
52
|
+
return await handleDiscussionRecord(args)
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
"analyst-record": tool({
|
|
56
|
+
description: "分析者记录 - 分析者使用,将自己的分析记录到个人日志",
|
|
57
|
+
args: {
|
|
58
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
59
|
+
role: tool.schema.string().describe("分析者角色名,如 tech, economic, risk"),
|
|
60
|
+
round: tool.schema.number().describe("当前轮次"),
|
|
61
|
+
question: tool.schema.string().optional().describe("主持人的问题"),
|
|
62
|
+
content: tool.schema.string().describe("分析内容"),
|
|
63
|
+
},
|
|
64
|
+
async execute(args, context) {
|
|
65
|
+
return await handleAnalystRecord(args)
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
"discussion-summary": tool({
|
|
69
|
+
description: "生成讨论分析报告",
|
|
70
|
+
args: {
|
|
71
|
+
topic: tool.schema.string().describe("讨论话题"),
|
|
72
|
+
summary: tool.schema.string().describe("讨论摘要"),
|
|
73
|
+
consensus: tool.schema.string().describe("共识点"),
|
|
74
|
+
disagreements: tool.schema.string().describe("分歧点"),
|
|
75
|
+
conclusion: tool.schema.string().describe("综合建议"),
|
|
76
|
+
},
|
|
77
|
+
async execute(args, context) {
|
|
78
|
+
return await handleDiscussionSummary(args)
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default discussionAgent
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 分析者 - 从特定角度分析问题,与其他分析者协作讨论
|
|
3
|
+
mode: subagent
|
|
4
|
+
temperature: 0.7
|
|
5
|
+
task_budget: 20
|
|
6
|
+
tools:
|
|
7
|
+
websearch: true
|
|
8
|
+
codesearch: true
|
|
9
|
+
webfetch: true
|
|
10
|
+
read: true
|
|
11
|
+
glob: true
|
|
12
|
+
grep: true
|
|
13
|
+
analyst-record: true
|
|
14
|
+
write: false
|
|
15
|
+
edit: false
|
|
16
|
+
bash: false
|
|
17
|
+
---
|
|
18
|
+
# 分析者
|
|
19
|
+
|
|
20
|
+
你是一个专业的分析者,负责从指定角度深入分析问题,并与主持人和其他分析者协作讨论。
|
|
21
|
+
|
|
22
|
+
## 核心职责
|
|
23
|
+
|
|
24
|
+
- 从你代表的专业角度分析问题
|
|
25
|
+
- 查阅资料,提供有据可查的分析
|
|
26
|
+
- 回应其他分析者的观点,进行补充或提出建设性意见
|
|
27
|
+
- 与其他分析者协作,共同完善对问题的理解和方案
|
|
28
|
+
|
|
29
|
+
## 行为准则
|
|
30
|
+
|
|
31
|
+
- **协作心态**: 你是讨论的参与者,不是对手。目标是共同完善方案,而非战胜他人
|
|
32
|
+
- **专业深度**: 从你的专业角度提供深入分析
|
|
33
|
+
- **知识共享**: 分享相关资料、研究和案例
|
|
34
|
+
- **建设性反馈**: 对其他人的观点,可以提出补充建议,而不是简单否定
|
|
35
|
+
- **承认局限**: 对不确定或有争议的部分,坦诚说明
|
|
36
|
+
- **逻辑严谨**: 论点需要有依据支撑
|
|
37
|
+
|
|
38
|
+
## 分析方法
|
|
39
|
+
|
|
40
|
+
1. **问题拆解**: 将复杂问题分解为多个维度
|
|
41
|
+
2. **资料收集**: 使用搜索工具查阅相关资料和数据
|
|
42
|
+
3. **利弊分析**: 分析方案的优缺点
|
|
43
|
+
4. **风险评估**: 识别潜在风险和应对措施
|
|
44
|
+
5. **可行性论证**: 评估方案的现实可行性
|
|
45
|
+
6. **综合建议**: 基于分析给出建设性建议
|
|
46
|
+
|
|
47
|
+
## 输出要求
|
|
48
|
+
|
|
49
|
+
直接输出你的分析内容,不需要额外解释。内容应该清晰、有条理、有依据。
|
|
50
|
+
|
|
51
|
+
## 记录要求
|
|
52
|
+
|
|
53
|
+
完成分析后,使用 analyst-record 工具记录:
|
|
54
|
+
- topic: 讨论话题(主持人在任务中已告知)
|
|
55
|
+
- role: 你的角色名称(主持人在任务中已告知)
|
|
56
|
+
- round: 当前轮次(主持人在任务中已告知)
|
|
57
|
+
- question: 主持人的问题(主持人在任务中已告知)
|
|
58
|
+
- content: 你的完整分析内容
|
|
59
|
+
|
|
60
|
+
分析会自动保存到你的个人日志文件中。
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 讨论主持人 - 发起话题、协调讨论、总结分析
|
|
3
|
+
mode: primary
|
|
4
|
+
permission:
|
|
5
|
+
task:
|
|
6
|
+
analyst: allow
|
|
7
|
+
tools:
|
|
8
|
+
discussion-start: true
|
|
9
|
+
discussion-record: true
|
|
10
|
+
discussion-summary: true
|
|
11
|
+
---
|
|
12
|
+
# 讨论主持人
|
|
13
|
+
|
|
14
|
+
你是一场协作式讨论的主持人,负责协调多个分析者从不同角度深入探讨话题,共同形成更完善的理解和方案。
|
|
15
|
+
|
|
16
|
+
**重要:你是在代替用户与各分析者协作。用户是观察者,只会观察讨论过程和结果。不要询问用户的意见或打扰用户。**
|
|
17
|
+
|
|
18
|
+
## 核心原则
|
|
19
|
+
|
|
20
|
+
1. **协作而非对立**: 各位分析者是协作关系,共同目标是深化理解和形成更好的方案
|
|
21
|
+
2. **角度互补**: 每个分析者从不同角度分析问题,补充彼此的视角
|
|
22
|
+
3. **建设性讨论**: 关注方案的合理性和可行性,而非争论胜负
|
|
23
|
+
4. **知识共享**: 鼓励分享资料、研究成果,相互学习
|
|
24
|
+
5. **自主决策**: 所有决策由你做出,不需要询问用户意见
|
|
25
|
+
|
|
26
|
+
## 工作流程
|
|
27
|
+
|
|
28
|
+
1. **初始化**: 使用 `discussion-start` 工具启动讨论
|
|
29
|
+
2. **讨论循环**:
|
|
30
|
+
- 根据话题自主确定参与的分析者数量和角色(如2-3个不同领域的专家)
|
|
31
|
+
- 依次或并行调用各分析者,请他们从各自角度分析问题
|
|
32
|
+
- 使用 `discussion-record` 记录每位分析者的观点
|
|
33
|
+
- 鼓励分析者阅读其他人的观点,进行回应和补充
|
|
34
|
+
- **提示**: 每次调用分析者时,可以告诉他们讨论记录位置:`{讨论目录}/record.log`
|
|
35
|
+
3. **终止判断**:
|
|
36
|
+
- 检查是否达到最大轮数
|
|
37
|
+
- 检查是否已形成较为完善的共识或方案
|
|
38
|
+
4. **总结**: 使用 `discussion-summary` 生成分析报告后,向用户汇报结果
|
|
39
|
+
|
|
40
|
+
## 分析者角色设定
|
|
41
|
+
|
|
42
|
+
**重要:角色设定原则**:
|
|
43
|
+
1. 每个分析者代表一个**专业角度**或**关注领域**
|
|
44
|
+
2. 角色应该互补,从不同维度分析问题
|
|
45
|
+
3. 避免设定"支持/反对"的对立角色
|
|
46
|
+
4. 每个角色都应该对问题的解决有建设性贡献
|
|
47
|
+
|
|
48
|
+
**角色设定示例**:
|
|
49
|
+
- 分析师A: 技术专家 - 关注技术可行性和实现难度
|
|
50
|
+
- 分析师B: 经济专家 - 关注成本收益和资源分配
|
|
51
|
+
- 分析师C: 风险分析师 - 关注潜在风险和应对措施
|
|
52
|
+
|
|
53
|
+
**根据话题灵活设定**:
|
|
54
|
+
- 分析者数量: 2-6人,根据场景决定数量
|
|
55
|
+
- 角色类型: 技术/经济/法律/伦理/实践/用户视角等
|
|
56
|
+
- 具体角色应根据话题特点来确定
|
|
57
|
+
|
|
58
|
+
## Task 工具调用规范
|
|
59
|
+
|
|
60
|
+
**重要**: 调用 task 工具时,prompt 参数必须遵循以下规则:
|
|
61
|
+
|
|
62
|
+
1. prompt 内容中**禁止使用中文引号** `""` 或 `''`
|
|
63
|
+
2. 话题内容用英文双引号或不用引号
|
|
64
|
+
3. prompt 应该是纯文本,不要包含特殊格式字符
|
|
65
|
+
4. **可以在 prompt 中告诉分析者他们可以使用搜索工具**
|
|
66
|
+
5. **可以提醒分析者使用 read 工具查阅之前的讨论内容**
|
|
67
|
+
|
|
68
|
+
**正确示例**:
|
|
69
|
+
|
|
70
|
+
task(
|
|
71
|
+
description="技术专家分析",
|
|
72
|
+
prompt="讨论话题是"Java学习规划",你的角色是 tech(技术专家),当前是第 1 轮。主持人的问题是:请从技术可行性角度分析,重点关注:1) 技术难度 2) 实现路径 3) 潜在技术风险。分析完成后,使用 analyst-record 工具记录:topic=Java学习规划, role=tech, round=1, question=请从技术可行性角度分析,重点关注技术难度、实现路径和潜在技术风险, content=你的分析内容。",
|
|
73
|
+
agent="analyst"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
## 行为准则
|
|
77
|
+
|
|
78
|
+
- 保持中立,促进协作而非对立
|
|
79
|
+
- 引导讨论聚焦核心问题和方案的完善
|
|
80
|
+
- 及时记录讨论内容
|
|
81
|
+
- 控制讨论在合理轮数内
|
|
82
|
+
- **严格遵守 Task 工具调用规范,避免 JSON 解析错误**
|
|
83
|
+
|
|
84
|
+
## 输出格式
|
|
85
|
+
|
|
86
|
+
所有讨论会自动保存到文件。讨论结束后,输出以下分析报告:
|
|
87
|
+
- **讨论摘要**: 整体讨论的主题和进展
|
|
88
|
+
- **各方观点**: 各分析者从不同角度的分析
|
|
89
|
+
- **共识点**: 各方达成一致的内容
|
|
90
|
+
- **分歧点**: 各方观点不一致的地方
|
|
91
|
+
- **综合建议**: 基于讨论的综合性建议
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 启动一场协作式讨论
|
|
3
|
+
agent: discussion-host
|
|
4
|
+
---
|
|
5
|
+
请作为讨论主持人启动一场协作式讨论。
|
|
6
|
+
|
|
7
|
+
讨论话题是: {{input}}
|
|
8
|
+
|
|
9
|
+
请按照以下步骤进行:
|
|
10
|
+
1. 使用 discussion-start 工具初始化讨论
|
|
11
|
+
2. 确定参与的分析者数量和角色(如技术专家、经济专家等)
|
|
12
|
+
3. 协调讨论循环,每轮调用各分析者
|
|
13
|
+
4. 使用 discussion-record 记录每轮分析
|
|
14
|
+
5. 判断是否形成共识或达到最大轮数
|
|
15
|
+
6. 使用 discussion-summary 生成分析报告
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { resolve, dirname, join } from "node:path";
|
|
4
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
5
|
+
import type { DebateSession } from "../types";
|
|
6
|
+
import {
|
|
7
|
+
generateDiscussionHeader,
|
|
8
|
+
generateSummarySection,
|
|
9
|
+
} from "../utils/logger";
|
|
10
|
+
import AGENT_DISCUSSION_HOST from "../prompts/agents/discussion-host.mdx";
|
|
11
|
+
import AGENT_ANALYST from "../prompts/agents/analyst.mdx";
|
|
12
|
+
import COMMAND_DISCUSSION from "../prompts/commands/discussion.mdx";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_MAX_ROUNDS = 10;
|
|
15
|
+
const DEFAULT_DISCUSSION_LOG_DIR = "discussion-logs";
|
|
16
|
+
const RECORD_FILE = "record.log";
|
|
17
|
+
const SUMMARY_FILE = "summarize.log";
|
|
18
|
+
|
|
19
|
+
function getConfigDir(): string {
|
|
20
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
21
|
+
return join(home, ".config", "opencode");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createDiscussionStartHandler(ctx: PluginInput) {
|
|
25
|
+
return async function handleDiscussionStart(args: {
|
|
26
|
+
topic: string;
|
|
27
|
+
analystRoles?: string;
|
|
28
|
+
maxRounds?: number;
|
|
29
|
+
}): Promise<string> {
|
|
30
|
+
try {
|
|
31
|
+
const { directory } = ctx;
|
|
32
|
+
const { topic, analystRoles, maxRounds = DEFAULT_MAX_ROUNDS } = args;
|
|
33
|
+
|
|
34
|
+
const safeTopic = topic
|
|
35
|
+
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")
|
|
36
|
+
.slice(0, 50);
|
|
37
|
+
const topicFolder = safeTopic;
|
|
38
|
+
|
|
39
|
+
const session: DebateSession = {
|
|
40
|
+
id: `discussion-${safeTopic}`,
|
|
41
|
+
topic,
|
|
42
|
+
questionerRole: analystRoles || "待定",
|
|
43
|
+
answererRole: "",
|
|
44
|
+
maxRounds,
|
|
45
|
+
currentRound: 0,
|
|
46
|
+
status: "pending",
|
|
47
|
+
logFile: topicFolder,
|
|
48
|
+
createdAt: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const header = generateDiscussionHeader(topic, analystRoles, maxRounds);
|
|
52
|
+
const logsDir = resolve(directory, DEFAULT_DISCUSSION_LOG_DIR);
|
|
53
|
+
const topicDir = join(logsDir, topicFolder);
|
|
54
|
+
|
|
55
|
+
if (!existsSync(logsDir)) {
|
|
56
|
+
await mkdir(logsDir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (existsSync(topicDir)) {
|
|
60
|
+
return `讨论主题文件夹已存在: ${DEFAULT_DISCUSSION_LOG_DIR}/${topicFolder}\n\n请使用新的讨论话题,或删除现有文件夹后重试。`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await mkdir(topicDir, { recursive: true });
|
|
64
|
+
|
|
65
|
+
await writeFile(join(topicDir, RECORD_FILE), header, "utf-8");
|
|
66
|
+
await writeFile(join(topicDir, SUMMARY_FILE), "", "utf-8");
|
|
67
|
+
|
|
68
|
+
return `讨论已启动!\n\n话题: ${topic}\n最大轮数: ${maxRounds}\n${
|
|
69
|
+
analystRoles ? `分析者角色: ${analystRoles}` : ""
|
|
70
|
+
}\n\n记录文件: ${DEFAULT_DISCUSSION_LOG_DIR}/${topicFolder}/${RECORD_FILE}\n\n请确定参与的分析者数量和角色(如技术专家、经济专家等),然后开始讨论循环。`;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
73
|
+
return `启动讨论失败: ${errMsg}`;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createDiscussionRecordHandler(ctx: PluginInput) {
|
|
79
|
+
return async function handleDiscussionRecord(args: {
|
|
80
|
+
topic: string;
|
|
81
|
+
round: number;
|
|
82
|
+
analystName: string;
|
|
83
|
+
content: string;
|
|
84
|
+
}): Promise<string> {
|
|
85
|
+
try {
|
|
86
|
+
const { directory } = ctx;
|
|
87
|
+
const { topic, round, analystName, content } = args;
|
|
88
|
+
|
|
89
|
+
const safeTopic = topic
|
|
90
|
+
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")
|
|
91
|
+
.slice(0, 50);
|
|
92
|
+
|
|
93
|
+
const entry = `### 第 ${round} 轮 - ${analystName}\n\n${content}\n\n---\n`;
|
|
94
|
+
const filePath = resolve(
|
|
95
|
+
directory,
|
|
96
|
+
DEFAULT_DISCUSSION_LOG_DIR,
|
|
97
|
+
safeTopic,
|
|
98
|
+
RECORD_FILE,
|
|
99
|
+
);
|
|
100
|
+
const existingContent = await readFile(filePath, "utf-8").catch(() => "");
|
|
101
|
+
await writeFile(filePath, existingContent + entry, "utf-8");
|
|
102
|
+
|
|
103
|
+
return `${analystName} 第 ${round} 轮分析已记录到 record.log`;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
106
|
+
return `记录失败: ${errMsg}`;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createAnalystRecordHandler(ctx: PluginInput) {
|
|
112
|
+
return async function handleAnalystRecord(args: {
|
|
113
|
+
topic: string;
|
|
114
|
+
role: string;
|
|
115
|
+
round: number;
|
|
116
|
+
question?: string;
|
|
117
|
+
content: string;
|
|
118
|
+
}): Promise<string> {
|
|
119
|
+
try {
|
|
120
|
+
const { directory } = ctx;
|
|
121
|
+
const { topic, role, round, question, content } = args;
|
|
122
|
+
|
|
123
|
+
const safeTopic = topic
|
|
124
|
+
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")
|
|
125
|
+
.slice(0, 50);
|
|
126
|
+
const safeRole = role.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
|
|
127
|
+
const analystLogFile = `analyst-${safeRole}_${round}.log`;
|
|
128
|
+
|
|
129
|
+
const entry = `### 第 ${round} 轮 - ${role}
|
|
130
|
+
|
|
131
|
+
**主持人的问题**: ${question || ""}
|
|
132
|
+
|
|
133
|
+
**分析内容**:
|
|
134
|
+
${content}
|
|
135
|
+
---
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const filePath = resolve(
|
|
139
|
+
directory,
|
|
140
|
+
DEFAULT_DISCUSSION_LOG_DIR,
|
|
141
|
+
safeTopic,
|
|
142
|
+
analystLogFile,
|
|
143
|
+
);
|
|
144
|
+
const existingContent = await readFile(filePath, "utf-8").catch(() => "");
|
|
145
|
+
await writeFile(filePath, existingContent + entry, "utf-8");
|
|
146
|
+
|
|
147
|
+
return `${role} 第 ${round} 轮分析已记录到 ${analystLogFile}`;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
150
|
+
return `记录失败: ${errMsg}`;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function createDiscussionSummaryHandler(ctx: PluginInput) {
|
|
156
|
+
return async function handleDiscussionSummary(args: {
|
|
157
|
+
topic: string;
|
|
158
|
+
summary: string;
|
|
159
|
+
consensus: string;
|
|
160
|
+
disagreements: string;
|
|
161
|
+
conclusion: string;
|
|
162
|
+
}): Promise<string> {
|
|
163
|
+
try {
|
|
164
|
+
const { directory } = ctx;
|
|
165
|
+
const { topic, summary, consensus, disagreements, conclusion } = args;
|
|
166
|
+
|
|
167
|
+
const safeTopic = topic
|
|
168
|
+
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")
|
|
169
|
+
.slice(0, 50);
|
|
170
|
+
const report = generateSummarySection(
|
|
171
|
+
summary,
|
|
172
|
+
consensus,
|
|
173
|
+
disagreements,
|
|
174
|
+
conclusion,
|
|
175
|
+
);
|
|
176
|
+
const filePath = resolve(
|
|
177
|
+
directory,
|
|
178
|
+
DEFAULT_DISCUSSION_LOG_DIR,
|
|
179
|
+
safeTopic,
|
|
180
|
+
SUMMARY_FILE,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const existingContent = await readFile(filePath, "utf-8").catch(() => "");
|
|
184
|
+
await writeFile(filePath, existingContent + report, "utf-8");
|
|
185
|
+
|
|
186
|
+
return `分析报告已生成并追加到 ${safeTopic}/${SUMMARY_FILE}`;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
189
|
+
return `生成报告失败: ${errMsg}`;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function createDiscussionSetupHandler() {
|
|
195
|
+
return async function handleDiscussionSetup(): Promise<string> {
|
|
196
|
+
try {
|
|
197
|
+
const configDir = getConfigDir();
|
|
198
|
+
const agentsDir = join(configDir, "agents");
|
|
199
|
+
const commandsDir = join(configDir, "commands");
|
|
200
|
+
|
|
201
|
+
const results: string[] = [];
|
|
202
|
+
|
|
203
|
+
if (!existsSync(agentsDir)) {
|
|
204
|
+
await mkdir(agentsDir, { recursive: true });
|
|
205
|
+
results.push(`Created: ${agentsDir}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!existsSync(commandsDir)) {
|
|
209
|
+
await mkdir(commandsDir, { recursive: true });
|
|
210
|
+
results.push(`Created: ${commandsDir}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await writeFile(
|
|
214
|
+
join(agentsDir, "discussion-host.md"),
|
|
215
|
+
AGENT_DISCUSSION_HOST,
|
|
216
|
+
"utf-8",
|
|
217
|
+
);
|
|
218
|
+
results.push("Created: discussion-host.md");
|
|
219
|
+
|
|
220
|
+
await writeFile(join(agentsDir, "analyst.md"), AGENT_ANALYST, "utf-8");
|
|
221
|
+
results.push("Created: analyst.md");
|
|
222
|
+
|
|
223
|
+
await writeFile(
|
|
224
|
+
join(commandsDir, "discussion.md"),
|
|
225
|
+
COMMAND_DISCUSSION,
|
|
226
|
+
"utf-8",
|
|
227
|
+
);
|
|
228
|
+
results.push("Created: discussion.md");
|
|
229
|
+
|
|
230
|
+
return `✅ 讨论插件配置完成!重启 opencode 即可使用 /discussion 命令启动讨论!`;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
233
|
+
return `配置失败: ${errMsg}`;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface DebateConfig {
|
|
2
|
+
readonly topic: string
|
|
3
|
+
readonly questionerRole?: string
|
|
4
|
+
readonly answererRole?: string
|
|
5
|
+
readonly maxRounds: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface DebateSession {
|
|
9
|
+
readonly id: string
|
|
10
|
+
readonly topic: string
|
|
11
|
+
readonly questionerRole: string
|
|
12
|
+
readonly answererRole: string
|
|
13
|
+
readonly maxRounds: number
|
|
14
|
+
readonly currentRound: number
|
|
15
|
+
readonly status: DebateStatus
|
|
16
|
+
readonly logFile: string
|
|
17
|
+
readonly createdAt: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DebateStatus = "pending" | "active" | "completed" | "cancelled"
|
|
21
|
+
|
|
22
|
+
export interface DebateRecord {
|
|
23
|
+
readonly round: number
|
|
24
|
+
readonly question: string
|
|
25
|
+
readonly answer: string
|
|
26
|
+
readonly timestamp: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DebateSummary {
|
|
30
|
+
readonly summary: string
|
|
31
|
+
readonly consensus: string
|
|
32
|
+
readonly disagreements: string
|
|
33
|
+
readonly conclusion: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DebateError {
|
|
37
|
+
readonly code: DebateErrorCode
|
|
38
|
+
readonly message: string
|
|
39
|
+
readonly details?: unknown
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type DebateErrorCode =
|
|
43
|
+
| "INIT_FAILED"
|
|
44
|
+
| "RECORD_FAILED"
|
|
45
|
+
| "SUMMARY_FAILED"
|
|
46
|
+
| "TIMEOUT"
|
|
47
|
+
| "INVALID_CONFIG"
|
|
48
|
+
|
|
49
|
+
export interface DebateContext {
|
|
50
|
+
readonly directory: string
|
|
51
|
+
readonly worktree?: string
|
|
52
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function generateDiscussionHeader(
|
|
2
|
+
topic: string,
|
|
3
|
+
analystRoles?: string,
|
|
4
|
+
maxRounds?: number,
|
|
5
|
+
): string {
|
|
6
|
+
const now = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
7
|
+
return `# 讨论记录
|
|
8
|
+
|
|
9
|
+
## 基本信息
|
|
10
|
+
|
|
11
|
+
- **话题**: ${topic}
|
|
12
|
+
- **时间**: ${now}
|
|
13
|
+
- **最大轮数**: ${maxRounds || 10}
|
|
14
|
+
- **状态**: 进行中
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 参与方
|
|
19
|
+
|
|
20
|
+
${analystRoles ? `分析者角色: ${analystRoles}` : "待定"}
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 讨论记录
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
`
|
|
28
|
+
;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function generateSummarySection(
|
|
32
|
+
summary: string,
|
|
33
|
+
consensus: string,
|
|
34
|
+
disagreements: string,
|
|
35
|
+
conclusion: string,
|
|
36
|
+
): string {
|
|
37
|
+
return `## 分析报告
|
|
38
|
+
|
|
39
|
+
### 讨论摘要
|
|
40
|
+
|
|
41
|
+
${summary}
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### 共识点
|
|
46
|
+
|
|
47
|
+
${consensus || "无"}
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### 分歧点
|
|
52
|
+
|
|
53
|
+
${disagreements || "无"}
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### 综合建议
|
|
58
|
+
|
|
59
|
+
${conclusion}
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
*讨论结束*
|
|
64
|
+
`
|
|
65
|
+
;
|
|
66
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# OpenCode Discussion Agent
|
|
2
|
+
|
|
3
|
+
An OpenCode plugin that enables collaborative discussion between multiple AI agents to explore topics and develop solutions together.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This plugin provides a discussion framework with:
|
|
8
|
+
- **Discussion Host** (primary agent): Orchestrates the discussion and coordinates analysts
|
|
9
|
+
- **Analysts** (subagents): Multiple specialists analyzing from different angles
|
|
10
|
+
|
|
11
|
+
The agents collaborate rather than debate - each brings their expertise to collectively deepen understanding and develop better solutions. All discussions are recorded for reference.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Custom tools for discussion management:
|
|
16
|
+
- `discussion-setup`: Auto-configure agents and commands (run once after install)
|
|
17
|
+
- `discussion-start`: Initialize a discussion session
|
|
18
|
+
- `discussion-record`: Record each analyst's analysis
|
|
19
|
+
- `discussion-summary`: Generate analysis report
|
|
20
|
+
- Flexible analyst roles - customize based on topic needs (technical, economic, risk, legal, etc.)
|
|
21
|
+
- Detailed logging with both summary and individual analyst records
|
|
22
|
+
- Consensus detection and configurable round limits
|
|
23
|
+
- Web search capabilities for real-time information
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Install Plugin
|
|
28
|
+
|
|
29
|
+
Add to your `opencode.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"plugin": ["opencode-debate-agent"]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Restart OpenCode
|
|
38
|
+
|
|
39
|
+
Restart your OpenCode session.
|
|
40
|
+
|
|
41
|
+
### 3. Run Setup
|
|
42
|
+
|
|
43
|
+
Input this during AI init:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
use discussion-setup to init discussion agents
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This will automatically create:
|
|
50
|
+
- `~/.config/opencode/agents/discussion-host.md`
|
|
51
|
+
- `~/.config/opencode/agents/analyst.md`
|
|
52
|
+
- `~/.config/opencode/commands/discussion.md`
|
|
53
|
+
|
|
54
|
+
### 4. Restart and Use
|
|
55
|
+
|
|
56
|
+
Restart OpenCode, then start a discussion:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
/discussion 如何优化团队协作流程
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Discussion Workflow
|
|
63
|
+
|
|
64
|
+
1. **Host** starts the discussion with a topic
|
|
65
|
+
2. **Host** assigns roles to analysts (e.g., Technical Expert, Economic Analyst, Risk Analyst)
|
|
66
|
+
3. Each **Analyst**:
|
|
67
|
+
- Reviews previous discussions
|
|
68
|
+
- Provides analysis from their perspective
|
|
69
|
+
- Responds to other analysts' viewpoints
|
|
70
|
+
- Records their analysis
|
|
71
|
+
4. **Host** summarizes consensus and disagreements
|
|
72
|
+
5. Final report generated
|
|
73
|
+
|
|
74
|
+
## Logging Structure
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
discussion-logs/{topic}/
|
|
78
|
+
├── record.log # Summary of all discussions
|
|
79
|
+
├── summarize.log # Final analysis report
|
|
80
|
+
└── analyst-{name}.log # Individual analyst's detailed records
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Customizing Analysts
|
|
84
|
+
|
|
85
|
+
The host can assign any roles based on the topic. Examples:
|
|
86
|
+
|
|
87
|
+
| Topic Type | Suggested Analysts |
|
|
88
|
+
|------------|------------------|
|
|
89
|
+
| Technical decisions | Technical Lead, User Experience, Security Expert |
|
|
90
|
+
| Business planning | Market Analyst, Financial Analyst, Operations Expert |
|
|
91
|
+
| Policy making | Legal Advisor, Ethics Expert, Stakeholder Representative |
|
|
92
|
+
| Project planning | Project Manager, Risk Analyst, Resource Planner |
|
|
93
|
+
|
|
94
|
+
Each analyst receives context including:
|
|
95
|
+
- Their assigned role and focus areas
|
|
96
|
+
- The discussion topic
|
|
97
|
+
- Access to previous discussion records
|
|
98
|
+
- Web search capabilities for research
|
|
99
|
+
|
|
100
|
+
## Customizing Models
|
|
101
|
+
|
|
102
|
+
The default configuration does not specify models. You can customize by editing the agent files:
|
|
103
|
+
|
|
104
|
+
### Edit Agent Markdown Files
|
|
105
|
+
|
|
106
|
+
Edit `~/.config/opencode/agents/discussion-host.md`:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
---
|
|
110
|
+
description: 讨论主持人
|
|
111
|
+
mode: primary
|
|
112
|
+
model: qwen3-max # Add your preferred model
|
|
113
|
+
---
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Edit `~/.config/opencode/agents/analyst.md`:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
---
|
|
120
|
+
description: 分析者
|
|
121
|
+
mode: subagent
|
|
122
|
+
model: claude-opus-4-5 # Add your preferred model
|
|
123
|
+
task_budget: 20
|
|
124
|
+
---
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Recommended Models
|
|
128
|
+
|
|
129
|
+
| Agent | Recommended Models |
|
|
130
|
+
|-------|------------------|
|
|
131
|
+
| discussion-host | qwen3-max, claude-sonnet-4-5 |
|
|
132
|
+
| analyst | qwen3-coder-plus, claude-opus-4-5 |
|
|
133
|
+
|
|
134
|
+
## Project Structure
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
opencode-debate-agent/
|
|
138
|
+
├── .opencode/
|
|
139
|
+
│ └── plugin/
|
|
140
|
+
│ ├── index.ts # Plugin entry point
|
|
141
|
+
│ ├── tools/
|
|
142
|
+
│ │ └── debate.ts # Discussion tool implementations
|
|
143
|
+
│ ├── types/
|
|
144
|
+
│ │ └── index.ts # Type definitions
|
|
145
|
+
│ └── utils/
|
|
146
|
+
│ └── logger.ts # Markdown formatting utilities
|
|
147
|
+
├── package.json
|
|
148
|
+
├── tsconfig.json
|
|
149
|
+
└── README.md
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Install dependencies
|
|
156
|
+
bun install
|
|
157
|
+
|
|
158
|
+
# Build
|
|
159
|
+
bun run build
|
|
160
|
+
|
|
161
|
+
# Type checking
|
|
162
|
+
bun run typecheck
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-discussion-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugin for structured debate discussions between AI agents",
|
|
5
|
+
"main": ".opencode/plugin/index.js",
|
|
6
|
+
"types": ".opencode/plugin/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
".opencode"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "bun build.ts",
|
|
12
|
+
"dev": "bun build.ts --watch",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"lint": "echo 'No linter configured'",
|
|
15
|
+
"test": "bun test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@opencode-ai/plugin": "^1.0.0",
|
|
19
|
+
"zod": "^3.23.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"bun-types": "^1.0.0",
|
|
23
|
+
"typescript": "^5.3.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@opencode-ai/plugin": "^1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"opencode",
|
|
30
|
+
"opencode-plugin",
|
|
31
|
+
"debate",
|
|
32
|
+
"agent",
|
|
33
|
+
"discussion"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/cywan1998/open-debate-agent"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/cywan1998/open-debate-agent/issues"
|
|
42
|
+
}
|
|
43
|
+
}
|