新功能:
- 新增 global_collaboration_rules 表
- POST /api/rules/global - 设置全局规约
- GET /api/rules/global - 获取全局规约
- 智能体领取任务时同时收到全局规约和项目规约
规约层级:
1. 全局规约 - 适用于所有项目和智能体
2. 项目规约 - 仅适用于特定项目
智能体领取任务时的响应示例:
{
"collaboration_rules": {
"global_rules": "全局规约内容",
"project_rules": "项目规约内容"
}
}
928 lines
26 KiB
JavaScript
928 lines
26 KiB
JavaScript
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
const cors = require('cors');
|
|
const bodyParser = require('body-parser');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
const os = require('os');
|
|
const multer = require('multer');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// 中间件
|
|
app.use(cors());
|
|
app.use(bodyParser.json({ limit: '50mb' }));
|
|
app.use(express.static('public'));
|
|
app.use('/uploads', express.static('uploads'));
|
|
|
|
// 配置文件上传
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
const dir = 'uploads/';
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
cb(null, dir);
|
|
},
|
|
filename: (req, file, cb) => {
|
|
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
|
|
cb(null, uniqueName);
|
|
}
|
|
});
|
|
|
|
const upload = multer({
|
|
storage,
|
|
limits: { fileSize: 10 * 1024 * 1024 } // 10MB
|
|
});
|
|
|
|
// PostgreSQL 连接池
|
|
const pool = new Pool({
|
|
host: process.env.DB_HOST || 'postgres',
|
|
port: process.env.DB_PORT || 5432,
|
|
database: process.env.DB_NAME || 'agent_tasks',
|
|
user: process.env.DB_USER || 'postgres',
|
|
password: process.env.DB_PASSWORD || 'postgres',
|
|
max: 20,
|
|
idleTimeoutMillis: 30000,
|
|
connectionTimeoutMillis: 2000,
|
|
});
|
|
|
|
// 测试数据库连接
|
|
pool.connect((err, client, release) => {
|
|
if (err) {
|
|
console.error('❌ 数据库连接失败:', err.message);
|
|
process.exit(1);
|
|
} else {
|
|
console.log('✅ 已连接到 PostgreSQL 数据库');
|
|
release();
|
|
initDatabase();
|
|
}
|
|
});
|
|
|
|
// 创建表结构
|
|
async function initDatabase() {
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
// 智能体表
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS agents (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(100),
|
|
capabilities JSONB,
|
|
status VARCHAR(50) DEFAULT 'idle',
|
|
registered_at BIGINT,
|
|
last_heartbeat BIGINT
|
|
)
|
|
`);
|
|
|
|
// 项目表
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS projects (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
name VARCHAR(500) NOT NULL,
|
|
description TEXT,
|
|
created_by VARCHAR(255),
|
|
created_at BIGINT,
|
|
updated_at BIGINT,
|
|
status VARCHAR(50) DEFAULT 'active'
|
|
)
|
|
`);
|
|
|
|
// 全局协作规约表
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS global_collaboration_rules (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
content TEXT NOT NULL,
|
|
created_by VARCHAR(255),
|
|
created_at BIGINT,
|
|
updated_at BIGINT
|
|
)
|
|
`);
|
|
|
|
// 项目协作规约表
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS collaboration_rules (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
project_id VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE,
|
|
content TEXT NOT NULL,
|
|
created_by VARCHAR(255),
|
|
created_at BIGINT,
|
|
updated_at BIGINT
|
|
)
|
|
`);
|
|
|
|
// 项目信息共享清单表
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS project_notes (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
project_id VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE,
|
|
content TEXT NOT NULL,
|
|
created_by VARCHAR(255),
|
|
created_at BIGINT,
|
|
updated_at BIGINT
|
|
)
|
|
`);
|
|
|
|
// 任务表(扩展)
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
project_id VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE,
|
|
parent_task_id VARCHAR(255) REFERENCES tasks(id) ON DELETE SET NULL,
|
|
title VARCHAR(500) NOT NULL,
|
|
description TEXT,
|
|
task_type VARCHAR(50) DEFAULT 'development',
|
|
priority VARCHAR(50) DEFAULT 'normal',
|
|
status VARCHAR(50) DEFAULT 'initial',
|
|
created_by VARCHAR(255),
|
|
assigned_to VARCHAR(255),
|
|
created_at BIGINT,
|
|
updated_at BIGINT,
|
|
completed_at BIGINT,
|
|
result TEXT
|
|
)
|
|
`);
|
|
|
|
// 附件表
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS attachments (
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
task_id VARCHAR(255) REFERENCES tasks(id) ON DELETE CASCADE,
|
|
filename VARCHAR(500) NOT NULL,
|
|
original_name VARCHAR(500) NOT NULL,
|
|
file_path VARCHAR(1000) NOT NULL,
|
|
file_size BIGINT,
|
|
mime_type VARCHAR(100),
|
|
uploaded_by VARCHAR(255),
|
|
uploaded_at BIGINT
|
|
)
|
|
`);
|
|
|
|
// 创建索引
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status)`);
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)`);
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id)`);
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id)`);
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to)`);
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_attachments_task ON attachments(task_id)`);
|
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_project_notes_project ON project_notes(project_id)`);
|
|
|
|
await client.query('COMMIT');
|
|
console.log('✅ 数据库表已初始化');
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
console.error('❌ 数据库初始化失败:', err);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// ==================== 项目管理 API ====================
|
|
|
|
// 创建项目
|
|
app.post('/api/projects', async (req, res) => {
|
|
const { name, description, created_by } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: '项目名称不能为空' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
await pool.query(
|
|
`INSERT INTO projects (id, name, description, created_by, created_at, updated_at, status)
|
|
VALUES ($1, $2, $3, $4, $5, $6, 'active')`,
|
|
[id, name, description || '', created_by || 'human', now, now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
name,
|
|
status: 'active',
|
|
message: '项目创建成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取所有项目
|
|
app.get('/api/projects', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM projects ORDER BY created_at DESC');
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取单个项目详情
|
|
app.get('/api/projects/:id', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM projects WHERE id = $1', [req.params.id]);
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: '项目不存在' });
|
|
}
|
|
res.json(result.rows[0]);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 更新项目
|
|
app.put('/api/projects/:id', async (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, description, status } = req.body;
|
|
|
|
const updates = [];
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
|
|
if (name) {
|
|
updates.push(`name = $${paramIndex++}`);
|
|
params.push(name);
|
|
}
|
|
if (description !== undefined) {
|
|
updates.push(`description = $${paramIndex++}`);
|
|
params.push(description);
|
|
}
|
|
if (status) {
|
|
updates.push(`status = $${paramIndex++}`);
|
|
params.push(status);
|
|
}
|
|
|
|
updates.push(`updated_at = $${paramIndex++}`);
|
|
params.push(Date.now());
|
|
params.push(id);
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
`UPDATE projects SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
|
params
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ error: '项目不存在' });
|
|
}
|
|
|
|
res.json({ message: '项目更新成功' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ==================== 协作规约 API ====================
|
|
|
|
// 设置全局协作规约
|
|
app.post('/api/rules/global', async (req, res) => {
|
|
const { content, created_by } = req.body;
|
|
|
|
if (!content) {
|
|
return res.status(400).json({ error: '规约内容不能为空' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
// 删除旧的全局规约(只保留一个)
|
|
await pool.query('DELETE FROM global_collaboration_rules');
|
|
|
|
// 创建新的全局规约
|
|
await pool.query(
|
|
`INSERT INTO global_collaboration_rules (id, content, created_by, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5)`,
|
|
[id, content, created_by || 'human', now, now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
message: '全局协作规约设置成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取全局协作规约
|
|
app.get('/api/rules/global', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM global_collaboration_rules');
|
|
res.json(result.rows[0] || null);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 设置项目协作规约
|
|
app.post('/api/projects/:projectId/rules', async (req, res) => {
|
|
const { projectId } = req.params;
|
|
const { content, created_by } = req.body;
|
|
|
|
if (!content) {
|
|
return res.status(400).json({ error: '规约内容不能为空' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
// 检查项目是否存在
|
|
const projectCheck = await pool.query('SELECT id FROM projects WHERE id = $1', [projectId]);
|
|
if (projectCheck.rows.length === 0) {
|
|
return res.status(404).json({ error: '项目不存在' });
|
|
}
|
|
|
|
// 删除旧规约(每个项目只保留一个)
|
|
await pool.query('DELETE FROM collaboration_rules WHERE project_id = $1', [projectId]);
|
|
|
|
// 创建新规约
|
|
await pool.query(
|
|
`INSERT INTO collaboration_rules (id, project_id, content, created_by, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
[id, projectId, content, created_by || 'human', now, now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
message: '协作规约设置成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取协作规约
|
|
app.get('/api/projects/:projectId/rules', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
'SELECT * FROM collaboration_rules WHERE project_id = $1',
|
|
[req.params.projectId]
|
|
);
|
|
res.json(result.rows[0] || null);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ==================== 信息共享清单 API ====================
|
|
|
|
// 添加共享笔记
|
|
app.post('/api/projects/:projectId/notes', async (req, res) => {
|
|
const { projectId } = req.params;
|
|
const { content, created_by } = req.body;
|
|
|
|
if (!content) {
|
|
return res.status(400).json({ error: '笔记内容不能为空' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
await pool.query(
|
|
`INSERT INTO project_notes (id, project_id, content, created_by, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
[id, projectId, content, created_by || 'human', now, now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
message: '笔记添加成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取共享笔记列表
|
|
app.get('/api/projects/:projectId/notes', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
'SELECT * FROM project_notes WHERE project_id = $1 ORDER BY created_at DESC',
|
|
[req.params.projectId]
|
|
);
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 更新共享笔记
|
|
app.put('/api/projects/:projectId/notes/:noteId', async (req, res) => {
|
|
const { noteId } = req.params;
|
|
const { content } = req.body;
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
'UPDATE project_notes SET content = $1, updated_at = $2 WHERE id = $3',
|
|
[content, Date.now(), noteId]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ error: '笔记不存在' });
|
|
}
|
|
|
|
res.json({ message: '笔记更新成功' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 删除共享笔记
|
|
app.delete('/api/projects/:projectId/notes/:noteId', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
'DELETE FROM project_notes WHERE id = $1',
|
|
[req.params.noteId]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ error: '笔记不存在' });
|
|
}
|
|
|
|
res.json({ message: '笔记删除成功' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ==================== 智能体 API ====================
|
|
|
|
// 注册智能体
|
|
app.post('/api/agents/register', async (req, res) => {
|
|
const { name, type, capabilities } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: '智能体名称不能为空' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
await pool.query(
|
|
`INSERT INTO agents (id, name, type, capabilities, status, registered_at, last_heartbeat)
|
|
VALUES ($1, $2, $3, $4, 'idle', $5, $6)`,
|
|
[id, name, type || 'general', JSON.stringify(capabilities || []), now, now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
name,
|
|
type: type || 'general',
|
|
status: 'idle',
|
|
message: '智能体注册成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取所有智能体
|
|
app.get('/api/agents', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM agents ORDER BY registered_at DESC');
|
|
res.json(result.rows.map(row => ({
|
|
...row,
|
|
capabilities: row.capabilities || []
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 更新智能体状态(心跳)
|
|
app.post('/api/agents/:id/heartbeat', async (req, res) => {
|
|
const { id } = req.params;
|
|
const { status } = req.body;
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
'UPDATE agents SET status = $1, last_heartbeat = $2 WHERE id = $3',
|
|
[status || 'idle', Date.now(), id]
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
return res.status(404).json({ error: '智能体不存在' });
|
|
}
|
|
|
|
res.json({ message: '心跳更新成功' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ==================== 任务管理 API ====================
|
|
|
|
// 创建任务(支持子任务)
|
|
app.post('/api/tasks', async (req, res) => {
|
|
const { project_id, parent_task_id, title, description, task_type, priority, created_by } = req.body;
|
|
|
|
if (!title) {
|
|
return res.status(400).json({ error: '任务标题不能为空' });
|
|
}
|
|
|
|
if (!project_id) {
|
|
return res.status(400).json({ error: '必须指定项目ID' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
await pool.query(
|
|
`INSERT INTO tasks (id, project_id, parent_task_id, title, description, task_type, priority, status, created_by, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, 'initial', $8, $9, $10)`,
|
|
[id, project_id, parent_task_id || null, title, description || '',
|
|
task_type || 'development', priority || 'normal', created_by || 'human', now, now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
title,
|
|
status: 'initial',
|
|
message: '任务创建成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取任务列表(支持按项目、状态、父任务过滤)
|
|
app.get('/api/tasks', async (req, res) => {
|
|
const { project_id, status, parent_task_id } = req.query;
|
|
|
|
try {
|
|
let query = 'SELECT * FROM tasks WHERE 1=1';
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
|
|
if (project_id) {
|
|
query += ` AND project_id = $${paramIndex++}`;
|
|
params.push(project_id);
|
|
}
|
|
|
|
if (status) {
|
|
query += ` AND status = $${paramIndex++}`;
|
|
params.push(status);
|
|
}
|
|
|
|
if (parent_task_id) {
|
|
query += ` AND parent_task_id = $${paramIndex++}`;
|
|
params.push(parent_task_id);
|
|
} else if (req.query.root === 'true') {
|
|
query += ' AND parent_task_id IS NULL';
|
|
}
|
|
|
|
query += ' ORDER BY created_at DESC';
|
|
|
|
const result = await pool.query(query, params);
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取任务详情(包含子任务和附件)
|
|
app.get('/api/tasks/:id', async (req, res) => {
|
|
try {
|
|
const taskResult = await pool.query('SELECT * FROM tasks WHERE id = $1', [req.params.id]);
|
|
if (taskResult.rows.length === 0) {
|
|
return res.status(404).json({ error: '任务不存在' });
|
|
}
|
|
|
|
const task = taskResult.rows[0];
|
|
|
|
// 获取子任务
|
|
const subtasksResult = await pool.query(
|
|
'SELECT * FROM tasks WHERE parent_task_id = $1 ORDER BY created_at',
|
|
[req.params.id]
|
|
);
|
|
|
|
// 获取附件
|
|
const attachmentsResult = await pool.query(
|
|
'SELECT * FROM attachments WHERE task_id = $1 ORDER BY uploaded_at',
|
|
[req.params.id]
|
|
);
|
|
|
|
res.json({
|
|
...task,
|
|
subtasks: subtasksResult.rows,
|
|
attachments: attachmentsResult.rows
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 领取任务
|
|
app.post('/api/tasks/:id/claim', async (req, res) => {
|
|
const { id } = req.params;
|
|
const { agent_id } = req.body;
|
|
|
|
if (!agent_id) {
|
|
return res.status(400).json({ error: '需要提供智能体ID' });
|
|
}
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
const taskResult = await client.query('SELECT status, project_id FROM tasks WHERE id = $1', [id]);
|
|
|
|
if (taskResult.rows.length === 0) {
|
|
await client.query('ROLLBACK');
|
|
return res.status(404).json({ error: '任务不存在' });
|
|
}
|
|
|
|
const task = taskResult.rows[0];
|
|
|
|
if (task.status !== 'initial' && task.status !== 'in_progress') {
|
|
await client.query('ROLLBACK');
|
|
return res.status(400).json({ error: '任务状态不允许领取' });
|
|
}
|
|
|
|
// 获取全局协作规约
|
|
const globalRulesResult = await client.query(
|
|
'SELECT content FROM global_collaboration_rules'
|
|
);
|
|
|
|
// 获取项目协作规约
|
|
const projectRulesResult = await client.query(
|
|
'SELECT content FROM collaboration_rules WHERE project_id = $1',
|
|
[task.project_id]
|
|
);
|
|
|
|
await client.query(
|
|
'UPDATE tasks SET status = $1, assigned_to = $2, updated_at = $3 WHERE id = $4',
|
|
['in_progress', agent_id, Date.now(), id]
|
|
);
|
|
|
|
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['busy', agent_id]);
|
|
|
|
await client.query('COMMIT');
|
|
|
|
const response = {
|
|
message: '任务领取成功'
|
|
};
|
|
|
|
// 返回规约给智能体
|
|
const rules = {};
|
|
|
|
if (globalRulesResult.rows.length > 0) {
|
|
rules.global_rules = globalRulesResult.rows[0].content;
|
|
}
|
|
|
|
if (projectRulesResult.rows.length > 0) {
|
|
rules.project_rules = projectRulesResult.rows[0].content;
|
|
}
|
|
|
|
if (rules.global_rules || rules.project_rules) {
|
|
response.collaboration_rules = rules;
|
|
response.notice = '⚠️ 请严格遵守协作规约';
|
|
}
|
|
|
|
res.json(response);
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// 更新任务状态
|
|
app.put('/api/tasks/:id', async (req, res) => {
|
|
const { id } = req.params;
|
|
const { status, result, description, task_type, priority } = req.body;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
const now = Date.now();
|
|
const updates = [];
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
|
|
if (status) {
|
|
updates.push(`status = $${paramIndex++}`);
|
|
params.push(status);
|
|
}
|
|
if (result !== undefined) {
|
|
updates.push(`result = $${paramIndex++}`);
|
|
params.push(result);
|
|
}
|
|
if (description !== undefined) {
|
|
updates.push(`description = $${paramIndex++}`);
|
|
params.push(description);
|
|
}
|
|
if (task_type) {
|
|
updates.push(`task_type = $${paramIndex++}`);
|
|
params.push(task_type);
|
|
}
|
|
if (priority) {
|
|
updates.push(`priority = $${paramIndex++}`);
|
|
params.push(priority);
|
|
}
|
|
if (status === 'completed') {
|
|
updates.push(`completed_at = $${paramIndex++}`);
|
|
params.push(now);
|
|
}
|
|
|
|
updates.push(`updated_at = $${paramIndex++}`);
|
|
params.push(now);
|
|
params.push(id);
|
|
|
|
const updateResult = await client.query(
|
|
`UPDATE tasks SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
|
params
|
|
);
|
|
|
|
if (updateResult.rowCount === 0) {
|
|
await client.query('ROLLBACK');
|
|
return res.status(404).json({ error: '任务不存在' });
|
|
}
|
|
|
|
// 如果任务完成,将智能体状态改为 idle
|
|
if (status === 'completed') {
|
|
const taskResult = await client.query('SELECT assigned_to FROM tasks WHERE id = $1', [id]);
|
|
if (taskResult.rows.length > 0 && taskResult.rows[0].assigned_to) {
|
|
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['idle', taskResult.rows[0].assigned_to]);
|
|
}
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
res.json({ message: '任务状态更新成功' });
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// ==================== 附件管理 API ====================
|
|
|
|
// 上传附件
|
|
app.post('/api/tasks/:taskId/attachments', upload.single('file'), async (req, res) => {
|
|
const { taskId } = req.params;
|
|
const { uploaded_by } = req.body;
|
|
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: '没有上传文件' });
|
|
}
|
|
|
|
const id = uuidv4();
|
|
const now = Date.now();
|
|
|
|
try {
|
|
await pool.query(
|
|
`INSERT INTO attachments (id, task_id, filename, original_name, file_path, file_size, mime_type, uploaded_by, uploaded_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
[id, taskId, req.file.filename, req.file.originalname, req.file.path,
|
|
req.file.size, req.file.mimetype, uploaded_by || 'human', now]
|
|
);
|
|
|
|
res.json({
|
|
id,
|
|
filename: req.file.filename,
|
|
original_name: req.file.originalname,
|
|
url: `/uploads/${req.file.filename}`,
|
|
message: '附件上传成功'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取任务附件列表
|
|
app.get('/api/tasks/:taskId/attachments', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
'SELECT * FROM attachments WHERE task_id = $1 ORDER BY uploaded_at DESC',
|
|
[req.params.taskId]
|
|
);
|
|
res.json(result.rows.map(row => ({
|
|
...row,
|
|
url: `/uploads/${row.filename}`
|
|
})));
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 删除附件
|
|
app.delete('/api/tasks/:taskId/attachments/:attachmentId', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
'SELECT file_path FROM attachments WHERE id = $1',
|
|
[req.params.attachmentId]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: '附件不存在' });
|
|
}
|
|
|
|
// 删除文件
|
|
const filePath = result.rows[0].file_path;
|
|
if (fs.existsSync(filePath)) {
|
|
fs.unlinkSync(filePath);
|
|
}
|
|
|
|
// 删除数据库记录
|
|
await pool.query('DELETE FROM attachments WHERE id = $1', [req.params.attachmentId]);
|
|
|
|
res.json({ message: '附件删除成功' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ==================== 统计信息 API ====================
|
|
|
|
app.get('/api/stats', async (req, res) => {
|
|
try {
|
|
const stats = {
|
|
total_agents: 0,
|
|
active_agents: 0,
|
|
total_projects: 0,
|
|
total_tasks: 0,
|
|
tasks_by_status: {},
|
|
tasks_by_type: {}
|
|
};
|
|
|
|
const results = await Promise.all([
|
|
pool.query('SELECT COUNT(*) as count FROM agents'),
|
|
pool.query('SELECT COUNT(*) as count FROM agents WHERE status = $1', ['busy']),
|
|
pool.query('SELECT COUNT(*) as count FROM projects'),
|
|
pool.query('SELECT COUNT(*) as count FROM tasks'),
|
|
pool.query('SELECT status, COUNT(*) as count FROM tasks GROUP BY status'),
|
|
pool.query('SELECT task_type, COUNT(*) as count FROM tasks GROUP BY task_type')
|
|
]);
|
|
|
|
stats.total_agents = parseInt(results[0].rows[0].count);
|
|
stats.active_agents = parseInt(results[1].rows[0].count);
|
|
stats.total_projects = parseInt(results[2].rows[0].count);
|
|
stats.total_tasks = parseInt(results[3].rows[0].count);
|
|
|
|
results[4].rows.forEach(row => {
|
|
stats.tasks_by_status[row.status] = parseInt(row.count);
|
|
});
|
|
|
|
results[5].rows.forEach(row => {
|
|
stats.tasks_by_type[row.task_type] = parseInt(row.count);
|
|
});
|
|
|
|
res.json(stats);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 健康检查
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
await pool.query('SELECT 1');
|
|
res.json({ status: 'healthy', database: 'connected' });
|
|
} catch (err) {
|
|
res.status(503).json({ status: 'unhealthy', error: err.message });
|
|
}
|
|
});
|
|
|
|
// 获取本机 IP 地址
|
|
function getLocalIP() {
|
|
const interfaces = os.networkInterfaces();
|
|
for (const name of Object.keys(interfaces)) {
|
|
for (const iface of interfaces[name]) {
|
|
if (iface.family === 'IPv4' && !iface.internal) {
|
|
return iface.address;
|
|
}
|
|
}
|
|
}
|
|
return 'localhost';
|
|
}
|
|
|
|
// 启动服务器
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
const localIP = getLocalIP();
|
|
console.log(`🚀 多智能体任务服务已启动 (v2.0)`);
|
|
console.log(` 本地访问: http://localhost:${PORT}`);
|
|
console.log(` 远程访问: http://${localIP}:${PORT}`);
|
|
});
|
|
|
|
// 优雅关闭
|
|
process.on('SIGINT', async () => {
|
|
console.log('\n正在关闭服务...');
|
|
await pool.end();
|
|
console.log('数据库连接已关闭');
|
|
process.exit(0);
|
|
}); |