pi-brainstorm 0.2.0 → 0.3.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/README.md +42 -9
- package/README.zh-CN.md +48 -9
- package/config/default.yaml +58 -0
- package/extensions/brainstorm.ts +685 -222
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -10,13 +10,14 @@ The plugin stores each participant's full contribution in a local meeting blackb
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- Multi-model brainstorming with GPT, DeepSeek,
|
|
13
|
+
- Multi-model brainstorming with configurable participants (default: GPT, DeepSeek, MiniMax).
|
|
14
14
|
- Debate / battle mode where agents prosecute, stress-test, and challenge positions.
|
|
15
|
+
- Configuration-driven: add, remove, or customize participants via YAML.
|
|
15
16
|
- Round-by-round summaries focused on consensus, disagreement, and next questions.
|
|
16
17
|
- Full participant contributions stored as Markdown files under `.pi-meetings/`.
|
|
17
18
|
- Compact visible cards in the main conversation instead of long pasted transcripts.
|
|
18
19
|
- JSONL index for lightweight cross-round context.
|
|
19
|
-
-
|
|
20
|
+
- Managed agent files generated from config — update one YAML, all agents sync.
|
|
20
21
|
|
|
21
22
|
## Install
|
|
22
23
|
|
|
@@ -29,7 +30,7 @@ pi install npm:pi-brainstorm
|
|
|
29
30
|
From GitHub:
|
|
30
31
|
|
|
31
32
|
```bash
|
|
32
|
-
pi install git:github.com/Jarcis-cy/pi-brainstorm@v0.
|
|
33
|
+
pi install git:github.com/Jarcis-cy/pi-brainstorm@v0.3.0
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
For local development:
|
|
@@ -38,15 +39,47 @@ For local development:
|
|
|
38
39
|
pi install /Users/jarcis/Project/pi-brainstorm
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
##
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Participants are defined in YAML. The extension loads config in this order (later overrides earlier):
|
|
45
|
+
|
|
46
|
+
1. Package default: `config/default.yaml` (shipped with the package)
|
|
47
|
+
2. User override: `~/.pi/agent/pi-brainstorm.yaml`
|
|
48
|
+
3. Project override: `.pi-brainstorm.yaml` or `.pi/pi-brainstorm.yaml`
|
|
49
|
+
|
|
50
|
+
Arrays (e.g. `participants`) replace entirely; objects deep-merge.
|
|
51
|
+
|
|
52
|
+
### Adding a new participant
|
|
53
|
+
|
|
54
|
+
Create `~/.pi/agent/pi-brainstorm.yaml` (user-level) or `.pi-brainstorm.yaml` (project-level):
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
participants:
|
|
58
|
+
- displayName: Claude
|
|
59
|
+
agentName: claude-brainstormer
|
|
60
|
+
description: Claude brainstorming consultant. Nuanced analyst for multi-model discussion sessions.
|
|
61
|
+
model: anthropic/claude-sonnet-4-20250514:xhigh
|
|
62
|
+
roleTitle: Nuanced Analyst
|
|
63
|
+
rolePrompt: |
|
|
64
|
+
你是多模型头脑风暴中的分析顾问。擅长细致入微的论证和长篇分析。用中文回答。
|
|
65
|
+
whatYouDo:
|
|
66
|
+
- 提供细致、深入的逐点分析
|
|
67
|
+
- 识别细微差别和边缘情况
|
|
68
|
+
- 撰写结构清晰的长篇论证
|
|
69
|
+
debatePersona:
|
|
70
|
+
label: THE ANALYST
|
|
71
|
+
prompt: |
|
|
72
|
+
DEBATE MODE. You are THE ANALYST. Dissect every argument with precision. Find the weakest link in every chain of reasoning. Use Chinese.
|
|
73
|
+
brainstormRole: Nuanced analyst
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
To replace the default participants entirely, define a full `participants` list in your override file.
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
User-level config can create or update managed agent files in `~/.pi/agent/agents/` after confirmation. Project-level config is used for the current session, but it does not auto-write global agent files; create those agents manually or move the config to `~/.pi/agent/pi-brainstorm.yaml` when you want automatic sync.
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
- `deepseek-brainstormer`
|
|
47
|
-
- `minimax-brainstormer`
|
|
80
|
+
### Managed agent files
|
|
48
81
|
|
|
49
|
-
|
|
82
|
+
Agent files generated by pi-brainstorm contain `<!-- managed-by: pi-brainstorm -->`. These files are overwritten when the config changes. Existing agent files without this marker are never touched.
|
|
50
83
|
|
|
51
84
|
## Commands
|
|
52
85
|
|
package/README.zh-CN.md
CHANGED
|
@@ -10,13 +10,14 @@ English README: [README.md](./README.md).
|
|
|
10
10
|
|
|
11
11
|
## 功能
|
|
12
12
|
|
|
13
|
-
-
|
|
13
|
+
- 多模型头脑风暴:参与者可配置(默认:GPT、DeepSeek、MiniMax)。
|
|
14
14
|
- 辩论 / battle 模式:Agent 会攻击、审视、反驳彼此的观点。
|
|
15
|
+
- 配置驱动:通过 YAML 添加、删除或自定义参与者。
|
|
15
16
|
- 每轮输出聚焦共识、分歧、关键问题和下一步方向。
|
|
16
17
|
- 参与者完整发言以 Markdown 文件保存到 `.pi-meetings/`。
|
|
17
18
|
- 主会话中展示紧凑发言卡片,而不是粘贴长篇 transcript。
|
|
18
19
|
- 使用 JSONL 索引作为轻量级跨轮上下文入口。
|
|
19
|
-
-
|
|
20
|
+
- 由配置自动生成托管的 agent 文件——更新一个 YAML,所有 agent 同步。
|
|
20
21
|
|
|
21
22
|
## 安装
|
|
22
23
|
|
|
@@ -29,7 +30,7 @@ pi install npm:pi-brainstorm
|
|
|
29
30
|
通过 GitHub 安装:
|
|
30
31
|
|
|
31
32
|
```bash
|
|
32
|
-
pi install git:github.com/Jarcis-cy/pi-brainstorm@v0.
|
|
33
|
+
pi install git:github.com/Jarcis-cy/pi-brainstorm@v0.3.0
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
本地开发安装:
|
|
@@ -38,15 +39,53 @@ pi install git:github.com/Jarcis-cy/pi-brainstorm@v0.2.0
|
|
|
38
39
|
pi install /Users/jarcis/Project/pi-brainstorm
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
##
|
|
42
|
+
## 配置
|
|
43
|
+
|
|
44
|
+
参与者通过 YAML 定义。插件按以下顺序加载配置(后者覆盖前者):
|
|
45
|
+
|
|
46
|
+
1. 包默认配置:`config/default.yaml`(随包发布)
|
|
47
|
+
2. 用户级覆盖:`~/.pi/agent/pi-brainstorm.yaml`
|
|
48
|
+
3. 项目级覆盖:`.pi-brainstorm.yaml` 或 `.pi/pi-brainstorm.yaml`
|
|
49
|
+
|
|
50
|
+
数组(如 `participants`)整体替换;对象字段深度合并。
|
|
51
|
+
|
|
52
|
+
### 添加新参与者
|
|
53
|
+
|
|
54
|
+
创建 `~/.pi/agent/pi-brainstorm.yaml`(用户级)或 `.pi-brainstorm.yaml`(项目级):
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
participants:
|
|
58
|
+
- displayName: Claude
|
|
59
|
+
agentName: claude-brainstormer
|
|
60
|
+
description: Claude 头脑风暴顾问。细致入微的分析师,用于多模型讨论。
|
|
61
|
+
model: anthropic/claude-sonnet-4-20250514:xhigh
|
|
62
|
+
roleTitle: 细致分析师
|
|
63
|
+
rolePrompt: |
|
|
64
|
+
你是多模型头脑风暴中的分析顾问。擅长细致入微的论证和长篇分析。用中文回答。
|
|
65
|
+
whatYouDo:
|
|
66
|
+
- 提供细致、深入的逐点分析
|
|
67
|
+
- 识别细微差别和边缘情况
|
|
68
|
+
- 撰写结构清晰的长篇论证
|
|
69
|
+
debatePersona:
|
|
70
|
+
label: THE ANALYST
|
|
71
|
+
prompt: |
|
|
72
|
+
DEBATE MODE. You are THE ANALYST. Dissect every argument with precision. Find the weakest link in every chain of reasoning. Use Chinese.
|
|
73
|
+
brainstormRole: 细致分析师
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
如需完全替换默认参与者,在覆盖文件中定义完整的 `participants` 列表即可。
|
|
77
|
+
|
|
78
|
+
用户级配置可以在确认后创建或更新 `~/.pi/agent/agents/` 下的受管理 agent 文件。项目级配置只影响当前会话编排,但不会自动写入全局 agent 文件;如果需要自动同步 agent,请手动创建对应 agent,或把配置移到 `~/.pi/agent/pi-brainstorm.yaml`。
|
|
42
79
|
|
|
43
|
-
|
|
80
|
+
### 托管的 Agent 文件
|
|
81
|
+
|
|
82
|
+
由 pi-brainstorm 生成的 agent 文件包含 `<!-- managed-by: pi-brainstorm -->` 标记。当配置变更时,这些文件会被覆盖。不含此标记的已有 agent 文件永远不会被修改。
|
|
83
|
+
|
|
84
|
+
## 前置条件
|
|
44
85
|
|
|
45
|
-
|
|
46
|
-
- `deepseek-brainstormer`
|
|
47
|
-
- `minimax-brainstormer`
|
|
86
|
+
该扩展依赖 pi 中已有的 `subagent` 工具。命令处理器会先创建本地会议记录,然后让主 Agent 调用配置中定义的参与者(默认:`gpt-brainstormer`、`deepseek-brainstormer`、`minimax-brainstormer`)。
|
|
48
87
|
|
|
49
|
-
第一次使用时,如果这些用户级 agent
|
|
88
|
+
第一次使用时,如果这些用户级 agent 不存在,扩展会询问是否写入内置默认定义。已有同名且不含托管标记的文件不会被覆盖。
|
|
50
89
|
|
|
51
90
|
## 命令
|
|
52
91
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# pi-brainstorm default configuration
|
|
2
|
+
# Each participant defines a subagent that joins brainstorming/debate sessions.
|
|
3
|
+
#
|
|
4
|
+
# To override: create ~/.pi/agent/pi-brainstorm.yaml (user-level)
|
|
5
|
+
# or .pi-brainstorm.yaml / .pi/pi-brainstorm.yaml (project-level)
|
|
6
|
+
# Later files override earlier. Arrays replace entirely; objects deep-merge.
|
|
7
|
+
|
|
8
|
+
participants:
|
|
9
|
+
- displayName: GPT
|
|
10
|
+
agentName: gpt-brainstormer
|
|
11
|
+
description: GPT brainstorming consultant. Visionary strategist for multi-model discussion sessions.
|
|
12
|
+
model: vendor-codex/gpt-5.5:xhigh
|
|
13
|
+
roleTitle: Visionary Strategist
|
|
14
|
+
rolePrompt: |
|
|
15
|
+
你是多模型头脑风暴中的愿景战略家。思考大局,发现别人忽略的机会,将复杂权衡综合为清晰方向。用中文回答。
|
|
16
|
+
whatYouDo:
|
|
17
|
+
- 提出创新的战略方向和解决方案
|
|
18
|
+
- 发现别人忽略的机会和盲点
|
|
19
|
+
- 把零散想法综合成连贯战略
|
|
20
|
+
debatePersona:
|
|
21
|
+
label: THE PROSECUTOR
|
|
22
|
+
prompt: |
|
|
23
|
+
DEBATE MODE. You are THE PROSECUTOR. Attack the other positions ruthlessly. Find every logical flaw, every hidden assumption, every missing edge case. Be relentless. Do not concede easily. Use Chinese.
|
|
24
|
+
brainstormRole: Visionary strategist
|
|
25
|
+
|
|
26
|
+
- displayName: DeepSeek
|
|
27
|
+
agentName: deepseek-brainstormer
|
|
28
|
+
description: DeepSeek brainstorming consultant. Meticulous systems thinker for multi-model discussion sessions.
|
|
29
|
+
model: deepseek/deepseek-v4-pro:xhigh
|
|
30
|
+
roleTitle: Meticulous Systems Thinker
|
|
31
|
+
rolePrompt: |
|
|
32
|
+
你是多模型头脑风暴中的系统思考者。分析结构、依赖、扩展上限和失败模式。用中文回答。
|
|
33
|
+
whatYouDo:
|
|
34
|
+
- 从结构、依赖和风险角度分析提案
|
|
35
|
+
- 识别隐藏耦合、扩展上限和失败模式
|
|
36
|
+
- 提出具体、可实现、可验证的技术优化方案
|
|
37
|
+
debatePersona:
|
|
38
|
+
label: THE SYSTEMS SKEPTIC
|
|
39
|
+
prompt: |
|
|
40
|
+
DEBATE MODE. You are THE SYSTEMS SKEPTIC. Dissect the structural implications of every proposal. What breaks at scale? Where are the hidden costs? What dependencies create risk? Be precise and unforgiving. Use Chinese.
|
|
41
|
+
brainstormRole: Systems thinker
|
|
42
|
+
|
|
43
|
+
- displayName: MiniMax
|
|
44
|
+
agentName: minimax-brainstormer
|
|
45
|
+
description: MiniMax brainstorming consultant. Creative lateral thinker for multi-model discussion sessions.
|
|
46
|
+
model: minimax-cn/MiniMax-M3:xhigh
|
|
47
|
+
roleTitle: Creative Lateral Thinker
|
|
48
|
+
rolePrompt: |
|
|
49
|
+
你是多模型头脑风暴中的创意顾问。跳出框框思考,挑战隐性假设,提出非常规方案。用中文回答。
|
|
50
|
+
whatYouDo:
|
|
51
|
+
- 从意想不到的角度切入问题
|
|
52
|
+
- 提出打破常规的创新方案
|
|
53
|
+
- 挑战团队隐性假设
|
|
54
|
+
debatePersona:
|
|
55
|
+
label: THE CONTRARIAN
|
|
56
|
+
prompt: |
|
|
57
|
+
DEBATE MODE. You are THE CONTRARIAN. Take the opposite position from the dominant view. Expose groupthink. Propose radical alternatives. Challenge the fundamental premise if needed. Use Chinese.
|
|
58
|
+
brainstormRole: Creative lateral thinker
|
package/extensions/brainstorm.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi-brainstorm — Multi-model brainstorm/debate extension for Pi
|
|
3
3
|
*
|
|
4
|
-
* Runs brainstorm and
|
|
5
|
-
* Full participant contributions are stored in a local filesystem
|
|
6
|
-
* while the main conversation sees compact cards and facilitator
|
|
4
|
+
* Runs brainstorm and debate sessions across multiple subagents configured
|
|
5
|
+
* via YAML. Full participant contributions are stored in a local filesystem
|
|
6
|
+
* blackboard, while the main conversation sees compact cards and facilitator
|
|
7
|
+
* synthesis.
|
|
7
8
|
*
|
|
8
9
|
* Features:
|
|
10
|
+
* - Configuration-driven participants (YAML)
|
|
9
11
|
* - meeting_append_entry tool — concurrency-safe append to meeting folder
|
|
10
12
|
* - meeting_read_index tool — read meeting index
|
|
11
13
|
* - meeting_read_entry tool — read full entry content
|
|
@@ -13,15 +15,629 @@
|
|
|
13
15
|
* - /debate command — open-ended multi-agent debate
|
|
14
16
|
* - meeting-entry message renderer — compact cards with expandable content
|
|
15
17
|
* - File watcher — auto-posts new entries into the main conversation
|
|
18
|
+
* - Managed agent file generation from config
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
import * as path from "node:path";
|
|
19
22
|
import * as fs from "node:fs";
|
|
20
23
|
import * as fsp from "node:fs/promises";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
21
25
|
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
26
|
import { Type } from "typebox";
|
|
23
27
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
24
28
|
import { Text, Box } from "@earendil-works/pi-tui";
|
|
29
|
+
import * as YAML from "yaml";
|
|
30
|
+
|
|
31
|
+
// ────────────────────────────────────────────────────────
|
|
32
|
+
// Types
|
|
33
|
+
// ────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
interface DebatePersona {
|
|
36
|
+
label: string;
|
|
37
|
+
prompt: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ParticipantConfig {
|
|
41
|
+
displayName: string;
|
|
42
|
+
agentName: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
model: string;
|
|
45
|
+
roleTitle?: string;
|
|
46
|
+
rolePrompt: string;
|
|
47
|
+
whatYouDo?: string[];
|
|
48
|
+
debatePersona?: DebatePersona;
|
|
49
|
+
brainstormRole?: string;
|
|
50
|
+
tools?: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface BrainstormConfig {
|
|
54
|
+
participants: ParticipantConfig[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ────────────────────────────────────────────────────────
|
|
58
|
+
// Constants
|
|
59
|
+
// ────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
const MANAGED_MARKER = "<!-- managed-by: pi-brainstorm -->";
|
|
62
|
+
const DEFAULT_TOOLS = [
|
|
63
|
+
"read",
|
|
64
|
+
"grep",
|
|
65
|
+
"find",
|
|
66
|
+
"ls",
|
|
67
|
+
"meeting_append_entry",
|
|
68
|
+
"meeting_read_index",
|
|
69
|
+
"meeting_read_entry",
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// ────────────────────────────────────────────────────────
|
|
73
|
+
// Config helpers
|
|
74
|
+
// ────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deep-merge two values. Arrays are replaced entirely; objects are
|
|
78
|
+
* shallow-merged recursively; scalars use the overlay value.
|
|
79
|
+
*/
|
|
80
|
+
function deepMerge(base: any, overlay: any): any {
|
|
81
|
+
if (overlay === null || overlay === undefined) return base;
|
|
82
|
+
if (base === null || base === undefined) return overlay;
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(base) && Array.isArray(overlay)) {
|
|
85
|
+
return overlay;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
typeof base === "object" &&
|
|
90
|
+
typeof overlay === "object" &&
|
|
91
|
+
!Array.isArray(base) &&
|
|
92
|
+
!Array.isArray(overlay)
|
|
93
|
+
) {
|
|
94
|
+
const result: Record<string, any> = { ...base };
|
|
95
|
+
for (const key of Object.keys(overlay)) {
|
|
96
|
+
result[key] =
|
|
97
|
+
key in result
|
|
98
|
+
? deepMerge(result[key], overlay[key])
|
|
99
|
+
: overlay[key];
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return overlay;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolve extension directory from import.meta.url.
|
|
109
|
+
*/
|
|
110
|
+
function getExtensionDir(): string {
|
|
111
|
+
return path.dirname(fileURLToPath(import.meta.url));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Load and merge config from all locations, in priority order (later wins):
|
|
116
|
+
* 1. Package default: config/default.yaml (relative to package root or extension dir)
|
|
117
|
+
* Also try brainstorm.yaml in extension dir (for manual installs)
|
|
118
|
+
* 2. User override: ~/.pi/agent/pi-brainstorm.yaml
|
|
119
|
+
* 3. Project override: {cwd}/.pi-brainstorm.yaml
|
|
120
|
+
* 4. Project override: {cwd}/.pi/pi-brainstorm.yaml
|
|
121
|
+
*/
|
|
122
|
+
function hasProjectConfig(cwd: string): boolean {
|
|
123
|
+
if (!cwd) return false;
|
|
124
|
+
return [
|
|
125
|
+
path.join(cwd, ".pi-brainstorm.yaml"),
|
|
126
|
+
path.join(cwd, ".pi", "pi-brainstorm.yaml"),
|
|
127
|
+
].some((candidate) => fs.existsSync(candidate));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function loadConfig(cwd: string): BrainstormConfig {
|
|
131
|
+
const extensionDir = getExtensionDir();
|
|
132
|
+
const packageRoot = path.dirname(extensionDir);
|
|
133
|
+
|
|
134
|
+
// Step 1: package/extension defaults
|
|
135
|
+
const defaultCandidates = [
|
|
136
|
+
path.join(packageRoot, "config", "default.yaml"),
|
|
137
|
+
path.join(extensionDir, "config", "default.yaml"),
|
|
138
|
+
path.join(extensionDir, "brainstorm.yaml"),
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
let merged: any = {};
|
|
142
|
+
let loadedAny = false;
|
|
143
|
+
|
|
144
|
+
for (const candidate of defaultCandidates) {
|
|
145
|
+
if (fs.existsSync(candidate)) {
|
|
146
|
+
try {
|
|
147
|
+
const raw = fs.readFileSync(candidate, "utf-8");
|
|
148
|
+
const parsed = YAML.parse(raw);
|
|
149
|
+
if (parsed && typeof parsed === "object") {
|
|
150
|
+
merged = deepMerge(merged, parsed);
|
|
151
|
+
loadedAny = true;
|
|
152
|
+
}
|
|
153
|
+
} catch (err: any) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Failed to parse config ${candidate}: ${err.message}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!loadedAny) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"No pi-brainstorm config found. Expected config/default.yaml in package root or extension directory."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Step 2: user override
|
|
168
|
+
const userPath = path.join(getAgentDir(), "pi-brainstorm.yaml");
|
|
169
|
+
if (fs.existsSync(userPath)) {
|
|
170
|
+
try {
|
|
171
|
+
const raw = fs.readFileSync(userPath, "utf-8");
|
|
172
|
+
const parsed = YAML.parse(raw);
|
|
173
|
+
if (parsed && typeof parsed === "object") {
|
|
174
|
+
merged = deepMerge(merged, parsed);
|
|
175
|
+
}
|
|
176
|
+
} catch (err: any) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Failed to parse user config ${userPath}: ${err.message}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Step 3: project overrides
|
|
184
|
+
const projectCandidates = cwd
|
|
185
|
+
? [path.join(cwd, ".pi-brainstorm.yaml"), path.join(cwd, ".pi", "pi-brainstorm.yaml")]
|
|
186
|
+
: [];
|
|
187
|
+
|
|
188
|
+
for (const candidate of projectCandidates) {
|
|
189
|
+
if (fs.existsSync(candidate)) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = fs.readFileSync(candidate, "utf-8");
|
|
192
|
+
const parsed = YAML.parse(raw);
|
|
193
|
+
if (parsed && typeof parsed === "object") {
|
|
194
|
+
merged = deepMerge(merged, parsed);
|
|
195
|
+
}
|
|
196
|
+
} catch (err: any) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Failed to parse project config ${candidate}: ${err.message}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return merged as BrainstormConfig;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Resolve and validate participants for a command invocation.
|
|
209
|
+
* Returns validated participant array; throws with a clear message on failure.
|
|
210
|
+
*/
|
|
211
|
+
function isSafeAgentName(value: string): boolean {
|
|
212
|
+
return /^[A-Za-z0-9][A-Za-z0-9_.-]{0,80}$/.test(value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function resolveParticipants(cwd: string): ParticipantConfig[] {
|
|
216
|
+
const config = loadConfig(cwd);
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
!config.participants ||
|
|
220
|
+
!Array.isArray(config.participants) ||
|
|
221
|
+
config.participants.length === 0
|
|
222
|
+
) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"pi-brainstorm config must define at least one participant under 'participants'."
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const requiredFields: (keyof ParticipantConfig)[] = [
|
|
229
|
+
"displayName",
|
|
230
|
+
"agentName",
|
|
231
|
+
"model",
|
|
232
|
+
"rolePrompt",
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < config.participants.length; i++) {
|
|
236
|
+
const p = config.participants[i];
|
|
237
|
+
for (const field of requiredFields) {
|
|
238
|
+
if (!p[field]) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Participant at index ${i} is missing required field "${field}".`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (typeof p.displayName !== "string" || !p.displayName.trim()) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Participant at index ${i} has invalid displayName.`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (typeof p.agentName !== "string" || !p.agentName.trim()) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Participant at index ${i} has invalid agentName.`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (!isSafeAgentName(p.agentName)) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Participant "${p.displayName}" has unsafe agentName "${p.agentName}". Use only letters, digits, dot, underscore, and hyphen; it must start with a letter or digit.`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (typeof p.model !== "string" || !p.model.trim()) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Participant at index ${i} has invalid model.`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (typeof p.rolePrompt !== "string" || !p.rolePrompt.trim()) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Participant at index ${i} has invalid rolePrompt.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return config.participants;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ────────────────────────────────────────────────────────
|
|
275
|
+
// Agent file generation
|
|
276
|
+
// ────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Generate the content of a managed agent markdown file from a participant config.
|
|
280
|
+
*/
|
|
281
|
+
function yamlScalar(value: string): string {
|
|
282
|
+
return JSON.stringify(value);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function generateAgentFile(participant: ParticipantConfig): string {
|
|
286
|
+
const tools = participant.tools && participant.tools.length > 0
|
|
287
|
+
? participant.tools
|
|
288
|
+
: DEFAULT_TOOLS;
|
|
289
|
+
const toolsStr = tools.join(", ");
|
|
290
|
+
|
|
291
|
+
const description =
|
|
292
|
+
participant.description ||
|
|
293
|
+
`${participant.displayName} brainstorming consultant.`;
|
|
294
|
+
|
|
295
|
+
const roleTitle = participant.roleTitle
|
|
296
|
+
? ` - ${participant.roleTitle}`
|
|
297
|
+
: "";
|
|
298
|
+
|
|
299
|
+
const whatYouDoLines = (participant.whatYouDo && participant.whatYouDo.length > 0)
|
|
300
|
+
? participant.whatYouDo.map((item) => `- ${item}`).join("\n")
|
|
301
|
+
: `- 参与多模型讨论并提供${participant.displayName}视角的分析`;
|
|
302
|
+
|
|
303
|
+
return [
|
|
304
|
+
MANAGED_MARKER,
|
|
305
|
+
"---",
|
|
306
|
+
`name: ${yamlScalar(participant.agentName)}`,
|
|
307
|
+
`description: ${yamlScalar(description)}`,
|
|
308
|
+
`tools: ${toolsStr}`,
|
|
309
|
+
`model: ${yamlScalar(participant.model)}`,
|
|
310
|
+
"---",
|
|
311
|
+
"",
|
|
312
|
+
`# ${participant.displayName} Brainstormer${roleTitle}`,
|
|
313
|
+
"",
|
|
314
|
+
participant.rolePrompt,
|
|
315
|
+
"",
|
|
316
|
+
"## What You Do",
|
|
317
|
+
whatYouDoLines,
|
|
318
|
+
"",
|
|
319
|
+
"## What You Do Not Do",
|
|
320
|
+
"- 写代码或修改项目文件,你只读项目文件",
|
|
321
|
+
"- 委派给其他 Agent",
|
|
322
|
+
"- 在聊天中直接粘贴长篇分析;当明确指示使用 meeting_append_entry 时,必须将完整贡献写入会议黑板,最终回复仅写 WROTE_ENTRY + 一句话摘要",
|
|
323
|
+
"",
|
|
324
|
+
"## Worker Preamble",
|
|
325
|
+
"You are a terminal worker. Work directly with tools. Do NOT spawn sub-agents.",
|
|
326
|
+
"",
|
|
327
|
+
].join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Ensure agent files exist for all configured participants.
|
|
332
|
+
* - Files with the managed marker are overwritten from current config.
|
|
333
|
+
* - Files without the marker are never touched.
|
|
334
|
+
* - Missing files are created after user confirmation.
|
|
335
|
+
*
|
|
336
|
+
* Returns true if all participants have existing agent files (managed or not).
|
|
337
|
+
*/
|
|
338
|
+
async function safeWriteAgentFile(targetPath: string, content: string, mode: "create" | "update"): Promise<void> {
|
|
339
|
+
const dir = path.dirname(targetPath);
|
|
340
|
+
assertDirectoryNoSymlink(dir, "agents directory");
|
|
341
|
+
assertPathInside(fs.realpathSync(dir), path.resolve(targetPath), "agent file");
|
|
342
|
+
|
|
343
|
+
if (mode === "create") {
|
|
344
|
+
await fsp.writeFile(targetPath, content, { encoding: "utf-8", flag: "wx" });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
assertExistingFileNoSymlink(targetPath, "agent file");
|
|
349
|
+
const tempPath = path.join(
|
|
350
|
+
dir,
|
|
351
|
+
`.${path.basename(targetPath)}.${process.pid}.${Date.now()}.tmp`
|
|
352
|
+
);
|
|
353
|
+
await fsp.writeFile(tempPath, content, { encoding: "utf-8", flag: "wx" });
|
|
354
|
+
try {
|
|
355
|
+
await fsp.rename(tempPath, targetPath);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
try {
|
|
358
|
+
await fsp.unlink(tempPath);
|
|
359
|
+
} catch {
|
|
360
|
+
// Ignore cleanup errors.
|
|
361
|
+
}
|
|
362
|
+
throw err;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function ensureAgentsFromConfig(
|
|
367
|
+
ctx: any,
|
|
368
|
+
participants: ParticipantConfig[],
|
|
369
|
+
options: { allowGlobalWrites: boolean }
|
|
370
|
+
): Promise<boolean> {
|
|
371
|
+
const agentsDir = path.join(getAgentDir(), "agents");
|
|
372
|
+
|
|
373
|
+
const planned: { filename: string; action: "create" | "update" }[] = [];
|
|
374
|
+
const protectedFiles: string[] = [];
|
|
375
|
+
|
|
376
|
+
for (const p of participants) {
|
|
377
|
+
const filename = `${p.agentName}.md`;
|
|
378
|
+
const targetPath = path.resolve(agentsDir, filename);
|
|
379
|
+
assertPathInside(agentsDir, targetPath, "agent file");
|
|
380
|
+
|
|
381
|
+
if (fs.existsSync(targetPath)) {
|
|
382
|
+
assertExistingFileNoSymlink(targetPath, "agent file");
|
|
383
|
+
const content = fs.readFileSync(targetPath, "utf-8");
|
|
384
|
+
if (content.includes(MANAGED_MARKER)) {
|
|
385
|
+
planned.push({ filename, action: "update" });
|
|
386
|
+
} else {
|
|
387
|
+
protectedFiles.push(filename);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
planned.push({ filename, action: "create" });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// If nothing to create or update, we're done
|
|
395
|
+
if (planned.length === 0) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!options.allowGlobalWrites) {
|
|
400
|
+
ctx.ui?.notify?.(
|
|
401
|
+
"Project-level pi-brainstorm config is active. For safety, this command will not create or update global agent files. Create the listed agents manually or move the config to ~/.pi/agent/pi-brainstorm.yaml.",
|
|
402
|
+
"warning"
|
|
403
|
+
);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Non-interactive mode
|
|
408
|
+
if (!ctx.hasUI) {
|
|
409
|
+
// Update managed files silently
|
|
410
|
+
await fsp.mkdir(agentsDir, { recursive: true });
|
|
411
|
+
assertDirectoryNoSymlink(agentsDir, "agents directory");
|
|
412
|
+
for (const plan of planned) {
|
|
413
|
+
if (plan.action === "update") {
|
|
414
|
+
const p = participants.find(
|
|
415
|
+
(p) => `${p.agentName}.md` === plan.filename
|
|
416
|
+
)!;
|
|
417
|
+
const targetPath = path.resolve(agentsDir, plan.filename);
|
|
418
|
+
assertPathInside(agentsDir, targetPath, "agent file");
|
|
419
|
+
await safeWriteAgentFile(
|
|
420
|
+
targetPath,
|
|
421
|
+
generateAgentFile(p),
|
|
422
|
+
"update"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Report missing
|
|
427
|
+
const missing = planned.filter((p) => p.action === "create");
|
|
428
|
+
if (missing.length > 0) {
|
|
429
|
+
ctx.ui?.notify?.(
|
|
430
|
+
`Missing meeting agents: ${missing.map((m) => m.filename).join(", ")}. Install them under ${agentsDir}.`,
|
|
431
|
+
"warning"
|
|
432
|
+
);
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Interactive mode: ask user
|
|
439
|
+
let message = "";
|
|
440
|
+
if (planned.length > 0) {
|
|
441
|
+
const actionWord = planned.some((p) => p.action === "update")
|
|
442
|
+
? "created/updated"
|
|
443
|
+
: "created";
|
|
444
|
+
message += `The following agent files will be ${actionWord}:\n`;
|
|
445
|
+
for (const p of planned) {
|
|
446
|
+
message += ` - ${p.filename} (${p.action})\n`;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (protectedFiles.length > 0) {
|
|
450
|
+
message += `\nThe following existing files are NOT managed by pi-brainstorm and will be left untouched:\n`;
|
|
451
|
+
for (const f of protectedFiles) {
|
|
452
|
+
message += ` - ${f}\n`;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const title = planned.some((p) => p.action === "update")
|
|
457
|
+
? "Update brainstorm agents?"
|
|
458
|
+
: "Install brainstorm agents?";
|
|
459
|
+
|
|
460
|
+
const ok = await ctx.ui.confirm(
|
|
461
|
+
title,
|
|
462
|
+
message + `\nFiles will be written to ${agentsDir}.`
|
|
463
|
+
);
|
|
464
|
+
if (!ok) return false;
|
|
465
|
+
|
|
466
|
+
await fsp.mkdir(agentsDir, { recursive: true });
|
|
467
|
+
assertDirectoryNoSymlink(agentsDir, "agents directory");
|
|
468
|
+
for (const plan of planned) {
|
|
469
|
+
const p = participants.find(
|
|
470
|
+
(p) => `${p.agentName}.md` === plan.filename
|
|
471
|
+
)!;
|
|
472
|
+
const targetPath = path.resolve(agentsDir, plan.filename);
|
|
473
|
+
assertPathInside(agentsDir, targetPath, "agent file");
|
|
474
|
+
await safeWriteAgentFile(targetPath, generateAgentFile(p), plan.action);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
ctx.ui.notify(`Updated ${planned.length} agent file(s).`, "info");
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ────────────────────────────────────────────────────────
|
|
482
|
+
// Prompt builders
|
|
483
|
+
// ────────────────────────────────────────────────────────
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Build the facilitator prompt for /brainstorm.
|
|
487
|
+
*/
|
|
488
|
+
function buildBrainstormPrompt(
|
|
489
|
+
topic: string,
|
|
490
|
+
absDir: string,
|
|
491
|
+
participants: ParticipantConfig[]
|
|
492
|
+
): string {
|
|
493
|
+
const consultantLines = participants
|
|
494
|
+
.map(
|
|
495
|
+
(p) =>
|
|
496
|
+
`- **${p.displayName}**: use the ${p.agentName} subagent. ${p.brainstormRole || p.roleTitle || "Consultant"}.`
|
|
497
|
+
)
|
|
498
|
+
.join("\n");
|
|
499
|
+
|
|
500
|
+
const agentTaskLines = participants
|
|
501
|
+
.map(
|
|
502
|
+
(p) =>
|
|
503
|
+
` - speaker: "${p.displayName}"`
|
|
504
|
+
)
|
|
505
|
+
.join("\n");
|
|
506
|
+
|
|
507
|
+
return [
|
|
508
|
+
`BLACKBOARD BRAINSTORMING SESSION: ${topic}`,
|
|
509
|
+
"",
|
|
510
|
+
`Meeting folder: \`${absDir}\``,
|
|
511
|
+
"",
|
|
512
|
+
"You are facilitating a round-robin brainstorming session using the MEETING BLACKBOARD.",
|
|
513
|
+
"Each consultant writes their FULL contribution to disk via meeting_append_entry.",
|
|
514
|
+
"",
|
|
515
|
+
"## Consultants (3 rounds)",
|
|
516
|
+
consultantLines,
|
|
517
|
+
"",
|
|
518
|
+
"## CRITICAL INSTRUCTIONS",
|
|
519
|
+
"",
|
|
520
|
+
"### For subagents (include in EVERY task):",
|
|
521
|
+
"1. Write your FULL contribution using the meeting_append_entry tool with:",
|
|
522
|
+
` - meetingDir: "${absDir}"`,
|
|
523
|
+
" - speaker: your display name, e.g.:",
|
|
524
|
+
agentTaskLines,
|
|
525
|
+
' - phase: "Round 1", "Round 2", or "Round 3"',
|
|
526
|
+
" - summary: a ONE-SENTENCE summary of your contribution",
|
|
527
|
+
" - content: your FULL analysis in Chinese (中文)",
|
|
528
|
+
"2. After writing, your FINAL ANSWER must be ONLY:",
|
|
529
|
+
" `WROTE_ENTRY: <your one-sentence summary>`",
|
|
530
|
+
"3. DO NOT paste your full analysis into the chat. The main agent and user will read it from the blackboard.",
|
|
531
|
+
"",
|
|
532
|
+
"### For you, the facilitator:",
|
|
533
|
+
"- Do NOT paste participant full text into chat. They are on the blackboard.",
|
|
534
|
+
"- After each round, read the index with meeting_read_index and present a structural overview.",
|
|
535
|
+
"- Optionally read full entries with meeting_read_entry when needed.",
|
|
536
|
+
"- Present each consultant's summary + your structural overview (conflict matrix, consensus table).",
|
|
537
|
+
"- When the user gives feedback, relay it VERBATIM to the consultants in the next round.",
|
|
538
|
+
"",
|
|
539
|
+
"## Protocol",
|
|
540
|
+
"Round 1: Each consultant gives initial analysis on the topic. Run all in parallel.",
|
|
541
|
+
"Round 2: Feed prior discussion back to each. Ask each to challenge the others and propose improvements.",
|
|
542
|
+
"Round 3: Each gives FINAL recommendation, synthesizing the best ideas.",
|
|
543
|
+
"",
|
|
544
|
+
"After Round 3, present the complete structural overview and a synthesized conclusion.",
|
|
545
|
+
"",
|
|
546
|
+
"## IMPORTANT",
|
|
547
|
+
"- All responses in Chinese (中文).",
|
|
548
|
+
"- Save transcript.md and (after user confirms) conclusion.md per the MEETING OUTPUT PROTOCOL.",
|
|
549
|
+
"- The user can intervene at any time to steer the discussion.",
|
|
550
|
+
].join("\n");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Build the facilitator prompt for /debate.
|
|
555
|
+
*/
|
|
556
|
+
function buildDebatePrompt(
|
|
557
|
+
topic: string,
|
|
558
|
+
absDir: string,
|
|
559
|
+
participants: ParticipantConfig[]
|
|
560
|
+
): string {
|
|
561
|
+
const debaterLines = participants
|
|
562
|
+
.map((p) => {
|
|
563
|
+
const dp = p.debatePersona;
|
|
564
|
+
if (dp) {
|
|
565
|
+
return `- **${p.displayName}** (${p.agentName}): ${dp.label} — Attack other positions, find flaws, expose assumptions.`;
|
|
566
|
+
}
|
|
567
|
+
return `- **${p.displayName}** (${p.agentName})`;
|
|
568
|
+
})
|
|
569
|
+
.join("\n");
|
|
570
|
+
|
|
571
|
+
// Build per-agent debate task prefixes for the facilitator to include
|
|
572
|
+
const taskPrefixLines = participants
|
|
573
|
+
.map((p) => {
|
|
574
|
+
const dp = p.debatePersona;
|
|
575
|
+
if (dp && dp.prompt) {
|
|
576
|
+
return `**${p.displayName}** (${p.agentName}):\n${dp.prompt}`;
|
|
577
|
+
}
|
|
578
|
+
return `**${p.displayName}** (${p.agentName}): Debate participant.`;
|
|
579
|
+
})
|
|
580
|
+
.join("\n\n");
|
|
581
|
+
|
|
582
|
+
const agentTaskLines = participants
|
|
583
|
+
.map(
|
|
584
|
+
(p) =>
|
|
585
|
+
` - speaker: "${p.displayName}"`
|
|
586
|
+
)
|
|
587
|
+
.join("\n");
|
|
588
|
+
|
|
589
|
+
return [
|
|
590
|
+
`⚔️ BLACKBOARD DEBATE: ${topic}`,
|
|
591
|
+
"",
|
|
592
|
+
`Meeting folder: \`${absDir}\``,
|
|
593
|
+
"",
|
|
594
|
+
"You are facilitating an OPEN-ENDED debate using the MEETING BLACKBOARD.",
|
|
595
|
+
"Each debater writes their FULL argument to disk via meeting_append_entry.",
|
|
596
|
+
"Continue until the debate CONVERGES or the user intervenes.",
|
|
597
|
+
"",
|
|
598
|
+
"## Debaters (cycling indefinitely)",
|
|
599
|
+
debaterLines,
|
|
600
|
+
"",
|
|
601
|
+
"## DEBATE PERSONAS (include in each subagent task)",
|
|
602
|
+
taskPrefixLines,
|
|
603
|
+
"",
|
|
604
|
+
"## CRITICAL INSTRUCTIONS",
|
|
605
|
+
"",
|
|
606
|
+
"### For subagents (include in EVERY task):",
|
|
607
|
+
"1. Write your FULL contribution using the meeting_append_entry tool with:",
|
|
608
|
+
` - meetingDir: "${absDir}"`,
|
|
609
|
+
" - speaker: your display name, e.g.:",
|
|
610
|
+
agentTaskLines,
|
|
611
|
+
' - phase: "Cycle 1", "Cycle 2", etc.',
|
|
612
|
+
" - summary: a ONE-SENTENCE summary of your argument",
|
|
613
|
+
" - content: your FULL argument in Chinese (中文)",
|
|
614
|
+
"2. After writing, your FINAL ANSWER must be ONLY:",
|
|
615
|
+
" `WROTE_ENTRY: <your one-sentence summary>`",
|
|
616
|
+
"3. DO NOT paste your full argument into the chat.",
|
|
617
|
+
"",
|
|
618
|
+
"### Include the FULL VERBATIM prior debate record in each subagent task.",
|
|
619
|
+
"Use meeting_read_index and meeting_read_entry to retrieve the complete debate history.",
|
|
620
|
+
"NEVER summarize or truncate the debate record when passing to subagents.",
|
|
621
|
+
"",
|
|
622
|
+
"### For you, the facilitator:",
|
|
623
|
+
"- Do NOT paste participant full text into chat. They are on the blackboard.",
|
|
624
|
+
"- Cycle through debaters in sequence (chain mode) so each sees all prior entries.",
|
|
625
|
+
"- Read the index with meeting_read_index frequently.",
|
|
626
|
+
"- Read full entries with meeting_read_entry when synthesizing.",
|
|
627
|
+
"- After EACH full cycle (all debaters spoke once), check for CONVERGENCE:",
|
|
628
|
+
" * Do 2+ agents agree on a specific conclusion?",
|
|
629
|
+
" * Did the last cycle introduce any NEW arguments?",
|
|
630
|
+
" * Did anyone explicitly concede?",
|
|
631
|
+
"- If NOT converged: run another cycle. Keep going.",
|
|
632
|
+
"- If converged: present synthesis to me.",
|
|
633
|
+
"",
|
|
634
|
+
"## Rules",
|
|
635
|
+
"- NEVER stop at a predetermined count. Only convergence or user intervention ends this debate.",
|
|
636
|
+
"- All responses in Chinese (中文).",
|
|
637
|
+
"- After convergence, save transcript.md immediately and (after user confirms) conclusion.md per the MEETING OUTPUT PROTOCOL.",
|
|
638
|
+
"- Present: (1) the debate arc, (2) who conceded what, (3) final synthesis.",
|
|
639
|
+
].join("\n");
|
|
640
|
+
}
|
|
25
641
|
|
|
26
642
|
// ────────────────────────────────────────────────────────
|
|
27
643
|
// Helpers
|
|
@@ -29,12 +645,14 @@ import { Text, Box } from "@earendil-works/pi-tui";
|
|
|
29
645
|
|
|
30
646
|
/** Sanitize a string for use in filenames (keep letters, digits, hyphens, underscores). */
|
|
31
647
|
function sanitizeFilenamePart(raw: string): string {
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
648
|
+
return (
|
|
649
|
+
raw
|
|
650
|
+
.replace(/[^a-zA-Z0-9\u4e00-\u9fff_-]/g, "_")
|
|
651
|
+
.replace(/_+/g, "_")
|
|
652
|
+
.replace(/^_|_$/g, "")
|
|
653
|
+
.slice(0, 60)
|
|
654
|
+
.toLowerCase() || "unknown"
|
|
655
|
+
);
|
|
38
656
|
}
|
|
39
657
|
|
|
40
658
|
/** Convert a topic string to a filesystem-safe slug. */
|
|
@@ -162,7 +780,11 @@ function startWatching(pi: ExtensionAPI, meetingDir: string): void {
|
|
|
162
780
|
try {
|
|
163
781
|
if (!fs.existsSync(entryPath)) return;
|
|
164
782
|
assertExistingFileNoSymlink(entryPath, "meeting entry");
|
|
165
|
-
assertPathInside(
|
|
783
|
+
assertPathInside(
|
|
784
|
+
fs.realpathSync(entriesDir),
|
|
785
|
+
fs.realpathSync(entryPath),
|
|
786
|
+
"meeting entry real path"
|
|
787
|
+
);
|
|
166
788
|
const parsed = parseEntryFilename(filename);
|
|
167
789
|
if (!parsed) return;
|
|
168
790
|
const summary = readEntrySummary(entryPath);
|
|
@@ -189,7 +811,7 @@ function startWatching(pi: ExtensionAPI, meetingDir: string): void {
|
|
|
189
811
|
);
|
|
190
812
|
});
|
|
191
813
|
|
|
192
|
-
watcher.on("error", () => {
|
|
814
|
+
(watcher as any).on?.("error", () => {
|
|
193
815
|
// Silently handle watcher errors (e.g., directory deleted)
|
|
194
816
|
});
|
|
195
817
|
|
|
@@ -221,116 +843,6 @@ interface MeetingManifest {
|
|
|
221
843
|
entryCount: number;
|
|
222
844
|
}
|
|
223
845
|
|
|
224
|
-
const BRAINSTORM_AGENT_FILES: Record<string, string> = {
|
|
225
|
-
"gpt-brainstormer.md": `---
|
|
226
|
-
name: gpt-brainstormer
|
|
227
|
-
description: GPT brainstorming consultant. Visionary strategist for multi-model discussion sessions.
|
|
228
|
-
tools: read, grep, find, ls, meeting_append_entry, meeting_read_index, meeting_read_entry
|
|
229
|
-
model: vendor-codex/gpt-5.5:xhigh
|
|
230
|
-
---
|
|
231
|
-
|
|
232
|
-
# GPT Brainstormer - Visionary Strategist
|
|
233
|
-
|
|
234
|
-
你是多模型头脑风暴中的愿景战略家。思考大局,发现别人忽略的机会,将复杂权衡综合为清晰方向。用中文回答。
|
|
235
|
-
|
|
236
|
-
## What You Do
|
|
237
|
-
- 提出创新的战略方向和解决方案
|
|
238
|
-
- 发现别人忽略的机会和盲点
|
|
239
|
-
- 把零散想法综合成连贯战略
|
|
240
|
-
|
|
241
|
-
## What You Do Not Do
|
|
242
|
-
- 写代码或修改项目文件,你只读项目文件
|
|
243
|
-
- 委派给其他 Agent
|
|
244
|
-
- 在聊天中直接粘贴长篇分析;当明确指示使用 meeting_append_entry 时,必须将完整贡献写入会议黑板,最终回复仅写 WROTE_ENTRY + 一句话摘要
|
|
245
|
-
|
|
246
|
-
## Worker Preamble
|
|
247
|
-
You are a terminal worker. Work directly with tools. Do NOT spawn sub-agents.
|
|
248
|
-
`,
|
|
249
|
-
"deepseek-brainstormer.md": `---
|
|
250
|
-
name: deepseek-brainstormer
|
|
251
|
-
description: DeepSeek brainstorming consultant. Meticulous systems thinker for multi-model discussion sessions.
|
|
252
|
-
tools: read, grep, find, ls, meeting_append_entry, meeting_read_index, meeting_read_entry
|
|
253
|
-
model: deepseek/deepseek-v4-pro:xhigh
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
# DeepSeek Brainstormer - Meticulous Systems Thinker
|
|
257
|
-
|
|
258
|
-
你是多模型头脑风暴中的系统思考者。分析结构、依赖、扩展上限和失败模式。用中文回答。
|
|
259
|
-
|
|
260
|
-
## What You Do
|
|
261
|
-
- 从结构、依赖和风险角度分析提案
|
|
262
|
-
- 识别隐藏耦合、扩展上限和失败模式
|
|
263
|
-
- 提出具体、可实现、可验证的技术优化方案
|
|
264
|
-
|
|
265
|
-
## What You Do Not Do
|
|
266
|
-
- 写代码或修改项目文件,你只读项目文件
|
|
267
|
-
- 委派给其他 Agent
|
|
268
|
-
- 在聊天中直接粘贴长篇分析;当明确指示使用 meeting_append_entry 时,必须将完整贡献写入会议黑板,最终回复仅写 WROTE_ENTRY + 一句话摘要
|
|
269
|
-
|
|
270
|
-
## Worker Preamble
|
|
271
|
-
You are a terminal worker. Work directly with tools. Do NOT spawn sub-agents.
|
|
272
|
-
`,
|
|
273
|
-
"minimax-brainstormer.md": `---
|
|
274
|
-
name: minimax-brainstormer
|
|
275
|
-
description: MiniMax brainstorming consultant. Creative lateral thinker for multi-model discussion sessions.
|
|
276
|
-
tools: read, grep, find, ls, meeting_append_entry, meeting_read_index, meeting_read_entry
|
|
277
|
-
model: minimax-cn/MiniMax-M3:xhigh
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
# MiniMax Brainstormer - Creative Lateral Thinker
|
|
281
|
-
|
|
282
|
-
你是多模型头脑风暴中的创意顾问。跳出框框思考,挑战隐性假设,提出非常规方案。用中文回答。
|
|
283
|
-
|
|
284
|
-
## What You Do
|
|
285
|
-
- 从意想不到的角度切入问题
|
|
286
|
-
- 提出打破常规的创新方案
|
|
287
|
-
- 挑战团队隐性假设
|
|
288
|
-
|
|
289
|
-
## What You Do Not Do
|
|
290
|
-
- 写代码或修改项目文件,你只读项目文件
|
|
291
|
-
- 委派给其他 Agent
|
|
292
|
-
- 在聊天中直接粘贴长篇分析;当明确指示使用 meeting_append_entry 时,必须将完整贡献写入会议黑板,最终回复仅写 WROTE_ENTRY + 一句话摘要
|
|
293
|
-
|
|
294
|
-
## Worker Preamble
|
|
295
|
-
You are a terminal worker. Work directly with tools. Do NOT spawn sub-agents.
|
|
296
|
-
`,
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
async function ensureBrainstormAgents(ctx: any): Promise<boolean> {
|
|
300
|
-
const agentsDir = path.join(getAgentDir(), "agents");
|
|
301
|
-
const missing = Object.keys(BRAINSTORM_AGENT_FILES).filter(
|
|
302
|
-
(filename) => !fs.existsSync(path.join(agentsDir, filename))
|
|
303
|
-
);
|
|
304
|
-
if (missing.length === 0) return true;
|
|
305
|
-
|
|
306
|
-
if (!ctx.hasUI) {
|
|
307
|
-
ctx.ui?.notify?.(
|
|
308
|
-
`Missing meeting agents: ${missing.join(", ")}. Install them under ${agentsDir}.`,
|
|
309
|
-
"warning"
|
|
310
|
-
);
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const ok = await ctx.ui.confirm(
|
|
315
|
-
"Install meeting brainstorm agents?",
|
|
316
|
-
`The blackboard meeting commands need these user-level agents:\n${missing
|
|
317
|
-
.map((name) => `- ${name}`)
|
|
318
|
-
.join("\n")}\n\nThey will be created under ${agentsDir}. Existing files are not overwritten.`
|
|
319
|
-
);
|
|
320
|
-
if (!ok) return false;
|
|
321
|
-
|
|
322
|
-
await fsp.mkdir(agentsDir, { recursive: true });
|
|
323
|
-
for (const filename of missing) {
|
|
324
|
-
const target = path.join(agentsDir, filename);
|
|
325
|
-
await fsp.writeFile(target, BRAINSTORM_AGENT_FILES[filename], {
|
|
326
|
-
encoding: "utf-8",
|
|
327
|
-
flag: "wx",
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
ctx.ui.notify(`Installed ${missing.length} meeting agent(s).`, "info");
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
846
|
async function readManifest(absDir: string): Promise<MeetingManifest | null> {
|
|
335
847
|
const manifestPath = path.join(absDir, "manifest.json");
|
|
336
848
|
try {
|
|
@@ -428,7 +940,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
428
940
|
// Read or create manifest
|
|
429
941
|
let manifest = await readManifest(absDir);
|
|
430
942
|
if (!manifest) {
|
|
431
|
-
// Should not normally happen — commands seed the manifest
|
|
432
943
|
manifest = {
|
|
433
944
|
topic: path.basename(absDir),
|
|
434
945
|
created: new Date().toISOString(),
|
|
@@ -620,7 +1131,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
620
1131
|
|
|
621
1132
|
try {
|
|
622
1133
|
assertExistingFileNoSymlink(absEntryPath, "meeting entry");
|
|
623
|
-
assertPathInside(
|
|
1134
|
+
assertPathInside(
|
|
1135
|
+
fs.realpathSync(absDir),
|
|
1136
|
+
fs.realpathSync(absEntryPath),
|
|
1137
|
+
"meeting entry real path"
|
|
1138
|
+
);
|
|
624
1139
|
const content = await fsp.readFile(absEntryPath, "utf-8");
|
|
625
1140
|
return {
|
|
626
1141
|
content: [
|
|
@@ -682,7 +1197,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
682
1197
|
try {
|
|
683
1198
|
assertExistingFileNoSymlink(details.path, "meeting entry");
|
|
684
1199
|
if (details.meetingDir) {
|
|
685
|
-
assertPathInside(
|
|
1200
|
+
assertPathInside(
|
|
1201
|
+
fs.realpathSync(details.meetingDir),
|
|
1202
|
+
fs.realpathSync(details.path),
|
|
1203
|
+
"meeting entry real path"
|
|
1204
|
+
);
|
|
686
1205
|
}
|
|
687
1206
|
const fullContent = fs.readFileSync(details.path, "utf-8");
|
|
688
1207
|
text += `\n\n${theme.fg("dim", fullContent)}`;
|
|
@@ -708,7 +1227,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
708
1227
|
return;
|
|
709
1228
|
}
|
|
710
1229
|
|
|
711
|
-
|
|
1230
|
+
// Resolve participants from config
|
|
1231
|
+
let participants: ParticipantConfig[];
|
|
1232
|
+
try {
|
|
1233
|
+
participants = resolveParticipants(ctx.cwd);
|
|
1234
|
+
} catch (err: any) {
|
|
1235
|
+
ctx.ui.notify(
|
|
1236
|
+
`pi-brainstorm config error: ${err.message}`,
|
|
1237
|
+
"error"
|
|
1238
|
+
);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
const agentsReady = await ensureAgentsFromConfig(ctx, participants, {
|
|
1243
|
+
allowGlobalWrites: !hasProjectConfig(ctx.cwd),
|
|
1244
|
+
});
|
|
712
1245
|
if (!agentsReady) return;
|
|
713
1246
|
|
|
714
1247
|
const topic = args.trim();
|
|
@@ -723,7 +1256,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
723
1256
|
// Create meeting folder structure
|
|
724
1257
|
await fsp.mkdir(path.join(absDir, "entries"), { recursive: true });
|
|
725
1258
|
assertDirectoryNoSymlink(absDir, "meeting directory");
|
|
726
|
-
assertDirectoryNoSymlink(
|
|
1259
|
+
assertDirectoryNoSymlink(
|
|
1260
|
+
path.join(absDir, "entries"),
|
|
1261
|
+
"entries directory"
|
|
1262
|
+
);
|
|
727
1263
|
|
|
728
1264
|
// Seed manifest
|
|
729
1265
|
const manifest: MeetingManifest = {
|
|
@@ -765,54 +1301,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
765
1301
|
);
|
|
766
1302
|
|
|
767
1303
|
// Send orchestration prompt to main agent
|
|
1304
|
+
const promptText = buildBrainstormPrompt(topic, absDir, participants);
|
|
768
1305
|
pi.sendUserMessage([
|
|
769
1306
|
{
|
|
770
1307
|
type: "text" as const,
|
|
771
|
-
text:
|
|
772
|
-
`BLACKBOARD BRAINSTORMING SESSION: ${topic}`,
|
|
773
|
-
"",
|
|
774
|
-
`Meeting folder: \`${absDir}\``,
|
|
775
|
-
"",
|
|
776
|
-
"You are facilitating a round-robin brainstorming session using the MEETING BLACKBOARD.",
|
|
777
|
-
"Each consultant writes their FULL contribution to disk via meeting_append_entry.",
|
|
778
|
-
"",
|
|
779
|
-
"## Consultants (3 rounds)",
|
|
780
|
-
"- **GPT**: use the gpt-brainstormer subagent. Visionary strategist.",
|
|
781
|
-
"- **DeepSeek**: use the deepseek-brainstormer subagent. Systems thinker.",
|
|
782
|
-
"- **MiniMax**: use the minimax-brainstormer subagent. Creative lateral thinker.",
|
|
783
|
-
"",
|
|
784
|
-
"## CRITICAL INSTRUCTIONS",
|
|
785
|
-
"",
|
|
786
|
-
"### For subagents (include in EVERY task):",
|
|
787
|
-
"1. Write your FULL contribution using the meeting_append_entry tool with:",
|
|
788
|
-
` - meetingDir: "${absDir}"`,
|
|
789
|
-
" - speaker: your name (GPT, DeepSeek, or MiniMax)",
|
|
790
|
-
' - phase: "Round 1", "Round 2", or "Round 3"',
|
|
791
|
-
" - summary: a ONE-SENTENCE summary of your contribution",
|
|
792
|
-
" - content: your FULL analysis in Chinese (中文)",
|
|
793
|
-
"2. After writing, your FINAL ANSWER must be ONLY:",
|
|
794
|
-
" `WROTE_ENTRY: <your one-sentence summary>`",
|
|
795
|
-
"3. DO NOT paste your full analysis into the chat. The main agent and user will read it from the blackboard.",
|
|
796
|
-
"",
|
|
797
|
-
"### For you, the facilitator:",
|
|
798
|
-
"- Do NOT paste participant full text into chat. They are on the blackboard.",
|
|
799
|
-
"- After each round, read the index with meeting_read_index and present a structural overview.",
|
|
800
|
-
"- Optionally read full entries with meeting_read_entry when needed.",
|
|
801
|
-
"- Present each consultant's summary + your structural overview (conflict matrix, consensus table).",
|
|
802
|
-
"- When the user gives feedback, relay it VERBATIM to the consultants in the next round.",
|
|
803
|
-
"",
|
|
804
|
-
"## Protocol",
|
|
805
|
-
"Round 1: Each consultant gives initial analysis on the topic. Run all 3 in parallel.",
|
|
806
|
-
"Round 2: Feed prior discussion back to each. Ask each to challenge the others and propose improvements.",
|
|
807
|
-
"Round 3: Each gives FINAL recommendation, synthesizing the best ideas.",
|
|
808
|
-
"",
|
|
809
|
-
"After Round 3, present the complete structural overview and a synthesized conclusion.",
|
|
810
|
-
"",
|
|
811
|
-
"## IMPORTANT",
|
|
812
|
-
"- All responses in Chinese (中文).",
|
|
813
|
-
"- Save transcript.md and (after user confirms) conclusion.md per the MEETING OUTPUT PROTOCOL.",
|
|
814
|
-
"- The user can intervene at any time to steer the discussion.",
|
|
815
|
-
].join("\n"),
|
|
1308
|
+
text: promptText,
|
|
816
1309
|
},
|
|
817
1310
|
]);
|
|
818
1311
|
},
|
|
@@ -829,7 +1322,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
829
1322
|
return;
|
|
830
1323
|
}
|
|
831
1324
|
|
|
832
|
-
|
|
1325
|
+
// Resolve participants from config
|
|
1326
|
+
let participants: ParticipantConfig[];
|
|
1327
|
+
try {
|
|
1328
|
+
participants = resolveParticipants(ctx.cwd);
|
|
1329
|
+
} catch (err: any) {
|
|
1330
|
+
ctx.ui.notify(
|
|
1331
|
+
`pi-brainstorm config error: ${err.message}`,
|
|
1332
|
+
"error"
|
|
1333
|
+
);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const agentsReady = await ensureAgentsFromConfig(ctx, participants, {
|
|
1338
|
+
allowGlobalWrites: !hasProjectConfig(ctx.cwd),
|
|
1339
|
+
});
|
|
833
1340
|
if (!agentsReady) return;
|
|
834
1341
|
|
|
835
1342
|
const topic = args.trim();
|
|
@@ -844,7 +1351,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
844
1351
|
// Create meeting folder structure
|
|
845
1352
|
await fsp.mkdir(path.join(absDir, "entries"), { recursive: true });
|
|
846
1353
|
assertDirectoryNoSymlink(absDir, "meeting directory");
|
|
847
|
-
assertDirectoryNoSymlink(
|
|
1354
|
+
assertDirectoryNoSymlink(
|
|
1355
|
+
path.join(absDir, "entries"),
|
|
1356
|
+
"entries directory"
|
|
1357
|
+
);
|
|
848
1358
|
|
|
849
1359
|
// Seed manifest
|
|
850
1360
|
const manifest: MeetingManifest = {
|
|
@@ -887,58 +1397,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
887
1397
|
);
|
|
888
1398
|
|
|
889
1399
|
// Send orchestration prompt to main agent
|
|
1400
|
+
const promptText = buildDebatePrompt(topic, absDir, participants);
|
|
890
1401
|
pi.sendUserMessage([
|
|
891
1402
|
{
|
|
892
1403
|
type: "text" as const,
|
|
893
|
-
text:
|
|
894
|
-
`⚔️ BLACKBOARD DEBATE: ${topic}`,
|
|
895
|
-
"",
|
|
896
|
-
`Meeting folder: \`${absDir}\``,
|
|
897
|
-
"",
|
|
898
|
-
"You are facilitating an OPEN-ENDED debate using the MEETING BLACKBOARD.",
|
|
899
|
-
"Each debater writes their FULL argument to disk via meeting_append_entry.",
|
|
900
|
-
"Continue until the debate CONVERGES or the user intervenes.",
|
|
901
|
-
"",
|
|
902
|
-
"## Debaters (cycling indefinitely)",
|
|
903
|
-
"- **GPT** (gpt-brainstormer): THE PROSECUTOR — Attack other positions ruthlessly. Find every logical flaw, hidden assumption, and missing edge case.",
|
|
904
|
-
"- **DeepSeek** (deepseek-brainstormer): THE SYSTEMS SKEPTIC — Dissect structural implications. What breaks at scale? Where are the hidden costs?",
|
|
905
|
-
"- **MiniMax** (minimax-brainstormer): THE CONTRARIAN — Take the opposite position. Expose groupthink. Propose radical alternatives.",
|
|
906
|
-
"",
|
|
907
|
-
"## CRITICAL INSTRUCTIONS",
|
|
908
|
-
"",
|
|
909
|
-
"### For subagents (include in EVERY task):",
|
|
910
|
-
"1. Write your FULL contribution using the meeting_append_entry tool with:",
|
|
911
|
-
` - meetingDir: "${absDir}"`,
|
|
912
|
-
" - speaker: your name (GPT, DeepSeek, or MiniMax)",
|
|
913
|
-
' - phase: "Cycle 1", "Cycle 2", etc.',
|
|
914
|
-
" - summary: a ONE-SENTENCE summary of your argument",
|
|
915
|
-
" - content: your FULL argument in Chinese (中文)",
|
|
916
|
-
"2. After writing, your FINAL ANSWER must be ONLY:",
|
|
917
|
-
" `WROTE_ENTRY: <your one-sentence summary>`",
|
|
918
|
-
"3. DO NOT paste your full argument into the chat.",
|
|
919
|
-
"",
|
|
920
|
-
"### Include the FULL VERBATIM prior debate record in each subagent task.",
|
|
921
|
-
"Use meeting_read_index and meeting_read_entry to retrieve the complete debate history.",
|
|
922
|
-
"NEVER summarize or truncate the debate record when passing to subagents.",
|
|
923
|
-
"",
|
|
924
|
-
"### For you, the facilitator:",
|
|
925
|
-
"- Do NOT paste participant full text into chat. They are on the blackboard.",
|
|
926
|
-
"- Run debaters in CHAIN mode (one at a time, each sees all prior entries).",
|
|
927
|
-
"- Read the index with meeting_read_index frequently.",
|
|
928
|
-
"- Read full entries with meeting_read_entry when synthesizing.",
|
|
929
|
-
"- After EACH full cycle (all 3 spoke), check for CONVERGENCE:",
|
|
930
|
-
" * Do 2+ agents agree on a specific conclusion?",
|
|
931
|
-
" * Did the last cycle introduce any NEW arguments?",
|
|
932
|
-
" * Did anyone explicitly concede?",
|
|
933
|
-
"- If NOT converged: run another cycle. Keep going.",
|
|
934
|
-
"- If converged: present synthesis to me.",
|
|
935
|
-
"",
|
|
936
|
-
"## Rules",
|
|
937
|
-
"- NEVER stop at a predetermined count. Only convergence or user intervention ends this debate.",
|
|
938
|
-
"- All responses in Chinese (中文).",
|
|
939
|
-
"- After convergence, save transcript.md immediately and (after user confirms) conclusion.md per the MEETING OUTPUT PROTOCOL.",
|
|
940
|
-
"- Present: (1) the debate arc, (2) who conceded what, (3) final synthesis.",
|
|
941
|
-
].join("\n"),
|
|
1404
|
+
text: promptText,
|
|
942
1405
|
},
|
|
943
1406
|
]);
|
|
944
1407
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-brainstorm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Multi-model brainstorming and debate sessions for pi subagents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -26,10 +26,14 @@
|
|
|
26
26
|
"homepage": "https://github.com/Jarcis-cy/pi-brainstorm#readme",
|
|
27
27
|
"files": [
|
|
28
28
|
"extensions",
|
|
29
|
+
"config",
|
|
29
30
|
"README.md",
|
|
30
31
|
"README.zh-CN.md",
|
|
31
32
|
"LICENSE"
|
|
32
33
|
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"yaml": "^2.7.0"
|
|
36
|
+
},
|
|
33
37
|
"peerDependencies": {
|
|
34
38
|
"@earendil-works/pi-coding-agent": "*",
|
|
35
39
|
"@earendil-works/pi-tui": "*",
|