@iamsamyiok/agents-chat 3.33.0 → 3.34.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/.env.example +2 -0
- package/README.md +2 -1
- package/app/lib/teamgen.js +138 -0
- package/app/public/index.html +112 -3
- package/app/server.js +36 -1
- package/package.json +1 -1
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ npm install -g opencode-ai
|
|
|
26
26
|
| 能力 | 说明 |
|
|
27
27
|
|---|---|
|
|
28
28
|
| 群聊编排 | 直接输入需求,管家智能体拆解成阶段计划,调度子智能体并行执行,自动验收不合格返工 |
|
|
29
|
+
| AI 组队 | 一句话描述需求,执行内核自动生成整套智能体团队(名称/图标/技能/提示词),预览后合并或替换应用 |
|
|
29
30
|
| 批量任务 | 粘贴一段文本一次导入多条任务(`1. xxx` 每行一条),可拖拽排序、批量执行 |
|
|
30
31
|
| 定时任务 | 行首写时间即定时(`2026-08-18 13:07 生成日报`),到点自动执行,停机错过的启动时补跑,可开机自启 |
|
|
31
32
|
| Git 隔离 | 导入时勾选「Git 隔离执行」:每任务独立 worktree 分支互不污染,完成后逐文件看 diff、一键合并或丢弃 |
|
|
@@ -84,7 +85,7 @@ OpenCode(推荐)/ Claude Code / Codex CLI / pi,页面右上角可切换。
|
|
|
84
85
|
git clone https://github.com/iamsamyiok/agents-chat
|
|
85
86
|
cd agents-chat && npm install
|
|
86
87
|
AGENTS_CHAT_MOCK=1 npm start # 演示模式开发,无需内核
|
|
87
|
-
npm test #
|
|
88
|
+
npm test # 81 个测试用例
|
|
88
89
|
```
|
|
89
90
|
|
|
90
91
|
零运行时依赖,Node 原生模块实现(HTTP/SSE/存储均为自研轻量层)。
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// AI 智能组队:由执行内核根据用户需求生成子智能体团队配置
|
|
2
|
+
// 纯函数模块(buildPrompt/extractTeamJSON/cleanSuggestedTeam)可独立单测;
|
|
3
|
+
// suggestTeam 注入 runAgentFn 便于测试内核交互链路
|
|
4
|
+
const agentMod = require('./agent');
|
|
5
|
+
|
|
6
|
+
const LIMITS = { name: 20, icon: 8, desc: 100, systemPrompt: 8000 };
|
|
7
|
+
const TEAM_MIN = 2;
|
|
8
|
+
const TEAM_MAX = 6;
|
|
9
|
+
const REQ_MAX = 2000;
|
|
10
|
+
const SUGGEST_TIMEOUT_MS = Number(process.env.AGENTS_CHAT_SUGGEST_TIMEOUT_MS) > 0
|
|
11
|
+
? Number(process.env.AGENTS_CHAT_SUGGEST_TIMEOUT_MS)
|
|
12
|
+
: 120000; // 真实内核生成整队提示词输出量大,60s 偏紧;.env 可覆盖
|
|
13
|
+
|
|
14
|
+
// 结构化生成提示词:要求内核输出纯 JSON 数组(容错解析见 extractTeamJSON)
|
|
15
|
+
function buildPrompt(requirements, existingNames) {
|
|
16
|
+
const avoid = (Array.isArray(existingNames) ? existingNames : [])
|
|
17
|
+
.map(n => String(n || '').trim()).filter(Boolean);
|
|
18
|
+
return [
|
|
19
|
+
'你是一位资深多智能体系统团队设计师。请根据用户需求,设计一支协作完成该需求的智能体团队。',
|
|
20
|
+
'',
|
|
21
|
+
'【用户需求】',
|
|
22
|
+
String(requirements || '').trim(),
|
|
23
|
+
'',
|
|
24
|
+
'【输出要求】只输出一个 JSON 数组,不要输出任何其他文字、解释或 markdown 代码块标记。数组包含 2 到 6 个对象,每个对象字段:',
|
|
25
|
+
'- name:智能体名称,简短(不超过 8 个字),团队内不得重名',
|
|
26
|
+
'- icon:单个 emoji 图标',
|
|
27
|
+
'- desc:一句话技能说明(不超过 50 字),明确该智能体的能力边界,供调度者据此分派任务',
|
|
28
|
+
'- systemPrompt:自定义提示词,包含角色定位、专业技能与输出要求,100 到 300 字,具体可执行',
|
|
29
|
+
'',
|
|
30
|
+
avoid.length ? `【命名避让】以下名称已被现有智能体占用,生成的 name 不得与之重复:${avoid.join('、')}` : '',
|
|
31
|
+
'',
|
|
32
|
+
'【设计原则】',
|
|
33
|
+
'1. 按需求拆解出真正需要的分工,每个智能体职责单一且清晰,避免出现职责重叠的成员',
|
|
34
|
+
'2. 语言与用户需求语言一致(用户用中文则全部用中文)',
|
|
35
|
+
'3. systemPrompt 要具体可执行,写清楚该角色拿到任务后应该怎么做、输出什么格式的成果',
|
|
36
|
+
'4. 若需求较简单,团队从简(2 到 3 个);需求复杂才增加分工'
|
|
37
|
+
].filter(line => line !== '').join('\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 容错提取 JSON 数组:裸数组 → ```json 代码块 → 首个 [ 到最后一个 ] 的子串
|
|
41
|
+
function extractTeamJSON(text) {
|
|
42
|
+
const raw = String(text || '').trim();
|
|
43
|
+
if (!raw) return null;
|
|
44
|
+
// 1. 整体就是合法 JSON
|
|
45
|
+
try { const v = JSON.parse(raw); if (Array.isArray(v)) return v; } catch { /* 继续尝试 */ }
|
|
46
|
+
// 2. markdown 代码块(```json ... ``` 或 ``` ... ```)
|
|
47
|
+
const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
48
|
+
if (fence) {
|
|
49
|
+
try { const v = JSON.parse(fence[1].trim()); if (Array.isArray(v)) return v; } catch { /* 继续尝试 */ }
|
|
50
|
+
}
|
|
51
|
+
// 3. 首个 [ 到最后一个 ] 之间(容忍前后杂文;字符串中含 ] 不影响,因为取最后一个 ])
|
|
52
|
+
const start = raw.indexOf('[');
|
|
53
|
+
const end = raw.lastIndexOf(']');
|
|
54
|
+
if (start >= 0 && end > start) {
|
|
55
|
+
try { const v = JSON.parse(raw.slice(start, end + 1)); if (Array.isArray(v)) return v; } catch { /* 放弃 */ }
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 清洗生成结果:字段类型归一 + 长度截断 + 空名补名 + 重名后缀 + 数量上限
|
|
61
|
+
// existingNames:现有智能体名(含管家),重名自动加序号,保证 @ 点名无歧义
|
|
62
|
+
function cleanSuggestedTeam(arr, existingNames) {
|
|
63
|
+
if (!Array.isArray(arr)) return [];
|
|
64
|
+
const names = new Set(['管家', 'butler']);
|
|
65
|
+
for (const n of existingNames || []) {
|
|
66
|
+
const s = String(n || '').trim();
|
|
67
|
+
if (s) names.add(s);
|
|
68
|
+
}
|
|
69
|
+
const clean = [];
|
|
70
|
+
for (const a of arr) {
|
|
71
|
+
if (!a || typeof a !== 'object') continue;
|
|
72
|
+
let name = String(a.name || '').replace(/\s+/g, '').slice(0, LIMITS.name);
|
|
73
|
+
if (!name) name = `智能体${clean.length + 1}`;
|
|
74
|
+
let final = name;
|
|
75
|
+
let i = 2;
|
|
76
|
+
while (names.has(final)) final = `${name}${i++}`;
|
|
77
|
+
names.add(final);
|
|
78
|
+
clean.push({
|
|
79
|
+
name: final,
|
|
80
|
+
icon: String(a.icon || '').trim().slice(0, LIMITS.icon),
|
|
81
|
+
desc: String(a.desc || '').slice(0, LIMITS.desc),
|
|
82
|
+
systemPrompt: String(a.systemPrompt || '').slice(0, LIMITS.systemPrompt)
|
|
83
|
+
});
|
|
84
|
+
if (clean.length >= TEAM_MAX) break;
|
|
85
|
+
}
|
|
86
|
+
return clean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 完整生成链路:调内核一次 → 容错解析 → 清洗 → 数量校验
|
|
90
|
+
// 返回 { success, agents, error };任何失败都不影响现有配置(调用方不落盘)
|
|
91
|
+
async function suggestTeam({ requirements, existingNames, runAgentFn, timeoutMs }) {
|
|
92
|
+
const run = runAgentFn || agentMod.runAgent;
|
|
93
|
+
const timeout = Number(timeoutMs) > 0 ? Number(timeoutMs) : SUGGEST_TIMEOUT_MS;
|
|
94
|
+
const scope = 'teamgen';
|
|
95
|
+
const prompt = buildPrompt(requirements, existingNames);
|
|
96
|
+
|
|
97
|
+
let settled = false;
|
|
98
|
+
let content = '';
|
|
99
|
+
let runError = '';
|
|
100
|
+
const done = new Promise((resolve) => {
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
if (settled) return;
|
|
103
|
+
settled = true;
|
|
104
|
+
runError = `生成超时(${Math.round(timeout / 1000)} 秒),请稍后重试或简化需求描述`;
|
|
105
|
+
try { agentMod.stopScope(scope); } catch { /* ignore */ }
|
|
106
|
+
resolve();
|
|
107
|
+
}, timeout);
|
|
108
|
+
try {
|
|
109
|
+
run(
|
|
110
|
+
{ id: scope, name: scope, model: '', behavior: 'echo', systemPrompt: '' },
|
|
111
|
+
prompt,
|
|
112
|
+
(chunk) => {
|
|
113
|
+
if (chunk && chunk.content) content += chunk.content;
|
|
114
|
+
if (chunk && chunk.error) runError = runError || String(chunk.error);
|
|
115
|
+
if (chunk && chunk.done && !settled) { settled = true; clearTimeout(timer); resolve(); }
|
|
116
|
+
},
|
|
117
|
+
scope
|
|
118
|
+
);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (!settled) { settled = true; clearTimeout(timer); runError = `调用内核失败:${e.message}`; resolve(); }
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
await done;
|
|
124
|
+
|
|
125
|
+
if (runError) return { success: false, agents: [], error: runError };
|
|
126
|
+
|
|
127
|
+
const parsed = extractTeamJSON(content);
|
|
128
|
+
if (!parsed) {
|
|
129
|
+
return { success: false, agents: [], error: '生成结果无法解析为团队配置,请换个说法重试(例如补充更多分工细节)' };
|
|
130
|
+
}
|
|
131
|
+
const agents = cleanSuggestedTeam(parsed, existingNames);
|
|
132
|
+
if (agents.length < TEAM_MIN) {
|
|
133
|
+
return { success: false, agents: [], error: `生成结果只有 ${agents.length} 个智能体,至少需要 ${TEAM_MIN} 个,请补充更多分工细节后重试` };
|
|
134
|
+
}
|
|
135
|
+
return { success: true, agents };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { buildPrompt, extractTeamJSON, cleanSuggestedTeam, suggestTeam, LIMITS, TEAM_MIN, TEAM_MAX, REQ_MAX };
|
package/app/public/index.html
CHANGED
|
@@ -215,14 +215,14 @@
|
|
|
215
215
|
.at-tx i { font-style: normal; color: #999; font-size: 11px; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 380px; }
|
|
216
216
|
|
|
217
217
|
/* ---------- 弹窗 ---------- */
|
|
218
|
-
#modalMask, #batchMask, #agentsMask, #historyMask, #helpMask, #flowMask, #cardMask, #teamsMask, #memMask, #diffMask, #welcomeMask { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: none; align-items: center; justify-content: center; z-index: 51; }
|
|
218
|
+
#modalMask, #batchMask, #agentsMask, #historyMask, #helpMask, #flowMask, #cardMask, #teamsMask, #memMask, #diffMask, #welcomeMask, #suggestMask { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: none; align-items: center; justify-content: center; z-index: 51; }
|
|
219
219
|
/* 弹窗出现动画:遮罩淡入(含毛玻璃)+ 内容上浮,全站弹窗统一节奏 */
|
|
220
220
|
div[id$="Mask"] { backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
|
|
221
221
|
div[id$="Mask"].show { animation: fade-in .18s ease; }
|
|
222
222
|
.modal, .preview-card { animation: modal-in .24s cubic-bezier(.2,.7,.3,1); }
|
|
223
223
|
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
224
224
|
@keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: none; } }
|
|
225
|
-
#modalMask.show, #batchMask.show, #agentsMask.show, #historyMask.show, #helpMask.show, #flowMask.show, #cardMask.show, #teamsMask.show, #memMask.show, #diffMask.show, #welcomeMask.show { display: flex; }
|
|
225
|
+
#modalMask.show, #batchMask.show, #agentsMask.show, #historyMask.show, #helpMask.show, #flowMask.show, #cardMask.show, #teamsMask.show, #memMask.show, #diffMask.show, #welcomeMask.show, #suggestMask.show { display: flex; }
|
|
226
226
|
/* 团队仓库卡片 */
|
|
227
227
|
.team-card { border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
|
|
228
228
|
.team-card .tc-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
@@ -588,6 +588,7 @@
|
|
|
588
588
|
<div class="tip">每个智能体都是一个独立的 AI Agent:执行内核由顶部下拉框统一选择(OpenCode / Claude Code / Codex / pi),<b>图标</b>用于辨认,<b>名称</b>用于 @ 点名,<b>技能说明</b>供管家调度参考,<b>自定义提示词</b>会在任务下发时附加在用户提示词前面。所有智能体使用所选内核的默认模型。「管家」为内置调度智能体,不可修改。</div>
|
|
589
589
|
<div class="butler-ro" id="butlerRo"></div>
|
|
590
590
|
<div class="ag-row" style="margin:8px 0 2px">
|
|
591
|
+
<button class="add-agent" style="flex:1" onclick="openSuggest()">🪄 AI 组队 · 一句话生成团队</button>
|
|
591
592
|
<button class="add-agent" style="flex:1" onclick="openTeams()">📦 团队仓库 · 一键换经典团队</button>
|
|
592
593
|
</div>
|
|
593
594
|
<div class="global-cwd-box">
|
|
@@ -658,6 +659,25 @@
|
|
|
658
659
|
</div>
|
|
659
660
|
</div>
|
|
660
661
|
</div>
|
|
662
|
+
<!-- AI 智能组队弹窗 -->
|
|
663
|
+
<div id="suggestMask">
|
|
664
|
+
<div class="modal wide">
|
|
665
|
+
<h3>🪄 AI 组队</h3>
|
|
666
|
+
<div class="tip">用一句话描述你的需求,AI 扮演团队设计师,生成一套分工明确的智能体团队(名称/图标/技能说明/提示词)。先预览再应用,应用后仍可自由增删改,点配置页「保存」才生效。</div>
|
|
667
|
+
<div class="ta-wrap"><textarea id="sgReqInput" style="min-height:88px" placeholder="例如:我要从零做一个跨境电商独立站,需要市场调研、选品分析、网站前后端开发、内容文案和上线测试,帮我组建一支能覆盖全流程的团队"></textarea></div>
|
|
668
|
+
<div class="ag-row" style="margin:8px 0">
|
|
669
|
+
<button id="sgRunBtn" class="primary" style="flex:1" onclick="runSuggest()">✨ 生成团队</button>
|
|
670
|
+
</div>
|
|
671
|
+
<div id="sgResult"></div>
|
|
672
|
+
<div class="modal-actions">
|
|
673
|
+
<button class="mini-btn" onclick="runSuggest()">🔄 重新生成</button>
|
|
674
|
+
<span style="flex:1"></span>
|
|
675
|
+
<button onclick="closeSuggest()">放弃</button>
|
|
676
|
+
<button class="mini-btn" style="background:#07c160;color:#fff" onclick="applySuggest('append')">+ 应用(合并到现有团队)</button>
|
|
677
|
+
<button class="primary" onclick="applySuggest('replace')">应用(替换全部子智能体)</button>
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
661
681
|
<!-- 聊天历史弹窗 -->
|
|
662
682
|
<div id="historyMask">
|
|
663
683
|
<div class="modal wide">
|
|
@@ -749,7 +769,7 @@
|
|
|
749
769
|
</div>
|
|
750
770
|
|
|
751
771
|
<script>
|
|
752
|
-
const PAGE_VERSION = '3.
|
|
772
|
+
const PAGE_VERSION = '3.34.0'; // 与服务端 /api/health.version 互检,不一致说明页面缓存过期
|
|
753
773
|
const AV_COLORS = ['#5b8def','#07c160','#fa9d3b','#10aeff','#8a6fe8','#fa5151','#e8a33d','#3aa7a3'];
|
|
754
774
|
const PHASE_LABEL = { plan: '调度规划', work: '执行', review: '验收', report: '汇总', talk: '圆桌发言', task: '任务执行' };
|
|
755
775
|
// 职业图标库(智能体配置可选)
|
|
@@ -3354,6 +3374,94 @@ function applyTeamPreset(i, mode) {
|
|
|
3354
3374
|
toast(`已应用「${t.name}」(${mode === 'replace' ? '替换' : '追加'} ${incoming.length} 个智能体),点「保存」后生效`);
|
|
3355
3375
|
}
|
|
3356
3376
|
|
|
3377
|
+
// ---------- AI 智能组队:一句话需求 → 内核生成团队 → 预览应用 ----------
|
|
3378
|
+
let sgDraft = []; // 预览态候选(仅前端内存,应用才进 editing)
|
|
3379
|
+
|
|
3380
|
+
function openSuggest() {
|
|
3381
|
+
document.getElementById('sgResult').innerHTML = '';
|
|
3382
|
+
sgDraft = [];
|
|
3383
|
+
document.getElementById('suggestMask').classList.add('show');
|
|
3384
|
+
setTimeout(() => document.getElementById('sgReqInput').focus(), 50);
|
|
3385
|
+
}
|
|
3386
|
+
function closeSuggest() { document.getElementById('suggestMask').classList.remove('show'); }
|
|
3387
|
+
|
|
3388
|
+
async function runSuggest() {
|
|
3389
|
+
const reqEl = document.getElementById('sgReqInput');
|
|
3390
|
+
const requirements = String(reqEl.value || '').trim();
|
|
3391
|
+
if (!requirements) { toast('先描述一下你的需求,AI 才知道要组什么团队'); reqEl.focus(); return; }
|
|
3392
|
+
const btn = document.getElementById('sgRunBtn');
|
|
3393
|
+
const out = document.getElementById('sgResult');
|
|
3394
|
+
btn.disabled = true;
|
|
3395
|
+
btn.textContent = '⏳ 生成中…(AI 正在设计团队分工,最长约 2 分钟)';
|
|
3396
|
+
out.innerHTML = '<div class="task-empty" style="padding:16px">AI 正在根据你的需求设计团队分工,请稍候…</div>';
|
|
3397
|
+
sgDraft = [];
|
|
3398
|
+
try {
|
|
3399
|
+
const r = await api('/api/agents/suggest', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ requirements }) });
|
|
3400
|
+
if (r.success) {
|
|
3401
|
+
sgDraft = r.agents || [];
|
|
3402
|
+
renderSuggestCards();
|
|
3403
|
+
toast(`已生成 ${sgDraft.length} 个智能体,确认后点下方「应用」`);
|
|
3404
|
+
} else {
|
|
3405
|
+
out.innerHTML = `<div class="task-empty" style="padding:16px;color:#c0392b">❌ ${esc(r.error || '生成失败,请稍后重试')}</div>`;
|
|
3406
|
+
}
|
|
3407
|
+
} catch (e) {
|
|
3408
|
+
out.innerHTML = `<div class="task-empty" style="padding:16px;color:#c0392b">❌ 生成失败:${esc(e.message)}</div>`;
|
|
3409
|
+
} finally {
|
|
3410
|
+
btn.disabled = false;
|
|
3411
|
+
btn.textContent = '✨ 生成团队';
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
function renderSuggestCards() {
|
|
3416
|
+
const el = document.getElementById('sgResult');
|
|
3417
|
+
if (!sgDraft.length) {
|
|
3418
|
+
el.innerHTML = '<div class="task-empty" style="padding:16px">暂无生成结果,输入需求后点「生成团队」</div>';
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
el.innerHTML = sgDraft.map((a, i) => `
|
|
3422
|
+
<div class="ag-card">
|
|
3423
|
+
<div class="ag-row">
|
|
3424
|
+
<span style="font-size:20px;align-self:center">${esc(a.icon || '🤖')}</span>
|
|
3425
|
+
<b style="align-self:center;flex:1">${esc(a.name)}</b>
|
|
3426
|
+
<button class="mini-btn gray" onclick="sgDraft.splice(${i},1);renderSuggestCards()">删除</button>
|
|
3427
|
+
</div>
|
|
3428
|
+
<input value="${escAttr(a.desc || '')}" placeholder="技能说明" oninput="sgDraft[${i}].desc=this.value">
|
|
3429
|
+
<details>
|
|
3430
|
+
<summary style="font-size:12px;color:#666;cursor:pointer;margin:4px 0">自定义提示词(点击展开查看,可直接修改)</summary>
|
|
3431
|
+
<textarea oninput="sgDraft[${i}].systemPrompt=this.value">${esc(a.systemPrompt || '')}</textarea>
|
|
3432
|
+
</details>
|
|
3433
|
+
</div>`).join('');
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
// 应用候选:append=合并到现有名单(重名自动加序号);replace=替换全部子智能体
|
|
3437
|
+
function applySuggest(mode) {
|
|
3438
|
+
if (!sgDraft.length) { toast('还没有可应用的生成结果'); return; }
|
|
3439
|
+
const incoming = sgDraft.map(a => ({
|
|
3440
|
+
id: '', behavior: 'echo',
|
|
3441
|
+
name: String(a.name || '').trim(),
|
|
3442
|
+
icon: String(a.icon || ''),
|
|
3443
|
+
desc: String(a.desc || ''),
|
|
3444
|
+
systemPrompt: String(a.systemPrompt || '')
|
|
3445
|
+
})).filter(a => a.name);
|
|
3446
|
+
if (!incoming.length) { toast('候选智能体缺少名称,无法应用'); return; }
|
|
3447
|
+
if (mode === 'replace') {
|
|
3448
|
+
editing = incoming;
|
|
3449
|
+
} else {
|
|
3450
|
+
const names = new Set(['管家', 'butler', ...editing.map(a => String(a.name || '').trim())]);
|
|
3451
|
+
for (const a of incoming) {
|
|
3452
|
+
let final = a.name, n = 2;
|
|
3453
|
+
while (names.has(final)) final = `${a.name}${n++}`;
|
|
3454
|
+
a.name = final;
|
|
3455
|
+
names.add(final);
|
|
3456
|
+
editing.push(a);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
sgDraft = [];
|
|
3460
|
+
renderAgentCards();
|
|
3461
|
+
closeSuggest();
|
|
3462
|
+
toast(`已应用 AI 生成的团队(${mode === 'replace' ? '替换' : '合并'} ${incoming.length} 个智能体),点配置页「保存」后生效`);
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3357
3465
|
function iconPicker(i) {
|
|
3358
3466
|
const cur = editing[i].icon || '';
|
|
3359
3467
|
return '<div class="icon-row">' + ICON_PRESETS.map(ic =>
|
|
@@ -3605,6 +3713,7 @@ function openHelp() {
|
|
|
3605
3713
|
<li>本机安装任一执行内核并登录模型(模型与密钥由内核自己管理,本程序不做任何 API 配置):<br>
|
|
3606
3714
|
<code>${KERNEL_INSTALL.opencode}</code><br><code>${KERNEL_INSTALL.claude}</code><br><code>${KERNEL_INSTALL.codex}</code><br><code>${KERNEL_INSTALL.pi}</code></li>
|
|
3607
3715
|
<li>启动本程序后浏览器打开 <code>http://localhost:3456</code>,会自动识别已安装的内核</li>
|
|
3716
|
+
<li>不知道配什么团队?「⚙ 智能体配置」里点「🪄 AI 组队」,一句话描述需求,AI 自动生成整套智能体团队(先预览再应用);也可从「📦 团队仓库」一键换经典团队</li>
|
|
3608
3717
|
<li>直接在输入框说话即可开始;粘贴任务清单可批量执行</li>
|
|
3609
3718
|
</ol>
|
|
3610
3719
|
</div>
|
package/app/server.js
CHANGED
|
@@ -69,7 +69,9 @@ process.on('unhandledRejection', (err) => {
|
|
|
69
69
|
const PORT = Number(process.argv.includes('--port') ? process.argv[process.argv.indexOf('--port') + 1] : (process.env.PORT || 3456));
|
|
70
70
|
const PUBLIC_DIR = path.join(__dirname, 'public');
|
|
71
71
|
const store = require('./lib/store');
|
|
72
|
-
const { runAgent, stopScope, stopAllChildren } = require('./lib/agent');
|
|
72
|
+
const { runAgent, resolveRunner, missingHint, stopScope, stopAllChildren } = require('./lib/agent');
|
|
73
|
+
const teamgen = require('./lib/teamgen');
|
|
74
|
+
let suggestInFlight = false; // AI 组队生成互斥:同刻仅一个(内核单次调用,防并发浪费)
|
|
73
75
|
const { runButler, runMentioned, runRoundtable, runTasks, prepareRerun } = require('./lib/orchestrator');
|
|
74
76
|
const oc = require('./lib/oc');
|
|
75
77
|
const memoryMod = require('./lib/memory');
|
|
@@ -598,6 +600,39 @@ const server = http.createServer(async (req, res) => {
|
|
|
598
600
|
return;
|
|
599
601
|
}
|
|
600
602
|
|
|
603
|
+
if (p === '/api/agents/suggest' && req.method === 'POST') {
|
|
604
|
+
// AI 智能组队:需求文本 → 执行内核生成团队配置(预览态,不落盘;应用走 /api/agents 保存链路)
|
|
605
|
+
const body = await readBody(req);
|
|
606
|
+
const requirements = String(body.requirements || '').trim();
|
|
607
|
+
if (!requirements || requirements.length > teamgen.REQ_MAX) {
|
|
608
|
+
json(res, 400, { success: false, error: `需求描述不能为空且不超过 ${teamgen.REQ_MAX} 字` });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (suggestInFlight) {
|
|
612
|
+
json(res, 409, { success: false, error: '上一次生成还在进行中,请稍候' });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const runner = resolveRunner();
|
|
616
|
+
if (runner.kind === 'demo' || runner.kind === 'missing') {
|
|
617
|
+
const why = runner.kind === 'demo'
|
|
618
|
+
? '当前为演示模式(AGENTS_CHAT_MOCK=1),删除 .env 中该配置并重启本程序后即可使用真实生成'
|
|
619
|
+
: missingHint(runner);
|
|
620
|
+
json(res, 200, { success: false, error: `AI 组队需要真实执行内核:${why}` });
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
suggestInFlight = true;
|
|
624
|
+
try {
|
|
625
|
+
const r = await teamgen.suggestTeam({
|
|
626
|
+
requirements,
|
|
627
|
+
existingNames: store.getAgents().map(a => a.name)
|
|
628
|
+
});
|
|
629
|
+
json(res, 200, r);
|
|
630
|
+
} finally {
|
|
631
|
+
suggestInFlight = false;
|
|
632
|
+
}
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
601
636
|
if (p === '/api/tasks' && req.method === 'GET') {
|
|
602
637
|
// 按 seq(拖拽可调)排序返回
|
|
603
638
|
json(res, 200, { success: true, tasks: store.getTasks() });
|