Commit 5b508ce8f6e010f218d4a00472d40d3bf6bd6533

Authored by Yi Bo
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.
server/routes/company.js
... ... @@ -324,6 +324,80 @@ router.post('/:companyId/hire', async (req, res) => {
324 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 401 // ── P0 FIX: 动态心跳挂载 ──
328 402 // 招聘成功后,立即为这个新 Agent 注册心跳定时器(如果它的 config.json 里 heartbeat.enabled = true)
329 403 try {
... ...
web/src/views/CompanyDashboard.vue
... ... @@ -159,17 +159,15 @@
159 159 v-if="!agent.heartbeatActive"
160 160 class="btn-agent-start"
161 161 @click="startAgentHeartbeat(agent.id)"
162   - title="启动心跳"
163   - >▶</button>
  162 + >▶ 启动</button>
164 163 <button
165 164 v-else
166 165 class="btn-agent-stop"
167 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 171 </div>
174 172 </span>
175 173 </div>
... ... @@ -962,7 +960,7 @@ function closeDropdown() {
962 960 .agent-status-table { display: flex; flex-direction: column; gap: 0; }
963 961 .table-header, .table-row {
964 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 964 gap: 8px; align-items: center;
967 965 padding: 10px 16px;
968 966 }
... ... @@ -1029,14 +1027,14 @@ function closeDropdown() {
1029 1027 .agent-name-link:hover { color: var(--accent-light); text-decoration: underline; }
1030 1028  
1031 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 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 1039 .btn-action-chat { color: var(--accent-light); }
1042 1040 .btn-action-skills { color: var(--blue-text, #60a5fa); }
... ...