Commit 5b508ce8f6e010f218d4a00472d40d3bf6bd6533
1 parent
a485e251
feat: Automate full hiring orchestration (Creator -> Skills -> Innovator)
- Added async orchestration in `/company/:id/hire` when no template is found. - Triggers Agent-Creator to write definition files based on scaffold. - Searches Arena (skillDb) for 3 basic skills matching the role and injects them. - Dispatches Innovator to run FDE pipeline and evaluate initial capabilities.
Showing
2 changed files
with
86 additions
and
14 deletions
server/routes/company.js
| @@ -324,6 +324,80 @@ router.post('/:companyId/hire', async (req, res) => { | @@ -324,6 +324,80 @@ router.post('/:companyId/hire', async (req, res) => { | ||
| 324 | type, name: name || role, description, customAgent, customSoul, customUser, | 324 | type, name: name || role, description, customAgent, customSoul, customUser, |
| 325 | }); | 325 | }); |
| 326 | 326 | ||
| 327 | + // ── 核心逻辑扩展:自动补充定义 + 搜索注入 3 个初始 Skill + Innovator 训练 ── | ||
| 328 | + if (result.status === 'training' || result.needsTraining) { | ||
| 329 | + (async () => { | ||
| 330 | + try { | ||
| 331 | + console.log(`[Hire] 🚀 自动流水线启动: 为新员工 ${role} 安排 [定义 → 技能注入 → 训练]`); | ||
| 332 | + const fs = require('fs'); | ||
| 333 | + const path = require('path'); | ||
| 334 | + const config = require('../config'); | ||
| 335 | + const skillDb = require('../services/skill-db'); | ||
| 336 | + | ||
| 337 | + const namespacedId = `${req.params.companyId}:${role}`; | ||
| 338 | + const agentDir = result.agentDir || path.resolve(__dirname, '../../data', req.params.companyId, 'agents', role); | ||
| 339 | + const PORT = config.port || 3000; | ||
| 340 | + | ||
| 341 | + // 1. 发起 Agent-Creator 编写骨架定义 (异步不等待) | ||
| 342 | + fetch(`http://127.0.0.1:${PORT}/api/agent/agent-creator/chat`, { | ||
| 343 | + method: 'POST', | ||
| 344 | + headers: { 'Content-Type': 'application/json' }, | ||
| 345 | + body: JSON.stringify({ | ||
| 346 | + message: `新员工 ${namespacedId} 已由脚手架创建空白目录在 \`../${req.params.companyId}/agents/${role}\`。工作需求: ${description || name || role}。\n请你按照标准模板,使用 Write 工具为该员工补充完整的定义文件(agent.md, identity.md, soul.md, user.md等)并使用工具 ls 验证。务必要使用 Write 写文件,不要使用 Edit 工具。写完后请答复。`, | ||
| 347 | + conversationId: `create-${req.params.companyId}-${role}-${Date.now().toString(36)}` | ||
| 348 | + }) | ||
| 349 | + }).catch(e => console.error('[Hire] Agent-Creator 请求错误:', e.message)); | ||
| 350 | + | ||
| 351 | + // 2. 去网上/Arena搜索并注入 3 个基础 Skill | ||
| 352 | + const skills = skillDb.searchSkills(role, { limit: 3 }); | ||
| 353 | + const skillsDir = path.join(agentDir, 'skills'); | ||
| 354 | + if (skills && skills.length > 0) { | ||
| 355 | + if (!fs.existsSync(skillsDir)) fs.mkdirSync(skillsDir, { recursive: true }); | ||
| 356 | + | ||
| 357 | + const copyDir = (src, dest) => { | ||
| 358 | + fs.mkdirSync(dest, { recursive: true }); | ||
| 359 | + const entries = fs.readdirSync(src, { withFileTypes: true }); | ||
| 360 | + for (const e of entries) { | ||
| 361 | + if (e.isDirectory()) copyDir(path.join(src, e.name), path.join(dest, e.name)); | ||
| 362 | + else fs.copyFileSync(path.join(src, e.name), path.join(dest, e.name)); | ||
| 363 | + } | ||
| 364 | + }; | ||
| 365 | + | ||
| 366 | + for (const skill of skills) { | ||
| 367 | + if (skill.directory && fs.existsSync(skill.directory)) { | ||
| 368 | + const targetPath = path.join(skillsDir, skill.skillName); | ||
| 369 | + if (!fs.existsSync(targetPath)) { | ||
| 370 | + try { | ||
| 371 | + copyDir(skill.directory, targetPath); | ||
| 372 | + console.log(`[Hire] 💉 成功为其注入基础技能: ${skill.skillName}`); | ||
| 373 | + } catch (e) { | ||
| 374 | + console.error(`[Hire] 注入技能 ${skill.skillName} 失败:`, e.message); | ||
| 375 | + } | ||
| 376 | + } | ||
| 377 | + } | ||
| 378 | + } | ||
| 379 | + } else { | ||
| 380 | + console.log(`[Hire] ⚠️ 网上/Arena 未找到该角色的匹配 Skill,将交由 Innovator 凭空生成`); | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + // 3. 延时 5 秒触发 Innovator 进行评估与训练 (等 creator 写入完毕) | ||
| 384 | + setTimeout(() => { | ||
| 385 | + fetch(`http://127.0.0.1:${PORT}/api/agent/innovator/chat`, { | ||
| 386 | + method: 'POST', | ||
| 387 | + headers: { 'Content-Type': 'application/json' }, | ||
| 388 | + body: JSON.stringify({ | ||
| 389 | + message: `公司 ${req.params.companyId} 的员工 ${role} 骨架和基础技能已注入完毕。请你使用 FDE pipeline 对该 Agent ${namespacedId} (特别是它的技能)进行评测和进化训练,直到 Benchmark 达标 (Score ≥ 95)。训练过程包括 Hunt(验证数据), Digest, Evolver 等。请立即开始评估和训练,并保持向我汇报。`, | ||
| 390 | + conversationId: `train-${req.params.companyId}-${role}-${Date.now().toString(36)}` | ||
| 391 | + }) | ||
| 392 | + }).catch(e => console.error('[Hire] Innovator 请求错误:', e.message)); | ||
| 393 | + }, 5000); | ||
| 394 | + | ||
| 395 | + } catch (err) { | ||
| 396 | + console.error(`[Hire Orchestration] 自动流水线编排异常: ${err.message}`); | ||
| 397 | + } | ||
| 398 | + })(); | ||
| 399 | + } | ||
| 400 | + | ||
| 327 | // ── P0 FIX: 动态心跳挂载 ── | 401 | // ── P0 FIX: 动态心跳挂载 ── |
| 328 | // 招聘成功后,立即为这个新 Agent 注册心跳定时器(如果它的 config.json 里 heartbeat.enabled = true) | 402 | // 招聘成功后,立即为这个新 Agent 注册心跳定时器(如果它的 config.json 里 heartbeat.enabled = true) |
| 329 | try { | 403 | try { |
web/src/views/CompanyDashboard.vue
| @@ -159,17 +159,15 @@ | @@ -159,17 +159,15 @@ | ||
| 159 | v-if="!agent.heartbeatActive" | 159 | v-if="!agent.heartbeatActive" |
| 160 | class="btn-agent-start" | 160 | class="btn-agent-start" |
| 161 | @click="startAgentHeartbeat(agent.id)" | 161 | @click="startAgentHeartbeat(agent.id)" |
| 162 | - title="启动心跳" | ||
| 163 | - >▶</button> | 162 | + >▶ 启动</button> |
| 164 | <button | 163 | <button |
| 165 | v-else | 164 | v-else |
| 166 | class="btn-agent-stop" | 165 | class="btn-agent-stop" |
| 167 | @click="stopAgentHeartbeat(agent.id)" | 166 | @click="stopAgentHeartbeat(agent.id)" |
| 168 | - title="停止心跳" | ||
| 169 | - >⏹</button> | ||
| 170 | - <button class="btn-action-chat" @click="goToAgentChat(agent.id)" title="聊天">💬</button> | ||
| 171 | - <button class="btn-action-skills" @click="toggleAgentSkills(agent.id)" title="技能">📋</button> | ||
| 172 | - <button class="btn-action-train" @click="triggerTrain(agent.id)" title="训练">🎓</button> | 167 | + >⏹ 停止</button> |
| 168 | + <button class="btn-action-chat" @click="goToAgentChat(agent.id)">💬 聊天</button> | ||
| 169 | + <button class="btn-action-skills" @click="toggleAgentSkills(agent.id)">📋 技能</button> | ||
| 170 | + <button class="btn-action-train" @click="triggerTrain(agent.id)">🎓 训练</button> | ||
| 173 | </div> | 171 | </div> |
| 174 | </span> | 172 | </span> |
| 175 | </div> | 173 | </div> |
| @@ -962,7 +960,7 @@ function closeDropdown() { | @@ -962,7 +960,7 @@ function closeDropdown() { | ||
| 962 | .agent-status-table { display: flex; flex-direction: column; gap: 0; } | 960 | .agent-status-table { display: flex; flex-direction: column; gap: 0; } |
| 963 | .table-header, .table-row { | 961 | .table-header, .table-row { |
| 964 | display: grid; | 962 | display: grid; |
| 965 | - grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1fr; | 963 | + grid-template-columns: 2fr 1fr 1fr 1fr 1fr 2.5fr; |
| 966 | gap: 8px; align-items: center; | 964 | gap: 8px; align-items: center; |
| 967 | padding: 10px 16px; | 965 | padding: 10px 16px; |
| 968 | } | 966 | } |
| @@ -1029,14 +1027,14 @@ function closeDropdown() { | @@ -1029,14 +1027,14 @@ function closeDropdown() { | ||
| 1029 | .agent-name-link:hover { color: var(--accent-light); text-decoration: underline; } | 1027 | .agent-name-link:hover { color: var(--accent-light); text-decoration: underline; } |
| 1030 | 1028 | ||
| 1031 | /* Action Button Group */ | 1029 | /* Action Button Group */ |
| 1032 | -.action-group { display: flex; gap: 4px; align-items: center; } | 1030 | +.action-group { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; } |
| 1033 | .action-group button { | 1031 | .action-group button { |
| 1034 | - width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--border-primary); | ||
| 1035 | - background: var(--bg-primary); cursor: pointer; font-size: 13px; | ||
| 1036 | - display: flex; align-items: center; justify-content: center; transition: all 0.15s; | ||
| 1037 | - padding: 0; | 1032 | + padding: 4px 8px; border-radius: 6px; border: 1px solid var(--border-primary); |
| 1033 | + background: var(--bg-primary); cursor: pointer; font-size: 11px; font-weight: 600; | ||
| 1034 | + display: flex; align-items: center; gap: 2px; transition: all 0.15s; | ||
| 1035 | + white-space: nowrap; | ||
| 1038 | } | 1036 | } |
| 1039 | -.action-group button:hover { background: var(--bg-surface-hover); border-color: var(--border-active); transform: scale(1.1); } | 1037 | +.action-group button:hover { background: var(--bg-surface-hover); border-color: var(--border-active); } |
| 1040 | 1038 | ||
| 1041 | .btn-action-chat { color: var(--accent-light); } | 1039 | .btn-action-chat { color: var(--accent-light); } |
| 1042 | .btn-action-skills { color: var(--blue-text, #60a5fa); } | 1040 | .btn-action-skills { color: var(--blue-text, #60a5fa); } |