From 7fd9eb0b218958bb57b5c6f11683725ce9eab826 Mon Sep 17 00:00:00 2001 From: work-1 Date: Sun, 15 Feb 2026 14:29:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=88=B0=20PostgreSQL=20?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=20Docker=20Compose=20=E9=83=A8?= =?UTF-8?q?=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要更新: - 替换 SQLite 为 PostgreSQL 数据库 - 添加 Docker Compose 配置文件 - 添加 Dockerfile 支持容器化部署 - 更新 README 添加详细的智能体注册指南 - 添加 .env.example 环境变量模板 - 修复 uuid 模块兼容性问题(降级到 9.x) - 完善 API 文档和使用示例 --- .env.example | 9 + .gitignore | 1 + Dockerfile | 19 +++ README.md | 401 +++++++++++++++++++++++++++++++++++++-------- docker-compose.yml | 50 ++++++ package-lock.json | 159 +++++++++++++++++- package.json | 17 +- server-postgres.js | 382 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 953 insertions(+), 85 deletions(-) create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 server-postgres.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ce2a3a6 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# 数据库配置 +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=agent_tasks +DB_USER=postgres +DB_PASSWORD=postgres + +# 应用配置 +PORT=3000 diff --git a/.gitignore b/.gitignore index 33f5748..dce3965 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ *.log .env .DS_Store +postgres_data/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..36187ea --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:18-alpine + +WORKDIR /app + +# 复制 package 文件 +COPY package*.json ./ + +# 安装依赖 +RUN npm ci --only=production + +# 复制应用代码 +COPY server-postgres.js ./server.js +COPY public ./public + +# 暴露端口 +EXPOSE 3000 + +# 启动应用 +CMD ["node", "server.js"] diff --git a/README.md b/README.md index 9e8c6ff..6c4eed0 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,163 @@ - ✅ **状态实时更新** - 跟踪任务和智能体的状态变化 - ✅ **Web 管理界面** - 可视化查看和管理任务 - ✅ **RESTful API** - 完整的 HTTP API 接口 +- ✅ **PostgreSQL 存储** - 稳定可靠的关系型数据库 +- ✅ **Docker 部署** - 一键启动完整环境 -## 快速开始 +## 快速开始(Docker Compose) + +### 前置要求 +- Docker +- Docker Compose ### 1. 启动服务 ```bash cd /data/openclaw/workspace/agent-task-service -node server.js +docker-compose up -d ``` -服务将在 `http://localhost:3000` 启动。 +服务将在以下地址启动: +- **Web 界面**: http://localhost:3000 +- **API 接口**: http://localhost:3000/api +- **PostgreSQL**: localhost:5432 -### 2. 访问 Web 界面 +### 2. 查看日志 -在浏览器中打开: `http://localhost:3000` +```bash +docker-compose logs -f app +``` + +### 3. 停止服务 + +```bash +docker-compose down +``` + +### 4. 完全清理(包括数据) + +```bash +docker-compose down -v +``` + +## 本地开发 + +### 前置要求 +- Node.js 18+ +- PostgreSQL 12+ + +### 1. 安装依赖 + +```bash +npm install +``` + +### 2. 配置数据库 + +复制环境变量模板: +```bash +cp .env.example .env +``` + +编辑 `.env` 文件,配置数据库连接: +```env +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=agent_tasks +DB_USER=postgres +DB_PASSWORD=your_password +PORT=3000 +``` + +### 3. 创建数据库 + +```bash +psql -U postgres -c "CREATE DATABASE agent_tasks;" +``` + +### 4. 启动服务 + +```bash +node server-postgres.js +``` + +## 智能体注册与使用指南 + +### 步骤 1: 注册智能体 + +智能体首次启动时需要向系统注册: + +```bash +curl -X POST http://localhost:3000/api/agents/register \ + -H "Content-Type: application/json" \ + -d '{ + "name": "我的智能体", + "type": "general", + "capabilities": ["数据分析", "文本处理", "API调用"] + }' +``` + +**响应示例:** +```json +{ + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "我的智能体", + "type": "general", + "status": "idle", + "message": "智能体注册成功" +} +``` + +**重要提示:** +- 保存返回的 `id`,后续所有操作都需要这个 ID +- `name`: 智能体的显示名称 +- `type`: 类型(可选)如 `general`, `search`, `coding`, `analysis` 等 +- `capabilities`: 能力列表(可选)用于说明智能体可以做什么 + +### 步骤 2: 维持心跳 + +智能体需要定期发送心跳(建议每 30-60 秒): + +```bash +curl -X POST http://localhost:3000/api/agents/{agent_id}/heartbeat \ + -H "Content-Type: application/json" \ + -d '{ + "status": "idle" + }' +``` + +状态值: +- `idle`: 空闲,可以接受新任务 +- `busy`: 忙碌中,正在执行任务 + +### 步骤 3: 查询待处理任务 + +```bash +curl http://localhost:3000/api/tasks?status=pending +``` + +### 步骤 4: 领取任务 + +```bash +curl -X POST http://localhost:3000/api/tasks/{task_id}/claim \ + -H "Content-Type: application/json" \ + -d '{ + "agent_id": "your-agent-id" + }' +``` + +### 步骤 5: 更新任务状态 + +执行完任务后,更新状态为完成: + +```bash +curl -X PUT http://localhost:3000/api/tasks/{task_id} \ + -H "Content-Type: application/json" \ + -d '{ + "status": "completed", + "result": "任务执行结果描述" + }' +``` ## API 文档 @@ -36,9 +178,9 @@ POST /api/agents/register Content-Type: application/json { - "name": "智能体名称", - "type": "general", // 可选: general, search, coding, etc. - "capabilities": ["搜索", "编程", "分析"] // 可选 + "name": "智能体名称", // 必填 + "type": "general", // 可选: general, search, coding, etc. + "capabilities": ["搜索", "编程"] // 可选: 能力列表 } 响应: @@ -87,10 +229,10 @@ POST /api/tasks Content-Type: application/json { - "title": "任务标题", - "description": "任务详细描述", - "priority": "normal", // low | normal | high - "created_by": "human" // human | agent_id + "title": "任务标题", // 必填 + "description": "任务详细描述", // 可选 + "priority": "normal", // 可选: low | normal | high + "created_by": "human" // 可选: human | agent_id } 响应: @@ -129,7 +271,7 @@ POST /api/tasks/:id/claim Content-Type: application/json { - "agent_id": "智能体ID" + "agent_id": "智能体ID" // 必填 } 响应: @@ -144,8 +286,8 @@ PUT /api/tasks/:id Content-Type: application/json { - "status": "completed", // pending | in_progress | completed - "result": "任务执行结果" // 可选 + "status": "completed", // 可选: pending | in_progress | completed + "result": "任务执行结果" // 可选 } 响应: @@ -189,14 +331,24 @@ GET /api/stats } ``` +#### 健康检查 +```http +GET /health + +响应: +{ + "status": "healthy", + "database": "connected" +} +``` + ## 智能体集成示例 -### Python 客户端示例 +### Python 客户端完整示例 ```python import requests import time -import uuid class TaskAgent: def __init__(self, name, base_url="http://localhost:3000"): @@ -206,51 +358,93 @@ class TaskAgent: def register(self): """注册智能体""" - response = requests.post(f"{self.base_url}/api/agents/register", json={ - "name": self.name, - "type": "general", - "capabilities": ["general_task"] - }) - data = response.json() - self.agent_id = data["id"] - print(f"✅ 智能体已注册: {self.agent_id}") + try: + response = requests.post(f"{self.base_url}/api/agents/register", json={ + "name": self.name, + "type": "general", + "capabilities": ["general_task", "data_processing"] + }) + response.raise_for_status() + data = response.json() + self.agent_id = data["id"] + print(f"✅ 智能体已注册: {self.agent_id}") + return True + except Exception as e: + print(f"❌ 注册失败: {e}") + return False def heartbeat(self, status="idle"): """发送心跳""" - requests.post( - f"{self.base_url}/api/agents/{self.agent_id}/heartbeat", - json={"status": status} - ) + try: + requests.post( + f"{self.base_url}/api/agents/{self.agent_id}/heartbeat", + json={"status": status}, + timeout=5 + ) + except Exception as e: + print(f"⚠️ 心跳发送失败: {e}") def get_pending_tasks(self): """获取待处理任务""" - response = requests.get(f"{self.base_url}/api/tasks?status=pending") - return response.json() + try: + response = requests.get( + f"{self.base_url}/api/tasks?status=pending", + timeout=5 + ) + response.raise_for_status() + return response.json() + except Exception as e: + print(f"❌ 获取任务失败: {e}") + return [] def claim_task(self, task_id): """领取任务""" - response = requests.post( - f"{self.base_url}/api/tasks/{task_id}/claim", - json={"agent_id": self.agent_id} - ) - return response.ok + try: + response = requests.post( + f"{self.base_url}/api/tasks/{task_id}/claim", + json={"agent_id": self.agent_id}, + timeout=5 + ) + response.raise_for_status() + return True + except Exception as e: + print(f"⚠️ 领取任务失败: {e}") + return False def update_task(self, task_id, status, result=None): """更新任务状态""" - payload = {"status": status} - if result: - payload["result"] = result - requests.put(f"{self.base_url}/api/tasks/{task_id}", json=payload) + try: + payload = {"status": status} + if result: + payload["result"] = result + response = requests.put( + f"{self.base_url}/api/tasks/{task_id}", + json=payload, + timeout=5 + ) + response.raise_for_status() + except Exception as e: + print(f"❌ 更新任务失败: {e}") def execute_task(self, task): - """执行任务(示例)""" + """执行任务(示例实现)""" print(f"🔄 正在执行任务: {task['title']}") - time.sleep(2) # 模拟任务执行 - return f"任务 '{task['title']}' 已完成" + print(f" 描述: {task.get('description', '无')}") + print(f" 优先级: {task['priority']}") + + # 模拟任务执行 + time.sleep(3) + + return f"任务 '{task['title']}' 已由 {self.name} 成功完成" def run(self): """主循环""" - self.register() + if not self.register(): + print("❌ 无法注册智能体,退出") + return + + print(f"🤖 智能体 '{self.name}' 开始运行...") + print("按 Ctrl+C 退出\n") while True: try: @@ -262,10 +456,11 @@ class TaskAgent: if tasks: task = tasks[0] - print(f"📋 发现任务: {task['title']}") + print(f"\n📋 发现新任务: {task['title']}") # 领取任务 if self.claim_task(task['id']): + print("✓ 任务已领取") self.heartbeat("busy") # 执行任务 @@ -273,22 +468,23 @@ class TaskAgent: # 更新任务状态 self.update_task(task['id'], "completed", result) - print(f"✅ 任务完成: {task['id']}") + print(f"✅ 任务完成\n") self.heartbeat("idle") - time.sleep(5) # 每5秒检查一次 + # 等待一段时间再检查 + time.sleep(5) except KeyboardInterrupt: - print("👋 智能体退出") + print("\n\n👋 收到退出信号,智能体停止运行") break except Exception as e: - print(f"❌ 错误: {e}") + print(f"❌ 运行时错误: {e}") time.sleep(5) # 使用示例 if __name__ == "__main__": - agent = TaskAgent("示例智能体") + agent = TaskAgent("Python智能体") agent.run() ``` @@ -368,7 +564,7 @@ class TaskAgent { const result = await this.executeTask(task); await this.updateTask(task.id, 'completed', result); - console.log(`✅ 任务完成: ${task.id}`); + console.log(`✅ 任务完成`); await this.heartbeat('idle'); } @@ -384,35 +580,71 @@ class TaskAgent { } // 使用示例 -const agent = new TaskAgent('示例智能体'); +const agent = new TaskAgent('JS智能体'); agent.run().catch(console.error); ``` ## 数据库结构 -系统使用 SQLite 数据库,包含以下表: - ### agents 表 -- `id` - 智能体唯一标识 -- `name` - 智能体名称 -- `type` - 智能体类型 -- `capabilities` - 能力列表(JSON) -- `status` - 状态(idle/busy) -- `registered_at` - 注册时间 -- `last_heartbeat` - 最后心跳时间 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | VARCHAR(255) | 智能体唯一标识(UUID) | +| name | VARCHAR(255) | 智能体名称 | +| type | VARCHAR(100) | 智能体类型 | +| capabilities | JSONB | 能力列表(JSON) | +| status | VARCHAR(50) | 状态(idle/busy) | +| registered_at | BIGINT | 注册时间(毫秒时间戳) | +| last_heartbeat | BIGINT | 最后心跳时间(毫秒时间戳) | ### tasks 表 -- `id` - 任务唯一标识 -- `title` - 任务标题 -- `description` - 任务描述 -- `priority` - 优先级(low/normal/high) -- `status` - 状态(pending/in_progress/completed) -- `created_by` - 创建者 -- `assigned_to` - 分配给的智能体 -- `created_at` - 创建时间 -- `updated_at` - 更新时间 -- `completed_at` - 完成时间 -- `result` - 任务结果 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | VARCHAR(255) | 任务唯一标识(UUID) | +| title | VARCHAR(500) | 任务标题 | +| description | TEXT | 任务描述 | +| priority | VARCHAR(50) | 优先级(low/normal/high) | +| status | VARCHAR(50) | 状态(pending/in_progress/completed) | +| created_by | VARCHAR(255) | 创建者 | +| assigned_to | VARCHAR(255) | 分配给的智能体 | +| created_at | BIGINT | 创建时间(毫秒时间戳) | +| updated_at | BIGINT | 更新时间(毫秒时间戳) | +| completed_at | BIGINT | 完成时间(毫秒时间戳) | +| result | TEXT | 任务结果 | + +## 架构说明 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 智能体 1 │ │ 智能体 2 │ │ 智能体 N │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + │ 注册/心跳/领取 │ │ + └────────────┬───────┴────────────────────┘ + │ + ▼ + ┌──────────────────────┐ + │ API 服务 (Node.js) │ + │ - RESTful API │ + │ - 任务调度 │ + │ - 状态管理 │ + └──────────┬───────────┘ + │ + ▼ + ┌──────────────────────┐ + │ PostgreSQL 数据库 │ + │ - agents 表 │ + │ - tasks 表 │ + └──────────────────────┘ + ▲ + │ + ┌──────────┴───────────┐ + │ Web 管理界面 │ + │ - 实时统计 │ + │ - 任务列表 │ + │ - 人工创建任务 │ + └──────────────────────┘ +``` ## 扩展建议 @@ -423,6 +655,31 @@ agent.run().catch(console.error); 5. **日志记录** - 详细的操作日志 6. **性能监控** - 任务执行时间和成功率统计 7. **分布式部署** - 支持多节点部署 +8. **任务超时** - 自动检测和重新分配超时任务 + +## 故障排查 + +### 数据库连接失败 +```bash +# 检查 PostgreSQL 是否运行 +docker-compose ps + +# 查看数据库日志 +docker-compose logs postgres + +# 重启服务 +docker-compose restart +``` + +### 智能体注册失败 +- 检查服务是否正常运行 +- 确认网络连接正常 +- 查看应用日志: `docker-compose logs app` + +### 任务领取失败 +- 确认任务状态为 `pending` +- 确认智能体 ID 正确 +- 任务可能已被其他智能体领取 ## 许可证 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ef2da10 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3.8' + +services: + # PostgreSQL 数据库 + postgres: + image: postgres:15-alpine + container_name: agent-task-postgres + environment: + POSTGRES_DB: agent_tasks + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - agent-network + + # 应用服务 + app: + build: . + container_name: agent-task-service + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_NAME: agent_tasks + DB_USER: postgres + DB_PASSWORD: postgres + PORT: 3000 + ports: + - "3000:3000" + depends_on: + postgres: + condition: service_healthy + networks: + - agent-network + restart: unless-stopped + +volumes: + postgres_data: + driver: local + +networks: + agent-network: + driver: bridge diff --git a/package-lock.json b/package-lock.json index 50c8f1f..f3b6b2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,13 +7,14 @@ "": { "name": "agent-task-service", "version": "1.0.0", - "license": "ISC", + "license": "MIT", "dependencies": { "body-parser": "^2.2.2", "cors": "^2.8.6", "express": "^5.2.1", + "pg": "^8.18.0", "sqlite3": "^5.1.7", - "uuid": "^13.0.0" + "uuid": "^9.0.1" } }, "node_modules/@gar/promisify": { @@ -1527,6 +1528,134 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pg": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.11.0", + "pg-pool": "^3.11.0", + "pg-protocol": "^1.11.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", + "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -1969,6 +2098,15 @@ "node": ">= 10" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", @@ -2193,16 +2331,16 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist-node/bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/vary": { @@ -2246,6 +2384,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index de6279a..d7deb4a 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,22 @@ { "name": "agent-task-service", "version": "1.0.0", - "description": "", - "main": "index.js", + "description": "多智能体任务管理系统", + "main": "server-postgres.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node server-postgres.js", + "dev": "node server-postgres.js", + "sqlite": "node server.js" }, - "keywords": [], - "author": "", - "license": "ISC", + "keywords": ["multi-agent", "task-management", "ai"], + "author": "work-1", + "license": "MIT", "dependencies": { "body-parser": "^2.2.2", "cors": "^2.8.6", "express": "^5.2.1", + "pg": "^8.18.0", "sqlite3": "^5.1.7", - "uuid": "^13.0.0" + "uuid": "^9.0.1" } } diff --git a/server-postgres.js b/server-postgres.js new file mode 100644 index 0000000..44a4f12 --- /dev/null +++ b/server-postgres.js @@ -0,0 +1,382 @@ +const express = require('express'); +const { Pool } = require('pg'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const { v4: uuidv4 } = require('uuid'); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// 中间件 +app.use(cors()); +app.use(bodyParser.json()); +app.use(express.static('public')); + +// 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 tasks ( + id VARCHAR(255) PRIMARY KEY, + title VARCHAR(500) NOT NULL, + description TEXT, + priority VARCHAR(50) DEFAULT 'normal', + status VARCHAR(50) DEFAULT 'pending', + created_by VARCHAR(255), + assigned_to VARCHAR(255), + created_at BIGINT, + updated_at BIGINT, + completed_at BIGINT, + result TEXT + ) + `); + + // 创建索引 + 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_assigned ON tasks(assigned_to) + `); + + await client.query('COMMIT'); + console.log('✅ 数据库表已初始化'); + } catch (err) { + await client.query('ROLLBACK'); + console.error('❌ 数据库初始化失败:', err); + } finally { + client.release(); + } +} + +// ==================== API 路由 ==================== + +// 1. 智能体注册 +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 }); + } +}); + +// 2. 获取所有智能体 +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 }); + } +}); + +// 3. 更新智能体状态(心跳) +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 }); + } +}); + +// 4. 创建任务 +app.post('/api/tasks', async (req, res) => { + const { title, description, priority, created_by } = req.body; + + if (!title) { + return res.status(400).json({ error: '任务标题不能为空' }); + } + + const id = uuidv4(); + const now = Date.now(); + + try { + await pool.query( + `INSERT INTO tasks (id, title, description, priority, status, created_by, created_at, updated_at) + VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)`, + [id, title, description || '', priority || 'normal', created_by || 'human', now, now] + ); + + res.json({ + id, + title, + status: 'pending', + message: '任务创建成功' + }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// 5. 获取所有任务 +app.get('/api/tasks', async (req, res) => { + const { status } = req.query; + + try { + let query = 'SELECT * FROM tasks'; + let params = []; + + if (status) { + query += ' WHERE status = $1'; + params.push(status); + } + + 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 }); + } +}); + +// 6. 领取任务 +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 FROM tasks WHERE id = $1', [id]); + + if (taskResult.rows.length === 0) { + await client.query('ROLLBACK'); + return res.status(404).json({ error: '任务不存在' }); + } + + if (taskResult.rows[0].status !== 'pending') { + await client.query('ROLLBACK'); + return res.status(400).json({ error: '任务已被领取或已完成' }); + } + + 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'); + res.json({ message: '任务领取成功' }); + } catch (err) { + await client.query('ROLLBACK'); + res.status(500).json({ error: err.message }); + } finally { + client.release(); + } +}); + +// 7. 更新任务状态 +app.put('/api/tasks/:id', async (req, res) => { + const { id } = req.params; + const { status, result } = 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 (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(); + } +}); + +// 8. 获取单个任务详情 +app.get('/api/tasks/:id', async (req, res) => { + try { + const result = await pool.query('SELECT * FROM tasks 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 }); + } +}); + +// 9. 统计信息 +app.get('/api/stats', async (req, res) => { + try { + const stats = { + total_agents: 0, + active_agents: 0, + total_tasks: 0, + pending_tasks: 0, + in_progress_tasks: 0, + completed_tasks: 0 + }; + + 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 tasks'), + pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['pending']), + pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['in_progress']), + pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['completed']) + ]); + + stats.total_agents = parseInt(results[0].rows[0].count); + stats.active_agents = parseInt(results[1].rows[0].count); + stats.total_tasks = parseInt(results[2].rows[0].count); + stats.pending_tasks = parseInt(results[3].rows[0].count); + stats.in_progress_tasks = parseInt(results[4].rows[0].count); + stats.completed_tasks = parseInt(results[5].rows[0].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 }); + } +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`🚀 多智能体任务服务已启动: http://localhost:${PORT}`); +}); + +// 优雅关闭 +process.on('SIGINT', async () => { + console.log('\n正在关闭服务...'); + await pool.end(); + console.log('数据库连接已关闭'); + process.exit(0); +});