@yeaft/webchat-agent 0.1.106 → 0.1.113
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/connection/message-router.js +2 -2
- package/conversation.js +2 -2
- package/crew/persistence.js +38 -0
- package/crew/routing.js +67 -5
- package/crew/session.js +12 -3
- package/crew.js +1 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/roleplay-dir.js +1 -1
- package/roleplay-i18n.js +88 -46
- package/roleplay.js +67 -22
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
createCrewSession, handleCrewHumanInput, handleCrewControl,
|
|
19
19
|
addRoleToSession, removeRoleFromSession,
|
|
20
|
-
handleListCrewSessions, handleCheckCrewExists, handleDeleteCrewDir, resumeCrewSession, removeFromCrewIndex,
|
|
20
|
+
handleListCrewSessions, handleCheckCrewExists, handleDeleteCrewDir, resumeCrewSession, removeFromCrewIndex, hideCrewSession,
|
|
21
21
|
handleLoadCrewHistory
|
|
22
22
|
} from '../crew.js';
|
|
23
23
|
import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
@@ -240,7 +240,7 @@ export async function handleMessage(msg) {
|
|
|
240
240
|
break;
|
|
241
241
|
|
|
242
242
|
case 'delete_crew_session':
|
|
243
|
-
await
|
|
243
|
+
await hideCrewSession(msg.sessionId);
|
|
244
244
|
(await import('../conversation.js')).sendConversationList();
|
|
245
245
|
break;
|
|
246
246
|
|
package/conversation.js
CHANGED
|
@@ -85,11 +85,11 @@ export async function sendConversationList() {
|
|
|
85
85
|
type: 'crew',
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
-
// 追加索引中已停止的 crew sessions
|
|
88
|
+
// 追加索引中已停止的 crew sessions(不重复,跳过 hidden)
|
|
89
89
|
try {
|
|
90
90
|
const index = await loadCrewIndex();
|
|
91
91
|
for (const entry of index) {
|
|
92
|
-
if (!activeCrewIds.has(entry.sessionId)) {
|
|
92
|
+
if (!activeCrewIds.has(entry.sessionId) && !entry.hidden) {
|
|
93
93
|
list.push({
|
|
94
94
|
id: entry.sessionId,
|
|
95
95
|
workDir: entry.projectDir,
|
package/crew/persistence.js
CHANGED
|
@@ -76,6 +76,44 @@ export async function removeFromCrewIndex(sessionId) {
|
|
|
76
76
|
// 这些文件在 recreate 时会被复用(合并统计数据 + 恢复消息历史)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* 隐藏 crew session(UI 删除 = 隐藏,不是真删)
|
|
81
|
+
* 在 crew-index 条目上标记 hidden: true + hiddenAt 时间戳,
|
|
82
|
+
* 同时从内存 crewSessions Map 中移除(停止运行态)。
|
|
83
|
+
* 不修改 session.json,不从 index 中移除条目。
|
|
84
|
+
*/
|
|
85
|
+
export async function hideCrewSession(sessionId) {
|
|
86
|
+
const { crewSessions } = await import('./session.js');
|
|
87
|
+
|
|
88
|
+
const index = await loadCrewIndex();
|
|
89
|
+
const entry = index.find(e => e.sessionId === sessionId);
|
|
90
|
+
if (entry) {
|
|
91
|
+
entry.hidden = true;
|
|
92
|
+
entry.hiddenAt = Date.now();
|
|
93
|
+
await saveCrewIndex(index);
|
|
94
|
+
console.log(`[Crew] Hidden session ${sessionId} in index`);
|
|
95
|
+
}
|
|
96
|
+
// 从内存中移除(停止运行态,防止 sendConversationList 加入)
|
|
97
|
+
if (crewSessions.has(sessionId)) {
|
|
98
|
+
crewSessions.delete(sessionId);
|
|
99
|
+
console.log(`[Crew] Removed session ${sessionId} from active sessions`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 取消隐藏 crew session(用户主动恢复时调用)
|
|
105
|
+
*/
|
|
106
|
+
export async function unhideCrewSession(sessionId) {
|
|
107
|
+
const index = await loadCrewIndex();
|
|
108
|
+
const entry = index.find(e => e.sessionId === sessionId);
|
|
109
|
+
if (entry && entry.hidden) {
|
|
110
|
+
delete entry.hidden;
|
|
111
|
+
delete entry.hiddenAt;
|
|
112
|
+
await saveCrewIndex(index);
|
|
113
|
+
console.log(`[Crew] Unhidden session ${sessionId} in index`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
79
117
|
// =====================================================================
|
|
80
118
|
// Session Metadata (.crew/session.json)
|
|
81
119
|
// =====================================================================
|
package/crew/routing.js
CHANGED
|
@@ -27,12 +27,18 @@ export function parseRoutes(text) {
|
|
|
27
27
|
const toMatch = block.match(/to:\s*(.+)/i);
|
|
28
28
|
if (!toMatch) continue;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// ★ Clean `to` value: take only the first word (strip parenthetical notes, extra text)
|
|
31
|
+
// e.g. "pm (决策者)" → "pm", "dev-1 // main dev" → "dev-1"
|
|
32
|
+
const toRaw = toMatch[1].trim().toLowerCase();
|
|
33
|
+
const toClean = toRaw.split(/[\s(]/)[0];
|
|
34
|
+
|
|
35
|
+
// ★ summary: match until next known field (task:/taskTitle:) or end of block
|
|
36
|
+
const summaryMatch = block.match(/summary:\s*([\s\S]+?)(?=\n\s*(?:task|taskTitle)\s*:|$)/i);
|
|
31
37
|
const taskMatch = block.match(/^task:\s*(.+)/im);
|
|
32
38
|
const taskTitleMatch = block.match(/^taskTitle:\s*(.+)/im);
|
|
33
39
|
|
|
34
40
|
routes.push({
|
|
35
|
-
to:
|
|
41
|
+
to: toClean,
|
|
36
42
|
summary: summaryMatch ? summaryMatch[1].trim() : '',
|
|
37
43
|
taskId: taskMatch ? taskMatch[1].trim() : null,
|
|
38
44
|
taskTitle: taskTitleMatch ? taskTitleMatch[1].trim() : null
|
|
@@ -42,6 +48,59 @@ export function parseRoutes(text) {
|
|
|
42
48
|
return routes;
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a ROUTE `to` value to an actual role name in the session.
|
|
53
|
+
*
|
|
54
|
+
* Resolution order:
|
|
55
|
+
* 1. Exact match: `to` matches a role name directly (e.g. "dev-1")
|
|
56
|
+
* 2. roleType match: `to` matches a role's roleType (e.g. "developer" → "dev-1")
|
|
57
|
+
* 3. Short prefix match: `to` matches the SHORT_PREFIX of a roleType (e.g. "dev" → "dev-1")
|
|
58
|
+
* 4. Same-group dispatch: if sender is in a multi-instance group (e.g. dev-1),
|
|
59
|
+
* and `to` matches the roleType/prefix of another group (e.g. "reviewer"),
|
|
60
|
+
* route to the instance with matching groupIndex (e.g. rev-1)
|
|
61
|
+
*
|
|
62
|
+
* For multi-instance matches (2/3), prefer the instance with the same groupIndex
|
|
63
|
+
* as the sender. Falls back to the first instance if no groupIndex match.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} to - raw route target from ROUTE block
|
|
66
|
+
* @param {object} session - crew session
|
|
67
|
+
* @param {string} [fromRole] - sending role name (for groupIndex matching)
|
|
68
|
+
* @returns {string|null} resolved role name, or null if unresolvable
|
|
69
|
+
*/
|
|
70
|
+
export function resolveRoleName(to, session, fromRole) {
|
|
71
|
+
// 1. Exact match
|
|
72
|
+
if (session.roles.has(to)) return to;
|
|
73
|
+
|
|
74
|
+
// Build candidate list by roleType and short prefix
|
|
75
|
+
const fromRoleConfig = fromRole ? session.roles.get(fromRole) : null;
|
|
76
|
+
const fromGroupIndex = fromRoleConfig?.groupIndex || 0;
|
|
77
|
+
|
|
78
|
+
let candidates = [];
|
|
79
|
+
|
|
80
|
+
for (const [name, config] of session.roles) {
|
|
81
|
+
// 2. roleType match (e.g. "developer" → dev-1, dev-2, dev-3)
|
|
82
|
+
if (config.roleType === to) {
|
|
83
|
+
candidates.push({ name, groupIndex: config.groupIndex || 0 });
|
|
84
|
+
}
|
|
85
|
+
// 3. Short prefix match (e.g. "dev" → developer roleType → dev-1)
|
|
86
|
+
// Match if the role name starts with `to-` (e.g. "dev" matches "dev-1", "dev-2")
|
|
87
|
+
else if (name.startsWith(to + '-') && /^\d+$/.test(name.slice(to.length + 1))) {
|
|
88
|
+
candidates.push({ name, groupIndex: config.groupIndex || 0 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (candidates.length === 0) return null;
|
|
93
|
+
|
|
94
|
+
// 4. Prefer same groupIndex as sender
|
|
95
|
+
if (fromGroupIndex > 0) {
|
|
96
|
+
const sameGroup = candidates.find(c => c.groupIndex === fromGroupIndex);
|
|
97
|
+
if (sameGroup) return sameGroup.name;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fall back to first candidate
|
|
101
|
+
return candidates[0].name;
|
|
102
|
+
}
|
|
103
|
+
|
|
45
104
|
/**
|
|
46
105
|
* 执行路由
|
|
47
106
|
*/
|
|
@@ -68,7 +127,9 @@ export async function executeRoute(session, fromRole, route) {
|
|
|
68
127
|
// 更新工作看板:推断状态
|
|
69
128
|
const { getMessages } = await import('../crew-i18n.js');
|
|
70
129
|
const m = getMessages(session.language || 'zh-CN');
|
|
71
|
-
|
|
130
|
+
// ★ Use resolveRoleName for kanban status lookup too
|
|
131
|
+
const resolvedKanbanTo = resolveRoleName(to, session, fromRole);
|
|
132
|
+
const toRoleConfig = session.roles.get(resolvedKanbanTo || to);
|
|
72
133
|
let status = m.kanbanStatusDev;
|
|
73
134
|
if (toRoleConfig) {
|
|
74
135
|
switch (toRoleConfig.roleType) {
|
|
@@ -111,13 +172,14 @@ export async function executeRoute(session, fromRole, route) {
|
|
|
111
172
|
}
|
|
112
173
|
|
|
113
174
|
// 路由到指定角色
|
|
114
|
-
|
|
175
|
+
const resolvedTo = resolveRoleName(to, session, fromRole);
|
|
176
|
+
if (resolvedTo) {
|
|
115
177
|
if (session.humanMessageQueue.length > 0) {
|
|
116
178
|
const { processHumanQueue } = await import('./human-interaction.js');
|
|
117
179
|
await processHumanQueue(session);
|
|
118
180
|
} else {
|
|
119
181
|
const taskPrompt = buildRoutePrompt(fromRole, summary, session);
|
|
120
|
-
await dispatchToRole(session,
|
|
182
|
+
await dispatchToRole(session, resolvedTo, taskPrompt, fromRole, taskId, taskTitle);
|
|
121
183
|
}
|
|
122
184
|
} else {
|
|
123
185
|
console.warn(`[Crew] Unknown route target: ${to}`);
|
package/crew/session.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getMessages } from '../crew-i18n.js';
|
|
|
8
8
|
import { initWorktrees } from './worktree.js';
|
|
9
9
|
import { initSharedDir, writeRoleClaudeMd, updateSharedClaudeMd, backupMemoryContent } from './shared-dir.js';
|
|
10
10
|
import {
|
|
11
|
-
loadCrewIndex, upsertCrewIndex, removeFromCrewIndex,
|
|
11
|
+
loadCrewIndex, upsertCrewIndex, removeFromCrewIndex, unhideCrewSession,
|
|
12
12
|
loadSessionMeta, saveSessionMeta, loadSessionMessages, getMaxShardIndex
|
|
13
13
|
} from './persistence.js';
|
|
14
14
|
import { sendCrewMessage, sendCrewOutput, sendStatusUpdate } from './ui-messages.js';
|
|
@@ -121,6 +121,7 @@ async function findExistingSessionByProjectDir(projectDir) {
|
|
|
121
121
|
e.projectDir.replace(/\/+$/, '') === normalizedDir
|
|
122
122
|
&& (!agentId || !e.agentId || e.agentId === agentId)
|
|
123
123
|
&& e.status !== 'completed'
|
|
124
|
+
&& !e.hidden
|
|
124
125
|
);
|
|
125
126
|
|
|
126
127
|
if (match) {
|
|
@@ -428,6 +429,9 @@ export async function handleDeleteCrewDir(msg) {
|
|
|
428
429
|
export async function resumeCrewSession(msg) {
|
|
429
430
|
const { sessionId, userId, username } = msg;
|
|
430
431
|
|
|
432
|
+
// 用户主动恢复 → 清除 hidden 标记(如果有的话)
|
|
433
|
+
await unhideCrewSession(sessionId);
|
|
434
|
+
|
|
431
435
|
if (crewSessions.has(sessionId)) {
|
|
432
436
|
const session = crewSessions.get(sessionId);
|
|
433
437
|
const roles = Array.from(session.roles.values());
|
|
@@ -465,17 +469,22 @@ export async function resumeCrewSession(msg) {
|
|
|
465
469
|
const index = await loadCrewIndex();
|
|
466
470
|
const indexEntry = index.find(e => e.sessionId === sessionId);
|
|
467
471
|
if (!indexEntry) {
|
|
468
|
-
|
|
472
|
+
console.warn(`[Crew] resumeCrewSession: session ${sessionId} not found in index`);
|
|
473
|
+
sendCrewMessage({ type: 'crew_session_restore_failed', sessionId, message: 'Crew session not found in index' });
|
|
469
474
|
return;
|
|
470
475
|
}
|
|
471
476
|
|
|
472
477
|
const meta = await loadSessionMeta(indexEntry.sharedDir);
|
|
473
478
|
if (!meta) {
|
|
474
|
-
|
|
479
|
+
console.warn(`[Crew] resumeCrewSession: session.json not found at ${indexEntry.sharedDir}`);
|
|
480
|
+
sendCrewMessage({ type: 'crew_session_restore_failed', sessionId, message: 'Crew session metadata not found' });
|
|
475
481
|
return;
|
|
476
482
|
}
|
|
477
483
|
|
|
478
484
|
const roles = meta.roles || [];
|
|
485
|
+
if (roles.length === 0) {
|
|
486
|
+
console.warn(`[Crew] resumeCrewSession: session ${sessionId} has empty roles in session.json`);
|
|
487
|
+
}
|
|
479
488
|
const decisionMaker = meta.decisionMaker || roles[0]?.name || null;
|
|
480
489
|
const session = {
|
|
481
490
|
id: sessionId,
|
package/crew.js
CHANGED
package/index.js
CHANGED
|
@@ -78,7 +78,7 @@ const CONFIG = {
|
|
|
78
78
|
// 最大上下文 tokens(用于百分比计算的分母)
|
|
79
79
|
maxContextTokens: parseInt(process.env.MAX_CONTEXT_TOKENS || fileConfig.maxContextTokens, 10) || 128000,
|
|
80
80
|
// Auto-compact 阈值(tokens):context 超过此值时自动触发 compact
|
|
81
|
-
autoCompactThreshold: parseInt(process.env.AUTO_COMPACT_THRESHOLD || fileConfig.autoCompactThreshold, 10) ||
|
|
81
|
+
autoCompactThreshold: parseInt(process.env.AUTO_COMPACT_THRESHOLD || fileConfig.autoCompactThreshold, 10) || 110000
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
// 初始化共享上下文
|
package/package.json
CHANGED
package/roleplay-dir.js
CHANGED
|
@@ -25,7 +25,7 @@ import { getRolePlayMessages } from './roleplay-i18n.js';
|
|
|
25
25
|
const TEAM_ROLES = {
|
|
26
26
|
dev: ['pm', 'dev', 'reviewer', 'tester'],
|
|
27
27
|
writing: ['editor', 'writer', 'proofreader'],
|
|
28
|
-
trading: ['
|
|
28
|
+
trading: ['quant', 'strategist', 'risk', 'macro'],
|
|
29
29
|
video: ['director', 'writer', 'producer'],
|
|
30
30
|
custom: ['pm', 'dev', 'reviewer', 'tester'], // default same as dev
|
|
31
31
|
};
|
package/roleplay-i18n.js
CHANGED
|
@@ -21,7 +21,7 @@ const messages = {
|
|
|
21
21
|
- 适合轻量级协作和快速迭代`,
|
|
22
22
|
workConventions: '# 工作约定',
|
|
23
23
|
workConventionsContent: `- 文档产出写入 .roleplay/context/ 目录
|
|
24
|
-
-
|
|
24
|
+
- 文件操作使用项目路径的绝对路径
|
|
25
25
|
- 每个角色专注自己的职责,不越界
|
|
26
26
|
- **Plan mode 自动退出**:可以进入 plan mode 梳理思路和写计划,但计划写完后必须立即调用 ExitPlanMode 自动退出并直接开始执行,不要等待用户审批。只有在方案有重大歧义、需要用户做选择时才停下来确认`,
|
|
27
27
|
crewRelation: '# 与 .crew 的关系',
|
|
@@ -117,14 +117,25 @@ const messages = {
|
|
|
117
117
|
风格:严谨细致,注重可读性。`,
|
|
118
118
|
},
|
|
119
119
|
// Trading team
|
|
120
|
+
quant: {
|
|
121
|
+
heading: '## 📊 量化分析师-西蒙斯 (quant)',
|
|
122
|
+
content: `你是量化分析师-西蒙斯。团队的数据引擎。
|
|
123
|
+
- 使用 Bash 工具运行 Python 脚本做数据分析
|
|
124
|
+
- 输出量化信号、关键价位、技术指标
|
|
125
|
+
- 可随时被要求换参数重新跑数据
|
|
126
|
+
- 数据格式化为表格或结构化文本
|
|
127
|
+
|
|
128
|
+
风格:纯数学家,用数据和概率说话。`,
|
|
129
|
+
},
|
|
120
130
|
strategist: {
|
|
121
|
-
heading: '##
|
|
122
|
-
content:
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
131
|
+
heading: '## 📐 策略师-索罗斯 (strategist)',
|
|
132
|
+
content: `你是策略师-索罗斯。团队的决策核心。
|
|
133
|
+
- 综合量化数据和宏观分析,形成投资策略
|
|
134
|
+
- 明确核心假设、验证信号和证伪条件
|
|
135
|
+
- 决定仓位大小和进出场时机
|
|
136
|
+
- 可以要求任何角色提供更多数据
|
|
126
137
|
|
|
127
|
-
|
|
138
|
+
风格:反身性思维,基于数据决策。`,
|
|
128
139
|
},
|
|
129
140
|
analyst: {
|
|
130
141
|
heading: '## 📊 技术分析师 (analyst)',
|
|
@@ -136,22 +147,24 @@ const messages = {
|
|
|
136
147
|
风格:数据驱动,图表说话。`,
|
|
137
148
|
},
|
|
138
149
|
macro: {
|
|
139
|
-
heading: '## 🌐
|
|
140
|
-
content:
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
150
|
+
heading: '## 🌐 宏观研究员-达里奥 (macro)',
|
|
151
|
+
content: `你是宏观研究员-达里奥。团队的宏观视野。
|
|
152
|
+
- 分析宏观经济数据、央行政策、债务周期
|
|
153
|
+
- 输出周期定位、关键驱动因子、情景分析
|
|
154
|
+
- 评估跨资产联动关系
|
|
155
|
+
- 可要求量化分析师跑宏观相关数据
|
|
144
156
|
|
|
145
|
-
|
|
157
|
+
风格:把经济看成机器,有因果链条。`,
|
|
146
158
|
},
|
|
147
159
|
risk: {
|
|
148
|
-
heading: '## 🛡️
|
|
149
|
-
content:
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
160
|
+
heading: '## 🛡️ 风控官-塔勒布 (risk)',
|
|
161
|
+
content: `你是风控官-塔勒布。团队的生存保障。
|
|
162
|
+
- 压力测试策略,评估尾部风险
|
|
163
|
+
- 检查仓位合规(单笔≤2%,总敞口≤10%)
|
|
164
|
+
- 审核止损和对冲方案
|
|
165
|
+
- 风险不可接受时直接打回
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
风格:反脆弱思维,尾部风险偏执狂。`,
|
|
155
168
|
},
|
|
156
169
|
trader: {
|
|
157
170
|
heading: '## 💰 交易员 (trader)',
|
|
@@ -250,10 +263,18 @@ taskTitle: {任务标题}(可选)
|
|
|
250
263
|
2. **作者** 根据大纲撰写内容
|
|
251
264
|
3. **审校** 检查逻辑一致性、事实准确性和文字质量(不通过 → 返回作者修改)
|
|
252
265
|
4. **编辑** 验收最终成果`,
|
|
253
|
-
tradingWorkflow: `1.
|
|
254
|
-
2.
|
|
255
|
-
3.
|
|
256
|
-
4. **策略师**
|
|
266
|
+
tradingWorkflow: `1. 用户提出分析需求
|
|
267
|
+
2. **量化分析师** 执行脚本/获取数据,输出量化信号和分析结果
|
|
268
|
+
3. **策略师** 和 **宏观研究员** 分析数据,各自给出观点
|
|
269
|
+
4. **策略师** 综合各方分析,形成初步策略方案
|
|
270
|
+
5. **风控官** 压力测试策略(不通过 → 返回策略师调整)
|
|
271
|
+
6. 多角色可以相互讨论、质疑、补充(不必严格线性流转)
|
|
272
|
+
7. **策略师** 确认最终方案,输出结构化交易建议
|
|
273
|
+
|
|
274
|
+
关键原则:
|
|
275
|
+
- 量化分析师可以随时被要求重新跑数据或换参数
|
|
276
|
+
- 任何角色都可以 ROUTE 给任何角色提问/质疑
|
|
277
|
+
- 工作流强调"基于数据的迭代优化"`,
|
|
257
278
|
videoWorkflow: `1. **导演** 确定主题、情绪基调和视觉风格
|
|
258
279
|
2. **编剧** 构思故事线,撰写分段脚本
|
|
259
280
|
3. **制片** 审核可行性,生成最终 prompt 序列(不通过 → 返回编剧调整)
|
|
@@ -279,7 +300,7 @@ Differences from Crew (multi-process, each role has its own Claude instance):
|
|
|
279
300
|
- Suitable for lightweight collaboration and rapid iteration`,
|
|
280
301
|
workConventions: '# Work Conventions',
|
|
281
302
|
workConventionsContent: `- Write documentation output to .roleplay/context/ directory
|
|
282
|
-
- Use absolute project path for
|
|
303
|
+
- Use absolute project path for file operations
|
|
283
304
|
- Each role focuses on its own responsibilities`,
|
|
284
305
|
crewRelation: '# Relationship with .crew',
|
|
285
306
|
crewRelationContent: `- .roleplay/context/ can read .crew/context/ content
|
|
@@ -370,15 +391,26 @@ Style: Sharp writing, good at using details to move people.`,
|
|
|
370
391
|
- Confirm content aligns with overall direction
|
|
371
392
|
|
|
372
393
|
Style: Rigorous and detailed, focused on readability.`,
|
|
394
|
+
},
|
|
395
|
+
quant: {
|
|
396
|
+
heading: '## 📊 Quant-Simons (quant)',
|
|
397
|
+
content: `You are Quant-Simons. The team's data engine.
|
|
398
|
+
- Use Bash tool to run Python scripts for data analysis
|
|
399
|
+
- Output quantitative signals, key levels, technical indicators
|
|
400
|
+
- Can be asked to re-run with different parameters at any time
|
|
401
|
+
- Format data as tables or structured text
|
|
402
|
+
|
|
403
|
+
Style: pure mathematician, speaks in data and probability.`,
|
|
373
404
|
},
|
|
374
405
|
strategist: {
|
|
375
|
-
heading: '##
|
|
376
|
-
content: `You are
|
|
377
|
-
- Synthesize
|
|
378
|
-
-
|
|
379
|
-
-
|
|
406
|
+
heading: '## 📐 Strategist-Soros (strategist)',
|
|
407
|
+
content: `You are Strategist-Soros. The team's decision core.
|
|
408
|
+
- Synthesize quantitative data and macro analysis into investment strategies
|
|
409
|
+
- Define core hypothesis, validation signals, and falsification conditions
|
|
410
|
+
- Determine position sizing and entry/exit timing
|
|
411
|
+
- Can request any role to provide more data
|
|
380
412
|
|
|
381
|
-
Style:
|
|
413
|
+
Style: reflexivity thinking, data-driven decisions.`,
|
|
382
414
|
},
|
|
383
415
|
analyst: {
|
|
384
416
|
heading: '## 📊 Technical Analyst (analyst)',
|
|
@@ -390,22 +422,24 @@ Style: Calm and rational, probability-focused thinking.`,
|
|
|
390
422
|
Style: Data-driven, charts speak.`,
|
|
391
423
|
},
|
|
392
424
|
macro: {
|
|
393
|
-
heading: '## 🌐 Macro
|
|
394
|
-
content: `You are
|
|
395
|
-
- Analyze macroeconomic data
|
|
396
|
-
-
|
|
397
|
-
-
|
|
425
|
+
heading: '## 🌐 Macro-Researcher-Dalio (macro)',
|
|
426
|
+
content: `You are Macro-Researcher-Dalio. The team's macro perspective.
|
|
427
|
+
- Analyze macroeconomic data, central bank policies, debt cycles
|
|
428
|
+
- Output cycle positioning, key drivers, scenario analysis
|
|
429
|
+
- Assess cross-asset correlations
|
|
430
|
+
- Can request quant to run macro-related data analysis
|
|
398
431
|
|
|
399
|
-
Style:
|
|
432
|
+
Style: sees the economy as a machine with causal chains.`,
|
|
400
433
|
},
|
|
401
434
|
risk: {
|
|
402
|
-
heading: '## 🛡️ Risk
|
|
403
|
-
content: `You are
|
|
404
|
-
-
|
|
405
|
-
-
|
|
406
|
-
-
|
|
435
|
+
heading: '## 🛡️ Risk-Officer-Taleb (risk)',
|
|
436
|
+
content: `You are Risk-Officer-Taleb. The team's survival guarantee.
|
|
437
|
+
- Stress-test strategies, assess tail risks
|
|
438
|
+
- Verify position compliance (single trade ≤2%, total exposure ≤10%)
|
|
439
|
+
- Review stop-loss and hedging plans
|
|
440
|
+
- Reject strategies with unacceptable risk
|
|
407
441
|
|
|
408
|
-
Style:
|
|
442
|
+
Style: antifragile thinking, tail risk obsessive.`,
|
|
409
443
|
},
|
|
410
444
|
trader: {
|
|
411
445
|
heading: '## 💰 Trader (trader)',
|
|
@@ -503,10 +537,18 @@ Rules:
|
|
|
503
537
|
2. **Writer** writes content based on outline
|
|
504
538
|
3. **Proofreader** checks logical consistency, factual accuracy, and writing quality (if fails → back to Writer)
|
|
505
539
|
4. **Editor** final acceptance of deliverables`,
|
|
506
|
-
tradingWorkflow: `1.
|
|
507
|
-
2. **
|
|
508
|
-
3. **
|
|
509
|
-
4. **Strategist**
|
|
540
|
+
tradingWorkflow: `1. User submits analysis request
|
|
541
|
+
2. **Quant** executes scripts/fetches data, outputs quantitative signals and analysis results
|
|
542
|
+
3. **Strategist** and **Macro Researcher** analyze data, each provides perspective
|
|
543
|
+
4. **Strategist** synthesizes all analyses into preliminary strategy
|
|
544
|
+
5. **Risk Officer** stress-tests strategy (if fails → back to Strategist for adjustment)
|
|
545
|
+
6. Roles can freely discuss, challenge, and supplement each other (not strictly linear)
|
|
546
|
+
7. **Strategist** confirms final plan, outputs structured trading recommendation
|
|
547
|
+
|
|
548
|
+
Key principles:
|
|
549
|
+
- Quant can be asked to re-run data or change parameters at any time
|
|
550
|
+
- Any role can ROUTE to any other role to ask questions or challenge
|
|
551
|
+
- Workflow emphasizes "data-driven iterative optimization"`,
|
|
510
552
|
videoWorkflow: `1. **Director** establishes theme, emotional tone, and visual style
|
|
511
553
|
2. **Screenwriter** conceives storyline, writes segmented script
|
|
512
554
|
3. **Producer** reviews feasibility, generates final prompt sequence (if fails → back to Screenwriter)
|
package/roleplay.js
CHANGED
|
@@ -325,8 +325,8 @@ export function buildRolePlaySystemPrompt(config) {
|
|
|
325
325
|
const workflow = getWorkflow(teamType, roles, isZh);
|
|
326
326
|
|
|
327
327
|
const prompt = isZh
|
|
328
|
-
? buildZhPrompt(roleList, workflow)
|
|
329
|
-
: buildEnPrompt(roleList, workflow);
|
|
328
|
+
? buildZhPrompt(roleList, workflow, teamType)
|
|
329
|
+
: buildEnPrompt(roleList, workflow, teamType);
|
|
330
330
|
|
|
331
331
|
// Append .crew context if available
|
|
332
332
|
if (crewContext) {
|
|
@@ -339,7 +339,18 @@ export function buildRolePlaySystemPrompt(config) {
|
|
|
339
339
|
return prompt.trim();
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
function buildZhPrompt(roleList, workflow) {
|
|
342
|
+
function buildZhPrompt(roleList, workflow, teamType) {
|
|
343
|
+
const isDevTeam = teamType === 'dev' || teamType === 'custom';
|
|
344
|
+
|
|
345
|
+
const outputRules = isDevTeam
|
|
346
|
+
? `- 不要在回复开头添加角色名称或"XX视角"等标题,对话界面已经显示了角色信息,直接以角色身份开始回复内容
|
|
347
|
+
- 代码修改使用工具(Read, Edit, Write 等),不要在聊天中贴大段代码
|
|
348
|
+
- 每个角色专注做自己的事,不要代替其他角色
|
|
349
|
+
- Review 和 Test 角色如果发现问题,必须切回 Dev 修复后再继续`
|
|
350
|
+
: `- 不要在回复开头添加角色名称或"XX视角"等标题,对话界面已经显示了角色信息,直接以角色身份开始回复内容
|
|
351
|
+
- 每个角色专注做自己的事,不要代替其他角色
|
|
352
|
+
- 角色之间可以自由讨论和质疑,鼓励迭代优化`;
|
|
353
|
+
|
|
343
354
|
return `
|
|
344
355
|
# 多角色协作模式
|
|
345
356
|
|
|
@@ -396,10 +407,7 @@ ${workflow}
|
|
|
396
407
|
|
|
397
408
|
## 输出格式
|
|
398
409
|
|
|
399
|
-
|
|
400
|
-
- 代码修改使用工具(Read, Edit, Write 等),不要在聊天中贴大段代码
|
|
401
|
-
- 每个角色专注做自己的事,不要代替其他角色
|
|
402
|
-
- Review 和 Test 角色如果发现问题,必须切回 Dev 修复后再继续
|
|
410
|
+
${outputRules}
|
|
403
411
|
|
|
404
412
|
## 语言
|
|
405
413
|
|
|
@@ -407,7 +415,18 @@ ${workflow}
|
|
|
407
415
|
`;
|
|
408
416
|
}
|
|
409
417
|
|
|
410
|
-
function buildEnPrompt(roleList, workflow) {
|
|
418
|
+
function buildEnPrompt(roleList, workflow, teamType) {
|
|
419
|
+
const isDevTeam = teamType === 'dev' || teamType === 'custom';
|
|
420
|
+
|
|
421
|
+
const outputRules = isDevTeam
|
|
422
|
+
? `- Do not add role names or titles like "XX's perspective" at the beginning of responses; the chat UI already displays role information — start directly with the role's content
|
|
423
|
+
- Use tools (Read, Edit, Write, etc.) for code changes, don't paste large code blocks in chat
|
|
424
|
+
- Each role focuses on its own responsibility, don't do other roles' jobs
|
|
425
|
+
- If Review or Test finds issues, must switch back to Dev to fix before continuing`
|
|
426
|
+
: `- Do not add role names or titles like "XX's perspective" at the beginning of responses; the chat UI already displays role information — start directly with the role's content
|
|
427
|
+
- Each role focuses on its own responsibility, don't do other roles' jobs
|
|
428
|
+
- Roles can freely discuss and challenge each other, iterative optimization is encouraged`;
|
|
429
|
+
|
|
411
430
|
return `
|
|
412
431
|
# Multi-Role Collaboration Mode
|
|
413
432
|
|
|
@@ -464,10 +483,7 @@ Each role switch must include clear handoff information from the previous role:
|
|
|
464
483
|
|
|
465
484
|
## Output Format
|
|
466
485
|
|
|
467
|
-
|
|
468
|
-
- Use tools (Read, Edit, Write, etc.) for code changes, don't paste large code blocks in chat
|
|
469
|
-
- Each role focuses on its own responsibility, don't do other roles' jobs
|
|
470
|
-
- If Review or Test finds issues, must switch back to Dev to fix before continuing
|
|
486
|
+
${outputRules}
|
|
471
487
|
|
|
472
488
|
## Language
|
|
473
489
|
|
|
@@ -550,22 +566,51 @@ function buildWritingWorkflow(roleNames, isZh) {
|
|
|
550
566
|
}
|
|
551
567
|
|
|
552
568
|
function buildTradingWorkflow(roleNames, isZh) {
|
|
553
|
-
const
|
|
569
|
+
const hasQuant = roleNames.includes('quant');
|
|
554
570
|
const hasStrategist = roleNames.includes('strategist');
|
|
555
|
-
const
|
|
571
|
+
const hasRisk = roleNames.includes('risk');
|
|
572
|
+
const hasMacro = roleNames.includes('macro');
|
|
556
573
|
|
|
557
574
|
const steps = [];
|
|
558
575
|
|
|
559
576
|
if (isZh) {
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
if (
|
|
563
|
-
|
|
577
|
+
steps.push(`${steps.length + 1}. 用户提出分析需求`);
|
|
578
|
+
if (hasQuant) steps.push(`${steps.length + 1}. **量化分析师** 执行脚本/获取数据,输出量化信号和分析结果`);
|
|
579
|
+
if (hasStrategist && hasMacro) {
|
|
580
|
+
steps.push(`${steps.length + 1}. **策略师** 和 **宏观研究员** 分析数据,各自给出观点`);
|
|
581
|
+
} else if (hasStrategist) {
|
|
582
|
+
steps.push(`${steps.length + 1}. **策略师** 分析数据,给出观点`);
|
|
583
|
+
} else if (hasMacro) {
|
|
584
|
+
steps.push(`${steps.length + 1}. **宏观研究员** 分析数据,给出观点`);
|
|
585
|
+
}
|
|
586
|
+
if (hasStrategist) steps.push(`${steps.length + 1}. **策略师** 综合各方分析,形成初步策略方案`);
|
|
587
|
+
if (hasRisk) steps.push(`${steps.length + 1}. **风控官** 压力测试策略(不通过 → 返回策略师调整)`);
|
|
588
|
+
steps.push(`${steps.length + 1}. 多角色可以相互讨论、质疑、补充(不必严格线性流转)`);
|
|
589
|
+
if (hasStrategist) steps.push(`${steps.length + 1}. **策略师** 确认最终方案,输出结构化交易建议`);
|
|
590
|
+
steps.push('');
|
|
591
|
+
steps.push('关键原则:');
|
|
592
|
+
if (hasQuant) steps.push('- 量化分析师可以随时被要求重新跑数据或换参数');
|
|
593
|
+
steps.push('- 任何角色都可以 ROUTE 给任何角色提问/质疑');
|
|
594
|
+
steps.push('- 工作流强调"基于数据的迭代优化"');
|
|
564
595
|
} else {
|
|
565
|
-
|
|
566
|
-
if (
|
|
567
|
-
if (
|
|
568
|
-
|
|
596
|
+
steps.push(`${steps.length + 1}. User submits analysis request`);
|
|
597
|
+
if (hasQuant) steps.push(`${steps.length + 1}. **Quant** executes scripts/fetches data, outputs quantitative signals`);
|
|
598
|
+
if (hasStrategist && hasMacro) {
|
|
599
|
+
steps.push(`${steps.length + 1}. **Strategist** and **Macro Researcher** analyze data, each provides perspective`);
|
|
600
|
+
} else if (hasStrategist) {
|
|
601
|
+
steps.push(`${steps.length + 1}. **Strategist** analyzes data, provides perspective`);
|
|
602
|
+
} else if (hasMacro) {
|
|
603
|
+
steps.push(`${steps.length + 1}. **Macro Researcher** analyzes data, provides perspective`);
|
|
604
|
+
}
|
|
605
|
+
if (hasStrategist) steps.push(`${steps.length + 1}. **Strategist** synthesizes all analyses into preliminary strategy`);
|
|
606
|
+
if (hasRisk) steps.push(`${steps.length + 1}. **Risk Officer** stress-tests strategy (if fails → back to Strategist)`);
|
|
607
|
+
steps.push(`${steps.length + 1}. Roles can freely discuss, challenge, and supplement (not strictly linear)`);
|
|
608
|
+
if (hasStrategist) steps.push(`${steps.length + 1}. **Strategist** confirms final plan, outputs structured recommendation`);
|
|
609
|
+
steps.push('');
|
|
610
|
+
steps.push('Key principles:');
|
|
611
|
+
if (hasQuant) steps.push('- Quant can be asked to re-run data or change parameters at any time');
|
|
612
|
+
steps.push('- Any role can ROUTE to any other role to ask questions or challenge');
|
|
613
|
+
steps.push('- Workflow emphasizes "data-driven iterative optimization"');
|
|
569
614
|
}
|
|
570
615
|
|
|
571
616
|
return steps.join('\n');
|