/** * Company Routes — 公司管理 API * * 挂载: /api/company */ const express = require('express'); const router = express.Router(); const { CompanyService } = require('../services/company-service'); const { ScheduleService } = require('../services/schedule-service'); const companyService = new CompanyService(); const scheduleService = new ScheduleService(); // ═══════════════════════════════════════ // Company CRUD // ═══════════════════════════════════════ // POST /api/company/create router.post('/create', (req, res) => { try { const { id, name, description } = req.body; if (!id || !name) return res.status(400).json({ error: 'id 和 name 为必填项' }); // 校验 id 格式 (小写字母、数字、-) if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(id) && id.length < 2) { return res.status(400).json({ error: 'id 格式无效 (只允许小写字母、数字、-)' }); } const company = companyService.create({ id, name, description }); res.json({ success: true, company }); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/list router.get('/list', (req, res) => { res.json(companyService.list()); }); // GET /api/company/templates router.get('/templates', (req, res) => { res.json(companyService.listTemplates()); }); // GET /api/company/:companyId router.get('/:companyId', (req, res) => { const company = companyService.get(req.params.companyId); if (!company) return res.status(404).json({ error: '公司不存在' }); res.json(company); }); // PUT /api/company/:companyId router.put('/:companyId', (req, res) => { const company = companyService.update(req.params.companyId, req.body); if (!company) return res.status(404).json({ error: '公司不存在' }); res.json({ success: true, company }); }); // POST /api/company/:companyId/shutdown router.post('/:companyId/shutdown', (req, res) => { const company = companyService.shutdown(req.params.companyId); if (!company) return res.status(404).json({ error: '公司不存在' }); res.json({ success: true, company }); }); // ═══════════════════════════════════════ // Agent 招聘 // ═══════════════════════════════════════ // POST /api/company/:companyId/hire router.post('/:companyId/hire', async (req, res) => { try { const { role, type, name, description, customAgent, customSoul, customUser } = req.body; if (!role) return res.status(400).json({ error: 'role 为必填项' }); const result = await companyService.hireWithTraining(req.params.companyId, role, { type, name: name || role, description, customAgent, customSoul, customUser, }); // ── P0 FIX: 动态心跳挂载 ── // 招聘成功后,立即为这个新 Agent 注册心跳定时器(如果它的 config.json 里 heartbeat.enabled = true) try { const heartbeatService = require('../services/heartbeat-service'); const namespacedId = `${req.params.companyId}:${role}`; heartbeatService._setupAgentHeartbeat(namespacedId); console.log(`[Hire] 💓 已为 ${namespacedId} 动态挂载心跳`); } catch (hbErr) { console.warn(`[Hire] ⚠️ 心跳挂载跳过: ${hbErr.message}`); } res.json(result); } catch (err) { res.status(400).json({ error: err.message }); } }); // POST /api/company/:companyId/fire router.post('/:companyId/fire', (req, res) => { const { agentId } = req.body; if (!agentId) return res.status(400).json({ error: 'agentId 为必填项' }); const result = companyService.fire(req.params.companyId, agentId); if (!result) return res.status(404).json({ error: 'Agent 不存在' }); res.json(result); }); // POST /api/company/:companyId/deposit-template — 训练合格后沉淀为模板 router.post('/:companyId/deposit-template', (req, res) => { try { const { agentId, benchmarkScore } = req.body; if (!agentId || benchmarkScore === undefined) { return res.status(400).json({ error: 'agentId 和 benchmarkScore 为必填项' }); } const result = companyService.depositTemplate( req.params.companyId, agentId, Number(benchmarkScore) ); res.json(result); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/:companyId/agents router.get('/:companyId/agents', (req, res) => { const agents = companyService.listAgents(req.params.companyId); res.json(agents); }); // ═══════════════════════════════════════ // Company Goals / OKR (公司级 OKR) // ═══════════════════════════════════════ const { GoalsService } = require('../services/goals'); // GET /api/company/:companyId/goals router.get('/:companyId/goals', (req, res) => { const goals = new GoalsService(req.params.companyId); res.json(goals.list()); }); // POST /api/company/:companyId/goals router.post('/:companyId/goals', (req, res) => { try { const { title, description, owner } = req.body; if (!title || !owner) { return res.status(400).json({ error: 'title 和 owner 为必填项' }); } const goals = new GoalsService(req.params.companyId); const goal = goals.create({ title, description, owner }); res.status(201).json(goal); } catch (err) { res.status(500).json({ error: err.message }); } }); // GET /api/company/:companyId/goals/:goalId router.get('/:companyId/goals/:goalId', (req, res) => { const goals = new GoalsService(req.params.companyId); const goal = goals.get(req.params.goalId); if (!goal) return res.status(404).json({ error: 'Goal not found' }); res.json(goal); }); // PUT /api/company/:companyId/goals/:goalId router.put('/:companyId/goals/:goalId', (req, res) => { const goals = new GoalsService(req.params.companyId); const result = goals.updateGoal(req.params.goalId, req.body); if (!result) return res.status(404).json({ error: 'Goal not found' }); res.json(result); }); // POST /api/company/:companyId/goals/:goalId/nodes router.post('/:companyId/goals/:goalId/nodes', (req, res) => { try { const { parentNodeId, title, owner, priority } = req.body; if (!title || !owner) { return res.status(400).json({ error: 'title 和 owner 为必填项' }); } const goals = new GoalsService(req.params.companyId); const result = goals.addNode(req.params.goalId, parentNodeId || null, { title, owner, priority, }); if (!result) return res.status(404).json({ error: 'Goal not found' }); res.status(201).json({ nodeId: result.nodeId, node: result.goal.nodes[result.nodeId] }); } catch (err) { res.status(500).json({ error: err.message }); } }); // PUT /api/company/:companyId/goals/:goalId/nodes/:nodeId router.put('/:companyId/goals/:goalId/nodes/:nodeId', (req, res) => { const goals = new GoalsService(req.params.companyId); const result = goals.updateNode(req.params.goalId, req.params.nodeId, req.body); if (!result) return res.status(404).json({ error: 'Node not found' }); res.json({ nodeId: result.nodeId, node: result.node }); }); // GET /api/company/:companyId/goals/:goalId/progress router.get('/:companyId/goals/:goalId/progress', (req, res) => { const goals = new GoalsService(req.params.companyId); const progress = goals.getProgress(req.params.goalId); if (!progress) return res.status(404).json({ error: 'Goal not found' }); res.json(progress); }); // ═══════════════════════════════════════ // Pipeline / SOP 流水线 // ═══════════════════════════════════════ const { PipelineService } = require('../services/pipeline-service'); // POST /api/company/:companyId/pipelines — 创建流水线 router.post('/:companyId/pipelines', (req, res) => { try { const pSvc = new PipelineService(req.params.companyId); const pipeline = pSvc.createPipeline(req.body); res.status(201).json(pipeline); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/:companyId/pipelines — 列出流水线 router.get('/:companyId/pipelines', (req, res) => { const pSvc = new PipelineService(req.params.companyId); res.json(pSvc.listPipelines()); }); // GET /api/company/:companyId/pipelines/:pipelineId — 获取流水线定义 router.get('/:companyId/pipelines/:pipelineId', (req, res) => { const pSvc = new PipelineService(req.params.companyId); const pipeline = pSvc.getPipeline(req.params.pipelineId); if (!pipeline) return res.status(404).json({ error: '流水线不存在' }); res.json(pipeline); }); // PUT /api/company/:companyId/pipelines/:pipelineId — 更新流水线 router.put('/:companyId/pipelines/:pipelineId', (req, res) => { const pSvc = new PipelineService(req.params.companyId); const pipeline = pSvc.updatePipeline(req.params.pipelineId, req.body); if (!pipeline) return res.status(404).json({ error: '流水线不存在' }); res.json(pipeline); }); // POST /api/company/:companyId/pipelines/:pipelineId/run — 启动运行 router.post('/:companyId/pipelines/:pipelineId/run', (req, res) => { try { const pSvc = new PipelineService(req.params.companyId); const run = pSvc.startRun(req.params.pipelineId, req.body); res.status(201).json(run); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/:companyId/pipelines/runs/list — 列出运行记录 router.get('/:companyId/pipelines/runs/list', (req, res) => { const pSvc = new PipelineService(req.params.companyId); res.json(pSvc.listRuns(req.query.pipelineId)); }); // GET /api/company/:companyId/pipelines/runs/:runId — 获取运行详情 router.get('/:companyId/pipelines/runs/:runId', (req, res) => { const pSvc = new PipelineService(req.params.companyId); const run = pSvc.getRun(req.params.runId); if (!run) return res.status(404).json({ error: '运行记录不存在' }); res.json(run); }); // POST /api/company/:companyId/pipelines/runs/:runId/advance — 推进步骤 router.post('/:companyId/pipelines/runs/:runId/advance', (req, res) => { try { const pSvc = new PipelineService(req.params.companyId); const { stepIndex, output, status } = req.body; if (stepIndex === undefined) return res.status(400).json({ error: 'stepIndex 为必填项' }); const run = pSvc.advanceStep(req.params.runId, Number(stepIndex), { output, status }); res.json(run); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/:companyId/pipelines/runs/:runId/current — 获取当前任务 router.get('/:companyId/pipelines/runs/:runId/current', (req, res) => { const pSvc = new PipelineService(req.params.companyId); const task = pSvc.getCurrentTask(req.params.runId); if (!task) return res.json({ message: '无就绪任务' }); res.json(task); }); // POST /api/company/:companyId/pipelines/runs/:runId/reject — COO 驳回步骤 router.post('/:companyId/pipelines/runs/:runId/reject', (req, res) => { try { const pSvc = new PipelineService(req.params.companyId); const { stepIndex, reason, reviewedBy } = req.body; if (stepIndex === undefined) return res.status(400).json({ error: 'stepIndex 为必填项' }); const run = pSvc.rejectStep(req.params.runId, Number(stepIndex), { reason: reason || '', reviewedBy: reviewedBy || 'coo', }); res.json(run); } catch (err) { res.status(400).json({ error: err.message }); } }); // ═══════════════════════════════════════ // Schedule (日程管理) // ═══════════════════════════════════════ // GET /api/company/:companyId/schedule — 获取日程 router.get('/:companyId/schedule', (req, res) => { res.json(scheduleService.getSchedule(req.params.companyId)); }); // POST /api/company/:companyId/schedule/slots — 添加日程 router.post('/:companyId/schedule/slots', (req, res) => { try { const slot = scheduleService.addSlot(req.params.companyId, req.body); res.json(slot); } catch (err) { res.status(400).json({ error: err.message }); } }); // PUT /api/company/:companyId/schedule/slots/:slotId — 更新日程 router.put('/:companyId/schedule/slots/:slotId', (req, res) => { const slot = scheduleService.updateSlot(req.params.companyId, req.params.slotId, req.body); if (!slot) return res.status(404).json({ error: '日程不存在' }); res.json(slot); }); // DELETE /api/company/:companyId/schedule/slots/:slotId — 删除日程 router.delete('/:companyId/schedule/slots/:slotId', (req, res) => { scheduleService.removeSlot(req.params.companyId, req.params.slotId); res.json({ success: true }); }); // ═══════════════════════════════════════ // DataSource (运营数据接入) // ═══════════════════════════════════════ const { DataSourceService } = require('../services/datasource-service'); const dataSourceService = new DataSourceService(); // GET /api/company/:companyId/data/metrics — 获取运营指标 router.get('/:companyId/data/metrics', async (req, res) => { try { const { source = 'mock', startDate, endDate } = req.query; const sd = startDate || new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10); const ed = endDate || new Date().toISOString().slice(0, 10); const data = await dataSourceService.fetchMetrics(source, { startDate: sd, endDate: ed }); res.json(data); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/:companyId/data/trending — 获取热点趋势 router.get('/:companyId/data/trending', async (req, res) => { try { const { source = 'mock', platform = 'weibo', count = '10' } = req.query; const data = await dataSourceService.fetchTrending(source, { platform, count: parseInt(count, 10) }); res.json(data); } catch (err) { res.status(400).json({ error: err.message }); } }); // GET /api/company/:companyId/data/attribution — 归因分析 router.get('/:companyId/data/attribution', async (req, res) => { try { const { source = 'mock' } = req.query; const now = new Date(); const currentEnd = now.toISOString().slice(0, 10); const currentStart = new Date(now.getTime() - 7 * 86400000).toISOString().slice(0, 10); const previousEnd = new Date(now.getTime() - 7 * 86400000).toISOString().slice(0, 10); const previousStart = new Date(now.getTime() - 14 * 86400000).toISOString().slice(0, 10); const analysis = await dataSourceService.analyzeAttribution( source, { startDate: currentStart, endDate: currentEnd }, { startDate: previousStart, endDate: previousEnd } ); res.json(analysis); } catch (err) { res.status(400).json({ error: err.message }); } }); module.exports = router;