company.js 11.6 KB
/**
 * Company Routes — 公司管理 API
 *
 * 挂载: /api/company
 */

const express = require('express');
const router = express.Router();
const { CompanyService } = require('../services/company-service');

const companyService = new CompanyService();

// ═══════════════════════════════════════
//  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 });
  }
});

module.exports = router;