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,7 +411,12 @@ router.post('/:companyId/hire', async (req, res) => { | ||
| 411 | }); | 411 | }); |
| 412 | 412 | ||
| 413 | // ── 核心逻辑扩展:自动补充定义 + 搜索注入 3 个初始 Skill + Innovator 训练 ── | 413 | // ── 核心逻辑扩展:自动补充定义 + 搜索注入 3 个初始 Skill + Innovator 训练 ── |
| 414 | + // trainConversationId 提前算好(不等 setTimeout 里再算):训练对话实际挂在 | ||
| 415 | + // org-innovator 名下(这条 fetch 打的是它),前端要能拼出正确链接直接跳过去, | ||
| 416 | + // 就得在 res.json 之前拿到这个值 —— 放进 setTimeout 回调里再算,响应早发完了。 | ||
| 417 | + let trainConversationId = null; | ||
| 414 | if (result.status === 'training' || result.needsTraining) { | 418 | if (result.status === 'training' || result.needsTraining) { |
| 419 | + trainConversationId = `train-${req.params.companyId}-${role}-${Date.now().toString(36)}`; | ||
| 415 | (async () => { | 420 | (async () => { |
| 416 | try { | 421 | try { |
| 417 | console.log(`[Hire] 🚀 自动流水线启动: 为新员工 ${role} 安排 [定义 → 技能注入 → 训练]`); | 422 | console.log(`[Hire] 🚀 自动流水线启动: 为新员工 ${role} 安排 [定义 → 技能注入 → 训练]`); |
| @@ -497,7 +502,7 @@ router.post('/:companyId/hire', async (req, res) => { | @@ -497,7 +502,7 @@ router.post('/:companyId/hire', async (req, res) => { | ||
| 497 | headers: { 'Content-Type': 'application/json' }, | 502 | headers: { 'Content-Type': 'application/json' }, |
| 498 | body: JSON.stringify({ | 503 | body: JSON.stringify({ |
| 499 | message: `公司 ${req.params.companyId} 的员工 ${role} 骨架和基础技能已注入完毕。请你使用 FDE pipeline 对该 Agent ${namespacedId} (特别是它的技能)进行评测和进化训练,直到 Benchmark 达标 (Score ≥ 95)。训练过程包括 Hunt(验证数据), Digest, Evolver 等。请立即开始评估和训练,并保持向我汇报。`, | 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 | }).catch(e => console.error('[Hire] Innovator 请求错误:', e.message)); | 507 | }).catch(e => console.error('[Hire] Innovator 请求错误:', e.message)); |
| 503 | }, 5000); | 508 | }, 5000); |
| @@ -519,7 +524,9 @@ router.post('/:companyId/hire', async (req, res) => { | @@ -519,7 +524,9 @@ router.post('/:companyId/hire', async (req, res) => { | ||
| 519 | console.warn(`[Hire] ⚠️ 心跳挂载跳过: ${hbErr.message}`); | 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 | } catch (err) { | 530 | } catch (err) { |
| 524 | res.status(400).json({ error: err.message }); | 531 | res.status(400).json({ error: err.message }); |
| 525 | } | 532 | } |
web/src/views/CompanyDashboard.vue
| @@ -930,18 +930,27 @@ async function submitHire() { | @@ -930,18 +930,27 @@ async function submitHire() { | ||
| 930 | hiring.value = true; | 930 | hiring.value = true; |
| 931 | try { | 931 | try { |
| 932 | const companyId = selectedCompany.value.id; | 932 | const companyId = selectedCompany.value.id; |
| 933 | + const trainConvIds = []; | ||
| 933 | for (const roleId of selectedTemplates.value) { | 934 | for (const roleId of selectedTemplates.value) { |
| 934 | const tpl = templates.value.find(t => t.id === roleId); | 935 | const tpl = templates.value.find(t => t.id === roleId); |
| 935 | if (!tpl) continue; | 936 | if (!tpl) continue; |
| 936 | - await axios.post(`/api/company/${companyId}/hire`, { | 937 | + const { data } = await axios.post(`/api/company/${companyId}/hire`, { |
| 937 | role: roleId, | 938 | role: roleId, |
| 938 | type: tpl?.type, | 939 | type: tpl?.type, |
| 939 | description: tpl?.description | 940 | description: tpl?.description |
| 940 | }); | 941 | }); |
| 942 | + if (data?.trainConversationId) trainConvIds.push(data.trainConversationId); | ||
| 941 | } | 943 | } |
| 942 | showHireModal.value = false; | 944 | showHireModal.value = false; |
| 943 | await loadCompanyStatus(companyId); | 945 | await loadCompanyStatus(companyId); |
| 944 | await loadCompanyAgents(companyId); | 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 | } catch (e) { | 954 | } catch (e) { |
| 946 | alert(`❌ 雇佣失败: ${e.response?.data?.error || e.message}`); | 955 | alert(`❌ 雇佣失败: ${e.response?.data?.error || e.message}`); |
| 947 | } finally { | 956 | } finally { |
| @@ -1411,17 +1420,19 @@ ${trainInstructions.value} | @@ -1411,17 +1420,19 @@ ${trainInstructions.value} | ||
| 1411 | 3. 使用更新后的标准调用 Evolver 进行训练。完成全部 FDE 后向上级汇报最终结果。` | 1420 | 3. 使用更新后的标准调用 Evolver 进行训练。完成全部 FDE 后向上级汇报最终结果。` |
| 1412 | 1421 | ||
| 1413 | // 发送请求,但不阻塞 UI | 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 | axios.post(`/api/agent/org-innovator/chat`, { | 1424 | axios.post(`/api/agent/org-innovator/chat`, { |
| 1417 | message, | 1425 | message, |
| 1418 | - conversationId: `train-${selectedCompany.value.id}-${agentId}-${Date.now().toString(36)}`, | 1426 | + conversationId: trainConvId, |
| 1419 | }).catch(e => { | 1427 | }).catch(e => { |
| 1420 | console.error('Retrain task error:', e) | 1428 | console.error('Retrain task error:', e) |
| 1421 | alert(`❌ 训练任务异常结束: ${e.message}`) | 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 | showTrainModal.value = false | 1436 | showTrainModal.value = false |
| 1426 | } catch (e) { | 1437 | } catch (e) { |
| 1427 | alert(`❌ 发送训练指令失败: ${e.message}`) | 1438 | alert(`❌ 发送训练指令失败: ${e.message}`) |