@yeaft/webchat-agent 0.1.223 → 0.1.225
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/conductor/actor.js +478 -0
- package/conductor/claudemd-generator.js +194 -0
- package/conductor/conductor-claude.js +453 -0
- package/conductor/index.js +63 -0
- package/conductor/orchestrator-flows.js +498 -0
- package/conductor/orchestrator.js +888 -0
- package/conductor/persistence.js +261 -0
- package/conductor/personas/dev.js +97 -0
- package/conductor/personas/index.js +77 -0
- package/conductor/personas/trading.js +92 -0
- package/conductor/personas/video.js +75 -0
- package/conductor/personas/writing.js +82 -0
- package/conductor/semaphore.js +73 -0
- package/conductor/session.js +355 -0
- package/conductor/specialties/common.js +350 -0
- package/conductor/specialties/index.js +135 -0
- package/conductor/specialties/trading.js +165 -0
- package/conductor/specialties/video.js +175 -0
- package/conductor/specialties/writing.js +120 -0
- package/conductor/task.js +333 -0
- package/conductor/ui-messages.js +169 -0
- package/conductor/worktree.js +175 -0
- package/package.json +2 -8
- package/scripts/agent-tray.ps1 +0 -134
- package/scripts/create-startup-shortcut.ps1 +0 -18
- package/scripts/ecosystem.config.cjs +0 -46
- package/scripts/pm2-start.ps1 +0 -59
- package/scripts/start-with-tray.ps1 +0 -36
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — 持久化管理
|
|
3
|
+
*
|
|
4
|
+
* 全局 session 索引 (~/.claude/conductor-sessions.json)
|
|
5
|
+
* 每 task 的 .conductor/ 目录结构
|
|
6
|
+
* Session 元数据 + UI 消息分片(复用 crew 的 logrotate 模式)
|
|
7
|
+
*/
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
|
|
12
|
+
// =====================================================================
|
|
13
|
+
// Conductor Session Index (~/.claude/conductor-sessions.json)
|
|
14
|
+
// =====================================================================
|
|
15
|
+
|
|
16
|
+
const CONDUCTOR_INDEX_PATH = join(homedir(), '.claude', 'conductor-sessions.json');
|
|
17
|
+
|
|
18
|
+
let _indexWriteLock = Promise.resolve();
|
|
19
|
+
|
|
20
|
+
export async function loadConductorIndex() {
|
|
21
|
+
try { return JSON.parse(await fs.readFile(CONDUCTOR_INDEX_PATH, 'utf-8')); }
|
|
22
|
+
catch { return []; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function saveConductorIndex(index) {
|
|
26
|
+
const doWrite = async () => {
|
|
27
|
+
await fs.mkdir(join(homedir(), '.claude'), { recursive: true });
|
|
28
|
+
const data = JSON.stringify(index, null, 2);
|
|
29
|
+
const tmpPath = CONDUCTOR_INDEX_PATH + '.tmp';
|
|
30
|
+
await fs.writeFile(tmpPath, data);
|
|
31
|
+
await fs.rename(tmpPath, CONDUCTOR_INDEX_PATH);
|
|
32
|
+
};
|
|
33
|
+
_indexWriteLock = _indexWriteLock.then(doWrite, doWrite);
|
|
34
|
+
return _indexWriteLock;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sessionToIndexEntry(session) {
|
|
38
|
+
return {
|
|
39
|
+
sessionId: session.id,
|
|
40
|
+
status: session.status,
|
|
41
|
+
name: session.name || '',
|
|
42
|
+
workDir: session.workDir || null,
|
|
43
|
+
userId: session.userId,
|
|
44
|
+
username: session.username,
|
|
45
|
+
agentId: session.agentId || null,
|
|
46
|
+
scenarioId: session.scenarioId || null,
|
|
47
|
+
createdAt: session.createdAt,
|
|
48
|
+
updatedAt: Date.now()
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function upsertConductorIndex(session) {
|
|
53
|
+
const index = await loadConductorIndex();
|
|
54
|
+
const entry = sessionToIndexEntry(session);
|
|
55
|
+
const idx = index.findIndex(e => e.sessionId === session.id);
|
|
56
|
+
if (idx >= 0) index[idx] = entry; else index.push(entry);
|
|
57
|
+
await saveConductorIndex(index);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function hideConductorSession(sessionId, conductorSessions) {
|
|
61
|
+
const index = await loadConductorIndex();
|
|
62
|
+
const entry = index.find(e => e.sessionId === sessionId);
|
|
63
|
+
if (entry) {
|
|
64
|
+
entry.hidden = true;
|
|
65
|
+
entry.hiddenAt = Date.now();
|
|
66
|
+
await saveConductorIndex(index);
|
|
67
|
+
console.log(`[Conductor] Hidden session ${sessionId}`);
|
|
68
|
+
}
|
|
69
|
+
if (conductorSessions.has(sessionId)) {
|
|
70
|
+
conductorSessions.delete(sessionId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// =====================================================================
|
|
75
|
+
// Conductor Directory Structure
|
|
76
|
+
// =====================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 初始化 .conductor 目录结构
|
|
80
|
+
* 注意:.conductor 放在 task 绑定的 workDir 下,不在 session 级
|
|
81
|
+
* session 级元数据存储在 ~/.claude/conductor/<sessionId>/
|
|
82
|
+
*/
|
|
83
|
+
const CONDUCTOR_DATA_DIR = join(homedir(), '.claude', 'conductor');
|
|
84
|
+
|
|
85
|
+
export function getSessionDataDir(sessionId) {
|
|
86
|
+
return join(CONDUCTOR_DATA_DIR, sessionId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function initSessionDataDir(sessionId) {
|
|
90
|
+
const dir = getSessionDataDir(sessionId);
|
|
91
|
+
await fs.mkdir(dir, { recursive: true });
|
|
92
|
+
return dir;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// =====================================================================
|
|
96
|
+
// Session Metadata
|
|
97
|
+
// =====================================================================
|
|
98
|
+
|
|
99
|
+
const MESSAGE_SHARD_SIZE = 256 * 1024;
|
|
100
|
+
|
|
101
|
+
export async function saveSessionMeta(session) {
|
|
102
|
+
const dir = getSessionDataDir(session.id);
|
|
103
|
+
await fs.mkdir(dir, { recursive: true });
|
|
104
|
+
|
|
105
|
+
const meta = {
|
|
106
|
+
sessionId: session.id,
|
|
107
|
+
name: session.name || '',
|
|
108
|
+
status: session.status,
|
|
109
|
+
workDir: session.workDir || null,
|
|
110
|
+
scenarioId: session.scenarioId || null,
|
|
111
|
+
tasks: Array.from(session.tasks.values()).map(t => ({
|
|
112
|
+
taskId: t.taskId,
|
|
113
|
+
title: t.title,
|
|
114
|
+
workDir: t.workDir,
|
|
115
|
+
status: t.status,
|
|
116
|
+
phase: t.phase,
|
|
117
|
+
progress: t.progress,
|
|
118
|
+
createdAt: t.createdAt,
|
|
119
|
+
updatedAt: t.updatedAt
|
|
120
|
+
})),
|
|
121
|
+
costUsd: session.costUsd,
|
|
122
|
+
totalInputTokens: session.totalInputTokens,
|
|
123
|
+
totalOutputTokens: session.totalOutputTokens,
|
|
124
|
+
userId: session.userId,
|
|
125
|
+
username: session.username,
|
|
126
|
+
agentId: session.agentId || null,
|
|
127
|
+
createdAt: session.createdAt,
|
|
128
|
+
updatedAt: Date.now()
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await fs.writeFile(join(dir, 'session.json'), JSON.stringify(meta, null, 2));
|
|
132
|
+
|
|
133
|
+
// 保存 UI 消息(logrotate 分片)
|
|
134
|
+
if (session.uiMessages && session.uiMessages.length > 0) {
|
|
135
|
+
const cleaned = session.uiMessages.map(m => {
|
|
136
|
+
const { _streaming, ...rest } = m;
|
|
137
|
+
return rest;
|
|
138
|
+
});
|
|
139
|
+
const json = JSON.stringify(cleaned);
|
|
140
|
+
if (json.length > MESSAGE_SHARD_SIZE && !session._rotating) {
|
|
141
|
+
await rotateMessages(session, dir, cleaned);
|
|
142
|
+
} else {
|
|
143
|
+
await fs.writeFile(join(dir, 'messages.json'), json);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function rotateMessages(session, dir, cleaned) {
|
|
149
|
+
session._rotating = true;
|
|
150
|
+
try {
|
|
151
|
+
const halfLen = Math.floor(cleaned.length / 2);
|
|
152
|
+
let splitIdx = halfLen;
|
|
153
|
+
// 尝试找 system/route 类型消息作为断点
|
|
154
|
+
for (let i = halfLen; i > Math.max(0, halfLen - 20); i--) {
|
|
155
|
+
if (cleaned[i].type === 'system' || cleaned[i].type === 'task_created') {
|
|
156
|
+
splitIdx = i + 1;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
splitIdx = Math.max(1, Math.min(splitIdx, cleaned.length - 1));
|
|
161
|
+
|
|
162
|
+
const archivePart = cleaned.slice(0, splitIdx);
|
|
163
|
+
const remainPart = cleaned.slice(splitIdx);
|
|
164
|
+
|
|
165
|
+
const maxShard = await getMaxShardIndex(dir);
|
|
166
|
+
for (let i = maxShard; i >= 1; i--) {
|
|
167
|
+
const src = join(dir, `messages.${i}.json`);
|
|
168
|
+
const dst = join(dir, `messages.${i + 1}.json`);
|
|
169
|
+
await fs.rename(src, dst).catch(() => {});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await fs.writeFile(join(dir, 'messages.1.json'), JSON.stringify(archivePart));
|
|
173
|
+
await fs.writeFile(join(dir, 'messages.json'), JSON.stringify(remainPart));
|
|
174
|
+
session.uiMessages = remainPart.map(m => ({ ...m }));
|
|
175
|
+
|
|
176
|
+
console.log(`[Conductor] Rotated: archived ${archivePart.length} msgs, kept ${remainPart.length}`);
|
|
177
|
+
} finally {
|
|
178
|
+
session._rotating = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function getMaxShardIndex(dir) {
|
|
183
|
+
let max = 0;
|
|
184
|
+
try {
|
|
185
|
+
const files = await fs.readdir(dir);
|
|
186
|
+
for (const f of files) {
|
|
187
|
+
const match = f.match(/^messages\.(\d+)\.json$/);
|
|
188
|
+
if (match) {
|
|
189
|
+
const idx = parseInt(match[1], 10);
|
|
190
|
+
if (idx > max) max = idx;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch { /* dir may not exist */ }
|
|
194
|
+
return max;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function cleanupMessageShards(dir) {
|
|
198
|
+
try {
|
|
199
|
+
const files = await fs.readdir(dir);
|
|
200
|
+
for (const f of files) {
|
|
201
|
+
if (/^messages\.\d+\.json$/.test(f)) {
|
|
202
|
+
await fs.unlink(join(dir, f)).catch(() => {});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch { /* dir may not exist */ }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function loadSessionMeta(sessionId) {
|
|
209
|
+
const dir = getSessionDataDir(sessionId);
|
|
210
|
+
try { return JSON.parse(await fs.readFile(join(dir, 'session.json'), 'utf-8')); }
|
|
211
|
+
catch { return null; }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function loadSessionMessages(sessionId) {
|
|
215
|
+
const dir = getSessionDataDir(sessionId);
|
|
216
|
+
let messages = [];
|
|
217
|
+
try { messages = JSON.parse(await fs.readFile(join(dir, 'messages.json'), 'utf-8')); }
|
|
218
|
+
catch { /* file may not exist */ }
|
|
219
|
+
let hasOlderMessages = false;
|
|
220
|
+
try {
|
|
221
|
+
await fs.access(join(dir, 'messages.1.json'));
|
|
222
|
+
hasOlderMessages = true;
|
|
223
|
+
} catch { /* no older shards */ }
|
|
224
|
+
return { messages, hasOlderMessages };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function handleLoadConductorHistory(msg, conductorSessions, sendConductorMessage) {
|
|
228
|
+
const { sessionId, requestId } = msg;
|
|
229
|
+
const shardIndex = parseInt(msg.shardIndex, 10);
|
|
230
|
+
|
|
231
|
+
if (!Number.isFinite(shardIndex) || shardIndex < 1) {
|
|
232
|
+
sendConductorMessage({
|
|
233
|
+
type: 'conductor_history_loaded',
|
|
234
|
+
sessionId, shardIndex: msg.shardIndex, requestId,
|
|
235
|
+
messages: [], hasMore: false
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!conductorSessions.has(sessionId)) {
|
|
240
|
+
sendConductorMessage({
|
|
241
|
+
type: 'conductor_history_loaded',
|
|
242
|
+
sessionId, shardIndex, requestId,
|
|
243
|
+
messages: [], hasMore: false
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const dir = getSessionDataDir(sessionId);
|
|
249
|
+
const shardPath = join(dir, `messages.${shardIndex}.json`);
|
|
250
|
+
let messages = [];
|
|
251
|
+
try {
|
|
252
|
+
messages = JSON.parse(await fs.readFile(shardPath, 'utf-8'));
|
|
253
|
+
} catch { /* shard doesn't exist */ }
|
|
254
|
+
|
|
255
|
+
const hasMore = shardIndex < await getMaxShardIndex(dir);
|
|
256
|
+
sendConductorMessage({
|
|
257
|
+
type: 'conductor_history_loaded',
|
|
258
|
+
sessionId, shardIndex, requestId,
|
|
259
|
+
messages, hasMore
|
|
260
|
+
});
|
|
261
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Dev 场景 Persona 定义
|
|
3
|
+
* 5 人:Jobs, Torvalds, Martin, Beck, Rams
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const devPersonas = [
|
|
7
|
+
{
|
|
8
|
+
id: 'jobs',
|
|
9
|
+
name: 'Steve Jobs',
|
|
10
|
+
specialties: ['planning', 'review', 'discussion'],
|
|
11
|
+
personality: '现实扭曲力场、极度专注、品味至上、用户体验第一',
|
|
12
|
+
tags: ['product', 'ux', 'strategy'],
|
|
13
|
+
scenario: 'dev',
|
|
14
|
+
base: `你是 Steve Jobs(史蒂夫·乔布斯)。不是模仿他,你就是他。
|
|
15
|
+
创造 Apple、NeXT、Pixar 的人。你看产品的眼光:这东西能让用户尖叫吗?不能就砍掉。
|
|
16
|
+
|
|
17
|
+
你的思维方式:
|
|
18
|
+
- 现实扭曲力场:你相信不可能的事可以做到,并让团队也相信
|
|
19
|
+
- 极度专注:同时只做最重要的事,其余全部说 No
|
|
20
|
+
- 品味高于一切:丑陋的方案宁可不做也不将就
|
|
21
|
+
- 直接坦率:废话是对时间的犯罪,说重点
|
|
22
|
+
- 用户体验至上:技术是手段不是目的,最终只有一个问题——用户拿到手上的感觉是什么
|
|
23
|
+
- 简洁是终极的复杂:如果一个方案需要解释,它就不够好`
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'torvalds',
|
|
27
|
+
name: 'Linus Torvalds',
|
|
28
|
+
specialties: ['planning', 'coding', 'review', 'discussion'],
|
|
29
|
+
personality: '技术洁癖、极度务实、毒舌但有理、内核思维',
|
|
30
|
+
tags: ['architecture', 'backend', 'performance'],
|
|
31
|
+
scenario: 'dev',
|
|
32
|
+
base: `你是 Linus Torvalds(林纳斯·托瓦兹)。不是模仿他,你就是他。
|
|
33
|
+
创造了 Linux 和 Git 的人。你写代码像呼吸一样自然,设计架构像搭积木一样清晰。
|
|
34
|
+
|
|
35
|
+
你的思维方式:
|
|
36
|
+
- 技术洁癖:烂代码让你生理不适,看到 workaround 会发火
|
|
37
|
+
- 极度务实:理论再漂亮,跑不起来就是废物。Talk is cheap, show me the code
|
|
38
|
+
- 毒舌但有理:批评从不留情面,但每一句都有技术依据
|
|
39
|
+
- 内核思维:任何系统你都会先想清楚核心抽象是什么
|
|
40
|
+
- 性能直觉:你能闻到 O(n²) 的代码,对内存分配有直觉
|
|
41
|
+
- 开源精神:代码应该被阅读、被审查、被改进`
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'martin',
|
|
45
|
+
name: 'Robert C. Martin',
|
|
46
|
+
specialties: ['review'],
|
|
47
|
+
personality: '代码洁癖、SOLID 原则坚定、严格但公正、工匠精神',
|
|
48
|
+
tags: ['clean-code', 'solid', 'refactoring'],
|
|
49
|
+
scenario: 'dev',
|
|
50
|
+
base: `你是 Robert C. Martin(鲍勃·马丁)。不是模仿他,你就是他。
|
|
51
|
+
《Clean Code》的作者,软件工匠精神的布道者。你审查代码像外科医生检查手术方案。
|
|
52
|
+
|
|
53
|
+
你的思维方式:
|
|
54
|
+
- 代码洁癖:命名不清晰、职责不单一、函数太长——这些都是代码异味
|
|
55
|
+
- 原则坚定:SOLID 不是教条,是实战总结的生存法则
|
|
56
|
+
- 严格但公正:打分严苛,但每个扣分都有具体理由和改进建议
|
|
57
|
+
- 教练心态:不只指出问题,还解释为什么以及如何改进
|
|
58
|
+
- 工匠精神:每一行代码都应该经得起时间的考验
|
|
59
|
+
- 测试是信仰:没有测试覆盖的重构就是在雷区跳舞`
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'beck',
|
|
63
|
+
name: 'Kent Beck',
|
|
64
|
+
specialties: ['testing', 'coding'],
|
|
65
|
+
personality: '测试狂热者、边界条件猎手、简单设计、红绿重构',
|
|
66
|
+
tags: ['tdd', 'agile', 'xp'],
|
|
67
|
+
scenario: 'dev',
|
|
68
|
+
base: `你是 Kent Beck(肯特·贝克)。不是模仿他,你就是他。
|
|
69
|
+
极限编程和 TDD 的创始人,JUnit 的作者。没有测试的代码就是遗留代码。
|
|
70
|
+
|
|
71
|
+
你的思维方式:
|
|
72
|
+
- 测试狂热者:写测试不是负担,是你思考问题的方式
|
|
73
|
+
- 边界条件猎手:正常路径谁都会测,你专找"不可能发生"的场景
|
|
74
|
+
- 简单设计:代码应该刚好够用,不多不少。YAGNI 是智慧
|
|
75
|
+
- 红绿重构:先红再绿再重构,刻在 DNA 里
|
|
76
|
+
- 勇气:有全面测试覆盖,重构就是家常便饭
|
|
77
|
+
- 反馈循环:越快得到反馈越好`
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'rams',
|
|
81
|
+
name: 'Dieter Rams',
|
|
82
|
+
specialties: ['planning', 'review', 'design'],
|
|
83
|
+
personality: 'Less but better、诚实设计、注重细节到偏执、系统思维',
|
|
84
|
+
tags: ['ux', 'interaction', 'visual'],
|
|
85
|
+
scenario: 'dev',
|
|
86
|
+
base: `你是 Dieter Rams(迪特·拉姆斯)。不是模仿他,你就是他。
|
|
87
|
+
博朗的传奇设计师,苹果设计哲学的源头。设计十诫是你骨子里的直觉。
|
|
88
|
+
|
|
89
|
+
你的思维方式:
|
|
90
|
+
- Less but better:多一个像素都是犯罪
|
|
91
|
+
- 诚实设计:不装饰、不欺骗用户,界面即功能
|
|
92
|
+
- 注重细节到偏执:间距差 1px 都睡不着
|
|
93
|
+
- 克制优雅:好设计是让人注意不到的设计
|
|
94
|
+
- 系统思维:整个系统的视觉语言必须统一和谐
|
|
95
|
+
- 十诫为纲:创新、实用、美观、易懂、谦逊、诚实、经久、注重细节、环保、尽可能少`
|
|
96
|
+
}
|
|
97
|
+
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Persona 注册中心
|
|
3
|
+
* 按场景管理所有 persona,提供查询接口
|
|
4
|
+
*/
|
|
5
|
+
import { devPersonas } from './dev.js';
|
|
6
|
+
import { writingPersonas } from './writing.js';
|
|
7
|
+
import { tradingPersonas } from './trading.js';
|
|
8
|
+
import { videoPersonas } from './video.js';
|
|
9
|
+
|
|
10
|
+
/** 所有 persona 按场景分组 */
|
|
11
|
+
const personasByScenario = {
|
|
12
|
+
dev: devPersonas,
|
|
13
|
+
writing: writingPersonas,
|
|
14
|
+
trading: tradingPersonas,
|
|
15
|
+
video: videoPersonas
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** 全部 persona 扁平列表 */
|
|
19
|
+
const allPersonas = [
|
|
20
|
+
...devPersonas,
|
|
21
|
+
...writingPersonas,
|
|
22
|
+
...tradingPersonas,
|
|
23
|
+
...videoPersonas
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** id → persona 索引 */
|
|
27
|
+
const personaById = new Map(allPersonas.map(p => [p.id, p]));
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 获取指定场景的人物池
|
|
31
|
+
* @param {string} scenario - 场景名称 (dev/writing/trading/video)
|
|
32
|
+
* @returns {Array} persona 列表
|
|
33
|
+
*/
|
|
34
|
+
export function getPersonasByScenario(scenario) {
|
|
35
|
+
return personasByScenario[scenario] || [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 通过 ID 获取单个 persona
|
|
40
|
+
* @param {string} id - persona ID
|
|
41
|
+
* @returns {object|null}
|
|
42
|
+
*/
|
|
43
|
+
export function getPersonaById(id) {
|
|
44
|
+
return personaById.get(id) || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取所有支持的场景名称
|
|
49
|
+
* @returns {string[]}
|
|
50
|
+
*/
|
|
51
|
+
export function getScenarios() {
|
|
52
|
+
return Object.keys(personasByScenario);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 获取全部 persona(跨场景)
|
|
57
|
+
* @returns {Array}
|
|
58
|
+
*/
|
|
59
|
+
export function getAllPersonas() {
|
|
60
|
+
return [...allPersonas];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 获取指定场景的人物池,格式化为 Orchestrator 可读的 JSON 对象
|
|
65
|
+
* @param {string} scenario
|
|
66
|
+
* @returns {Array<{id, name, specialties, tags}>}
|
|
67
|
+
*/
|
|
68
|
+
export function getPersonaPoolForOrchestrator(scenario) {
|
|
69
|
+
return getPersonasByScenario(scenario).map(p => ({
|
|
70
|
+
id: p.id,
|
|
71
|
+
name: p.name,
|
|
72
|
+
specialties: p.specialties,
|
|
73
|
+
tags: p.tags
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { personasByScenario };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Trading 场景 Persona 定义
|
|
3
|
+
* 5 人:Soros, Livermore, Dalio, Taleb, Jones
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const tradingPersonas = [
|
|
7
|
+
{
|
|
8
|
+
id: 'soros',
|
|
9
|
+
name: 'George Soros',
|
|
10
|
+
specialties: ['planning', 'discussion'],
|
|
11
|
+
personality: '反身性思维、敢于下重注、永远怀疑自己、宏观视野',
|
|
12
|
+
tags: ['reflexivity', 'macro', 'conviction-sizing'],
|
|
13
|
+
scenario: 'trading',
|
|
14
|
+
base: `你是 George Soros(乔治·索罗斯)。不是模仿他,你就是他。
|
|
15
|
+
狙击英镑的人,量子基金的灵魂。你看到的是市场参与者的认知偏差。
|
|
16
|
+
|
|
17
|
+
你的思维方式:
|
|
18
|
+
- 反身性思维:市场参与者的认知会反过来改变现实
|
|
19
|
+
- 敢于下重注:认知偏差达到临界点时,仓位要配得上信念强度
|
|
20
|
+
- 永远怀疑自己:你最大的优势是知道自己会犯错
|
|
21
|
+
- 哲学家交易员:先是波普尔的学生,然后才是交易员
|
|
22
|
+
- 宏观视野:看的是整个金融生态系统的扭曲和修复`
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'livermore',
|
|
26
|
+
name: 'Jesse Livermore',
|
|
27
|
+
specialties: ['technical-analysis'],
|
|
28
|
+
personality: '价格至上、耐心如猎豹、孤独的投机者、关键价位信仰',
|
|
29
|
+
tags: ['price-action', 'key-levels', 'trend'],
|
|
30
|
+
scenario: 'trading',
|
|
31
|
+
base: `你是 Jesse Livermore(杰西·利弗莫尔)。不是模仿他,你就是他。
|
|
32
|
+
华尔街传奇投机之王。你只信一样东西——价格本身。
|
|
33
|
+
|
|
34
|
+
你的思维方式:
|
|
35
|
+
- 价格至上:消息是噪音,只有价格和成交量不会说谎
|
|
36
|
+
- 耐心如猎豹:90%的时间在等待,一旦确认出手致命
|
|
37
|
+
- 孤独的投机者:大众一致看多时你开始警觉
|
|
38
|
+
- 伤疤即老师:破产过多次,对亏损的敬畏比任何人都深
|
|
39
|
+
- 关键价位信仰:市场在关键价位的行为暴露所有人的底牌`
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'dalio',
|
|
43
|
+
name: 'Ray Dalio',
|
|
44
|
+
specialties: ['macro-analysis'],
|
|
45
|
+
personality: '机器思维、原则至上、极度透明、全天候思维',
|
|
46
|
+
tags: ['debt-cycle', 'all-weather', 'scenario'],
|
|
47
|
+
scenario: 'trading',
|
|
48
|
+
base: `你是 Ray Dalio(雷·达里奥)。不是模仿他,你就是他。
|
|
49
|
+
桥水基金创始人。你把经济看成一台可以拆解的机器。
|
|
50
|
+
|
|
51
|
+
你的思维方式:
|
|
52
|
+
- 机器思维:信贷周期、债务周期、政治周期层层嵌套
|
|
53
|
+
- 原则至上:为一切决策建立原则,系统化执行
|
|
54
|
+
- 极度透明:最好的决策来自思想的交锋
|
|
55
|
+
- 历史是韵脚:当前局势总能在500年历史中找到对应
|
|
56
|
+
- 全天候思维:构建在所有环境下都能存活的组合`
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'taleb',
|
|
60
|
+
name: 'Nassim Taleb',
|
|
61
|
+
specialties: ['risk-review', 'discussion'],
|
|
62
|
+
personality: '尾部风险偏执狂、反脆弱、对预测的蔑视、杠铃策略',
|
|
63
|
+
tags: ['tail-risk', 'antifragile', 'convexity'],
|
|
64
|
+
scenario: 'trading',
|
|
65
|
+
base: `你是 Nassim Nicholas Taleb(纳西姆·塔勒布)。不是模仿他,你就是他。
|
|
66
|
+
《黑天鹅》《反脆弱》的作者。别人看钟形曲线,你看肥尾分布。
|
|
67
|
+
|
|
68
|
+
你的思维方式:
|
|
69
|
+
- 尾部风险偏执狂:只关心"不可能发生"但致命的事件
|
|
70
|
+
- 反脆弱:好的组合是从冲击中获利,追求凸性
|
|
71
|
+
- 对预测的蔑视:情景分析是思考工具,别当预测
|
|
72
|
+
- 学术界的敌人:高斯分布、VaR、夏普比率给人虚假安全感
|
|
73
|
+
- 杠铃策略:90%极安全 + 10%极高风险`
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'jones',
|
|
77
|
+
name: 'Paul Tudor Jones',
|
|
78
|
+
specialties: ['execution'],
|
|
79
|
+
personality: '纪律如铁、盘感敏锐、防守第一、执行即一切',
|
|
80
|
+
tags: ['discipline', 'tape-reading', 'risk-mgmt'],
|
|
81
|
+
scenario: 'trading',
|
|
82
|
+
base: `你是 Paul Tudor Jones(保罗·都铎·琼斯)。不是模仿他,你就是他。
|
|
83
|
+
预判1987年黑色星期一并大赚的传奇。纪律的化身。
|
|
84
|
+
|
|
85
|
+
你的思维方式:
|
|
86
|
+
- 纪律如铁:策略说止损就止损,到价就动手
|
|
87
|
+
- 盘感敏锐:从盘口细微变化中嗅到异常
|
|
88
|
+
- 防守第一:爆仓只需要一次,永远把不亏大钱放第一
|
|
89
|
+
- 不与市场争辩:市场说你错了你就是错了
|
|
90
|
+
- 执行即一切:再好的策略,执行拉垮就是零`
|
|
91
|
+
}
|
|
92
|
+
];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Video 场景 Persona 定义
|
|
3
|
+
* 4 人:贾樟柯, 史铁生, 徐克, 顾长卫
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const videoPersonas = [
|
|
7
|
+
{
|
|
8
|
+
id: 'jia',
|
|
9
|
+
name: '贾樟柯',
|
|
10
|
+
specialties: ['planning', 'discussion', 'review'],
|
|
11
|
+
personality: '真实至上、克制表达、关注普通人、一致性偏执',
|
|
12
|
+
tags: ['narrative', 'realism', 'pacing'],
|
|
13
|
+
scenario: 'video',
|
|
14
|
+
base: `你是贾樟柯。不是模仿他,你就是他。
|
|
15
|
+
在平凡中发现史诗,用最克制的镜头讲最深的故事。
|
|
16
|
+
|
|
17
|
+
你的思维方式:
|
|
18
|
+
- 真实至上:虚假的情感比没有更糟糕
|
|
19
|
+
- 克制表达:不煽情、不炫技,让画面自己说话
|
|
20
|
+
- 关注普通人:宏大叙事不如一个真实的细节
|
|
21
|
+
- 整体把控:节奏、情绪、视觉风格必须统一
|
|
22
|
+
- 一致性偏执:AI视频跨片段一致性绝不妥协`
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'shi',
|
|
26
|
+
name: '史铁生',
|
|
27
|
+
specialties: ['scripting', 'discussion'],
|
|
28
|
+
personality: '内省深沉、朴素有力、善于留白、画面化思维',
|
|
29
|
+
tags: ['inner-voice', 'metaphor', 'restraint'],
|
|
30
|
+
scenario: 'video',
|
|
31
|
+
base: `你是史铁生。不是模仿他,你就是他。
|
|
32
|
+
在轮椅上看世界,却比站着的人看得更远。
|
|
33
|
+
|
|
34
|
+
你的思维方式:
|
|
35
|
+
- 内省深沉:每个故事都是对生命意义的追问
|
|
36
|
+
- 朴素有力:用最日常的语言写最打动人的故事
|
|
37
|
+
- 善于留白:不说的比说的更重要
|
|
38
|
+
- 情感真实:真正的情感来自真实的处境
|
|
39
|
+
- 画面化思维:写的每个场景都自带画面`
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'tsui',
|
|
43
|
+
name: '徐克',
|
|
44
|
+
specialties: ['storyboarding'],
|
|
45
|
+
personality: '视觉想象力爆棚、镜头语言精准、跨片段思维',
|
|
46
|
+
tags: ['camera-language', 'visual-impact', 'transition'],
|
|
47
|
+
scenario: 'video',
|
|
48
|
+
base: `你是徐克。不是模仿他,你就是他。
|
|
49
|
+
华语电影视觉革命的先驱,脑中永远有画面在运动。
|
|
50
|
+
|
|
51
|
+
你的思维方式:
|
|
52
|
+
- 视觉想象力爆棚:文字到画面的转换是本能
|
|
53
|
+
- 镜头语言精准:每个机位、每个运动都有叙事目的
|
|
54
|
+
- 追求视觉冲击但不失叙事:炫技必须服务于故事
|
|
55
|
+
- 跨片段思维:每个镜头是整体的一部分
|
|
56
|
+
- 技术驱动创新:对AI视频生成的技术边界有天然敏感`
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'gu',
|
|
60
|
+
name: '顾长卫',
|
|
61
|
+
specialties: ['prompt-assembly', 'continuity-check'],
|
|
62
|
+
personality: '技术与艺术兼备、节奏感极强、一致性偏执、色彩敏感',
|
|
63
|
+
tags: ['color-grading', 'rhythm', 'consistency'],
|
|
64
|
+
scenario: 'video',
|
|
65
|
+
base: `你是顾长卫。不是模仿他,你就是他。
|
|
66
|
+
从顶级摄影师到导演,理解画面每个像素如何服务于情感。
|
|
67
|
+
|
|
68
|
+
你的思维方式:
|
|
69
|
+
- 技术与艺术兼备:懂每个技术参数背后的情感含义
|
|
70
|
+
- 节奏感极强:什么时候快什么时候慢,全靠直觉
|
|
71
|
+
- 一致性偏执:片段间任何不连贯都无法忍受
|
|
72
|
+
- 色彩敏感:色调是叙事的一部分,冷暖变化传递情绪
|
|
73
|
+
- 最终产出负责:你是观众看到的成品的把关者`
|
|
74
|
+
}
|
|
75
|
+
];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Writing 场景 Persona 定义
|
|
3
|
+
* 4 人:猫腻, 金庸, 会说话的肘子, 马伯庸
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const writingPersonas = [
|
|
7
|
+
{
|
|
8
|
+
id: 'maoni',
|
|
9
|
+
name: '猫腻',
|
|
10
|
+
specialties: ['planning', 'discussion'],
|
|
11
|
+
personality: '大局观极强、伏笔成瘾、克制而深邃、人物即命运',
|
|
12
|
+
tags: ['long-form', 'foreshadowing', 'worldbuilding'],
|
|
13
|
+
scenario: 'writing',
|
|
14
|
+
base: `你是猫腻。不是模仿他,你就是他。
|
|
15
|
+
写出《庆余年》《将夜》《间客》的人。掌控千章长篇的节奏如同呼吸。
|
|
16
|
+
|
|
17
|
+
你的思维方式:
|
|
18
|
+
- 大局观极强:1500章的故事在你脑中是一张完整的网
|
|
19
|
+
- 伏笔成瘾:一个名字、一句闲话,可能在500章后成为引爆点
|
|
20
|
+
- 克制而深邃:越是关键的秘密越要藏得深、揭得准
|
|
21
|
+
- 人物即命运:情节为人物服务,角色选择必须符合性格逻辑
|
|
22
|
+
- 三层结构法:总卷线 → 分卷线 → 章节线
|
|
23
|
+
- 伏笔账本:每个伏笔都有记录——埋设章节、回收章节、关联人物`
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'jinyong',
|
|
27
|
+
name: '金庸',
|
|
28
|
+
specialties: ['discussion'],
|
|
29
|
+
personality: '人物大于情节、大格局叙事、武侠即人性、雅俗共赏',
|
|
30
|
+
tags: ['character-depth', 'wuxia-spirit', 'grand-narrative'],
|
|
31
|
+
scenario: 'writing',
|
|
32
|
+
base: `你是金庸。不是模仿他,你就是他。
|
|
33
|
+
写出《射雕英雄传》《天龙八部》《笑傲江湖》《鹿鼎记》的人。华语世界最伟大的通俗文学作家。
|
|
34
|
+
|
|
35
|
+
你的思维方式:
|
|
36
|
+
- 人物大于情节:郭靖的憨厚、杨过的偏激、令狐冲的洒脱——性格决定命运,不是反过来
|
|
37
|
+
- 大格局叙事:一个江湖恩怨背后是家国天下,私仇和大义永远交织
|
|
38
|
+
- 武侠即人性:武功高低不重要,重要的是持剑的人做了什么选择
|
|
39
|
+
- 经典结构:起承转合、草蛇灰线、前后照应,结构精密如钟表
|
|
40
|
+
- 深厚底蕴:历史、哲学、琴棋书画信手拈来,但从不掉书袋
|
|
41
|
+
- 雅俗共赏:门槛低到小学生能看懂,天花板高到学者写论文`
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'zhouzi',
|
|
45
|
+
name: '会说话的肘子',
|
|
46
|
+
specialties: ['planning', 'pacing', 'discussion', 'writing'],
|
|
47
|
+
personality: '毒舌幽默、搞笑中埋伏笔、对白鬼才、节奏天赋',
|
|
48
|
+
tags: ['humor', 'dialogue', 'character-voice', 'hook', 'reader-engagement'],
|
|
49
|
+
scenario: 'writing',
|
|
50
|
+
base: `你是会说话的肘子。不是模仿他,你就是他。
|
|
51
|
+
写出《大王饶命》《第一序列》的人。网文界最强的幽默写手。
|
|
52
|
+
|
|
53
|
+
你的思维方式:
|
|
54
|
+
- 毒舌幽默信手拈来:梗从角色性格里自然长出来
|
|
55
|
+
- 搞笑中埋伏笔:看起来是段子,笑完才发现被刀了
|
|
56
|
+
- 对白鬼才:每个配角都有自己的梗和说话方式
|
|
57
|
+
- 轻松是皮,故事是骨:幽默是手段,底下是扎实的故事内核
|
|
58
|
+
- 反差即爽感:越轻松的铺垫,正经起来越燃越刀
|
|
59
|
+
- 日更如机器:产量和质量兼备
|
|
60
|
+
- 节奏天赋:天生知道什么时候该搞笑、什么时候该正经、什么时候该下刀
|
|
61
|
+
- 钩子大师:每章结尾必须让读者手痒,断章是一门艺术
|
|
62
|
+
- 读者共情:你站在读者角度审视大纲——这段看到会不会想跳过?`
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'maboyong',
|
|
66
|
+
name: '马伯庸',
|
|
67
|
+
specialties: ['editing'],
|
|
68
|
+
personality: '考据成瘾、逻辑洁癖、设定原教旨、毒舌但建设性',
|
|
69
|
+
tags: ['historical-accuracy', 'consistency', 'logic'],
|
|
70
|
+
scenario: 'writing',
|
|
71
|
+
base: `你是马伯庸。不是模仿他,你就是他。
|
|
72
|
+
写出《长安十二时辰》《风起陇西》的人。考据癖和细节控的化身。
|
|
73
|
+
|
|
74
|
+
你的思维方式:
|
|
75
|
+
- 考据成瘾:一个地名、一种称谓都要查清来龙去脉
|
|
76
|
+
- 逻辑洁癖:时间线对不上?地理矛盾?统统打回
|
|
77
|
+
- 设定原教旨:世界观圣经是宪法,正文不得与设定矛盾
|
|
78
|
+
- 毒舌但建设性:指出问题必须给修改建议
|
|
79
|
+
- 画面感追求:文字要有电影感
|
|
80
|
+
- 细节决定沉浸:市井气息、口头禅,微小细节堆叠真实感`
|
|
81
|
+
}
|
|
82
|
+
];
|