Commit 0ed020c3948396e69a535081cd887c93f7760fe9
1 parent
4c6d2325
fix(agents): 训练对话挂在 org-innovator 名下时,前端不再靠猜找链接
招聘自动训练与「重新训练」按钮都是给 org-innovator 发消息、由它去执行训练,
所以对话记录一直落在 org-innovator 的会话库里,而不是被训练的那个 agent 自己——
conversationId 里带目标 agent 的名字只是命名巧合,容易让人在错的 agent 页面下找不到记录
(今天实测:手工模板训练页删除前产生的一条记录就复现过这个问题)。
- company.js /hire:训练 conversationId 提前算好并通过 res.json 带回给前端
(不再等 setTimeout 回调里才生成,那时响应早发完了)
- CompanyDashboard.vue 重新训练按钮:发完请求立即 window.open 到正确的
/agent/org-innovator/{convId}
- CompanyDashboard.vue 招聘弹窗:雇佣触发自动训练时,弹窗给出可复制的正确链接
(不自动开新标签页——这条训练要等 agent-creator 先写完定义,立刻打开大概率是空的)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Showing
2 changed files
with
25 additions
and
7 deletions
server/routes/company.js
| ... | ... | @@ -411,7 +411,12 @@ router.post('/:companyId/hire', async (req, res) => { |
| 411 | 411 | }); |
| 412 | 412 | |
| 413 | 413 | // ── 核心逻辑扩展:自动补充定义 + 搜索注入 3 个初始 Skill + Innovator 训练 ── |
| 414 | + // trainConversationId 提前算好(不等 setTimeout 里再算):训练对话实际挂在 | |
| 415 | + // org-innovator 名下(这条 fetch 打的是它),前端要能拼出正确链接直接跳过去, | |
| 416 | + // 就得在 res.json 之前拿到这个值 —— 放进 setTimeout 回调里再算,响应早发完了。 | |
| 417 | + let trainConversationId = null; | |
| 414 | 418 | if (result.status === 'training' || result.needsTraining) { |
| 419 | + trainConversationId = `train-${req.params.companyId}-${role}-${Date.now().toString(36)}`; | |
| 415 | 420 | (async () => { |
| 416 | 421 | try { |
| 417 | 422 | console.log(`[Hire] 🚀 自动流水线启动: 为新员工 ${role} 安排 [定义 → 技能注入 → 训练]`); |
| ... | ... | @@ -497,7 +502,7 @@ router.post('/:companyId/hire', async (req, res) => { |
| 497 | 502 | headers: { 'Content-Type': 'application/json' }, |
| 498 | 503 | body: JSON.stringify({ |
| 499 | 504 | message: `公司 ${req.params.companyId} 的员工 ${role} 骨架和基础技能已注入完毕。请你使用 FDE pipeline 对该 Agent ${namespacedId} (特别是它的技能)进行评测和进化训练,直到 Benchmark 达标 (Score ≥ 95)。训练过程包括 Hunt(验证数据), Digest, Evolver 等。请立即开始评估和训练,并保持向我汇报。`, |
| 500 | - conversationId: `train-${req.params.companyId}-${role}-${Date.now().toString(36)}` | |
| 505 | + conversationId: trainConversationId | |
| 501 | 506 | }) |
| 502 | 507 | }).catch(e => console.error('[Hire] Innovator 请求错误:', e.message)); |
| 503 | 508 | }, 5000); |
| ... | ... | @@ -519,7 +524,9 @@ router.post('/:companyId/hire', async (req, res) => { |
| 519 | 524 | console.warn(`[Hire] ⚠️ 心跳挂载跳过: ${hbErr.message}`); |
| 520 | 525 | } |
| 521 | 526 | |
| 522 | - res.json(result); | |
| 527 | + // trainConversationId 只在真的触发了训练时非空——前端凭它拼 | |
| 528 | + // `/agent/org-innovator/{trainConversationId}` 直达训练对话,不用再去猜挂在哪个 agent 下面。 | |
| 529 | + res.json({ ...result, trainConversationId }); | |
| 523 | 530 | } catch (err) { |
| 524 | 531 | res.status(400).json({ error: err.message }); |
| 525 | 532 | } | ... | ... |
web/src/views/CompanyDashboard.vue
| ... | ... | @@ -930,18 +930,27 @@ async function submitHire() { |
| 930 | 930 | hiring.value = true; |
| 931 | 931 | try { |
| 932 | 932 | const companyId = selectedCompany.value.id; |
| 933 | + const trainConvIds = []; | |
| 933 | 934 | for (const roleId of selectedTemplates.value) { |
| 934 | 935 | const tpl = templates.value.find(t => t.id === roleId); |
| 935 | 936 | if (!tpl) continue; |
| 936 | - await axios.post(`/api/company/${companyId}/hire`, { | |
| 937 | + const { data } = await axios.post(`/api/company/${companyId}/hire`, { | |
| 937 | 938 | role: roleId, |
| 938 | 939 | type: tpl?.type, |
| 939 | 940 | description: tpl?.description |
| 940 | 941 | }); |
| 942 | + if (data?.trainConversationId) trainConvIds.push(data.trainConversationId); | |
| 941 | 943 | } |
| 942 | 944 | showHireModal.value = false; |
| 943 | 945 | await loadCompanyStatus(companyId); |
| 944 | 946 | await loadCompanyAgents(companyId); |
| 947 | + if (trainConvIds.length > 0) { | |
| 948 | + // 训练对话挂在 org-innovator 名下,且要等 agent-creator 填完定义后才真正开始 | |
| 949 | + // (company.js /hire 里有 5 秒+delay),链接现在打开大概率是空的,所以不自动开新标签页, | |
| 950 | + // 只把正确地址交出来,用户过一会儿自己点。 | |
| 951 | + const links = trainConvIds.map(id => `${window.location.origin}/agent/org-innovator/${id}`).join('\n') | |
| 952 | + alert(`✅ 雇佣成功,已自动排队 Innovator 训练。\n训练对话稍后可在这里查看(挂在 org-innovator 名下,不是新员工自己):\n${links}`) | |
| 953 | + } | |
| 945 | 954 | } catch (e) { |
| 946 | 955 | alert(`❌ 雇佣失败: ${e.response?.data?.error || e.message}`); |
| 947 | 956 | } finally { |
| ... | ... | @@ -1411,17 +1420,19 @@ ${trainInstructions.value} |
| 1411 | 1420 | 3. 使用更新后的标准调用 Evolver 进行训练。完成全部 FDE 后向上级汇报最终结果。` |
| 1412 | 1421 | |
| 1413 | 1422 | // 发送请求,但不阻塞 UI |
| 1414 | - // org-innovator:27-naming §3.4 批次把 `innovator` 改名后这里没跟着改, | |
| 1415 | - // 「重新训练」按钮从 2026-08-16 起一直静默 404(36 号文档阶段二实测发现)。 | |
| 1423 | + const trainConvId = `train-${selectedCompany.value.id}-${agentId}-${Date.now().toString(36)}` | |
| 1416 | 1424 | axios.post(`/api/agent/org-innovator/chat`, { |
| 1417 | 1425 | message, |
| 1418 | - conversationId: `train-${selectedCompany.value.id}-${agentId}-${Date.now().toString(36)}`, | |
| 1426 | + conversationId: trainConvId, | |
| 1419 | 1427 | }).catch(e => { |
| 1420 | 1428 | console.error('Retrain task error:', e) |
| 1421 | 1429 | alert(`❌ 训练任务异常结束: ${e.message}`) |
| 1422 | 1430 | }) |
| 1423 | 1431 | |
| 1424 | - alert(`✅ 训练指令已发送给 Innovator,将开始在后台进行自动评估和重训 ${agentId}\n您可以在 Agent 的实时状态面板中观察进展。`) | |
| 1432 | + // 训练对话实际挂在 org-innovator 名下(不是被训练的 agentId 自己), | |
| 1433 | + // 直接开新标签页跳过去,别指望用户自己拼对 URL。 | |
| 1434 | + window.open(`/agent/org-innovator/${trainConvId}`, '_blank') | |
| 1435 | + alert(`✅ 训练指令已发送给 Innovator,将开始在后台进行自动评估和重训 ${agentId}\n训练对话已在新标签页打开(挂在 org-innovator 名下)。`) | |
| 1425 | 1436 | showTrainModal.value = false |
| 1426 | 1437 | } catch (e) { |
| 1427 | 1438 | alert(`❌ 发送训练指令失败: ${e.message}`) | ... | ... |