Переглянути джерело

business_domain优化及bug修复。 cursor自动执行任务脚本优化。

maxiaolong 5 днів тому
батько
коміт
3fe3c045ef

+ 12 - 12
app/api/business_domain/routes.py

@@ -115,7 +115,7 @@ def bd_list():
         }))
     except Exception as e:
         logger.error(f"获取业务领域列表失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("获取业务领域列表失败", error=str(e)))
 
 
 @bp.route('/detail', methods=['POST'])
@@ -156,7 +156,7 @@ def bd_detail():
         return jsonify(success(domain_data))
     except Exception as e:
         logger.error(f"获取业务领域详情失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("获取业务领域详情失败", error=str(e)))
 
 
 @bp.route('/delete', methods=['POST'])
@@ -190,7 +190,7 @@ def bd_delete():
             return jsonify(failed("业务领域删除失败"))
     except Exception as e:
         logger.error(f"删除业务领域失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("删除业务领域失败", error=str(e)))
 
 
 @bp.route('/save', methods=['POST'])
@@ -232,7 +232,7 @@ def bd_save():
         return jsonify(success(saved_data))
     except Exception as e:
         logger.error(f"保存业务领域失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("保存业务领域失败", error=str(e)))
 
 
 @bp.route('/update', methods=['POST'])
@@ -267,7 +267,7 @@ def bd_update():
         return jsonify(success(updated_data))
     except Exception as e:
         logger.error(f"更新业务领域失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("更新业务领域失败", error=str(e)))
 
 
 @bp.route('/upload', methods=['POST'])
@@ -344,7 +344,7 @@ def bd_upload():
         }))
     except Exception as e:
         logger.error(f"文件上传失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("文件上传失败", error=str(e)))
 
 
 @bp.route('/download', methods=['GET'])
@@ -400,7 +400,7 @@ def bd_download():
         )
     except Exception as e:
         logger.error(f"文件下载失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("文件下载失败", error=str(e)))
     finally:
         if response:
             response.close()
@@ -448,7 +448,7 @@ def bd_graph_all():
         return jsonify(success(graph_data))
     except Exception as e:
         logger.error(f"获取业务领域图谱失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("获取业务领域图谱失败", error=str(e)))
 
 
 @bp.route('/ddlparse', methods=['POST'])
@@ -598,7 +598,7 @@ def bd_ddl_parse():
     except Exception as e:
         logger.error(f"解析DDL语句失败: {str(e)}")
         logger.error(traceback.format_exc())
-        return jsonify(failed(str(e)))
+        return jsonify(failed("解析DDL语句失败", error=str(e)))
 
 
 @bp.route('/search', methods=['POST'])
@@ -672,7 +672,7 @@ def bd_search():
         }))
     except Exception as e:
         logger.error(f"业务领域关联元数据搜索失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("业务领域关联元数据搜索失败", error=str(e)))
 
 
 @bp.route('/compose', methods=['POST'])
@@ -721,7 +721,7 @@ def bd_compose():
         return jsonify(success(response_data))
     except Exception as e:
         logger.error(f"组合创建业务领域失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("组合创建业务领域失败", error=str(e)))
 
 
 @bp.route('/labellist', methods=['POST'])
@@ -777,4 +777,4 @@ def bd_label_list():
         }))
     except Exception as e:
         logger.error(f"获取标签列表失败: {str(e)}")
-        return jsonify(failed(str(e)))
+        return jsonify(failed("获取标签列表失败", error=str(e)))

+ 1 - 1
app/api/meta_data/__init__.py

@@ -2,4 +2,4 @@ from flask import Blueprint
 
 bp = Blueprint('meta_data', __name__)
 
-from app.api.meta_data import routes 
+from app.api.meta_data import routes  # noqa: F401, E402

+ 113 - 9
app/core/business_domain/business_domain.py

@@ -305,7 +305,7 @@ def get_business_domain_by_id(domain_id):
                     }
                 }
                 parsed_data.append(meta_data)
-            
+
             domain_data["parsed_data"] = parsed_data
             
             # 确保所有必需字段都有默认值
@@ -317,6 +317,7 @@ def get_business_domain_by_id(domain_id):
                 "data_sensitivity": "",
                 "storage_location": "/",
                 "create_time": "",
+                "update_time": "",
                 "type": "",
                 "category": "",
                 "url": "",
@@ -415,10 +416,11 @@ def save_business_domain(data):
                 "update_time": get_formatted_time()
             }
 
-            # 添加可选字段
+            # 添加可选字段(不包含 parsed_data,它通过关系处理)
             optional_fields = [
                 "describe", "type", "category", "leader",
-                "organization", "status", "keywords"
+                "organization", "status", "keywords",
+                "data_sensitivity", "frequency", "url", "storage_location"
             ]
             for field in optional_fields:
                 if data.get(field) is not None:
@@ -439,6 +441,49 @@ def save_business_domain(data):
             domain_id = created_node["n"].id
             logger.info(f"成功创建业务领域节点,ID: {domain_id}")
 
+            # 处理 parsed_data - 创建 DataMeta 节点和 INCLUDES 关系
+            parsed_data = data.get("parsed_data")
+            if parsed_data and isinstance(parsed_data, list):
+                for item in parsed_data:
+                    if not item.get("name_zh"):
+                        continue
+                    # 创建或获取 DataMeta 节点
+                    meta_cypher = """
+                    MERGE (m:DataMeta {name_zh: $name_zh})
+                    ON CREATE SET m.name_en = $name_en,
+                                m.create_time = $create_time,
+                                m.data_type = $data_type,
+                                m.status = true
+                    ON MATCH SET m.data_type = $data_type,
+                                m.status = true
+                    RETURN m
+                    """
+                    meta_result = session.run(meta_cypher, {
+                        'name_zh': item.get('name_zh', ''),
+                        'name_en': item.get('name_en', ''),
+                        'create_time': get_formatted_time(),
+                        'data_type': item.get('data_type', 'varchar(255)')
+                    })
+                    meta_record = meta_result.single()
+
+                    if meta_record and meta_record["m"]:
+                        meta_id = meta_record["m"].id
+                        # 创建 INCLUDES 关系
+                        rel_cypher = """
+                        MATCH (n:BusinessDomain), (m:DataMeta)
+                        WHERE id(n) = $domain_id AND id(m) = $meta_id
+                        MERGE (n)-[r:INCLUDES]->(m)
+                        RETURN r
+                        """
+                        session.run(rel_cypher, {
+                            'domain_id': domain_id,
+                            'meta_id': meta_id
+                        })
+                        logger.info(
+                            f"创建 BusinessDomain 与 DataMeta 关系: "
+                            f"{domain_id} -> {meta_id}"
+                        )
+
             # 处理标签关系
             tag_id = data.get("tag")
             if tag_id:
@@ -525,13 +570,14 @@ def update_business_domain(data):
             raise ValueError(f"业务领域ID不是有效的整数: {domain_id}")
         
         with neo4j_driver.get_session() as session:
-            # 构建更新字段(过滤掉 id、parsed_data 和 None 值)
+            # 构建更新字段(过滤掉特殊字段和 None 值)
+            # parsed_data 通过 INCLUDES 关系处理,不存储为节点属性
             update_fields = {}
+            excluded = ("id", "tag", "data_source", "parsed_data")
             for key, value in data.items():
-                excluded = ("id", "parsed_data", "tag", "data_source")
                 if key not in excluded and value is not None:
                     update_fields[key] = value
-            
+
             # 添加更新时间
             update_fields["update_time"] = get_formatted_time()
             
@@ -629,11 +675,66 @@ def update_business_domain(data):
                         )
                     except (ValueError, TypeError):
                         logger.warning(f"数据源ID不是有效的整数: {data_source_id}")
-            
+
+            # 处理 parsed_data - 更新 DataMeta 节点和 INCLUDES 关系
+            parsed_data = data.get("parsed_data")
+            if parsed_data is not None:
+                # 先删除旧的 INCLUDES 关系
+                delete_includes_cypher = """
+                MATCH (n:BusinessDomain)-[r:INCLUDES]->(m:DataMeta)
+                WHERE id(n) = $domain_id
+                DELETE r
+                """
+                session.run(
+                    delete_includes_cypher, {'domain_id': domain_id_int}
+                )
+
+                # 创建新的关系
+                if isinstance(parsed_data, list):
+                    for item in parsed_data:
+                        if not item.get("name_zh"):
+                            continue
+                        # 创建或获取 DataMeta 节点
+                        meta_cypher = """
+                        MERGE (m:DataMeta {name_zh: $name_zh})
+                        ON CREATE SET m.name_en = $name_en,
+                                    m.create_time = $create_time,
+                                    m.data_type = $data_type,
+                                    m.status = true
+                        ON MATCH SET m.data_type = $data_type,
+                                    m.status = true
+                        RETURN m
+                        """
+                        meta_result = session.run(meta_cypher, {
+                            'name_zh': item.get('name_zh', ''),
+                            'name_en': item.get('name_en', ''),
+                            'create_time': get_formatted_time(),
+                            'data_type': item.get('data_type', 'varchar(255)')
+                        })
+                        meta_record = meta_result.single()
+
+                        if meta_record and meta_record["m"]:
+                            meta_id = meta_record["m"].id
+                            # 创建 INCLUDES 关系
+                            rel_cypher = """
+                            MATCH (n:BusinessDomain), (m:DataMeta)
+                            WHERE id(n) = $domain_id AND id(m) = $meta_id
+                            MERGE (n)-[r:INCLUDES]->(m)
+                            RETURN r
+                            """
+                            session.run(rel_cypher, {
+                                'domain_id': domain_id_int,
+                                'meta_id': meta_id
+                            })
+                            logger.info(
+                                f"更新 BusinessDomain 与 DataMeta 关系: "
+                                f"{domain_id_int} -> {meta_id}"
+                            )
+
             # 构建返回数据
             node_data = serialize_node_properties(updated_node["n"])
             node_data["id"] = updated_node["n"].id
-            
+
             logger.info(f"成功更新业务领域,ID: {domain_id_int}")
             return node_data
 
@@ -874,12 +975,15 @@ def business_domain_compose(data):
             # 添加可选字段
             optional_fields = [
                 "describe", "type", "category", "leader",
-                "organization", "status", "keywords"
+                "organization", "status", "keywords",
+                "data_sensitivity", "frequency", "url", "storage_location"
             ]
             for field in optional_fields:
                 if data.get(field) is not None:
                     node_props[field] = data[field]
 
+            # 注意: parsed_data 通过 INCLUDES 关系处理,不存储为节点属性
+
             # 构建CREATE语句
             props_str = ", ".join([f"{k}: ${k}" for k in node_props.keys()])
             cypher = f"""

+ 27 - 16
app/models/result.py

@@ -1,14 +1,15 @@
 from flask import jsonify, make_response
 
+
 def success(data=None, message="操作成功", code=200):
     """
     Return a standardized success response
-    
+
     Args:
         data: The data to return
         message: A success message
         code: HTTP status code
-        
+
     Returns:
         dict: A standardized success response
     """
@@ -18,33 +19,38 @@ def success(data=None, message="操作成功", code=200):
         "data": data
     }
 
-def failed(message="操作失败", code=500, data=None):
+
+def failed(message="操作失败", code=500, data=None, error=None):
     """
     Return a standardized error response
-    
+
     Args:
         message: An error message
         code: HTTP status code
         data: Optional data to return
-        
+        error: Detailed error information
+
     Returns:
         dict: A standardized error response
     """
-    return {
+    result = {
         "code": code,
         "message": message,
-        "success": False,
         "data": data
     }
+    if error is not None:
+        result["error"] = error
+    return result
+
 
 def json_response(data, status_code=200):
     """
     Create a JSON response with proper headers
-    
+
     Args:
         data: The data to return (will be passed to jsonify)
         status_code: HTTP status code
-        
+
     Returns:
         Flask Response object with proper JSON headers
     """
@@ -52,30 +58,35 @@ def json_response(data, status_code=200):
     response.headers['Content-Type'] = 'application/json; charset=utf-8'
     return response
 
+
 def success_response(data=None, message="操作成功", status_code=200):
     """
     Return a standardized success response with proper headers
-    
+
     Args:
         data: The data to return
-        message: A success message  
+        message: A success message
         status_code: HTTP status code
-        
+
     Returns:
         Flask Response object with proper JSON headers
     """
     return json_response(success(data, message, status_code), status_code)
 
-def failed_response(message="操作失败", status_code=500, data=None):
+
+def failed_response(message="操作失败", status_code=500, data=None, error=None):
     """
     Return a standardized error response with proper headers
-    
+
     Args:
         message: An error message
         status_code: HTTP status code
         data: Optional data to return
-        
+        error: Detailed error information
+
     Returns:
         Flask Response object with proper JSON headers
     """
-    return json_response(failed(message, status_code, data), status_code) 
+    return json_response(
+        failed(message, status_code, data, error), status_code
+    )

+ 966 - 0
docs/business_domain_api.md

@@ -0,0 +1,966 @@
+# 业务领域 API 接口文档
+
+> 本文档为前端开发人员提供业务领域(Business Domain)模块的 API 接口说明。
+
+## 目录
+
+- [通用说明](#通用说明)
+- [接口列表](#接口列表)
+  - [1. 获取业务领域列表](#1-获取业务领域列表)
+  - [2. 获取业务领域详情](#2-获取业务领域详情)
+  - [3. 删除业务领域](#3-删除业务领域)
+  - [4. 保存业务领域](#4-保存业务领域)
+  - [5. 更新业务领域](#5-更新业务领域)
+  - [6. 上传文件](#6-上传文件)
+  - [7. 下载文件](#7-下载文件)
+  - [8. 获取关系图谱](#8-获取关系图谱)
+  - [9. 解析DDL语句](#9-解析ddl语句)
+  - [10. 搜索关联元数据](#10-搜索关联元数据)
+  - [11. 组合创建业务领域](#11-组合创建业务领域)
+  - [12. 获取标签列表](#12-获取标签列表)
+- [错误码说明](#错误码说明)
+
+---
+
+## 通用说明
+
+### 基础URL
+
+```
+/api/bd
+```
+
+### 统一响应格式
+
+**成功响应:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": { ... }
+}
+```
+
+**失败响应:**
+
+```json
+{
+  "code": 500,
+  "message": "错误描述",
+  "data": null,
+  "error": "详细错误信息"
+}
+```
+
+### 请求头
+
+大部分接口需要设置 `Content-Type`:
+
+```
+Content-Type: application/json
+```
+
+文件上传接口需要设置:
+
+```
+Content-Type: multipart/form-data
+```
+
+---
+
+## 接口列表
+
+### 1. 获取业务领域列表
+
+获取业务领域的分页列表,支持多种过滤条件。
+
+**请求URL:** `POST /api/bd/list`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| current | integer | 否 | 1 | 当前页码 |
+| size | integer | 否 | 10 | 每页大小 |
+| name_en | string | 否 | - | 英文名称过滤条件 |
+| name_zh | string | 否 | - | 中文名称过滤条件 |
+| type | string | 否 | "all" | 类型过滤条件,"all"表示不过滤 |
+| category | string | 否 | - | 分类过滤条件 |
+| tag | string | 否 | - | 标签过滤条件 |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| code | integer | 状态码 |
+| message | string | 消息 |
+| data.records | array | 业务领域列表 |
+| data.total | integer | 总数量 |
+| data.size | integer | 每页大小 |
+| data.current | integer | 当前页码 |
+
+**请求示例:**
+
+```javascript
+// JavaScript (fetch)
+const response = await fetch('/api/bd/list', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify({
+    current: 1,
+    size: 10,
+    name_zh: "订单"
+  })
+});
+const data = await response.json();
+```
+
+```javascript
+// JavaScript (axios)
+const { data } = await axios.post('/api/bd/list', {
+  current: 1,
+  size: 10,
+  name_zh: "订单"
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [
+      {
+        "id": 12345,
+        "name_zh": "订单管理",
+        "name_en": "order_management",
+        "type": "table",
+        "category": "业务",
+        "describe": "订单管理业务领域",
+        "create_time": "2024-01-15 10:30:00"
+      }
+    ],
+    "total": 100,
+    "size": 10,
+    "current": 1
+  }
+}
+```
+
+**错误响应:**
+
+```json
+{
+  "code": 500,
+  "message": "获取业务领域列表失败",
+  "data": null,
+  "error": "Database connection failed"
+}
+```
+
+---
+
+### 2. 获取业务领域详情
+
+根据ID获取单个业务领域的详细信息。
+
+**请求URL:** `POST /api/bd/detail`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | integer | 是 | 业务领域节点ID |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| code | integer | 状态码 |
+| message | string | 消息 |
+| data | object | 业务领域详情对象 |
+
+**请求示例:**
+
+```javascript
+// JavaScript (fetch)
+const response = await fetch('/api/bd/detail', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify({
+    id: 12345
+  })
+});
+const data = await response.json();
+```
+
+```javascript
+// JavaScript (axios)
+const { data } = await axios.post('/api/bd/detail', {
+  id: 12345
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "id": 12345,
+    "name_zh": "订单管理",
+    "name_en": "order_management",
+    "type": "table",
+    "category": "业务",
+    "describe": "订单管理业务领域",
+    "tag": 100,
+    "data_source": 200,
+    "create_time": "2024-01-15 10:30:00",
+    "update_time": "2024-01-16 14:20:00"
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+| 500 | 业务领域ID不能为空 | id参数缺失 |
+| 500 | 业务领域ID必须为整数 | id格式错误 |
+| 500 | 业务领域不存在 | 找不到对应记录 |
+
+---
+
+### 3. 删除业务领域
+
+根据ID删除业务领域。
+
+**请求URL:** `POST /api/bd/delete`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | integer | 是 | 业务领域节点ID |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| code | integer | 状态码 |
+| message | string | 消息 |
+| data.message | string | 删除结果描述 |
+
+**请求示例:**
+
+```javascript
+// JavaScript (axios)
+const { data } = await axios.post('/api/bd/delete', {
+  id: 12345
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "message": "业务领域删除成功"
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+| 500 | 业务领域ID不能为空 | id参数缺失 |
+| 500 | 业务领域删除失败 | 删除操作失败 |
+
+---
+
+### 4. 保存业务领域
+
+保存业务领域,支持新建和更新两种模式。
+
+**请求URL:** `POST /api/bd/save`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | integer | 否 | 业务领域ID(有则更新,无则新建) |
+| name_zh | string | 新建时必填 | 中文名称 |
+| name_en | string | 新建时必填 | 英文名称 |
+| describe | string | 否 | 描述 |
+| type | string | 否 | 类型 |
+| category | string | 否 | 分类 |
+| tag | integer | 否 | 标签ID |
+| data_source | integer | 否 | 数据源ID |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| code | integer | 状态码 |
+| message | string | 消息 |
+| data | object | 保存后的业务领域数据 |
+
+**请求示例:**
+
+```javascript
+// 新建
+const { data } = await axios.post('/api/bd/save', {
+  name_zh: "客户管理",
+  name_en: "customer_management",
+  type: "table",
+  category: "业务",
+  describe: "客户信息管理业务领域"
+});
+
+// 更新
+const { data } = await axios.post('/api/bd/save', {
+  id: 12345,
+  describe: "更新后的描述"
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "id": 12346,
+    "name_zh": "客户管理",
+    "name_en": "customer_management",
+    "type": "table",
+    "category": "业务",
+    "describe": "客户信息管理业务领域",
+    "create_time": "2024-01-17 09:00:00"
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+| 500 | 新建时 name_zh 和 name_en 为必填项 | 新建时缺少必填字段 |
+
+---
+
+### 5. 更新业务领域
+
+更新已存在的业务领域。
+
+**请求URL:** `POST /api/bd/update`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | integer | 是 | 业务领域节点ID |
+| name_zh | string | 否 | 中文名称 |
+| name_en | string | 否 | 英文名称 |
+| describe | string | 否 | 描述 |
+| tag | integer | 否 | 标签ID |
+| data_source | integer | 否 | 数据源ID |
+
+**请求示例:**
+
+```javascript
+const { data } = await axios.post('/api/bd/update', {
+  id: 12345,
+  name_zh: "订单管理系统",
+  describe: "更新后的描述信息"
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "id": 12345,
+    "name_zh": "订单管理系统",
+    "name_en": "order_management",
+    "describe": "更新后的描述信息",
+    "update_time": "2024-01-17 10:30:00"
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 参数不完整 | 未提供id参数 |
+
+---
+
+### 6. 上传文件
+
+上传业务领域相关文件到对象存储。
+
+**请求URL:** `POST /api/bd/upload`
+
+**Content-Type:** `multipart/form-data`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| file | File | 是 | 上传的文件 |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| data.filename | string | 原始文件名 |
+| data.size | integer | 文件大小(字节) |
+| data.type | string | 文件类型(扩展名) |
+| data.url | string | 文件存储路径 |
+
+**请求示例:**
+
+```javascript
+// JavaScript (FormData)
+const formData = new FormData();
+formData.append('file', fileInput.files[0]);
+
+const response = await fetch('/api/bd/upload', {
+  method: 'POST',
+  body: formData
+});
+const data = await response.json();
+```
+
+```javascript
+// JavaScript (axios)
+const formData = new FormData();
+formData.append('file', file);
+
+const { data } = await axios.post('/api/bd/upload', formData, {
+  headers: {
+    'Content-Type': 'multipart/form-data'
+  }
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "filename": "schema.sql",
+    "size": 2048,
+    "type": "sql",
+    "url": "business_domain/schema_20240117103000.sql"
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 没有找到上传的文件 | 请求中无file字段 |
+| 500 | 未选择文件 | 文件名为空 |
+| 500 | 不支持的文件类型 | 文件扩展名不在允许列表中 |
+
+---
+
+### 7. 下载文件
+
+下载业务领域相关文件。
+
+**请求URL:** `GET /api/bd/download`
+
+**请求参数(Query String):**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| url | string | 是 | 文件存储路径(从上传接口返回) |
+
+**返回:**
+
+成功时返回文件流(作为附件下载),失败时返回 JSON 错误信息。
+
+**请求示例:**
+
+```javascript
+// 方式1:直接打开链接下载
+window.open('/api/bd/download?url=business_domain/schema_20240117103000.sql');
+
+// 方式2:使用fetch下载
+const response = await fetch('/api/bd/download?url=' + encodeURIComponent(fileUrl));
+if (response.ok) {
+  const blob = await response.blob();
+  const url = window.URL.createObjectURL(blob);
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = 'filename.sql';
+  a.click();
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 文件路径不能为空 | 未提供url参数 |
+| 500 | 文件获取失败 | MinIO获取文件失败 |
+
+---
+
+### 8. 获取关系图谱
+
+获取业务领域的完整关系图谱数据,用于可视化展示。
+
+**请求URL:** `POST /api/bd/graphall`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| id | integer | 是 | - | 业务领域节点ID |
+| meta | boolean | 否 | true | 是否包含元数据节点 |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| data.nodes | array | 节点列表 |
+| data.lines | array | 关系列表 |
+
+**请求示例:**
+
+```javascript
+const { data } = await axios.post('/api/bd/graphall', {
+  id: 12345,
+  meta: true
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "nodes": [
+      {
+        "id": 12345,
+        "name": "订单管理",
+        "type": "BusinessDomain",
+        "properties": { ... }
+      },
+      {
+        "id": 12346,
+        "name": "order_id",
+        "type": "MetaData",
+        "properties": { ... }
+      }
+    ],
+    "lines": [
+      {
+        "from": 12345,
+        "to": 12346,
+        "type": "HAS_METADATA",
+        "properties": { ... }
+      }
+    ]
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+| 500 | 业务领域ID不能为空 | id参数缺失 |
+| 500 | 业务领域ID必须为整数 | id格式错误 |
+
+---
+
+### 9. 解析DDL语句
+
+解析 SQL DDL 语句,提取表结构信息,用于快速创建业务领域。
+
+**请求URL:** `POST /api/bd/ddlparse`
+
+**请求方式:**
+
+支持两种方式提交 DDL:
+
+1. **文件上传方式** (`multipart/form-data`)
+2. **JSON方式** (`application/json`)
+
+**请求参数(文件上传方式):**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| file | File | 是 | SQL文件(.sql扩展名) |
+
+**请求参数(JSON方式):**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| sql | string | 是 | SQL DDL 内容 |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| data | array | 解析后的DDL列表,包含表信息和字段信息 |
+| data[].table_info | object | 表信息 |
+| data[].columns | array | 字段列表 |
+| data[].exist | boolean | 表是否已存在于系统中 |
+
+**请求示例:**
+
+```javascript
+// 方式1:文件上传
+const formData = new FormData();
+formData.append('file', sqlFile);
+
+const { data } = await axios.post('/api/bd/ddlparse', formData, {
+  headers: {
+    'Content-Type': 'multipart/form-data'
+  }
+});
+
+// 方式2:JSON提交
+const { data } = await axios.post('/api/bd/ddlparse', {
+  sql: `
+    CREATE TABLE orders (
+      id INT PRIMARY KEY,
+      customer_id INT,
+      amount DECIMAL(10,2),
+      create_time DATETIME
+    );
+  `
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": [
+    {
+      "table_info": {
+        "name_en": "orders",
+        "name_zh": "订单表",
+        "comment": "订单信息表"
+      },
+      "columns": [
+        {
+          "name_en": "id",
+          "name_zh": "主键",
+          "data_type": "INT",
+          "is_primary": true,
+          "comment": "主键ID"
+        },
+        {
+          "name_en": "customer_id",
+          "name_zh": "客户ID",
+          "data_type": "INT",
+          "is_primary": false,
+          "comment": "关联客户ID"
+        }
+      ],
+      "exist": false
+    }
+  ]
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 只接受SQL文件 | 上传的文件不是.sql格式 |
+| 500 | SQL内容不能为空 | 未提供SQL内容 |
+| 500 | 未找到有效的CREATE TABLE语句 | DDL解析失败 |
+
+---
+
+### 10. 搜索关联元数据
+
+搜索与业务领域关联的元数据列表。
+
+**请求URL:** `POST /api/bd/search`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| id | integer | 是 | - | 业务领域节点ID |
+| current | integer | 否 | 1 | 当前页码 |
+| size | integer | 否 | 10 | 每页大小 |
+| name_en | string | 否 | - | 英文名称过滤条件 |
+| name_zh | string | 否 | - | 中文名称过滤条件 |
+| category | string | 否 | - | 分类过滤条件 |
+| tag | string | 否 | - | 标签过滤条件 |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| data.records | array | 元数据列表 |
+| data.total | integer | 总数量 |
+| data.size | integer | 每页大小 |
+| data.current | integer | 当前页码 |
+
+**请求示例:**
+
+```javascript
+const { data } = await axios.post('/api/bd/search', {
+  id: 12345,
+  current: 1,
+  size: 20,
+  name_zh: "订单"
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [
+      {
+        "id": 23456,
+        "name_zh": "订单ID",
+        "name_en": "order_id",
+        "data_type": "INT",
+        "category": "主键",
+        "create_time": "2024-01-15 10:30:00"
+      }
+    ],
+    "total": 50,
+    "size": 20,
+    "current": 1
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+| 500 | 业务领域ID不能为空 | id参数缺失 |
+| 500 | 业务领域ID必须为整数 | id格式错误 |
+
+---
+
+### 11. 组合创建业务领域
+
+从已有的业务领域和元数据中组合创建新的业务领域。
+
+**请求URL:** `POST /api/bd/compose`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| name_zh | string | 是 | 中文名称 |
+| name_en | string | 否 | 英文名称(不提供则自动翻译) |
+| id_list | array | 是 | 关联的业务领域和元数据列表 |
+| describe | string | 否 | 描述 |
+| type | string | 否 | 类型 |
+| category | string | 否 | 分类 |
+| tag | integer | 否 | 标签ID |
+| data_source | integer | 否 | 数据源ID |
+
+**id_list 格式:**
+
+```json
+[
+  {
+    "domain_id": 123,
+    "metaData": [
+      { "id": 456 },
+      { "id": 789 }
+    ]
+  }
+]
+```
+
+**请求示例:**
+
+```javascript
+const { data } = await axios.post('/api/bd/compose', {
+  name_zh: "销售分析域",
+  name_en: "sales_analysis",
+  describe: "整合订单和客户数据的销售分析业务领域",
+  id_list: [
+    {
+      domain_id: 12345,
+      metaData: [
+        { id: 23456 },
+        { id: 23457 }
+      ]
+    },
+    {
+      domain_id: 12346,
+      metaData: [
+        { id: 23458 }
+      ]
+    }
+  ]
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "business_domain": {
+      "id": 12350,
+      "name_zh": "销售分析域",
+      "name_en": "sales_analysis",
+      "describe": "整合订单和客户数据的销售分析业务领域",
+      "create_time": "2024-01-17 11:00:00"
+    }
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+| 500 | name_zh 为必填项 | 缺少中文名称 |
+| 500 | id_list 为必填项 | 缺少关联列表 |
+
+---
+
+### 12. 获取标签列表
+
+获取可用于业务领域关联的数据标签列表。
+
+**请求URL:** `POST /api/bd/labellist`
+
+**请求参数:**
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| current | integer | 否 | 1 | 当前页码 |
+| size | integer | 否 | 10 | 每页大小 |
+| name_en | string | 否 | - | 英文名称过滤条件 |
+| name_zh | string | 否 | - | 中文名称过滤条件 |
+| category | string | 否 | - | 分类过滤条件 |
+| group | string | 否 | - | 分组过滤条件 |
+
+**返回参数:**
+
+| 参数名 | 类型 | 说明 |
+|--------|------|------|
+| data.records | array | 标签列表 |
+| data.total | integer | 总数量 |
+| data.size | integer | 每页大小 |
+| data.current | integer | 当前页码 |
+
+**请求示例:**
+
+```javascript
+const { data } = await axios.post('/api/bd/labellist', {
+  current: 1,
+  size: 20,
+  category: "业务标签"
+});
+```
+
+**返回示例:**
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [
+      {
+        "id": 100,
+        "name_zh": "核心业务",
+        "name_en": "core_business",
+        "category": "业务标签",
+        "group": "优先级",
+        "create_time": "2024-01-10 09:00:00"
+      }
+    ],
+    "total": 30,
+    "size": 20,
+    "current": 1
+  }
+}
+```
+
+**错误码:**
+
+| code | message | 说明 |
+|------|---------|------|
+| 500 | 请求数据不能为空 | 未提供请求体 |
+
+---
+
+## 错误码说明
+
+### 通用错误码
+
+| code | 说明 |
+|------|------|
+| 200 | 请求成功 |
+| 500 | 服务器内部错误 |
+
+### 常见错误消息
+
+| message | 说明 | 解决方案 |
+|---------|------|----------|
+| 请求数据不能为空 | 未提供请求体或请求体为空 | 确保发送有效的JSON请求体 |
+| 业务领域ID不能为空 | 必填参数id缺失 | 添加id参数 |
+| 业务领域ID必须为整数 | id参数类型错误 | 确保id为整数类型 |
+| 业务领域不存在 | 指定ID的记录不存在 | 检查ID是否正确 |
+| 参数不完整 | 缺少必填参数 | 检查请求参数完整性 |
+| 新建时 name_zh 和 name_en 为必填项 | 新建时缺少必填字段 | 添加name_zh和name_en参数 |
+| 不支持的文件类型 | 上传文件类型不在允许列表中 | 检查文件扩展名 |
+
+---
+
+## 更新日志
+
+| 版本 | 日期 | 更新内容 |
+|------|------|----------|
+| 1.0.0 | 2025-12-06 | 初始版本 |
+

+ 121 - 156
scripts/AUTO_TASKS_使用说明.md

@@ -1,237 +1,202 @@
-# 自动任务执行脚本 - Windows使用说明
+# 自动任务执行脚本 - 使用说明
 
 ## 🚀 快速开始
 
-### 方式1:前台运行(可以看到实时输出)⭐推荐
+### 推荐方式:使用启动器
 
-双击运行:
-```
-scripts\start_auto_tasks.bat
-```
-
-或者命令行:
-```cmd
-cd scripts
-start_auto_tasks.bat
-```
-
-**特点**:
-- ✅ 可以看到实时日志输出
-- ✅ 按 Ctrl+C 可以停止
-- ⚠️ 关闭窗口会停止服务
-
----
-
-### 方式2:后台运行(无窗口)⭐推荐生产环境
+双击运行启动器脚本,根据菜单选择运行模式:
 
-双击运行:
 ```
-scripts\start_auto_tasks_background.bat
+scripts\start_task_scheduler.bat
 ```
 
-**特点**:
-- ✅ 在后台运行,不占用窗口
-- ✅ 日志输出到 `logs\auto_execute.log`
-- ✅ 关闭命令行窗口后仍继续运行
-- ⚠️ 需要手动停止进程
+启动器支持以下模式:
+1. **前台运行** - 可以看到实时日志,按 Ctrl+C 停止
+2. **后台运行** - 无窗口运行,日志输出到文件
+3. **执行一次** - 只检查一次 pending 任务
+4. **前台运行 + 自动 Chat** - 自动向 Cursor 发送任务提醒
+5. **后台运行 + 自动 Chat** - 后台运行并自动发送 Chat
+6. **查看服务状态** - 检查进程状态和日志
+7. **停止服务** - 停止后台运行的服务
 
 ---
 
-### 方式3:命令行直接运行
+### 命令行方式
 
 ```cmd
-cd G:\code-lab\DataOps-platform
-python scripts\auto_execute_tasks.py
+cd G:\code-lab\DataOps-platform-new
+python scripts\auto_execute_tasks.py [选项]
 ```
 
-参数说明:
-- 默认:每5分钟检查一次(300秒)
-- `--once`:只执行一次检查,不持续运行
-- `--interval N`:设置检查间隔(秒)
+**可用选项:**
+
+| 选项 | 说明 |
+|------|------|
+| `--once` | 只执行一次检查,不循环 |
+| `--interval N` | 设置检查间隔(秒),默认 300 |
+| `--enable-chat` | 启用自动 Cursor Chat |
+| `--chat-input-pos "x,y"` | 指定 Chat 输入框位置 |
+| `--chat-message "消息"` | 自定义 Chat 消息内容 |
+
+**示例:**
 
-示例:
 ```cmd
 # 执行一次
 python scripts\auto_execute_tasks.py --once
 
-# 每10分钟检查一次
+# 每 10 分钟检查一次
 python scripts\auto_execute_tasks.py --interval 600
-```
-
----
-
-## 📋 管理命令
 
-### 检查服务状态
+# 启用自动 Chat
+python scripts\auto_execute_tasks.py --enable-chat --chat-input-pos "1180,965"
 
-双击运行:
-```
-scripts\check_auto_tasks.bat
+# 完整示例
+python scripts\auto_execute_tasks.py --interval 300 --enable-chat --chat-input-pos "1180,965"
 ```
 
-或者命令行:
-```cmd
-cd scripts
-check_auto_tasks.bat
-```
-
-**功能**:
-- ✅ 检查进程是否在运行
-- ✅ 显示进程ID和启动时间
-- ✅ 显示最近20行日志
-- ✅ 执行一次手动检查
-
----
-
-### 停止服务
-
-双击运行:
-```
-scripts\stop_auto_tasks.bat
-```
-
-或者命令行:
-```cmd
-cd scripts
-stop_auto_tasks.bat
-```
-
-**说明**:
-- 自动查找并停止所有 `auto_execute_tasks.py` 进程
-- 如果无法停止,可以在任务管理器中手动结束 Python 进程
-
 ---
 
 ## 📊 工作流程
 
 ```
-1. 脚本启动
-   ↓
-2. 连接数据库(从 mcp-servers/task-manager/config.json 读取配置)
-   ↓
-3. 查询所有 status = 'pending' 的任务
-   ↓
-4. 如果有pending任务:
-   - 打印任务详情(供Cursor识别)
-   - 创建 .cursor/pending_tasks.json 通知文件
-   ↓
-5. 等待指定间隔时间(默认5分钟)
-   ↓
-6. 重复步骤3-5
+┌─────────────────────────────────────────────────────────────┐
+│                    auto_execute_tasks.py                    │
+└─────────────────────────────┬───────────────────────────────┘
+                              │
+      ┌───────────────────────┼───────────────────────┐
+      ▼                       ▼                       ▼
+┌─────────────┐      ┌─────────────────┐      ┌──────────────┐
+│ 1. 同步完成 │      │ 2. 获取pending  │      │ 3. 生成文件  │
+│    状态     │      │    任务         │      │              │
+└─────────────┘      └─────────────────┘      └──────────────┘
+      │                       │                       │
+      │                       ▼                       │
+      │              ┌─────────────────┐              │
+      │              │ 4. 创建任务文件 │              │
+      │              │    更新状态为   │              │
+      │              │    processing   │              │
+      │              └─────────────────┘              │
+      │                       │                       │
+      │                       ▼                       │
+      │        ┌──────────────────────────────┐       │
+      │        │ 5. 写入 pending_tasks.json   │       │
+      │        │ 6. 生成 instructions.md      │       │
+      │        └──────────────────────────────┘       │
+      │                       │                       │
+      │                       ▼                       │
+      │        ┌──────────────────────────────┐       │
+      │        │ 7. (可选) 发送 Cursor Chat   │       │
+      │        └──────────────────────────────┘       │
+      │                       │                       │
+      └───────────────────────┼───────────────────────┘
+                              ▼
+                    ┌─────────────────┐
+                    │  等待下一次检查  │
+                    └─────────────────┘
 ```
 
 ---
 
-## 📝 日志文件
-
-### 前台运行
-- 日志直接输出到控制台
-
-### 后台运行
-- 日志文件:`logs\auto_execute.log`
+## 📁 生成的文件
 
-查看日志:
-```cmd
-# 查看全部日志
-type logs\auto_execute.log
-
-# 查看最后50行
-powershell "Get-Content logs\auto_execute.log -Tail 50"
-```
+| 文件 | 说明 |
+|------|------|
+| `.cursor/pending_tasks.json` | 待处理任务列表(JSON 格式) |
+| `.cursor/task_execute_instructions.md` | Cursor 执行指令文件 |
+| `.cursor/task_trigger.txt` | 触发器标记文件 |
+| `logs/auto_execute.log` | 日志文件(后台模式) |
+| `app/core/data_flow/*.py` | 任务占位文件(可配置路径) |
 
 ---
 
 ## ⚙️ 配置说明
 
 ### 数据库配置
+
 编辑文件:`mcp-servers/task-manager/config.json`
 
 ```json
 {
   "database": {
-    "uri": "postgresql://postgres:dataOps@192.168.3.143:5432/dataops"
+    "uri": "postgresql://user:password@host:5432/database"
   }
 }
 ```
 
-### 检查间隔
-默认:300秒(5分钟)
+### 自动 Chat 配置
+
+启用自动 Chat 需要安装以下依赖:
+
+```cmd
+pip install pywin32 pyautogui pyperclip
+```
 
-修改方式:
-1. 编辑批处理文件中的 `--interval 300` 参数
-2. 或直接命令行运行:`python scripts\auto_execute_tasks.py --interval 600`
+**获取 Chat 输入框位置:**
+1. 打开 Cursor 并显示 Chat 面板
+2. 将鼠标移动到 Chat 输入框
+3. 记录鼠标坐标(可使用屏幕坐标工具)
+4. 使用 `--chat-input-pos "x,y"` 参数指定
 
 ---
 
 ## 🔍 故障排查
 
-### 问题1:脚本无法启动
+### 问题 1:脚本无法启动
 
-**检查**
-1. Python是否安装:`python --version`
-2. psycopg2是否安装:`pip show psycopg2-binary`
+**检查**
+1. Python 是否安装:`python --version`
+2. psycopg2 是否安装:`pip show psycopg2-binary`
 3. 如果未安装:`pip install psycopg2-binary`
 
-### 问题2:无法连接数据库
+### 问题 2:无法连接数据库
 
-**检查**
-1. PostgreSQL服务是否运行
+**检查**
+1. PostgreSQL 服务是否运行
 2. `mcp-servers/task-manager/config.json` 配置是否正确
 3. 网络连接是否正常
 
-### 问题3:找不到pending任务
+### 问题 3:自动 Chat 不工作
 
-**检查**:
-1. 数据库中是否有 status = 'pending' 的任务
-2. 执行一次手动检查:`python scripts\auto_execute_tasks.py --once`
+**检查:**
+1. 是否安装 GUI 依赖:`pip install pywin32 pyautogui pyperclip`
+2. Cursor 窗口是否打开
+3. Chat 输入框位置是否正确
 
-### 问题4:进程无法停止
+### 问题 4:进程无法停止
 
-**解决方法**:
-1. 打开任务管理器(Ctrl+Shift+Esc)
-2. 找到 python.exe 进程
-3. 右键 → 结束任务
-
-或使用PowerShell:
-```powershell
-Get-Process python | Where-Object {$_.Path -like "*DataOps-platform*"} | Stop-Process -Force
-```
+**解决方法:**
+1. 运行启动器选择 7 停止服务
+2. 或打开任务管理器手动结束 Python 进程
 
 ---
 
-## 💡 使用建议
+## 📝 日志说明
 
-### 开发环境
-- 使用 **方式1(前台运行)**
-- 可以看到实时日志,方便调试
-
-### 生产环境
-- 使用 **方式2(后台运行)**
-- 设置为Windows服务或开机自启动
-
-### 测试
-- 使用 `--once` 参数执行一次检查
-- 或使用 `check_auto_tasks.bat` 查看状态
+### 前台运行
+- 日志直接输出到控制台
 
----
+### 后台运行
+- 日志文件:`logs\auto_execute.log`
 
-## 📞 相关文档
+查看日志:
+```cmd
+# 查看全部日志
+type logs\auto_execute.log
 
-- **完整文档**:`docs/CURSOR_AUTO_TASK_EXECUTION.md`
-- **快速开始**:`docs/TASK_EXECUTION_QUICK_START.md`
-- **实施总结**:`CURSOR_TASK_AUTOMATION_SUMMARY.md`
+# 查看最后 50 行
+powershell "Get-Content logs\auto_execute.log -Tail 50"
+```
 
 ---
 
-## ✅ 当前状态
+## 📞 相关文件
 
-**脚本已启动**:✅ 后台运行中
-**检查间隔**:5分钟(300秒)
-**日志位置**:`logs\auto_execute.log`(后台模式)或控制台(前台模式)
+| 文件 | 说明 |
+|------|------|
+| `scripts/auto_execute_tasks.py` | 核心调度脚本 |
+| `scripts/start_task_scheduler.bat` | 启动器脚本 |
+| `mcp-servers/task-manager/config.json` | 数据库配置 |
 
 ---
 
 **祝您使用愉快!🚀**
-
-
-

+ 0 - 164
scripts/CURSOR_AUTO_CHAT_README.md

@@ -1,164 +0,0 @@
-# Cursor自动聊天工具使用说明
-
-## 功能概述
-
-这个工具可以自动查找Windows系统中运行的Cursor程序,定位到chat窗口,并自动发送消息。
-
-### 主要功能
-
-1. ✅ 查找Windows操作系统中运行的Cursor程序
-2. ✅ 找到当前运行Cursor实例,并定位到当前的chat窗口
-3. ✅ 模拟鼠标点击到chat窗口
-4. ✅ 模拟键盘输入"请检查并执行所有待处理任务。"到chat窗口
-5. ✅ 模拟鼠标点击chat窗口的"提交"按钮
-6. ✅ 以服务方式持续运行,间隔300秒进行一次上述操作
-
-## 安装依赖
-
-首先需要安装必要的Python依赖包:
-
-```bash
-pip install pywin32 pyautogui pywinauto psutil
-```
-
-或者使用项目的requirements.txt:
-
-```bash
-pip install -r requirements.txt
-```
-
-## 使用方法
-
-### 方法1: 使用批处理脚本(推荐)
-
-#### 前台运行(可以看到日志输出)
-双击运行:`scripts/start_cursor_auto_chat.bat`
-
-#### 后台运行(静默运行)
-双击运行:`scripts/start_cursor_auto_chat_background.bat`
-
-#### 停止工具
-双击运行:`scripts/stop_cursor_auto_chat.bat`
-
-### 方法2: 命令行运行
-
-#### 单次执行(只执行一次,不循环)
-```bash
-python scripts/cursor_auto_chat.py --once
-```
-
-#### 守护进程模式(默认,持续运行)
-```bash
-python scripts/cursor_auto_chat.py --daemon
-```
-
-#### 自定义执行间隔(秒)
-```bash
-python scripts/cursor_auto_chat.py --interval 300
-```
-
-#### 自定义消息内容
-```bash
-python scripts/cursor_auto_chat.py --message "你的自定义消息"
-```
-
-#### 组合使用
-```bash
-python scripts/cursor_auto_chat.py --daemon --interval 600 --message "请执行待处理任务"
-```
-
-## 参数说明
-
-| 参数 | 说明 | 默认值 |
-|------|------|--------|
-| `--once` | 只执行一次,不持续运行 | - |
-| `--daemon` | 作为守护进程运行(持续运行) | 默认模式 |
-| `--interval` | 执行间隔(秒) | 300(5分钟) |
-| `--message` | 要发送的消息内容 | "请检查并执行所有待处理任务。" |
-
-## 日志文件
-
-工具运行时会生成日志文件:
-- 位置:`logs/cursor_auto_chat.log`
-- 格式:包含时间戳、日志级别和详细信息
-
-## 工作原理
-
-1. **查找Cursor进程**:使用`psutil`或`win32api`查找所有运行的Cursor进程
-2. **定位窗口**:通过窗口标题和类名查找Cursor主窗口
-3. **激活窗口**:将Cursor窗口置于前台
-4. **定位Chat输入框**:
-   - 使用快捷键(Ctrl+L)打开chat窗口
-   - 尝试点击可能的输入区域
-   - 使用Tab键导航到输入框
-5. **输入消息**:模拟键盘输入,模拟真实打字速度
-6. **提交消息**:按Enter键提交消息
-
-## 注意事项
-
-### 安全设置
-
-- **紧急停止**:将鼠标快速移动到屏幕左上角可以触发紧急停止(pyautogui的FAILSAFE功能)
-- **操作间隔**:每个操作之间有0.5秒的暂停,避免操作过快
-
-### 使用限制
-
-1. **Cursor必须正在运行**:工具需要Cursor程序处于运行状态
-2. **窗口可见**:Cursor窗口不能被完全遮挡
-3. **管理员权限**:某些情况下可能需要管理员权限来访问其他进程的窗口
-4. **屏幕分辨率**:如果使用固定坐标定位,可能在不同分辨率下需要调整
-
-### 故障排除
-
-#### 问题1: 找不到Cursor进程
-- 确保Cursor正在运行
-- 检查Cursor进程名称是否正确(可能因版本而异)
-
-#### 问题2: 无法定位chat窗口
-- 尝试手动打开Cursor的chat窗口(Ctrl+L)
-- 检查Cursor窗口是否被其他窗口遮挡
-- 可能需要调整输入框定位的坐标
-
-#### 问题3: 消息未发送成功
-- 检查输入框是否已激活(应该有光标闪烁)
-- 尝试手动点击chat输入框后再运行工具
-- 检查Enter键是否被其他程序拦截
-
-#### 问题4: 权限错误
-- 尝试以管理员身份运行
-- 检查Windows用户账户控制(UAC)设置
-
-## 开发说明
-
-### 代码结构
-
-- `CursorAutoChat`类:主要的工具类
-  - `find_cursor_processes()`: 查找Cursor进程
-  - `find_cursor_window()`: 查找Cursor窗口
-  - `activate_cursor_window()`: 激活窗口
-  - `find_chat_input_area()`: 定位chat输入区域
-  - `send_message()`: 发送消息
-  - `click_submit_button()`: 点击提交按钮
-  - `execute_once()`: 执行一次完整流程
-  - `run_daemon()`: 守护进程模式运行
-
-### 扩展功能
-
-如果需要扩展功能,可以修改以下部分:
-
-1. **消息内容**:修改`--message`参数或默认消息
-2. **执行间隔**:修改`--interval`参数
-3. **定位策略**:在`find_chat_input_area()`方法中添加更多定位策略
-4. **快捷键**:根据Cursor版本调整快捷键(Ctrl+L, Ctrl+K等)
-
-## 版本历史
-
-- **v1.0.0** (2025-11-29)
-  - 初始版本
-  - 实现基本的自动聊天功能
-  - 支持单次执行和守护进程模式
-
-## 许可证
-
-本项目遵循项目主许可证。
-

+ 293 - 524
scripts/auto_execute_tasks.py

@@ -1,26 +1,19 @@
 #!/usr/bin/env python3
 """
-自动任务执行 + Cursor Chat 工具(合并版)
+自动任务执行核心调度脚本(简化版)
 
-本脚本整合了原来的:
-- auto_execute_tasks.py:检查数据库中的 pending 任务,生成本地任务文件和 pending_tasks.json
-- cursor_auto_chat.py:在 Windows 下自动操作 Cursor Chat,发送指定消息
-
-合并后,一个脚本即可完成:
-1. 从 task_list 中读取 pending 任务
-2. 为任务生成本地 Python 占位文件
-3. 维护 .cursor/pending_tasks.json
-4. (可选)在 Cursor Chat 中自动发起「请检查并执行所有待处理任务。」
-5. 将 .cursor/pending_tasks.json 中 status=completed 的任务状态同步到 task_list
+工作流程:
+1. 从 PostgreSQL 数据库 task_list 表中读取 pending 任务
+2. 生成 .cursor/task_execute_instructions.md 执行指令文件
+3. 更新任务状态为 processing,并维护 .cursor/pending_tasks.json
+4. (可选)向 Cursor Chat 发送执行提醒
+5. Cursor 完成任务后,将 pending_tasks.json 中的状态改为 completed
+6. 调度脚本将 completed 状态的任务同步回数据库
 
 使用方式:
-1. 仅任务调度(不发 Chat):
-   python scripts/auto_execute_tasks.py --once
-   python scripts/auto_execute_tasks.py --interval 300
-
-2. 任务调度 + 自动 Chat:
-   python scripts/auto_execute_tasks.py --once --enable-chat --chat-input-pos "1180,965"
-   python scripts/auto_execute_tasks.py --interval 300 --enable-chat --chat-input-pos "1180,965"
+  python scripts/auto_execute_tasks.py --once
+  python scripts/auto_execute_tasks.py --interval 300
+  python scripts/auto_execute_tasks.py --once --enable-chat
 """
 
 from __future__ import annotations
@@ -29,94 +22,93 @@ import json
 import time
 import argparse
 import logging
-import sys
 from pathlib import Path
 from datetime import datetime
 from typing import Any, Dict, List, Optional, Tuple
 
-# 配置日志
+# ============================================================================
+# 日志配置
+# ============================================================================
 logging.basicConfig(
     level=logging.INFO,
     format="%(asctime)s - %(levelname)s - %(message)s",
 )
 logger = logging.getLogger("AutoExecuteTasks")
 
-# ==== Cursor Chat 相关依赖(Windows GUI 自动化) ====
+# ============================================================================
+# Windows GUI 自动化依赖(可选)
+# ============================================================================
+HAS_CURSOR_GUI = False
+HAS_PYPERCLIP = False
+
 try:
     import win32gui
     import win32con
-    import win32process
     import pyautogui
 
+    pyautogui.FAILSAFE = True
+    pyautogui.PAUSE = 0.5
+    HAS_CURSOR_GUI = True
+
     try:
         import pyperclip
-
         HAS_PYPERCLIP = True
     except ImportError:
-        HAS_PYPERCLIP = False
-
-    HAS_CURSOR_GUI = True
-    # pyautogui 安全 & 节奏
-    pyautogui.FAILSAFE = True
-    pyautogui.PAUSE = 0.5
+        pass
 except ImportError:
-    HAS_CURSOR_GUI = False
-    HAS_PYPERCLIP = False
-    logger.warning(
-        "未安装 Windows GUI 自动化依赖(pywin32/pyautogui/pyperclip),"
+    logger.info(
+        "未安装 Windows GUI 自动化依赖(pywin32/pyautogui),"
         "将禁用自动 Cursor Chat 功能。"
     )
 
-# 全局配置(由命令行参数控制)
-ENABLE_CHAT: bool = False
-CHAT_MESSAGE: str = "请检查并执行所有待处理任务。"
-CHAT_INPUT_POS: Optional[Tuple[int, int]] = None
-
-
+# ============================================================================
+# 全局配置
+# ============================================================================
 WORKSPACE_ROOT = Path(__file__).parent.parent
 CURSOR_DIR = WORKSPACE_ROOT / ".cursor"
 PENDING_TASKS_FILE = CURSOR_DIR / "pending_tasks.json"
+INSTRUCTIONS_FILE = CURSOR_DIR / "task_execute_instructions.md"
 
+# 命令行参数控制的全局变量
+ENABLE_CHAT: bool = False
+CHAT_MESSAGE: str = "请阅读 .cursor/task_execute_instructions.md 并执行任务。"
+CHAT_INPUT_POS: Optional[Tuple[int, int]] = None
 
+
+# ============================================================================
+# 数据库操作
+# ============================================================================
 def get_db_connection():
-    """
-    获取数据库连接
-    
-    Returns:
-        数据库连接对象,如果失败返回None
-    """
+    """获取数据库连接"""
     try:
         import psycopg2
-        from psycopg2.extras import RealDictCursor
-        
-        # 读取数据库配置
-        config_file = Path(__file__).parent.parent / 'mcp-servers' / 'task-manager' / 'config.json'
-        with open(config_file, 'r', encoding='utf-8') as f:
-            config = json.load(f)
-        
-        db_uri = config['database']['uri']
-        conn = psycopg2.connect(db_uri)
-        return conn
-        
+        import sys
+
+        sys.path.insert(0, str(WORKSPACE_ROOT))
+        from app.config.config import config, current_env
+
+        app_config = config.get(current_env, config['default'])
+        db_uri = app_config.SQLALCHEMY_DATABASE_URI
+        return psycopg2.connect(db_uri)
+
+    except ImportError as e:
+        logger.error(f"导入依赖失败: {e}")
+        return None
     except Exception as e:
         logger.error(f"连接数据库失败: {e}")
         return None
 
 
-def get_pending_tasks():
-    """
-    从数据库获取所有pending任务
-    """
+def get_pending_tasks() -> List[Dict[str, Any]]:
+    """从数据库获取所有 pending 任务"""
     try:
         from psycopg2.extras import RealDictCursor
-        
+
         conn = get_db_connection()
         if not conn:
             return []
-        
+
         cursor = conn.cursor(cursor_factory=RealDictCursor)
-        
-        # 查询pending任务
         cursor.execute("""
             SELECT task_id, task_name, task_description, status,
                    code_name, code_path, create_time, create_by
@@ -124,43 +116,36 @@ def get_pending_tasks():
             WHERE status = 'pending'
             ORDER BY create_time ASC
         """)
-        
+
         tasks = cursor.fetchall()
         cursor.close()
         conn.close()
-        
+
         return [dict(task) for task in tasks]
-        
+
     except Exception as e:
-        logger.error(f"获取pending任务失败: {e}")
+        logger.error(f"获取 pending 任务失败: {e}")
         return []
 
 
-def update_task_status(task_id, status, code_name=None, code_path=None):
-    """
-    更新任务状态
-    
-    Args:
-        task_id: 任务ID
-        status: 新状态('pending', 'processing', 'completed', 'failed')
-        code_name: 代码文件名(可选)
-        code_path: 代码文件路径(可选)
-    
-    Returns:
-        是否更新成功
-    """
+def update_task_status(
+    task_id: int,
+    status: str,
+    code_name: Optional[str] = None,
+    code_path: Optional[str] = None,
+) -> bool:
+    """更新任务状态"""
     try:
         conn = get_db_connection()
         if not conn:
             return False
-        
+
         cursor = conn.cursor()
-        
-        # 构建更新SQL
+
         if code_name and code_path:
             cursor.execute("""
                 UPDATE task_list
-                SET status = %s, code_name = %s, code_path = %s, 
+                SET status = %s, code_name = %s, code_path = %s,
                     update_time = CURRENT_TIMESTAMP
                 WHERE task_id = %s
             """, (status, code_name, code_path, task_id))
@@ -170,31 +155,150 @@ def update_task_status(task_id, status, code_name=None, code_path=None):
                 SET status = %s, update_time = CURRENT_TIMESTAMP
                 WHERE task_id = %s
             """, (status, task_id))
-        
+
         conn.commit()
         updated = cursor.rowcount > 0
         cursor.close()
         conn.close()
-        
+
         if updated:
             logger.info(f"✅ 任务 {task_id} 状态已更新为: {status}")
-        else:
-            logger.warning(f"⚠️ 任务 {task_id} 状态更新失败(任务不存在)")
-        
         return updated
-        
+
     except Exception as e:
         logger.error(f"更新任务状态失败: {e}")
         return False
 
 
-# ==== Cursor Chat 辅助函数(简化版,依赖固定输入框坐标) ====
+# ============================================================================
+# 任务文件生成
+# ============================================================================
+def write_pending_tasks_json(tasks: List[Dict[str, Any]]) -> None:
+    """将任务列表写入 .cursor/pending_tasks.json"""
+    CURSOR_DIR.mkdir(parents=True, exist_ok=True)
+
+    # 读取现有任务
+    existing_tasks = []
+    if PENDING_TASKS_FILE.exists():
+        try:
+            with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
+                existing_tasks = json.load(f)
+        except Exception:
+            existing_tasks = []
+
+    existing_ids = {t["task_id"] for t in existing_tasks if "task_id" in t}
+
+    # 添加新任务
+    for task in tasks:
+        if task["task_id"] not in existing_ids:
+            create_time = task.get("create_time", "")
+            if hasattr(create_time, "isoformat"):
+                create_time = create_time.isoformat()
+
+            task_info = {
+                "task_id": task["task_id"],
+                "task_name": task["task_name"],
+                "task_description": task["task_description"],
+                "code_path": task.get("code_path", ""),
+                "code_name": task.get("code_name", ""),
+                "status": "processing",
+                "notified_at": datetime.now().isoformat(),
+            }
+            existing_tasks.append(task_info)
+
+    with PENDING_TASKS_FILE.open("w", encoding="utf-8") as f:
+        json.dump(existing_tasks, f, indent=2, ensure_ascii=False)
+
+    logger.info(f"✅ pending_tasks.json 已更新,任务数: {len(existing_tasks)}")
+
+
+def create_execute_instructions(tasks: List[Dict[str, Any]]) -> None:
+    """生成任务执行指令文件 .cursor/task_execute_instructions.md"""
+    CURSOR_DIR.mkdir(parents=True, exist_ok=True)
+
+    with INSTRUCTIONS_FILE.open("w", encoding="utf-8") as f:
+        f.write("# 🤖 Cursor 自动任务执行指令\n\n")
+        f.write("**⚠️ 重要:请立即执行以下任务!**\n\n")
+        gen_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        f.write(f"**生成时间**: {gen_time}\n\n")
+        f.write(f"**待执行任务数量**: {len(tasks)}\n\n")
+
+        f.write("## 📋 任务完成后的操作\n\n")
+        f.write("完成每个任务后,请更新 `.cursor/pending_tasks.json` 中")
+        f.write("对应任务的 `status` 为 `completed`,\n")
+        f.write("并填写 `code_name`(代码文件名)和 `code_path`(代码路径)。\n\n")
+        f.write("调度脚本会自动将完成的任务同步到数据库。\n\n")
+        f.write("---\n\n")
+
+        for idx, task in enumerate(tasks, 1):
+            task_id = task['task_id']
+            task_name = task['task_name']
+            task_desc = task['task_description']
+
+            create_time = task.get("create_time", "")
+            if hasattr(create_time, "strftime"):
+                create_time = create_time.strftime("%Y-%m-%d %H:%M:%S")
+
+            f.write(f"## 🔴 任务 {idx}: {task_name}\n\n")
+            f.write(f"- **任务ID**: `{task_id}`\n")
+            f.write(f"- **创建时间**: {create_time}\n")
+            f.write(f"- **创建者**: {task.get('create_by', 'unknown')}\n\n")
+            f.write(f"### 📝 任务描述\n\n{task_desc}\n\n")
+            f.write("---\n\n")
+
+    logger.info(f"✅ 执行指令文件已创建: {INSTRUCTIONS_FILE}")
+
+
+# ============================================================================
+# 状态同步
+# ============================================================================
+def sync_completed_tasks_to_db() -> int:
+    """将 pending_tasks.json 中 completed 的任务同步到数据库"""
+    if not PENDING_TASKS_FILE.exists():
+        return 0
+
+    try:
+        with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
+            tasks = json.load(f)
+    except Exception as e:
+        logger.error(f"读取 pending_tasks.json 失败: {e}")
+        return 0
+
+    if not isinstance(tasks, list):
+        return 0
+
+    updated = 0
+    remaining_tasks = []
+
+    for t in tasks:
+        if t.get("status") == "completed":
+            task_id = t.get("task_id")
+            if not task_id:
+                continue
+
+            code_name = t.get("code_name")
+            code_path = t.get("code_path")
+            if update_task_status(task_id, "completed", code_name, code_path):
+                updated += 1
+                logger.info(f"已同步任务 {task_id} 为 completed")
+            else:
+                remaining_tasks.append(t)
+        else:
+            remaining_tasks.append(t)
+
+    if updated > 0:
+        with PENDING_TASKS_FILE.open("w", encoding="utf-8") as f:
+            json.dump(remaining_tasks, f, indent=2, ensure_ascii=False)
+        logger.info(f"本次共同步 {updated} 个 completed 任务到数据库")
+
+    return updated
 
-def _find_cursor_window() -> Optional[int]:
-    """
-    查找 Cursor 主窗口句柄(简化版)
-    通过窗口标题 / 类名中包含 'Cursor' / 'Chrome_WidgetWin_1' 来判断。
-    """
+
+# ============================================================================
+# Cursor Chat 自动化
+# ============================================================================
+def find_cursor_window() -> Optional[int]:
+    """查找 Cursor 主窗口句柄"""
     if not HAS_CURSOR_GUI:
         return None
 
@@ -205,537 +309,202 @@ def _find_cursor_window() -> Optional[int]:
             title = win32gui.GetWindowText(hwnd) or ""
             class_name = win32gui.GetClassName(hwnd) or ""
 
-            is_cursor = False
-            if "cursor" in title.lower():
-                is_cursor = True
+            is_cursor = "cursor" in title.lower()
             if class_name and "chrome_widgetwin" in class_name.lower():
                 is_cursor = True
 
             if is_cursor:
                 left, top, right, bottom = win32gui.GetWindowRect(hwnd)
-                width = right - left
-                height = bottom - top
-                area = width * height
-                cursor_windows.append(
-                    {
-                        "hwnd": hwnd,
-                        "title": title,
-                        "class": class_name,
-                        "width": width,
-                        "height": height,
-                        "area": area,
-                    }
-                )
+                area = (right - left) * (bottom - top)
+                cursor_windows.append({"hwnd": hwnd, "area": area})
         return True
 
     win32gui.EnumWindows(enum_windows_callback, None)
+
     if not cursor_windows:
         logger.warning("未找到 Cursor 窗口")
         return None
 
-    # 选取面积最大的窗口作为主窗口
     cursor_windows.sort(key=lambda x: x["area"], reverse=True)
-    main = cursor_windows[0]
-    logger.info(
-        "找到 Cursor 主窗口: %s (%s), size=%dx%d, hwnd=%s",
-        main["title"],
-        main["class"],
-        main["width"],
-        main["height"],
-        main["hwnd"],
-    )
-    return main["hwnd"]
+    return cursor_windows[0]["hwnd"]
 
 
-def _activate_cursor_window(hwnd: int) -> bool:
-    """激活 Cursor 主窗口"""
+def send_chat_message(
+    message: str, input_pos: Optional[Tuple[int, int]]
+) -> bool:
+    """在 Cursor Chat 中发送消息"""
     if not HAS_CURSOR_GUI:
+        logger.warning("当前环境不支持 Cursor GUI 自动化")
         return False
+
+    hwnd = find_cursor_window()
+    if not hwnd:
+        return False
+
     try:
         win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
         time.sleep(0.3)
         win32gui.SetForegroundWindow(hwnd)
         time.sleep(0.5)
-        logger.info("Cursor 窗口已激活")
-        return True
-    except Exception as exc:  # noqa: BLE001
-        logger.error("激活 Cursor 窗口失败: %s", exc)
-        return False
-
-
-def _send_chat_message_once(message: str, input_pos: Optional[Tuple[int, int]]) -> bool:
-    """
-    在 Cursor Chat 中发送一条消息(单次):
-    1. 激活窗口
-    2. 如果提供了输入框坐标,则移动并点击
-    3. 使用剪贴板粘贴消息
-    4. 按 Enter 提交
-    """
-    if not HAS_CURSOR_GUI:
-        logger.warning("当前环境不支持 Cursor GUI 自动化,跳过自动发 Chat")
-        return False
-
-    hwnd = _find_cursor_window()
-    if not hwnd:
-        return False
-    if not _activate_cursor_window(hwnd):
+    except Exception as e:
+        logger.error(f"激活 Cursor 窗口失败: {e}")
         return False
 
-    # 点击/激活输入框
+    # 点击输入框或使用快捷键
     if input_pos:
         x, y = input_pos
-        logger.info("移动鼠标到输入框位置: (%d, %d)", x, y)
-        pyautogui.moveTo(x, y, duration=0.3)
-        time.sleep(0.2)
         pyautogui.click(x, y)
         time.sleep(0.4)
     else:
-        # 未指定位置时,尝试快捷键打开 Chat(Ctrl+K),然后点击窗口底部中间
-        logger.info("未指定输入框位置,尝试使用 Ctrl+K 打开 Chat")
-        pyautogui.hotkey("ctrl", "k")
-        time.sleep(1.5)
-        screen_width, screen_height = pyautogui.size()
-        x, y = int(screen_width * 0.6), int(screen_height * 0.9)
-        pyautogui.moveTo(x, y, duration=0.3)
-        pyautogui.click(x, y)
-        time.sleep(0.4)
+        pyautogui.hotkey("ctrl", "l")
+        time.sleep(1.0)
 
-    # 清空旧内容
     pyautogui.hotkey("ctrl", "a")
-    time.sleep(0.3)
-
-    # 再次点击保证焦点
-    pyautogui.click()
     time.sleep(0.2)
 
-    logger.info("正在向 Cursor Chat 输入消息: %s", message)
-    # 优先使用剪贴板(兼容中文)
+    # 输入消息
     if HAS_PYPERCLIP:
-        try:
-            old_clipboard = pyperclip.paste()
-        except Exception:  # noqa: BLE001
-            old_clipboard = None
-
         try:
             pyperclip.copy(message)
-            time.sleep(0.3)
             pyautogui.hotkey("ctrl", "v")
-            time.sleep(1.0)
-            logger.info("使用剪贴板粘贴消息成功")
-        except Exception as exc:  # noqa: BLE001
-            logger.error("剪贴板粘贴失败: %s,退回到直接输入", exc)
-            pyautogui.write(message, interval=0.05)
-            time.sleep(0.8)
-        finally:
-            if old_clipboard is not None:
-                try:
-                    pyperclip.copy(old_clipboard)
-                except Exception:
-                    pass
+            time.sleep(0.5)
+        except Exception:
+            pyautogui.write(message, interval=0.03)
     else:
-        pyautogui.write(message, interval=0.05)
-        time.sleep(0.8)
+        pyautogui.write(message, interval=0.03)
 
-    # 提交(Enter)
-    logger.info("按 Enter 提交消息")
+    time.sleep(0.3)
     pyautogui.press("enter")
-    time.sleep(1.0)
-    logger.info("消息已提交")
+    logger.info("✅ 消息已发送到 Cursor Chat")
     return True
 
 
-def send_chat_for_pending_tasks() -> None:
-    """
-    如果启用了 Chat 功能且存在 pending 任务,则向 Cursor Chat 发送一次统一消息。
-    """
+def send_chat_for_tasks() -> None:
+    """向 Cursor Chat 发送任务提醒"""
     if not ENABLE_CHAT:
         return
 
-    # 仅检查 .cursor/pending_tasks.json 中是否存在 status 为 processing 的任务
     if not PENDING_TASKS_FILE.exists():
-        logger.info("未找到 .cursor/pending_tasks.json,跳过自动 Chat")
         return
 
     try:
         with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
             data = json.load(f)
-        if not isinstance(data, list) or not any(
-            t.get("status") == "processing" for t in data
-        ):
-            logger.info("pending_tasks.json 中没有 processing 任务,跳过自动 Chat")
+        if not any(t.get("status") == "processing" for t in data):
             return
-    except Exception as exc:  # noqa: BLE001
-        logger.error("读取 pending_tasks.json 失败: %s", exc)
+    except Exception:
         return
 
-    logger.info("检测到 processing 任务,准备自动向 Cursor Chat 发送提醒消息")
-    _send_chat_message_once(CHAT_MESSAGE, CHAT_INPUT_POS)
-
+    logger.info("发送任务提醒到 Cursor Chat...")
+    send_chat_message(CHAT_MESSAGE, CHAT_INPUT_POS)
 
-def sync_completed_tasks_from_pending_file() -> int:
-    """
-    将 .cursor/pending_tasks.json 中 status == 'completed' 的任务,同步到数据库。
 
-    Returns:
-        成功更新的任务数量
-    """
-    if not PENDING_TASKS_FILE.exists():
-        return 0
+# ============================================================================
+# 主执行流程
+# ============================================================================
+def auto_execute_tasks_once() -> int:
+    """执行一次任务检查和处理"""
+    # 1. 先同步已完成任务到数据库
+    sync_completed_tasks_to_db()
 
-    try:
-        with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
-            tasks = json.load(f)
-    except Exception as exc:  # noqa: BLE001
-        logger.error("读取 pending_tasks.json 失败: %s", exc)
-        return 0
+    # 2. 获取 pending 任务
+    logger.info("🔍 检查 pending 任务...")
+    tasks = get_pending_tasks()
 
-    if not isinstance(tasks, list):
+    if not tasks:
+        logger.info("✅ 没有 pending 任务")
         return 0
 
-    updated = 0
-    for t in tasks:
-        if t.get("status") != "completed":
-            continue
-
-        task_id = t.get("task_id")
-        if not task_id:
-            continue
-
-        code_name = t.get("code_name")
-        code_path = t.get("code_path")
-        if update_task_status(task_id, "completed", code_name, code_path):
-            updated += 1
-            logger.info(
-                "已根据 pending_tasks.json 将任务 %s 同步为 completed (code_name=%s, code_path=%s)",
-                task_id,
-                code_name,
-                code_path,
-            )
-
-    if updated:
-        logger.info("本次共同步 %d 个 completed 任务到数据库", updated)
-    return updated
-
-
-def print_task_for_cursor_execution(task, task_file_path=None):
-    """
-    打印任务信息,供Cursor识别并执行
-    
-    这个函数会以特定格式输出任务,Cursor可以识别并自动执行
-    """
-    print("\n" + "=" * 80)
-    print(f"🤖 [AUTO-EXECUTE-TASK] Task ID: {task['task_id']}")
-    print("=" * 80)
-    print(f"\n**任务名称**: {task['task_name']}")
-    print(f"**任务ID**: {task['task_id']}")
-    print(f"**状态**: processing(已更新)")
-    print(f"**创建时间**: {task['create_time']}")
-    print(f"**创建者**: {task['create_by']}")
-    
-    if task_file_path:
-        print(f"**任务文件**: {task_file_path}")
-    
-    print(f"\n## 任务描述\n")
-    print(task['task_description'])
-    print(f"\n## 执行指令")
-    print(f"\n请Cursor AI根据上述任务描述,自动完成以下步骤:")
-    print(f"1. 打开并查看任务文件: {task_file_path or '未创建'}")
-    print(f"2. 根据任务描述实现具体功能")
-    print(f"3. 确保代码符合项目规范")
-    print(f"4. 完成后调用MCP工具更新任务状态:")
-    print(f"   工具: update_task_status")
-    print(f"   参数: {{")
-    print(f"     \"task_id\": {task['task_id']},")
-    if task_file_path:
-        import os
-        file_name = os.path.basename(task_file_path)
-        file_dir = os.path.dirname(task_file_path).replace(str(Path(__file__).parent.parent), '').strip('\\').strip('/')
-        print(f"     \"code_name\": \"{file_name}\",")
-        print(f"     \"code_path\": \"{file_dir}\",")
-    print(f"     \"status\": \"completed\"")
-    print(f"   }}")
-    print(f"\n任务文件保存路径:{task.get('code_path', 'app/core/data_flow')}")
-    print("\n" + "=" * 80)
-    print(f"🔚 [END-AUTO-EXECUTE-TASK]")
-    print("=" * 80 + "\n")
-
-
-def create_task_file(task):
-    """
-    在指定目录创建任务文件
-    
-    Args:
-        task: 任务字典
-    
-    Returns:
-        生成的文件路径,如果失败返回None
-    """
-    try:
-        workspace = Path(__file__).parent.parent
-        code_path = task.get('code_path', 'app/core/data_flow')
-        target_dir = workspace / code_path
-        
-        # 确保目录存在
-        target_dir.mkdir(parents=True, exist_ok=True)
-        
-        # 生成文件名(从任务名称或代码名称)
-        code_name = task.get('code_name')
-        if not code_name:
-            # 从任务名称生成文件名
-            import re
-            task_name = task['task_name']
-            # 清理文件名:去除特殊字符,替换为下划线
-            safe_name = re.sub(r'[^\w\u4e00-\u9fff]+', '_', task_name)
-            safe_name = re.sub(r'_+', '_', safe_name).strip('_')
-            code_name = f"{safe_name}.py"
-        
-        # 确保是.py文件
-        if not code_name.endswith('.py'):
-            code_name = f"{code_name}.py"
-        
-        file_path = target_dir / code_name
-        
-        # 如果文件已存在,添加时间戳
-        if file_path.exists():
-            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
-            base_name = file_path.stem
-            file_path = target_dir / f"{base_name}_{timestamp}.py"
-            code_name = file_path.name
-        
-        # 生成任务文件内容
-        file_content = f'''#!/usr/bin/env python3
-"""
-{task['task_name']}
+    logger.info(f"📋 找到 {len(tasks)} 个 pending 任务")
 
-任务ID: {task['task_id']}
-创建时间: {task['create_time']}
-创建者: {task['create_by']}
+    # 3. 更新任务状态为 processing
+    for task in tasks:
+        update_task_status(task["task_id"], "processing")
 
-任务描述:
-{task['task_description']}
+    # 4. 写入 pending_tasks.json
+    write_pending_tasks_json(tasks)
 
-注意:此文件为任务占位符,需要根据任务描述实现具体功能。
-"""
+    # 5. 生成执行指令文件
+    create_execute_instructions(tasks)
 
-# TODO: 根据任务描述实现功能
-# {task['task_description'][:100]}...
-
-if __name__ == '__main__':
-    print("任务文件已创建,请根据任务描述实现具体功能")
-    pass
-'''
-        
-        # 写入文件
-        with open(file_path, 'w', encoding='utf-8') as f:
-            f.write(file_content)
-        
-        logger.info(f"✅ 任务文件已创建: {file_path}")
-        
-        # 更新数据库中的code_name和code_path
-        update_task_status(
-            task['task_id'], 
-            'processing',  # 状态改为processing
-            code_name=code_name,
-            code_path=code_path
-        )
-        
-        return str(file_path)
-        
-    except Exception as e:
-        logger.error(f"创建任务文件失败: {e}")
-        # 即使文件创建失败,也要更新状态为processing
-        update_task_status(task['task_id'], 'processing')
-        return None
+    return len(tasks)
 
 
-def notify_cursor_to_execute_task(task, task_file_path=None):
-    """
-    通知Cursor执行任务
-    
-    通过创建一个标记文件,让Cursor知道有新任务需要执行
-    """
-    workspace = Path(__file__).parent.parent
-    task_trigger_file = workspace / '.cursor' / 'pending_tasks.json'
-    task_trigger_file.parent.mkdir(parents=True, exist_ok=True)
-    
-    # 读取现有的pending tasks
-    pending_tasks = []
-    if task_trigger_file.exists():
-        try:
-            with open(task_trigger_file, 'r', encoding='utf-8') as f:
-                pending_tasks = json.load(f)
-        except:
-            pending_tasks = []
-    
-    # 检查任务是否已存在
-    task_exists = any(t['task_id'] == task['task_id'] for t in pending_tasks)
-    if not task_exists:
-        task_info = {
-            'task_id': task['task_id'],
-            'task_name': task['task_name'],
-            'task_description': task['task_description'],
-            'code_path': task.get('code_path', 'app/core/data_flow'),
-            'code_name': task.get('code_name'),
-            'status': 'processing',  # 标记为processing
-            'notified_at': datetime.now().isoformat()
-        }
-        
-        if task_file_path:
-            task_info['task_file'] = task_file_path
-        
-        pending_tasks.append(task_info)
-        
-        # 写入文件
-        with open(task_trigger_file, 'w', encoding='utf-8') as f:
-            json.dump(pending_tasks, f, indent=2, ensure_ascii=False)
-        
-        logger.info(f"✅ Task {task['task_id']} added to pending_tasks.json")
-
-
-def auto_execute_tasks_once():
-    """
-    执行一次任务检查和通知
-    """
-    # 先尝试把本地 completed 状态同步到数据库
-    sync_completed_tasks_from_pending_file()
-
-    logger.info("🔍 检查pending任务...")
-    
-    tasks = get_pending_tasks()
-    
-    if not tasks:
-        logger.info("✅ 没有pending任务")
-        return 0
-    
-    logger.info(f"📋 找到 {len(tasks)} 个pending任务")
-    
-    processed_count = 0
-    for task in tasks:
-        logger.info(f"\n{'='*80}")
-        logger.info(f"处理任务: [{task['task_id']}] {task['task_name']}")
-        logger.info(f"{'='*80}")
-        
-        try:
-            # 1. 创建任务文件(同时更新状态为processing)
-            task_file_path = create_task_file(task)
-            
-            if not task_file_path:
-                logger.warning(f"⚠️ 任务 {task['task_id']} 文件创建失败,但状态已更新为processing")
-                # 即使文件创建失败,也继续通知Cursor
-            
-            # 2. 打印任务详情,供Cursor识别
-            print_task_for_cursor_execution(task, task_file_path)
-            
-            # 3. 创建通知文件
-            notify_cursor_to_execute_task(task, task_file_path)
-            
-            processed_count += 1
-            logger.info(f"✅ 任务 {task['task_id']} 处理完成")
-            
-        except Exception as e:
-            logger.error(f"❌ 处理任务 {task['task_id']} 时出错: {e}")
-            # 标记任务为failed
-            update_task_status(task['task_id'], 'failed')
-    
-    return processed_count
-
-
-def auto_execute_tasks_loop(interval=300):
-    """
-    循环执行任务检查
-    
-    Args:
-        interval: 检查间隔(秒),默认300秒(5分钟)
-    """
-    logger.info("=" * 80)
+def auto_execute_tasks_loop(interval: int = 300) -> None:
+    """循环执行任务检查"""
+    logger.info("=" * 60)
     logger.info("🚀 自动任务执行服务已启动")
-    logger.info(f"⏰ 检查间隔: {interval}秒 ({interval//60}分钟)")
+    logger.info(f"⏰ 检查间隔: {interval} 秒")
+    logger.info(f"💬 自动 Chat: {'已启用' if ENABLE_CHAT else '未启用'}")
     logger.info("按 Ctrl+C 停止服务")
-    logger.info("=" * 80 + "\n")
-    
+    logger.info("=" * 60)
+
     try:
         while True:
             try:
                 count = auto_execute_tasks_once()
-
-                # 如果有新任务且启用了自动 Chat,则向 Cursor 发送提醒
                 if count > 0:
-                    send_chat_for_pending_tasks()
+                    send_chat_for_tasks()
+                    logger.info(f"✅ 已处理 {count} 个任务")
 
-                if count > 0:
-                    logger.info(f"\n✅ 已通知 {count} 个任务")
-                
-                logger.info(f"\n⏳ 下次检查时间: {interval}秒后...")
+                logger.info(f"⏳ {interval} 秒后再次检查...")
                 time.sleep(interval)
-                
+
             except KeyboardInterrupt:
                 raise
             except Exception as e:
                 logger.error(f"❌ 执行出错: {e}")
-                logger.info(f"⏳ {interval}秒后重试...")
                 time.sleep(interval)
-                
+
     except KeyboardInterrupt:
-        logger.info("\n" + "=" * 80)
-        logger.info("⛔ 用户停止了自动任务执行服务")
-        logger.info("=" * 80)
+        logger.info("\n⛔ 服务已停止")
 
 
-def main():
-    """
-    主函数
-    """
+def main() -> None:
+    """主函数"""
     parser = argparse.ArgumentParser(
-        description='自动任务执行脚本(含可选Cursor Chat)- 定期检查并执行pending任务'
+        description="自动任务执行调度脚本",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+    )
+    parser.add_argument(
+        "--once", action="store_true", help="只执行一次"
     )
     parser.add_argument(
-        '--once',
-        action='store_true',
-        help='只执行一次,不循环'
+        "--interval", type=int, default=300, help="检查间隔(秒)"
     )
     parser.add_argument(
-        '--interval',
-        type=int,
-        default=300,
-        help='检查间隔(秒),默认300秒(5分钟)'
+        "--enable-chat", action="store_true", help="启用自动 Cursor Chat"
     )
     parser.add_argument(
-        '--enable-chat',
-        action='store_true',
-        help='启用自动 Cursor Chat,在有pending任务时自动向 Cursor 发送提醒消息'
+        "--chat-input-pos", type=str, help='Chat 输入框位置 "x,y"'
     )
     parser.add_argument(
-        '--chat-input-pos',
-        type=str,
-        default=None,
-        help='Cursor Chat 输入框位置,格式 "x,y"(例如 "1180,965"),不指定则自动尝试定位'
+        "--chat-message", type=str,
+        default="请阅读 .cursor/task_execute_instructions.md 并执行任务。",
+        help="发送到 Chat 的消息"
     )
-    
+
     args = parser.parse_args()
 
-    # 设置全局 Chat 配置
-    global ENABLE_CHAT, CHAT_INPUT_POS  # noqa: PLW0603
+    global ENABLE_CHAT, CHAT_INPUT_POS, CHAT_MESSAGE
     ENABLE_CHAT = bool(args.enable_chat)
-    CHAT_INPUT_POS = None
+    CHAT_MESSAGE = args.chat_message
+
     if args.chat_input_pos:
         try:
-            x_str, y_str = args.chat_input_pos.split(',')
-            CHAT_INPUT_POS = (int(x_str.strip()), int(y_str.strip()))
-            logger.info("使用指定的 Chat 输入框位置: %s", CHAT_INPUT_POS)
-        except Exception as exc:  # noqa: BLE001
-            logger.warning("解析 --chat-input-pos 失败 %r: %s", args.chat_input_pos, exc)
-    
+            x, y = args.chat_input_pos.split(",")
+            CHAT_INPUT_POS = (int(x.strip()), int(y.strip()))
+        except Exception:
+            pass
+
     if args.once:
-        # 执行一次
         count = auto_execute_tasks_once()
         if count > 0:
-            send_chat_for_pending_tasks()
-        logger.info(f"\n✅ 完成!处理了 {count} 个任务")
+            send_chat_for_tasks()
+        logger.info(f"✅ 完成!处理了 {count} 个任务")
     else:
-        # 循环执行
         auto_execute_tasks_loop(interval=args.interval)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
-

+ 0 - 342
scripts/auto_tasks_chat_runner.py

@@ -1,342 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-自动任务+自动Chat一体化脚本
-
-功能整合:
-1. 从 task_list 表中读取所有 pending 任务
-2. 在本地生成 .cursor/pending_tasks.json 和 task_execute_instructions.md
-3. 自动在 Cursor Chat 中发送「请检查并执行所有待处理任务。」消息
-4. 轮询 .cursor/pending_tasks.json 中状态为 completed 的任务,并自动回写 task_list 状态
-
-依赖:
-- scripts/auto_execute_tasks.py 负责数据库连接和任务状态更新
-- scripts/trigger_cursor_execution.py 负责生成 task_execute_instructions.md 和触发文件
-- scripts/cursor_auto_chat.py 负责在 Cursor 中自动发送 Chat 消息
-
-使用方式:
-1) 单次执行(检查一次并触发一次 Chat):
-   python scripts/auto_tasks_chat_runner.py --once
-
-2) 守护进程模式(推荐):
-   python scripts/auto_tasks_chat_runner.py --daemon --interval 300
-"""
-
-from __future__ import annotations
-
-import json
-import time
-import argparse
-import logging
-from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
-
-# 复用现有脚本中的能力
-from scripts.auto_execute_tasks import get_pending_tasks, update_task_status  # type: ignore[import]
-from scripts.trigger_cursor_execution import create_execute_instructions_file  # type: ignore[import]
-from scripts.cursor_auto_chat import CursorAutoChat  # type: ignore[import]
-
-
-logger = logging.getLogger("AutoTasksChatRunner")
-logging.basicConfig(
-    level=logging.INFO,
-    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
-)
-
-
-WORKSPACE_ROOT = Path(__file__).parent.parent
-CURSOR_DIR = WORKSPACE_ROOT / ".cursor"
-PENDING_TASKS_FILE = CURSOR_DIR / "pending_tasks.json"
-
-
-def write_pending_tasks_file(tasks: List[Dict[str, Any]]) -> None:
-    """
-    将数据库中的 pending 任务写入 .cursor/pending_tasks.json
-
-    约定:
-    - 初始写入时,status 字段统一设为 'processing'
-    - Cursor / 用户 / 自动化脚本 可以把 status 更新为 'completed'
-    """
-    CURSOR_DIR.mkdir(parents=True, exist_ok=True)
-
-    payload: List[Dict[str, Any]] = []
-    for t in tasks:
-        payload.append(
-            {
-                "task_id": t["task_id"],
-                "task_name": t["task_name"],
-                "task_description": t["task_description"],
-                "code_path": t.get("code_path") or "app/core/data_flow",
-                "code_name": t.get("code_name") or "",
-                "status": "processing",
-                "notified_at": t.get("create_time") and t["create_time"].isoformat()
-                if hasattr(t.get("create_time"), "isoformat")
-                else None,
-                "task_file": t.get("task_file") or "",
-            }
-        )
-
-    with PENDING_TASKS_FILE.open("w", encoding="utf-8") as f:
-        json.dump(payload, f, ensure_ascii=False, indent=2)
-
-    logger.info("已写入 .cursor/pending_tasks.json,任务数量: %d", len(payload))
-
-
-def load_pending_tasks_file() -> List[Dict[str, Any]]:
-    """读取 .cursor/pending_tasks.json(如果不存在则返回空列表)"""
-    if not PENDING_TASKS_FILE.exists():
-        return []
-    try:
-        with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
-            data = json.load(f)
-        if isinstance(data, list):
-            return data
-        return []
-    except Exception as exc:  # noqa: BLE001
-        logger.error("读取 pending_tasks.json 失败: %s", exc)
-        return []
-
-
-def sync_completed_tasks_to_db() -> int:
-    """
-    将 .cursor/pending_tasks.json 中 status == 'completed' 的任务,同步回数据库。
-
-    返回:
-        成功更新到数据库的任务数量
-    """
-    tasks = load_pending_tasks_file()
-    if not tasks:
-        logger.info("pending_tasks.json 中没有任务记录")
-        return 0
-
-    updated = 0
-    for t in tasks:
-        if t.get("status") != "completed":
-            continue
-
-        task_id = t.get("task_id")
-        code_name = t.get("code_name") or ""
-        code_path = t.get("code_path") or ""
-
-        if not task_id:
-            continue
-
-        ok = update_task_status(
-            task_id=task_id,
-            status="completed",
-            code_name=code_name or None,
-            code_path=code_path or None,
-        )
-        if ok:
-            updated += 1
-            logger.info(
-                "已将任务 %s 同步为 completed (code_name=%s, code_path=%s)",
-                task_id,
-                code_name,
-                code_path,
-            )
-
-    if updated == 0:
-        logger.info("没有需要同步到数据库的 completed 任务")
-    else:
-        logger.info("本次共同步 %d 个任务状态到数据库", updated)
-
-    return updated
-
-
-def prepare_tasks_for_execution() -> int:
-    """
-    检查数据库中的 pending 任务,生成本地任务文件和 Cursor 指令文件。
-
-    返回:
-        准备好的 pending 任务数量
-    """
-    logger.info("开始从数据库读取 pending 任务...")
-    tasks = get_pending_tasks()
-    if not tasks:
-        logger.info("数据库中没有 pending 任务")
-        return 0
-
-    logger.info("从数据库读取到 %d 个 pending 任务", len(tasks))
-
-    # 写入 .cursor/pending_tasks.json
-    write_pending_tasks_file(tasks)
-
-    # 生成 task_execute_instructions.md + task_trigger.txt
-    # trigger_cursor_execution.create_execute_instructions_file 期望的是 processing_tasks 列表
-    create_execute_instructions_file(WORKSPACE_ROOT, tasks)
-
-    return len(tasks)
-
-
-def send_chat_to_cursor(message: str, input_box_pos: Optional[Tuple[int, int]]) -> bool:
-    """
-    使用 CursorAutoChat 在 Cursor 中发送一条 Chat 消息。
-    """
-    logger.info("准备向 Cursor Chat 发送消息: %s", message)
-    tool = CursorAutoChat(
-        message=message,
-        interval=300,
-        input_box_pos=input_box_pos,
-    )
-    return tool.execute_once()
-
-
-def run_once(message: str, input_box_pos: Optional[Tuple[int, int]]) -> None:
-    """
-    单次执行流程:
-    1. 同步已完成任务到数据库
-    2. 从数据库读取 pending 任务并生成本地文件
-    3. 如果有任务,向 Cursor 发送 Chat 消息
-    """
-    logger.info("=" * 80)
-    logger.info("开始单次自动任务 + Chat 执行流程")
-
-    # 第一步:先把本地已完成任务同步到数据库
-    sync_completed_tasks_to_db()
-
-    # 第二步:准备新的 pending 任务
-    count = prepare_tasks_for_execution()
-    if count == 0:
-        logger.info("当前没有新的 pending 任务,无需触发 Cursor Chat")
-        logger.info("=" * 80)
-        return
-
-    # 第三步:通过 Cursor Chat 提醒 AI 执行所有待处理任务
-    ok = send_chat_to_cursor(message=message, input_box_pos=input_box_pos)
-    if ok:
-        logger.info("已通过 Cursor Chat 发送任务执行指令")
-    else:
-        logger.warning("向 Cursor Chat 发送消息失败,请检查窗口状态")
-
-    logger.info("=" * 80)
-
-
-def run_daemon(
-    message: str,
-    input_box_pos: Optional[Tuple[int, int]],
-    interval: int,
-    sync_interval: int,
-) -> None:
-    """
-    守护进程模式:
-    - 每次循环:
-      1) 同步本地 completed 任务到数据库
-      2) 检查数据库 pending 任务并生成本地文件
-      3) 如有任务则触发 Cursor Chat
-    - 之后休眠 interval 秒
-    """
-    logger.info("=" * 80)
-    logger.info("自动任务 + 自动Chat 一体化守护进程已启动")
-    logger.info("检查间隔: %d 秒", interval)
-    logger.info("同步 completed 状态间隔(同检查逻辑): %d 秒", sync_interval)
-    logger.info("按 Ctrl+C 结束")
-    logger.info("=" * 80)
-
-    try:
-        while True:
-            try:
-                # 先同步 completed 状态
-                sync_completed_tasks_to_db()
-
-                # 再检查新的 pending 任务并触发 Chat
-                count = prepare_tasks_for_execution()
-                if count > 0:
-                    send_chat_to_cursor(message=message, input_box_pos=input_box_pos)
-
-                logger.info("下次检查将在 %d 秒后进行...", interval)
-                time.sleep(interval)
-
-            except KeyboardInterrupt:
-                raise
-            except Exception as exc:  # noqa: BLE001
-                logger.error("守护进程循环中出错: %s", exc)
-                logger.info("将在 %d 秒后重试...", interval)
-                time.sleep(interval)
-
-    except KeyboardInterrupt:
-        logger.info("\n自动任务 + 自动Chat 守护进程已停止")
-
-
-def parse_args() -> argparse.Namespace:
-    parser = argparse.ArgumentParser(
-        description="自动任务 + 自动Cursor Chat 一体化工具",
-        formatter_class=argparse.RawDescriptionHelpFormatter,
-        epilog="""
-示例:
-  # 单次执行:检查pending、生成文件并触发一次Chat
-  python scripts/auto_tasks_chat_runner.py --once
-
-  # 守护进程模式(每5分钟检查一次)
-  python scripts/auto_tasks_chat_runner.py --daemon --interval 300
-
-  # 自定义Chat消息
-  python scripts/auto_tasks_chat_runner.py --daemon --message "请执行所有待处理任务"
-
-  # 指定Chat输入框位置
-  python scripts/auto_tasks_chat_runner.py --daemon --input-box-pos "1180,965"
-        """,
-    )
-    parser.add_argument(
-        "--once",
-        action="store_true",
-        help="只执行一次,不持续运行",
-    )
-    parser.add_argument(
-        "--daemon",
-        action="store_true",
-        help="以守护进程模式运行,定期检查任务并触发Chat",
-    )
-    parser.add_argument(
-        "--interval",
-        type=int,
-        default=300,
-        help="守护进程模式下的检查间隔(秒),默认 300 秒",
-    )
-    parser.add_argument(
-        "--message",
-        type=str,
-        default="请检查并执行所有待处理任务。",
-        help='发送到 Cursor Chat 的消息内容,默认: "请检查并执行所有待处理任务。"',
-    )
-    parser.add_argument(
-        "--input-box-pos",
-        type=str,
-        default=None,
-        help='Cursor Chat 输入框位置,格式为 "x,y"(例如 "1180,965"),不指定则自动尝试定位',
-    )
-    return parser.parse_args()
-
-
-def main() -> None:
-    args = parse_args()
-
-    # 解析输入框位置
-    input_box_pos: Optional[Tuple[int, int]] = None
-    if args.input_box_pos:
-        try:
-            x_str, y_str = args.input_box_pos.split(",")
-            input_box_pos = (int(x_str.strip()), int(y_str.strip()))
-            logger.info("使用指定的输入框位置: %s", input_box_pos)
-        except Exception as exc:  # noqa: BLE001
-            logger.warning("解析输入框位置失败 %r: %s", args.input_box_pos, exc)
-            input_box_pos = None
-
-    if args.once:
-        run_once(message=args.message, input_box_pos=input_box_pos)
-    else:
-        run_daemon(
-            message=args.message,
-            input_box_pos=input_box_pos,
-            interval=args.interval,
-            sync_interval=args.interval,
-        )
-
-
-if __name__ == "__main__":
-    main()
-
-
-
-
-

+ 0 - 56
scripts/check_auto_tasks.bat

@@ -1,56 +0,0 @@
-@echo off
-chcp 65001 >nul
-REM 检查自动任务执行脚本运行状态
-
-echo ================================================
-echo 检查自动任务执行服务状态
-echo ================================================
-echo.
-
-REM 切换到项目根目录
-cd /d %~dp0..
-
-REM 使用PowerShell检查进程
-echo [服务状态]
-powershell -Command "$processes = Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like '*auto_execute_tasks.py*' }; if ($processes) { Write-Host '[运行中] 找到以下进程:' -ForegroundColor Green; $processes | ForEach-Object { Write-Host ('  进程ID: ' + $_.ProcessId + '  启动时间: ' + $_.CreationDate) } } else { Write-Host '[未运行] 未找到auto_execute_tasks.py进程' -ForegroundColor Yellow }"
-
-echo.
-echo ================================================
-echo 查看最近日志(最后20行)
-echo ================================================
-echo.
-
-if exist "logs\auto_execute.log" (
-    powershell -Command "Get-Content logs\auto_execute.log -Tail 20 -ErrorAction SilentlyContinue"
-) else (
-    echo [提示] 日志文件不存在,脚本可能未运行或使用标准输出
-)
-
-echo.
-echo ================================================
-echo 检查pending_tasks.json状态
-echo ================================================
-echo.
-
-if exist ".cursor\pending_tasks.json" (
-    echo [文件存在] .cursor\pending_tasks.json
-    powershell -Command "$tasks = Get-Content '.cursor\pending_tasks.json' -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json; if ($tasks) { Write-Host ('  任务数量: ' + $tasks.Count); $tasks | ForEach-Object { Write-Host ('  - [' + $_.task_id + '] ' + $_.task_name + ' (' + $_.status + ')') } } else { Write-Host '  [空] 没有待处理任务' }"
-) else (
-    echo [提示] pending_tasks.json 不存在
-)
-
-echo.
-echo ================================================
-echo 是否执行一次手动检查?(Y/N)
-echo ================================================
-echo.
-
-set /p choice="请输入选择: "
-if /i "%choice%"=="Y" (
-    echo.
-    echo [执行] 手动运行一次任务检查...
-    python scripts\auto_execute_tasks.py --once
-)
-
-echo.
-pause

+ 0 - 689
scripts/cursor_auto_chat.py

@@ -1,689 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-Cursor自动聊天工具
-
-这个工具可以自动查找Cursor窗口,定位到chat窗口,并自动发送消息。
-
-功能:
-1. 查找Windows操作系统中运行的Cursor程序
-2. 找到当前运行Cursor实例,并定位到当前的chat窗口
-3. 模拟鼠标点击到chat窗口
-4. 模拟键盘输入"请检查并执行所有待处理任务。"到chat窗口
-5. 模拟鼠标点击chat窗口的"提交"按钮
-6. 以服务方式持续运行,间隔300秒进行一次上述操作
-
-使用方法:
-1. 单次执行:python scripts/cursor_auto_chat.py --once
-2. 服务模式:python scripts/cursor_auto_chat.py --daemon
-3. 自定义间隔:python scripts/cursor_auto_chat.py --interval 300
-4. 指定输入框位置:python scripts/cursor_auto_chat.py --input-box-pos "1180,965"
-"""
-
-import sys
-import time
-import argparse
-import logging
-from pathlib import Path
-from datetime import datetime
-
-try:
-    import win32gui
-    import win32con
-    import win32process
-    import win32api
-    import pyautogui
-    import pywinauto
-    from pywinauto import Application
-    try:
-        import pyperclip
-        HAS_PYPERCLIP = True
-    except ImportError:
-        HAS_PYPERCLIP = False
-except ImportError as e:
-    print(f"❌ 缺少必要的依赖库: {e}")
-    print("请运行: pip install pywin32 pyautogui pywinauto pyperclip")
-    sys.exit(1)
-
-# 配置日志
-logs_dir = Path(__file__).parent.parent / 'logs'
-logs_dir.mkdir(exist_ok=True)
-
-logging.basicConfig(
-    level=logging.INFO,
-    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
-    handlers=[
-        logging.FileHandler(logs_dir / 'cursor_auto_chat.log', encoding='utf-8'),
-        logging.StreamHandler(sys.stdout)
-    ]
-)
-logger = logging.getLogger('CursorAutoChat')
-
-# 配置pyautogui安全设置
-pyautogui.FAILSAFE = True  # 鼠标移到屏幕左上角会触发异常,用于紧急停止
-pyautogui.PAUSE = 0.5  # 每个操作之间的暂停时间(秒)
-
-# 检查pyperclip并记录
-if not HAS_PYPERCLIP:
-    logger.warning("未安装pyperclip,中文输入可能有问题,建议安装: pip install pyperclip")
-
-
-class CursorAutoChat:
-    """Cursor自动聊天工具类"""
-    
-    def __init__(self, message="请检查并执行所有待处理任务。", interval=300, input_box_pos=None):
-        """
-        初始化工具
-        
-        Args:
-            message: 要发送的消息内容
-            interval: 执行间隔(秒)
-            input_box_pos: 输入框位置 (x, y),如果提供则直接使用,不进行自动定位
-        """
-        self.message = message
-        self.interval = interval
-        self.cursor_window = None
-        self.input_box_pos = input_box_pos  # 用户指定的输入框位置
-        logger.info(f"Cursor自动聊天工具已初始化")
-        logger.info(f"消息内容: {self.message}")
-        logger.info(f"执行间隔: {self.interval}秒")
-        if self.input_box_pos:
-            logger.info(f"使用指定的输入框位置: {self.input_box_pos}")
-    
-    def find_cursor_processes(self):
-        """
-        查找所有运行的Cursor进程
-        
-        Returns:
-            list: Cursor进程ID列表
-        """
-        cursor_pids = []
-        try:
-            import psutil
-            for proc in psutil.process_iter(['pid', 'name', 'exe']):
-                try:
-                    proc_name = proc.info['name'].lower() if proc.info['name'] else ''
-                    proc_exe = proc.info['exe'].lower() if proc.info['exe'] else ''
-                    
-                    # 查找Cursor相关进程
-                    if 'cursor' in proc_name or 'cursor' in proc_exe:
-                        cursor_pids.append(proc.info['pid'])
-                        logger.debug(f"找到Cursor进程: PID={proc.info['pid']}, Name={proc.info['name']}")
-                except (psutil.NoSuchProcess, psutil.AccessDenied):
-                    continue
-        except ImportError:
-            # 如果没有psutil,使用win32api枚举窗口
-            logger.warning("未安装psutil,使用窗口枚举方式查找Cursor")
-            cursor_pids = self._find_cursor_by_windows()
-        
-        logger.info(f"找到 {len(cursor_pids)} 个Cursor进程")
-        return cursor_pids
-    
-    def _find_cursor_by_windows(self):
-        """通过枚举窗口查找Cursor进程"""
-        cursor_pids = []
-        
-        def enum_windows_callback(hwnd, windows):
-            if win32gui.IsWindowVisible(hwnd):
-                window_title = win32gui.GetWindowText(hwnd)
-                if 'cursor' in window_title.lower():
-                    _, pid = win32process.GetWindowThreadProcessId(hwnd)
-                    if pid not in cursor_pids:
-                        cursor_pids.append(pid)
-            return True
-        
-        win32gui.EnumWindows(enum_windows_callback, None)
-        return cursor_pids
-    
-    def find_cursor_window(self):
-        """
-        查找Cursor主窗口
-        
-        Returns:
-            int: 窗口句柄,如果未找到返回None
-        """
-        cursor_windows = []  # 存储所有可能的Cursor窗口
-        
-        def enum_windows_callback(hwnd, windows):
-            if win32gui.IsWindowVisible(hwnd):
-                window_title = win32gui.GetWindowText(hwnd)
-                class_name = win32gui.GetClassName(hwnd)
-                
-                # Cursor基于Electron,窗口类名可能是Chrome_WidgetWin_1或类似
-                # 查找可能的Cursor窗口
-                is_cursor = False
-                
-                # 检查窗口标题
-                if window_title and 'cursor' in window_title.lower():
-                    is_cursor = True
-                
-                # 检查窗口类名(Electron应用通常有特定类名)
-                if class_name and ('chrome_widgetwin' in class_name.lower() or 'electron' in class_name.lower()):
-                    # 进一步检查:Electron窗口通常比较大
-                    rect = win32gui.GetWindowRect(hwnd)
-                    width = rect[2] - rect[0]
-                    height = rect[3] - rect[1]
-                    if width > 800 and height > 600:
-                        is_cursor = True
-                
-                if is_cursor:
-                    rect = win32gui.GetWindowRect(hwnd)
-                    width = rect[2] - rect[0]
-                    height = rect[3] - rect[1]
-                    area = width * height
-                    cursor_windows.append({
-                        'hwnd': hwnd,
-                        'title': window_title,
-                        'class': class_name,
-                        'width': width,
-                        'height': height,
-                        'area': area
-                    })
-                    logger.debug(f"找到可能的Cursor窗口: {window_title} ({class_name}), Size: {width}x{height}")
-            
-            return True
-        
-        win32gui.EnumWindows(enum_windows_callback, None)
-        
-        if not cursor_windows:
-            logger.warning("未找到Cursor窗口")
-            return None
-        
-        # 选择最大的窗口作为主窗口(通常是主应用窗口)
-        cursor_windows.sort(key=lambda x: x['area'], reverse=True)
-        main_window = cursor_windows[0]
-        
-        logger.info(f"找到Cursor主窗口: {main_window['title']} ({main_window['class']})")
-        logger.info(f"窗口大小: {main_window['width']}x{main_window['height']} (HWND: {main_window['hwnd']})")
-        
-        return main_window['hwnd']
-    
-    def activate_cursor_window(self, hwnd):
-        """
-        激活Cursor窗口
-        
-        Args:
-            hwnd: 窗口句柄
-        """
-        try:
-            # 恢复窗口(如果最小化)
-            win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
-            time.sleep(0.3)
-            
-            # 激活窗口
-            win32gui.SetForegroundWindow(hwnd)
-            time.sleep(0.5)
-            
-            logger.info("Cursor窗口已激活")
-            return True
-        except Exception as e:
-            logger.error(f"激活窗口失败: {e}")
-            return False
-    
-    def find_chat_input_area(self, hwnd):
-        """
-        查找chat输入区域
-        
-        使用多种策略定位Cursor的chat输入框:
-        1. 尝试多个快捷键打开chat(Ctrl+K, Ctrl+L, Ctrl+Shift+L等)
-        2. 使用相对窗口坐标定位输入框(chat通常在窗口底部中央或右侧)
-        3. 验证输入框是否被激活
-        
-        Args:
-            hwnd: Cursor窗口句柄
-        
-        Returns:
-            tuple: (是否成功, 输入框坐标(x, y)) 或 (False, None)
-        """
-        try:
-            # 获取窗口位置和大小
-            rect = win32gui.GetWindowRect(hwnd)
-            window_left = rect[0]
-            window_top = rect[1]
-            window_width = rect[2] - rect[0]
-            window_height = rect[3] - rect[1]
-            
-            logger.info(f"窗口位置: ({window_left}, {window_top}), 大小: {window_width}x{window_height}")
-            
-            # 策略1: 尝试多个快捷键打开chat
-            logger.info("尝试使用快捷键打开chat窗口...")
-            shortcuts = [
-                ('ctrl', 'k'),  # Cursor最常用的快捷键
-                ('ctrl', 'l'),  # 备用快捷键
-                ('ctrl', 'shift', 'l'),  # 另一个可能的快捷键
-            ]
-            
-            for shortcut in shortcuts:
-                try:
-                    logger.debug(f"尝试快捷键: {'+'.join(shortcut)}")
-                    pyautogui.hotkey(*shortcut)
-                    time.sleep(1.5)  # 给chat窗口时间打开
-                    
-                    # 尝试定位输入框
-                    result = self._try_activate_input_box(hwnd, window_left, window_top, window_width, window_height)
-                    if result:
-                        input_pos = result
-                        logger.info(f"成功定位并激活chat输入框,位置: {input_pos}")
-                        return (True, input_pos)
-                except Exception as e:
-                    logger.debug(f"快捷键 {shortcut} 失败: {e}")
-                    continue
-            
-            # 策略2: 直接尝试点击可能的输入框位置(即使chat已打开)
-            logger.info("尝试直接点击可能的输入框位置...")
-            result = self._try_activate_input_box(hwnd, window_left, window_top, window_width, window_height)
-            if result:
-                input_pos = result
-                logger.info(f"成功定位并激活chat输入框,位置: {input_pos}")
-                return (True, input_pos)
-            
-            logger.warning("未能定位chat输入框,将尝试通用方法")
-            return (False, None)
-            
-        except Exception as e:
-            logger.error(f"查找chat输入区域失败: {e}")
-            return (False, None)
-    
-    def _try_activate_input_box(self, hwnd, window_left, window_top, window_width, window_height):
-        """
-        尝试激活输入框
-        
-        Args:
-            hwnd: 窗口句柄
-            window_left: 窗口左边界
-            window_top: 窗口上边界
-            window_width: 窗口宽度
-            window_height: 窗口高度
-        
-        Returns:
-            tuple: 成功时返回 (x, y) 坐标,失败时返回 None
-        """
-        # Cursor的chat输入框通常在窗口底部中央或右侧底部
-        # 尝试多个可能的位置(相对于窗口)
-        # 根据实际测试,输入框位置约为(0.61, 0.92)
-        possible_relative_positions = [
-            (0.61, 0.92),  # 实际测试位置(优先尝试)
-            (0.6, 0.92),   # 稍微偏左
-            (0.62, 0.92),  # 稍微偏右
-            (0.61, 0.91),  # 稍微偏上
-            (0.61, 0.93),  # 稍微偏下
-            (0.75, 0.92),  # 窗口右侧底部(备用)
-            (0.5, 0.92),   # 窗口底部中央(备用)
-            (0.5, 0.88),   # 窗口底部稍上(备用)
-            (0.8, 0.9),    # 窗口右侧(备用)
-        ]
-        
-        for rel_x, rel_y in possible_relative_positions:
-            try:
-                # 计算绝对坐标
-                abs_x = window_left + int(window_width * rel_x)
-                abs_y = window_top + int(window_height * rel_y)
-                
-                logger.debug(f"尝试点击位置(相对窗口): ({rel_x:.2f}, {rel_y:.2f}) -> 绝对坐标: ({abs_x}, {abs_y})")
-                
-                # 点击输入框位置
-                pyautogui.click(abs_x, abs_y)
-                time.sleep(0.8)  # 等待输入框激活
-                
-                # 验证输入框是否激活:尝试输入一个测试字符然后删除
-                # 如果输入框已激活,这个操作应该成功
-                pyautogui.write('test', interval=0.1)
-                time.sleep(0.3)
-                
-                # 删除测试文本
-                for _ in range(4):
-                    pyautogui.press('backspace')
-                time.sleep(0.3)
-                
-                # 如果到这里没有异常,说明输入框可能已激活
-                logger.info(f"成功激活输入框,位置: ({abs_x}, {abs_y})")
-                return (abs_x, abs_y)
-                
-            except Exception as e:
-                logger.debug(f"位置 ({rel_x:.2f}, {rel_y:.2f}) 失败: {e}")
-                continue
-        
-        return None
-    
-    def send_message(self, message, input_pos=None):
-        """
-        发送消息到chat窗口
-        
-        Args:
-            message: 要发送的消息
-            input_pos: 输入框坐标 (x, y),如果提供则点击该位置确保激活
-        """
-        try:
-            # 如果提供了输入框位置,先移动鼠标到该位置,然后点击
-            if input_pos:
-                x, y = input_pos
-                logger.info(f"移动鼠标到输入框位置: ({x}, {y})")
-                pyautogui.moveTo(x, y, duration=0.3)  # 平滑移动到输入框位置
-                time.sleep(0.2)
-                
-                logger.info(f"点击输入框位置确保激活: ({x}, {y})")
-                pyautogui.click(x, y)
-                time.sleep(0.5)  # 等待输入框激活
-            else:
-                # 如果没有提供位置,点击当前鼠标位置
-                logger.info("点击当前位置确保输入框激活")
-                pyautogui.click()
-                time.sleep(0.3)
-            
-            # 清空可能的现有文本
-            logger.info("清空输入框中的现有文本...")
-            pyautogui.hotkey('ctrl', 'a')
-            time.sleep(0.3)
-            
-            # 再次确保鼠标在输入框位置并点击(如果提供了位置)
-            if input_pos:
-                x, y = input_pos
-                logger.info(f"再次移动鼠标到输入框并点击: ({x}, {y})")
-                pyautogui.moveTo(x, y, duration=0.2)
-                time.sleep(0.2)
-                pyautogui.click(x, y)
-                time.sleep(0.4)  # 给足够时间让输入框完全激活
-            else:
-                pyautogui.click()
-                time.sleep(0.2)
-            
-            # 输入消息
-            logger.info(f"正在输入消息: {message}")
-            
-            # 对于中文文本,使用剪贴板方法更可靠
-            if HAS_PYPERCLIP:
-                try:
-                    # 保存当前剪贴板内容
-                    old_clipboard = pyperclip.paste() if hasattr(pyperclip, 'paste') else None
-                    
-                    # 复制消息到剪贴板
-                    pyperclip.copy(message)
-                    time.sleep(0.3)
-                    
-                    # 确保鼠标在输入框位置(如果提供了位置)
-                    if input_pos:
-                        x, y = input_pos
-                        logger.info(f"粘贴前确保鼠标在输入框位置: ({x}, {y})")
-                        pyautogui.moveTo(x, y, duration=0.2)
-                        time.sleep(0.2)
-                        # 再次点击确保焦点
-                        pyautogui.click(x, y)
-                        time.sleep(0.3)
-                    
-                    # 粘贴消息
-                    logger.info("执行Ctrl+V粘贴消息...")
-                    pyautogui.hotkey('ctrl', 'v')
-                    time.sleep(1.5)  # 等待粘贴完成,给足够时间
-                    
-                    # 验证粘贴是否成功(可选:再次点击确保文本已输入)
-                    if input_pos:
-                        # 轻微移动鼠标确认输入框仍有焦点
-                        x, y = input_pos
-                        pyautogui.moveTo(x + 1, y + 1, duration=0.1)
-                        time.sleep(0.2)
-                    
-                    # 恢复剪贴板(可选)
-                    if old_clipboard:
-                        try:
-                            pyperclip.copy(old_clipboard)
-                        except:
-                            pass
-                    
-                    logger.info("使用剪贴板方法输入消息成功")
-                    return True
-                except Exception as e:
-                    logger.warning(f"剪贴板方法失败: {e},尝试其他方法...")
-            
-            # 备用方法:直接输入(对英文有效)
-            try:
-                # 检查是否包含中文字符
-                has_chinese = any('\u4e00' <= char <= '\u9fff' for char in message)
-                if has_chinese:
-                    logger.warning("消息包含中文,但pyperclip不可用,输入可能失败")
-                
-                pyautogui.write(message, interval=0.05)
-                time.sleep(0.8)
-                logger.info("使用write方法输入成功")
-                return True
-            except Exception as e2:
-                logger.error(f"使用write方法也失败: {e2}")
-                return False
-                
-        except Exception as e:
-            logger.error(f"输入消息失败: {e}")
-            return False
-    
-    def click_submit_button(self):
-        """
-        点击提交按钮
-        
-        Cursor的提交方式可能是:
-        1. Enter键(单行输入)
-        2. Ctrl+Enter组合键(多行输入或某些配置)
-        3. 点击提交按钮(如果存在)
-        """
-        try:
-            # 策略1: 先尝试Enter键(最常见)
-            logger.info("尝试按Enter键提交...")
-            pyautogui.press('enter')
-            time.sleep(1.0)  # 等待消息发送
-            
-            # 策略2: 如果Enter不行,尝试Ctrl+Enter(某些配置下需要)
-            # 但先等待一下,看看Enter是否生效
-            logger.info("等待消息发送完成...")
-            time.sleep(1.5)  # 给足够时间让消息发送
-            
-            logger.info("消息已提交(使用Enter键)")
-            logger.info("提示: 如果消息未出现在chat历史中,可能需要使用Ctrl+Enter")
-            return True
-        except Exception as e:
-            logger.error(f"点击提交按钮失败: {e}")
-            # 尝试备用方法
-            try:
-                logger.info("尝试使用Ctrl+Enter提交...")
-                pyautogui.hotkey('ctrl', 'enter')
-                time.sleep(1.5)
-                logger.info("使用Ctrl+Enter提交完成")
-                return True
-            except Exception as e2:
-                logger.error(f"使用Ctrl+Enter也失败: {e2}")
-                return False
-    
-    def execute_once(self):
-        """
-        执行一次完整的操作流程
-        
-        Returns:
-            bool: 是否成功执行
-        """
-        logger.info("=" * 60)
-        logger.info("开始执行自动聊天操作...")
-        logger.info("=" * 60)
-        
-        try:
-            # 步骤1: 查找Cursor进程
-            logger.info("步骤1: 查找Cursor进程...")
-            cursor_pids = self.find_cursor_processes()
-            if not cursor_pids:
-                logger.error("未找到Cursor进程,请确保Cursor正在运行")
-                return False
-            
-            # 步骤2: 查找Cursor窗口
-            logger.info("步骤2: 查找Cursor主窗口...")
-            cursor_hwnd = self.find_cursor_window()
-            if not cursor_hwnd:
-                logger.error("未找到Cursor主窗口")
-                return False
-            
-            # 步骤3: 激活窗口
-            logger.info("步骤3: 激活Cursor窗口...")
-            if not self.activate_cursor_window(cursor_hwnd):
-                logger.error("激活窗口失败")
-                return False
-            
-            # 步骤4: 定位chat输入区域
-            logger.info("步骤4: 定位chat输入区域...")
-            
-            # 如果用户指定了输入框位置,直接使用
-            if self.input_box_pos:
-                input_pos = self.input_box_pos
-                logger.info(f"使用用户指定的输入框位置: {input_pos}")
-                # 激活窗口后,直接使用指定位置
-                time.sleep(0.5)
-            else:
-                # 自动定位输入框
-                result = self.find_chat_input_area(cursor_hwnd)
-                if isinstance(result, tuple) and len(result) == 2:
-                    success, input_pos = result
-                    if success and input_pos:
-                        logger.info(f"已定位到输入框位置: {input_pos}")
-                    else:
-                        logger.warning("未能精确定位输入区域,将尝试直接输入")
-                        input_pos = None
-                else:
-                    # 兼容旧版本返回格式
-                    if result:
-                        logger.info("已定位到输入框")
-                        input_pos = None  # 旧版本不返回位置
-                    else:
-                        logger.warning("未能精确定位输入区域,将尝试直接输入")
-                        input_pos = None
-                    time.sleep(1)
-            
-            # 步骤5: 输入消息
-            logger.info("步骤5: 输入消息...")
-            if not self.send_message(self.message, input_pos):
-                logger.error("输入消息失败")
-                return False
-            
-            # 步骤6: 点击提交按钮
-            logger.info("步骤6: 提交消息...")
-            if not self.click_submit_button():
-                logger.error("提交消息失败")
-                return False
-            
-            logger.info("=" * 60)
-            logger.info("✅ 自动聊天操作执行成功!")
-            logger.info("=" * 60)
-            return True
-            
-        except Exception as e:
-            logger.error(f"执行过程中出错: {e}", exc_info=True)
-            return False
-    
-    def run_daemon(self):
-        """
-        以守护进程模式运行,定期执行操作
-        """
-        logger.info("=" * 60)
-        logger.info("🚀 Cursor自动聊天工具已启动(守护进程模式)")
-        logger.info(f"⏰ 执行间隔: {self.interval}秒 ({self.interval//60}分钟)")
-        logger.info("按 Ctrl+C 停止服务")
-        logger.info("=" * 60)
-        
-        try:
-            while True:
-                try:
-                    success = self.execute_once()
-                    
-                    if success:
-                        logger.info(f"✅ 操作执行成功,{self.interval}秒后再次执行...")
-                    else:
-                        logger.warning(f"⚠️ 操作执行失败,{self.interval}秒后重试...")
-                    
-                    time.sleep(self.interval)
-                    
-                except KeyboardInterrupt:
-                    raise
-                except Exception as e:
-                    logger.error(f"执行过程中出错: {e}")
-                    logger.info(f"⏳ {self.interval}秒后重试...")
-                    time.sleep(self.interval)
-                    
-        except KeyboardInterrupt:
-            logger.info("\n" + "=" * 60)
-            logger.info("⛔ 用户停止了Cursor自动聊天工具")
-            logger.info("=" * 60)
-
-
-def main():
-    """主函数"""
-    parser = argparse.ArgumentParser(
-        description='Cursor自动聊天工具 - 自动向Cursor chat发送消息',
-        formatter_class=argparse.RawDescriptionHelpFormatter,
-        epilog="""
-示例:
-  # 单次执行
-  python scripts/cursor_auto_chat.py --once
-  
-  # 守护进程模式(默认)
-  python scripts/cursor_auto_chat.py --daemon
-  
-  # 自定义间隔(秒)
-  python scripts/cursor_auto_chat.py --interval 300
-  
-  # 自定义消息
-  python scripts/cursor_auto_chat.py --message "你的消息内容"
-  
-  # 指定输入框位置
-  python scripts/cursor_auto_chat.py --input-box-pos "1180,965"
-        """
-    )
-    
-    parser.add_argument(
-        '--once',
-        action='store_true',
-        help='只执行一次,不持续运行'
-    )
-    parser.add_argument(
-        '--daemon',
-        action='store_true',
-        help='作为守护进程运行(默认模式)'
-    )
-    parser.add_argument(
-        '--interval',
-        type=int,
-        default=300,
-        help='执行间隔(秒),默认300秒(5分钟)'
-    )
-    parser.add_argument(
-        '--message',
-        type=str,
-        default='请检查并执行所有待处理任务。',
-        help='要发送的消息内容,默认: "请检查并执行所有待处理任务。"'
-    )
-    parser.add_argument(
-        '--input-box-pos',
-        type=str,
-        default=None,
-        help='输入框位置,格式: "x,y" (例如: "1180,965"),如果提供则直接使用该位置,不进行自动定位'
-    )
-    
-    args = parser.parse_args()
-    
-    # 解析输入框位置(如果提供)
-    input_box_pos = None
-    if args.input_box_pos:
-        try:
-            parts = args.input_box_pos.split(',')
-            if len(parts) == 2:
-                input_box_pos = (int(parts[0].strip()), int(parts[1].strip()))
-                logger.info(f"解析输入框位置: {input_box_pos}")
-            else:
-                logger.warning(f"输入框位置格式错误,应使用 'x,y' 格式: {args.input_box_pos}")
-        except ValueError as e:
-            logger.warning(f"无法解析输入框位置: {e}")
-    
-    # 创建工具实例
-    tool = CursorAutoChat(message=args.message, interval=args.interval, input_box_pos=input_box_pos)
-    
-    # 根据参数运行
-    if args.once:
-        tool.execute_once()
-    else:
-        tool.run_daemon()
-
-
-if __name__ == '__main__':
-    main()
-

+ 0 - 276
scripts/cursor_task_agent.py

@@ -1,276 +0,0 @@
-#!/usr/bin/env python3
-"""
-Cursor任务执行Agent
-
-这个脚本会定期检查task-manager MCP中的pending任务,
-并自动通过Cursor CLI或API触发任务执行。
-
-使用方法:
-1. 直接运行:python scripts/cursor_task_agent.py
-2. 作为后台服务运行:python scripts/cursor_task_agent.py --daemon
-3. 单次检查:python scripts/cursor_task_agent.py --once
-"""
-
-import json
-import time
-import argparse
-import logging
-import sys
-from pathlib import Path
-from datetime import datetime
-
-# 配置日志
-logging.basicConfig(
-    level=logging.INFO,
-    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
-    handlers=[
-        logging.FileHandler('logs/cursor_task_agent.log'),
-        logging.StreamHandler(sys.stdout)
-    ]
-)
-logger = logging.getLogger('CursorTaskAgent')
-
-
-class CursorTaskAgent:
-    """
-    Cursor任务执行Agent
-    
-    负责:
-    1. 定期从MCP获取pending任务
-    2. 为每个任务创建Cursor执行指令
-    3. 触发Cursor执行任务(通过创建临时提示文件)
-    """
-    
-    def __init__(self, check_interval=300, workspace_path=None):
-        """
-        初始化Agent
-        
-        Args:
-            check_interval: 检查间隔(秒),默认300秒(5分钟)
-            workspace_path: 工作区路径
-        """
-        self.check_interval = check_interval
-        self.workspace_path = workspace_path or Path(__file__).parent.parent
-        self.prompt_dir = self.workspace_path / '.cursor' / 'task_prompts'
-        self.prompt_dir.mkdir(parents=True, exist_ok=True)
-        
-        logger.info(f"Cursor Task Agent initialized")
-        logger.info(f"Workspace: {self.workspace_path}")
-        logger.info(f"Prompt directory: {self.prompt_dir}")
-        logger.info(f"Check interval: {self.check_interval}s")
-    
-    def get_pending_tasks_from_db(self):
-        """
-        从数据库直接获取pending任务
-        
-        注意:这里我们绕过MCP,直接连接数据库
-        因为MCP的轮询机制有限制
-        """
-        try:
-            import psycopg2
-            from psycopg2.extras import RealDictCursor
-            
-            # 从配置文件读取数据库连接信息
-            config_file = self.workspace_path / 'mcp-servers' / 'task-manager' / 'config.json'
-            with open(config_file, 'r', encoding='utf-8') as f:
-                config = json.load(f)
-            
-            db_uri = config['database']['uri']
-            
-            # 连接数据库
-            conn = psycopg2.connect(db_uri)
-            cursor = conn.cursor(cursor_factory=RealDictCursor)
-            
-            # 查询pending任务
-            cursor.execute("""
-                SELECT task_id, task_name, task_description, status, 
-                       code_name, code_path, create_time, create_by
-                FROM task_list
-                WHERE status = 'pending'
-                ORDER BY create_time ASC
-            """)
-            
-            tasks = cursor.fetchall()
-            
-            cursor.close()
-            conn.close()
-            
-            logger.info(f"Found {len(tasks)} pending tasks")
-            return [dict(task) for task in tasks]
-            
-        except Exception as e:
-            logger.error(f"Failed to get pending tasks from database: {e}")
-            return []
-    
-    def create_task_prompt(self, task):
-        """
-        为任务创建Cursor提示文件
-        
-        这个文件会告诉用户有新任务需要执行
-        """
-        prompt_file = self.prompt_dir / f"task_{task['task_id']}.md"
-        
-        prompt_content = f"""# 🔔 新任务通知
-
-**任务ID**: {task['task_id']}  
-**任务名称**: {task['task_name']}  
-**创建时间**: {task['create_time']}  
-**创建者**: {task['create_by']}
-
----
-
-## 📋 任务描述
-
-{task['task_description']}
-
----
-
-## 🚀 执行指令
-
-请在Cursor中执行以下操作来完成此任务:
-
-### 方式1:使用MCP工具(推荐)
-
-在Cursor Chat中输入:
-
-```
-@task-manager 请执行task_id={task['task_id']}的任务
-```
-
-或者直接使用工具:
-```
-调用工具: execute_task
-参数: {{
-  "task_id": {task['task_id']},
-  "auto_complete": true
-}}
-```
-
-### 方式2:手动执行
-
-1. 阅读上述任务描述
-2. 根据描述开发相应的Python代码
-3. 完成后调用update_task_status工具更新状态
-
----
-
-**⚠️ 注意**:任务完成后,此提示文件将自动删除。
-"""
-        
-        # 写入提示文件
-        with open(prompt_file, 'w', encoding='utf-8') as f:
-            f.write(prompt_content)
-        
-        logger.info(f"Created task prompt: {prompt_file}")
-        return prompt_file
-    
-    def check_and_notify_tasks(self):
-        """
-        检查pending任务并创建通知
-        """
-        logger.info("Checking for pending tasks...")
-        
-        tasks = self.get_pending_tasks_from_db()
-        
-        if not tasks:
-            logger.info("No pending tasks found")
-            return 0
-        
-        # 为每个任务创建提示文件
-        for task in tasks:
-            try:
-                prompt_file = self.create_task_prompt(task)
-                logger.info(f"Task {task['task_id']} ({task['task_name']}) - prompt created at {prompt_file}")
-            except Exception as e:
-                logger.error(f"Failed to create prompt for task {task['task_id']}: {e}")
-        
-        return len(tasks)
-    
-    def run_once(self):
-        """
-        执行一次检查
-        """
-        logger.info("=" * 60)
-        logger.info("Running single check...")
-        count = self.check_and_notify_tasks()
-        logger.info(f"Check completed. Found {count} pending tasks.")
-        logger.info("=" * 60)
-        return count
-    
-    def run_daemon(self):
-        """
-        作为守护进程运行,定期检查任务
-        """
-        logger.info("=" * 60)
-        logger.info("Starting Cursor Task Agent in daemon mode...")
-        logger.info(f"Will check for new tasks every {self.check_interval} seconds")
-        logger.info("Press Ctrl+C to stop")
-        logger.info("=" * 60)
-        
-        try:
-            while True:
-                try:
-                    count = self.check_and_notify_tasks()
-                    logger.info(f"Next check in {self.check_interval} seconds...")
-                    time.sleep(self.check_interval)
-                except KeyboardInterrupt:
-                    raise
-                except Exception as e:
-                    logger.error(f"Error during check: {e}")
-                    logger.info(f"Retrying in {self.check_interval} seconds...")
-                    time.sleep(self.check_interval)
-        except KeyboardInterrupt:
-            logger.info("\n" + "=" * 60)
-            logger.info("Cursor Task Agent stopped by user")
-            logger.info("=" * 60)
-
-
-def main():
-    """
-    主函数
-    """
-    parser = argparse.ArgumentParser(
-        description='Cursor任务执行Agent - 自动检查并通知pending任务'
-    )
-    parser.add_argument(
-        '--once',
-        action='store_true',
-        help='只执行一次检查,不持续运行'
-    )
-    parser.add_argument(
-        '--daemon',
-        action='store_true',
-        help='作为守护进程运行(与--once互斥)'
-    )
-    parser.add_argument(
-        '--interval',
-        type=int,
-        default=300,
-        help='检查间隔(秒),默认300秒(5分钟)'
-    )
-    
-    args = parser.parse_args()
-    
-    # 创建logs目录
-    logs_dir = Path(__file__).parent.parent / 'logs'
-    logs_dir.mkdir(exist_ok=True)
-    
-    # 创建Agent实例
-    agent = CursorTaskAgent(check_interval=args.interval)
-    
-    # 根据参数运行
-    if args.once:
-        agent.run_once()
-    else:
-        # 默认或明确指定daemon模式
-        agent.run_daemon()
-
-
-if __name__ == '__main__':
-    main()
-
-
-
-
-
-

+ 0 - 111
scripts/field_standardization.py

@@ -1,111 +0,0 @@
-#!/usr/bin/env python3
-"""
-Neo4j 字段名标准化脚本
-用于批量替换 app/core 目录下的字段名
-"""
-
-import re
-import os
-from pathlib import Path
-
-# 定义替换规则
-REPLACEMENTS = [
-    # 基本字段替换
-    (r'\bn\.name\s+CONTAINS', 'n.name_zh CONTAINS'),
-    (r'\bn\.name\s*=', 'n.name_zh ='),
-    (r'\bn\.name\s*=~', 'n.name_zh =~'),
-    (r'\bn\.name\s+as\s+name\b', 'n.name_zh as name_zh'),
-    (r'\bn\.name\s+as\s+cn_name', 'n.name_zh as cn_name'),
-    (r'text:\s*n\.name\b', 'text: n.name_zh'),
-    (r'text:\s*\(n\.name\)', 'text:(n.name_zh)'),
-    (r'name:\s*n\.name\b', 'name_zh: n.name_zh'),
-    (r'{id:\s*id\([^)]+\),\s*name:\s*[^.]+\.name\b', lambda m: str(m.group(0).replace('name:', 'name_zh:'))),
-    
-    # en_name 替换
-    (r'\bn\.en_name\s+CONTAINS', 'n.name_en CONTAINS'),
-    (r'\bn\.en_name\s*=~', 'n.name_en =~'),
-    (r'\bn\.en_name\s+as\s+en_name', 'n.name_en as en_name'),
-    (r'en_name:\s*n\.en_name', 'name_en: n.name_en'),
-    
-    # time/createTime 替换
-    (r'\bn\.time\s+CONTAINS', 'n.create_time CONTAINS'),
-    (r'\bn\.time\s+as\s+time', 'n.create_time as time'),
-    (r'ORDER\s+BY\s+n\.time', 'ORDER BY n.create_time'),
-    (r'\bn\.createTime\s+CONTAINS', 'n.create_time CONTAINS'),
-    (r'ORDER\s+BY\s+n\.createTime', 'ORDER BY n.create_time'),
-    (r'time:\s*n\.time', 'create_time: n.create_time'),
-]
-
-# 需要处理的文件列表
-FILES_TO_PROCESS = [
-    'app/core/data_model/model.py',
-    'app/core/data_resource/resource.py',
-    'app/core/data_flow/dataflows.py',
-    'app/core/production_line/production_line.py',
-]
-
-def process_file(filepath):
-    """处理单个文件"""
-    print(f"Processing: {filepath}")
-    
-    with open(filepath, 'r', encoding='utf-8') as f:
-        content = f.read()
-    
-    original_content = content
-    changes = 0
-    
-    # 应用所有替换规则
-    for pattern, replacement in REPLACEMENTS:
-        if callable(replacement):
-            # 如果replacement是函数,使用re.sub(函数作为repl参数)
-            # 类型: Callable[[re.Match[str]], str]
-            new_content = re.sub(pattern, replacement, content)  # type: ignore[arg-type]
-        else:
-            # 如果replacement是字符串,直接使用
-            new_content = re.sub(pattern, str(replacement), content)
-        
-        if new_content != content:
-            changes += len(re.findall(pattern, content))
-            content = new_content
-    
-    # 如果有变更,写入文件
-    if content != original_content:
-        with open(filepath, 'w', encoding='utf-8') as f:
-            f.write(content)
-        print(f"  ✓ Applied {changes} changes")
-        return changes
-    else:
-        print(f"  - No changes needed")
-        return 0
-
-def main():
-    """主函数"""
-    print("=" * 60)
-    print("Neo4j 字段名标准化脚本")
-    print("=" * 60)
-    
-    total_changes = 0
-    processed_files = 0
-    
-    for filepath in FILES_TO_PROCESS:
-        if os.path.exists(filepath):
-            changes = process_file(filepath)
-            total_changes += changes
-            if changes > 0:
-                processed_files += 1
-        else:
-            print(f"Warning: {filepath} not found")
-    
-    print("=" * 60)
-    print(f"Summary:")
-    print(f"  Files processed: {processed_files}")
-    print(f"  Total changes: {total_changes}")
-    print("=" * 60)
-
-if __name__ == '__main__':
-    main()
-
-
-
-
-

+ 0 - 29
scripts/run_once.bat

@@ -1,29 +0,0 @@
-@echo off
-chcp 65001 >nul
-REM 执行一次任务检查(不循环)
-
-echo ================================================
-echo 执行一次任务检查
-echo ================================================
-echo.
-
-REM 切换到项目根目录
-cd /d %~dp0..
-
-REM 检查Python
-python --version >nul 2>&1
-if errorlevel 1 (
-    echo [错误] 未找到Python
-    pause
-    exit /b 1
-)
-
-echo [信息] 当前目录: %cd%
-echo [执行] 正在检查pending任务...
-echo.
-
-python scripts\auto_execute_tasks.py --once
-
-echo.
-pause
-

+ 0 - 47
scripts/start_auto_tasks.bat

@@ -1,47 +0,0 @@
-@echo off
-chcp 65001 >nul
-REM 启动自动任务执行脚本
-REM 每5分钟检查一次pending任务
-
-echo ================================================
-echo 启动自动任务执行服务...
-echo ================================================
-echo.
-
-REM 切换到项目根目录(确保相对路径正确)
-cd /d %~dp0..
-
-REM 检查Python是否安装
-python --version >nul 2>&1
-if errorlevel 1 (
-    echo [错误] 未找到Python,请先安装Python
-    pause
-    exit /b 1
-)
-
-REM 检查脚本文件是否存在
-if not exist "scripts\auto_execute_tasks.py" (
-    echo [错误] 未找到脚本文件: scripts\auto_execute_tasks.py
-    pause
-    exit /b 1
-)
-
-REM 检查数据库配置是否存在
-if not exist "mcp-servers\task-manager\config.json" (
-    echo [错误] 未找到数据库配置: mcp-servers\task-manager\config.json
-    pause
-    exit /b 1
-)
-
-REM 创建logs目录
-if not exist "logs" mkdir logs
-
-echo [信息] 当前目录: %cd%
-echo [信息] 正在启动自动任务执行服务...
-echo [信息] 检查间隔: 5分钟 (300秒)
-echo [信息] 按 Ctrl+C 可停止服务
-echo.
-
-python scripts\auto_execute_tasks.py --interval 300
-
-pause

+ 0 - 55
scripts/start_auto_tasks_background.bat

@@ -1,55 +0,0 @@
-@echo off
-chcp 65001 >nul
-REM 在后台启动自动任务执行脚本(无窗口)
-
-echo ================================================
-echo 在后台启动自动任务执行服务...
-echo ================================================
-echo.
-
-REM 切换到项目根目录(确保相对路径正确)
-cd /d %~dp0..
-
-REM 检查Python是否安装
-python --version >nul 2>&1
-if errorlevel 1 (
-    echo [错误] 未找到Python,请先安装Python
-    pause
-    exit /b 1
-)
-
-REM 检查脚本文件是否存在
-if not exist "scripts\auto_execute_tasks.py" (
-    echo [错误] 未找到脚本文件: scripts\auto_execute_tasks.py
-    pause
-    exit /b 1
-)
-
-REM 检查数据库配置是否存在
-if not exist "mcp-servers\task-manager\config.json" (
-    echo [错误] 未找到数据库配置: mcp-servers\task-manager\config.json
-    pause
-    exit /b 1
-)
-
-REM 创建logs目录
-if not exist "logs" mkdir logs
-
-echo [信息] 当前目录: %cd%
-echo [信息] 正在后台启动自动任务执行服务...
-echo [信息] 检查间隔: 5分钟 (300秒)
-echo [信息] 日志输出: logs\auto_execute.log
-echo.
-
-REM 后台运行并输出到日志
-start /B "" python scripts\auto_execute_tasks.py --interval 300 > logs\auto_execute.log 2>&1
-
-echo [成功] 服务已在后台启动!
-echo.
-echo [提示] 相关命令:
-echo   - 查看状态: scripts\check_auto_tasks.bat
-echo   - 停止服务: scripts\stop_auto_tasks.bat
-echo   - 查看日志: type logs\auto_execute.log
-echo.
-
-pause

+ 0 - 17
scripts/start_cursor_auto_chat.bat

@@ -1,17 +0,0 @@
-@echo off
-REM Cursor自动聊天工具启动脚本(前台运行)
-REM 使用方法: 双击此文件或命令行运行
-
-cd /d %~dp0\..
-
-echo ========================================
-echo Cursor自动聊天工具
-echo ========================================
-echo.
-echo 使用指定的输入框位置: 1180,965
-echo.
-
-python scripts/cursor_auto_chat.py --daemon --input-box-pos "1180,965"
-
-pause
-

+ 0 - 26
scripts/start_cursor_auto_chat_background.bat

@@ -1,26 +0,0 @@
-@echo off
-REM Cursor自动聊天工具启动脚本(后台运行)
-REM 使用方法: 双击此文件,工具将在后台运行
-
-cd /d %~dp0\..
-
-echo ========================================
-echo 正在后台启动Cursor自动聊天工具...
-echo ========================================
-echo.
-echo 使用指定的输入框位置: 1180,965
-echo.
-
-start /B python scripts/cursor_auto_chat.py --daemon --input-box-pos "1180,965"
-
-echo 工具已在后台启动
-echo 日志文件位置: logs\cursor_auto_chat.log
-echo.
-echo 要停止工具,请运行: scripts\stop_cursor_auto_chat.bat
-echo.
-
-pause
-
-
-
-

+ 0 - 36
scripts/start_cursor_task_trigger.bat

@@ -1,36 +0,0 @@
-@echo off
-REM 启动Cursor任务执行触发器(定期模式)
-
-echo ================================================
-echo 启动Cursor任务执行触发器...
-echo ================================================
-echo.
-
-REM 检查Python是否安装
-python --version >nul 2>&1
-if errorlevel 1 (
-    echo [错误] 未找到Python,请先安装Python
-    pause
-    exit /b 1
-)
-
-REM 检查脚本文件是否存在
-if not exist "scripts\trigger_cursor_execution.py" (
-    echo [错误] 未找到脚本文件: scripts\trigger_cursor_execution.py
-    pause
-    exit /b 1
-)
-
-echo [信息] 正在启动Cursor任务执行触发器...
-echo [信息] 检查间隔: 5分钟 (300秒)
-echo [信息] 任务指令文件: .cursor\task_execute_instructions.md
-echo [信息] Cursor会自动检测并执行任务
-echo [信息] 按 Ctrl+C 停止服务
-echo.
-
-REM 切换到项目根目录
-cd /d %~dp0..
-python scripts\trigger_cursor_execution.py --interval 300
-
-pause
-

+ 0 - 48
scripts/start_cursor_task_trigger_background.bat

@@ -1,48 +0,0 @@
-@echo off
-REM 在后台启动Cursor任务执行触发器(无窗口)
-
-echo ================================================
-echo 在后台启动Cursor任务执行触发器...
-echo ================================================
-echo.
-
-REM 检查Python是否安装
-python --version >nul 2>&1
-if errorlevel 1 (
-    echo [错误] 未找到Python,请先安装Python
-    pause
-    exit /b 1
-)
-
-REM 检查脚本文件是否存在
-if not exist "scripts\trigger_cursor_execution.py" (
-    echo [错误] 未找到脚本文件: scripts\trigger_cursor_execution.py
-    pause
-    exit /b 1
-)
-
-REM 创建logs目录
-if not exist "logs" mkdir logs
-
-echo [信息] 正在后台启动Cursor任务执行触发器...
-echo [信息] 检查间隔: 5分钟 (300秒)
-echo [信息] 任务指令文件: .cursor\task_execute_instructions.md
-echo [信息] 日志输出: logs\cursor_task_trigger.log
-echo.
-
-REM 切换到项目根目录并后台运行
-cd /d %~dp0..
-start /B python scripts\trigger_cursor_execution.py --interval 300 > logs\cursor_task_trigger.log 2>&1
-
-echo [成功] 服务已在后台启动!
-echo [提示] 使用 scripts\stop_cursor_task_trigger.bat 可停止服务
-echo [提示] 使用 scripts\check_cursor_task_trigger.bat 可查看服务状态
-echo.
-
-pause
-
-
-
-
-
-

+ 188 - 0
scripts/start_task_scheduler.bat

@@ -0,0 +1,188 @@
+@echo off
+chcp 65001 >nul
+REM ============================================================
+REM 自动任务调度脚本启动器
+REM ============================================================
+REM 功能:启动核心任务调度脚本 auto_execute_tasks.py
+REM 支持前台运行、后台运行、单次执行等多种模式
+REM ============================================================
+
+setlocal enabledelayedexpansion
+
+REM 切换到项目根目录
+cd /d %~dp0..
+
+echo.
+echo ========================================================
+echo           自动任务调度脚本启动器
+echo ========================================================
+echo.
+
+REM 检查 Python 是否安装
+python --version >nul 2>&1
+if errorlevel 1 (
+    echo [错误] 未找到 Python,请先安装 Python
+    pause
+    exit /b 1
+)
+
+REM 检查脚本文件是否存在
+if not exist "scripts\auto_execute_tasks.py" (
+    echo [错误] 未找到脚本文件: scripts\auto_execute_tasks.py
+    pause
+    exit /b 1
+)
+
+REM 检查数据库配置是否存在
+if not exist "mcp-servers\task-manager\config.json" (
+    echo [错误] 未找到数据库配置: mcp-servers\task-manager\config.json
+    pause
+    exit /b 1
+)
+
+REM 创建 logs 目录
+if not exist "logs" mkdir logs
+
+echo [信息] 当前目录: %cd%
+echo.
+echo 请选择运行模式:
+echo.
+echo   1. 前台运行(可以看到实时日志,按 Ctrl+C 停止)
+echo   2. 后台运行(无窗口,日志输出到 logs\auto_execute.log)
+echo   3. 执行一次(只检查一次 pending 任务)
+echo   4. 前台运行 + 启用自动 Chat
+echo   5. 后台运行 + 启用自动 Chat
+echo   6. 查看服务状态
+echo   7. 停止服务
+echo   0. 退出
+echo.
+
+set /p choice="请输入选择 [1-7, 0]: "
+
+if "%choice%"=="1" goto :run_foreground
+if "%choice%"=="2" goto :run_background
+if "%choice%"=="3" goto :run_once
+if "%choice%"=="4" goto :run_foreground_chat
+if "%choice%"=="5" goto :run_background_chat
+if "%choice%"=="6" goto :check_status
+if "%choice%"=="7" goto :stop_service
+if "%choice%"=="0" goto :exit
+
+echo [错误] 无效的选择,请重新运行
+pause
+exit /b 1
+
+:run_foreground
+echo.
+echo [启动] 前台运行模式(检查间隔: 5分钟)
+echo [提示] 按 Ctrl+C 可停止服务
+echo.
+python scripts\auto_execute_tasks.py --interval 300
+pause
+goto :exit
+
+:run_background
+echo.
+echo [启动] 后台运行模式(检查间隔: 5分钟)
+echo [信息] 日志输出到: logs\auto_execute.log
+start /B "" python scripts\auto_execute_tasks.py --interval 300 > logs\auto_execute.log 2>&1
+echo.
+echo [成功] 服务已在后台启动!
+echo.
+echo [提示] 相关命令:
+echo   - 查看日志: type logs\auto_execute.log
+echo   - 停止服务: 再次运行此脚本选择 7
+echo.
+pause
+goto :exit
+
+:run_once
+echo.
+echo [执行] 单次检查模式
+echo.
+python scripts\auto_execute_tasks.py --once
+echo.
+pause
+goto :exit
+
+:run_foreground_chat
+echo.
+set /p chat_pos="请输入 Chat 输入框位置 (格式: x,y,直接回车使用默认): "
+echo.
+echo [启动] 前台运行模式 + 自动 Chat
+if "%chat_pos%"=="" (
+    python scripts\auto_execute_tasks.py --interval 300 --enable-chat
+) else (
+    python scripts\auto_execute_tasks.py --interval 300 --enable-chat --chat-input-pos "%chat_pos%"
+)
+pause
+goto :exit
+
+:run_background_chat
+echo.
+set /p chat_pos="请输入 Chat 输入框位置 (格式: x,y,直接回车使用默认): "
+echo.
+echo [启动] 后台运行模式 + 自动 Chat
+echo [信息] 日志输出到: logs\auto_execute.log
+if "%chat_pos%"=="" (
+    start /B "" python scripts\auto_execute_tasks.py --interval 300 --enable-chat > logs\auto_execute.log 2>&1
+) else (
+    start /B "" python scripts\auto_execute_tasks.py --interval 300 --enable-chat --chat-input-pos "%chat_pos%" > logs\auto_execute.log 2>&1
+)
+echo.
+echo [成功] 服务已在后台启动!
+echo.
+pause
+goto :exit
+
+:check_status
+echo.
+echo ========================================================
+echo                   服务状态检查
+echo ========================================================
+echo.
+
+echo [进程状态]
+powershell -Command "$processes = Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like '*auto_execute_tasks.py*' }; if ($processes) { Write-Host '[运行中] 找到以下进程:' -ForegroundColor Green; $processes | ForEach-Object { Write-Host ('  进程ID: ' + $_.ProcessId) } } else { Write-Host '[未运行] 未找到 auto_execute_tasks.py 进程' -ForegroundColor Yellow }"
+
+echo.
+echo ========================================================
+echo                   最近日志(最后 20 行)
+echo ========================================================
+echo.
+
+if exist "logs\auto_execute.log" (
+    powershell -Command "Get-Content logs\auto_execute.log -Tail 20 -ErrorAction SilentlyContinue"
+) else (
+    echo [提示] 日志文件不存在
+)
+
+echo.
+echo ========================================================
+echo                   pending_tasks.json 状态
+echo ========================================================
+echo.
+
+if exist ".cursor\pending_tasks.json" (
+    echo [文件存在] .cursor\pending_tasks.json
+    powershell -Command "$tasks = Get-Content '.cursor\pending_tasks.json' -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json; if ($tasks) { Write-Host ('  任务数量: ' + $tasks.Count); $tasks | ForEach-Object { Write-Host ('  - [' + $_.task_id + '] ' + $_.task_name + ' (' + $_.status + ')') } } else { Write-Host '  [空] 没有待处理任务' }"
+) else (
+    echo [提示] pending_tasks.json 不存在
+)
+
+echo.
+pause
+goto :exit
+
+:stop_service
+echo.
+echo [操作] 正在停止服务...
+powershell -Command "$processes = Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like '*auto_execute_tasks.py*' }; if ($processes) { Write-Host '[找到] 以下进程将被停止:' -ForegroundColor Yellow; $processes | ForEach-Object { Write-Host ('  进程ID: ' + $_.ProcessId); Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Write-Host '[完成] 进程已停止' -ForegroundColor Green } else { Write-Host '[提示] 未找到运行中的进程' -ForegroundColor Cyan }"
+echo.
+pause
+goto :exit
+
+:exit
+endlocal
+exit /b 0
+

+ 0 - 22
scripts/stop_auto_tasks.bat

@@ -1,22 +0,0 @@
-@echo off
-chcp 65001 >nul
-REM 停止自动任务执行脚本
-
-echo ================================================
-echo 停止自动任务执行服务...
-echo ================================================
-echo.
-
-REM 切换到项目根目录
-cd /d %~dp0..
-
-REM 使用PowerShell查找和停止进程
-echo [查找] 正在查找auto_execute_tasks.py进程...
-powershell -Command "$processes = Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like '*auto_execute_tasks.py*' }; if ($processes) { Write-Host '[找到] 以下进程将被停止:' -ForegroundColor Yellow; $processes | ForEach-Object { Write-Host ('  进程ID: ' + $_.ProcessId); Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Write-Host '[完成] 进程已停止' -ForegroundColor Green } else { Write-Host '[提示] 未找到运行中的进程' -ForegroundColor Cyan }"
-
-echo.
-echo [验证] 再次检查进程状态...
-powershell -Command "$processes = Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like '*auto_execute_tasks.py*' }; if ($processes) { Write-Host '[警告] 仍有进程在运行,请手动在任务管理器中结束' -ForegroundColor Red } else { Write-Host '[确认] 所有进程已停止' -ForegroundColor Green }"
-
-echo.
-pause

+ 0 - 26
scripts/stop_cursor_auto_chat.bat

@@ -1,26 +0,0 @@
-@echo off
-REM Cursor自动聊天工具停止脚本
-REM 使用方法: 双击此文件停止运行中的工具
-
-echo ========================================
-echo 正在停止Cursor自动聊天工具...
-echo ========================================
-echo.
-
-REM 查找并终止Python进程(运行cursor_auto_chat.py的进程)
-taskkill /F /FI "WINDOWTITLE eq *cursor_auto_chat*" 2>nul
-taskkill /F /IM python.exe /FI "COMMANDLINE eq *cursor_auto_chat.py*" 2>nul
-
-REM 如果上面的方法不行,尝试更通用的方法
-for /f "tokens=2" %%a in ('tasklist /FI "IMAGENAME eq python.exe" /FO LIST ^| findstr /I "cursor_auto_chat"') do (
-    taskkill /F /PID %%a 2>nul
-)
-
-echo 工具已停止
-echo.
-
-pause
-
-
-
-

+ 0 - 280
scripts/trigger_cursor_execution.py

@@ -1,280 +0,0 @@
-#!/usr/bin/env python3
-"""
-Cursor任务执行触发器
-
-这个脚本会读取pending_tasks.json,并以明确格式输出任务信息,
-供Cursor识别并自动执行。
-
-支持两种模式:
-1. 单次执行:python scripts/trigger_cursor_execution.py --once
-2. 定期执行:python scripts/trigger_cursor_execution.py --interval 300
-
-定期执行模式下,脚本会:
-- 定期检查processing任务
-- 自动生成执行指令文件(.cursor/task_execute_instructions.md)
-- 输出任务信息供Cursor识别
-"""
-
-import json
-import sys
-import time
-import argparse
-import logging
-from pathlib import Path
-from datetime import datetime
-
-# 配置日志
-logging.basicConfig(
-    level=logging.INFO,
-    format='%(asctime)s - %(levelname)s - %(message)s'
-)
-logger = logging.getLogger('CursorTaskTrigger')
-
-
-def get_task_file_path(task, workspace):
-    """获取任务文件路径"""
-    import os
-    import re
-    
-    task_file = task.get('task_file')
-    if not task_file and task.get('code_path'):
-        # 从任务名称生成文件名
-        task_name = task['task_name']
-        safe_name = re.sub(r'[^\w\u4e00-\u9fff]+', '_', task_name)
-        safe_name = re.sub(r'_+', '_', safe_name).strip('_')
-        code_path = task.get('code_path', 'app/core/data_flow')
-        task_file = os.path.join(workspace, code_path, f"{safe_name}.py")
-        if not os.path.exists(task_file):
-            task_file = None
-    return task_file
-
-
-def create_execute_instructions_file(workspace, processing_tasks):
-    """
-    创建任务执行指令文件,供Cursor自动读取
-    
-    Args:
-        workspace: 工作区路径
-        processing_tasks: 待处理任务列表
-    """
-    instructions_file = workspace / '.cursor' / 'task_execute_instructions.md'
-    instructions_file.parent.mkdir(parents=True, exist_ok=True)
-    
-    with open(instructions_file, 'w', encoding='utf-8') as f:
-        f.write(f"# 🤖 Cursor自动任务执行指令\n\n")
-        f.write(f"**⚠️ 重要:Cursor AI请立即执行此文件中的任务!**\n\n")
-        f.write(f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
-        f.write(f"**待执行任务数量**: {len(processing_tasks)}\n\n")
-        f.write(f"**执行状态**: ⏳ 等待执行\n\n")
-        f.write("---\n\n")
-        f.write(f"## 📋 执行指令\n\n")
-        f.write(f"**Cursor AI:请按照以下步骤执行任务:**\n\n")
-        f.write(f"1. 依次处理每个任务\n")
-        f.write(f"2. 打开任务文件并实现功能\n")
-        f.write(f"3. 完成后调用MCP工具更新状态\n\n")
-        f.write("---\n\n")
-        
-        for idx, task in enumerate(processing_tasks, 1):
-            task_file = get_task_file_path(task, workspace)
-            
-            f.write(f"## 🔴 任务 {idx}/{len(processing_tasks)}: [{task['task_id']}] {task['task_name']}\n\n")
-            f.write(f"**执行状态**: ⏳ 待执行\n\n")
-            f.write(f"**任务文件**: `{task_file or '未创建'}`\n\n")
-            f.write(f"### 📝 任务描述\n\n{task['task_description']}\n\n")
-            f.write(f"### ✅ 执行步骤(Cursor AI请按此执行)\n\n")
-            f.write(f"#### 步骤1:打开任务文件\n")
-            f.write(f"- 文件路径: `{task_file or 'N/A'}`\n")
-            f.write(f"- 如果文件不存在,需要先创建\n\n")
-            f.write(f"#### 步骤2:实现功能\n")
-            f.write(f"- 仔细阅读上述任务描述\n")
-            f.write(f"- 根据描述实现完整的功能\n")
-            f.write(f"- 确保代码符合项目规范(PEP 8)\n")
-            f.write(f"- 添加必要的错误处理和日志记录\n\n")
-            f.write(f"#### 步骤3:验证代码\n")
-            f.write(f"- 确保代码可以正常运行\n")
-            f.write(f"- 检查代码逻辑是否正确\n\n")
-            f.write(f"#### 步骤4:更新任务状态\n")
-            f.write(f"- 调用MCP工具 `update_task_status`\n")
-            f.write(f"- 使用以下参数:\n\n")
-            f.write(f"```json\n")
-            f.write(f"{{\n")
-            f.write(f"  \"tool\": \"update_task_status\",\n")
-            f.write(f"  \"task_id\": {task['task_id']},\n")
-            if task_file:
-                import os
-                file_name = os.path.basename(task_file)
-                file_dir = os.path.dirname(task_file).replace(str(workspace), '').strip('\\').strip('/').replace('\\', '/')
-                f.write(f"  \"code_name\": \"{file_name}\",\n")
-                f.write(f"  \"code_path\": \"{file_dir}\",\n")
-            f.write(f"  \"status\": \"completed\"\n")
-            f.write(f"}}\n")
-            f.write(f"```\n\n")
-            f.write(f"**⚠️ 重要:必须完成上述所有步骤!**\n\n")
-            f.write("---\n\n")
-    
-    logger.info(f"✅ 任务执行指令文件已创建: {instructions_file}")
-    
-    # 创建触发器文件,更新时间戳
-    trigger_file = workspace / '.cursor' / 'task_trigger.txt'
-    with open(trigger_file, 'w', encoding='utf-8') as f:
-        f.write(f"CURSOR_AUTO_EXECUTE_TASK_TRIGGER\n")
-        f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
-        f.write(f"状态: 有 {len(processing_tasks)} 个任务待执行\n\n")
-        f.write(f"此文件用于触发Cursor自动执行任务。\n")
-        f.write(f"当此文件更新时间在5分钟内时,Cursor应该检查 .cursor/task_execute_instructions.md 并执行其中的任务。\n")
-    
-    logger.info(f"✅ 触发器文件已更新: {trigger_file}")
-
-
-def execute_tasks_once():
-    """
-    执行一次任务检查和触发
-    
-    Returns:
-        找到的任务数量
-    """
-    workspace = Path(__file__).parent.parent
-    pending_tasks_file = workspace / '.cursor' / 'pending_tasks.json'
-    
-    if not pending_tasks_file.exists():
-        logger.info("⚠️ 没有找到pending任务文件")
-        return 0
-    
-    try:
-        with open(pending_tasks_file, 'r', encoding='utf-8') as f:
-            pending_tasks = json.load(f)
-        
-        if not pending_tasks:
-            logger.info("✅ 没有pending任务")
-            return 0
-        
-        # 处理所有任务(如果没有status字段,默认为processing)
-        # 只处理状态为processing或没有status的任务
-        processing_tasks = [
-            t for t in pending_tasks 
-            if t.get('status', 'processing') == 'processing' or 'status' not in t
-        ]
-        
-        if not processing_tasks:
-            logger.info("✅ 没有processing状态的任务")
-            return 0
-        
-        logger.info(f"📋 找到 {len(processing_tasks)} 个processing任务,需要执行")
-        
-        # 创建执行指令文件
-        create_execute_instructions_file(workspace, processing_tasks)
-        
-        # 输出任务信息供Cursor识别
-        print("\n" + "=" * 80)
-        print(f"🤖 [CURSOR-AUTO-EXECUTE] 发现 {len(processing_tasks)} 个待执行任务")
-        print("=" * 80 + "\n")
-        
-        for task in processing_tasks:
-            task_file = get_task_file_path(task, workspace)
-            
-            print(f"\n🤖 [CURSOR-EXECUTE-TASK]")
-            print(f"任务ID: {task['task_id']}")
-            print(f"任务名称: {task['task_name']}")
-            print(f"任务文件: {task_file or '未创建'}")
-            print(f"\n任务描述:\n{task['task_description']}")
-            print(f"\n执行指令:")
-            print(f"1. 打开任务文件: {task_file or task.get('code_path', 'app/core/data_flow') + '/' + task['task_name'] + '.py'}")
-            print(f"2. 根据任务描述实现功能")
-            print(f"3. 调用MCP工具更新状态:")
-            print(f"   工具: update_task_status")
-            print(f"   参数: {{")
-            print(f"     \"task_id\": {task['task_id']},")
-            if task_file:
-                import os
-                file_path = task_file
-                file_name = os.path.basename(file_path)
-                file_dir = os.path.dirname(file_path).replace(str(workspace), '').strip('\\').strip('/').replace('\\', '/')
-                print(f"     \"code_name\": \"{file_name}\",")
-                print(f"     \"code_path\": \"{file_dir}\",")
-            print(f"     \"status\": \"completed\"")
-            print(f"   }}")
-            print(f"\n🔚 [END-CURSOR-EXECUTE-TASK]\n")
-            print("=" * 80)
-        
-        print(f"\n💡 提示:任务执行指令已保存到 .cursor/task_execute_instructions.md")
-        print(f"💡 Cursor可以自动读取此文件并执行任务\n")
-        
-        return len(processing_tasks)
-        
-    except Exception as e:
-        logger.error(f"❌ 读取任务文件失败: {e}")
-        return 0
-
-
-def execute_tasks_loop(interval=300):
-    """
-    循环执行任务检查和触发
-    
-    Args:
-        interval: 检查间隔(秒),默认300秒(5分钟)
-    """
-    logger.info("=" * 80)
-    logger.info("🚀 Cursor任务执行触发器已启动(定期模式)")
-    logger.info(f"⏰ 检查间隔: {interval}秒 ({interval//60}分钟)")
-    logger.info("按 Ctrl+C 停止服务")
-    logger.info("=" * 80 + "\n")
-    
-    try:
-        while True:
-            try:
-                count = execute_tasks_once()
-                
-                if count > 0:
-                    logger.info(f"\n✅ 已触发 {count} 个任务执行")
-                    logger.info(f"💡 任务指令已保存到 .cursor/task_execute_instructions.md")
-                    logger.info(f"💡 Cursor将自动读取并执行任务\n")
-                
-                logger.info(f"\n⏳ 下次检查时间: {interval}秒后...")
-                time.sleep(interval)
-                
-            except KeyboardInterrupt:
-                raise
-            except Exception as e:
-                logger.error(f"❌ 执行出错: {e}")
-                logger.info(f"⏳ {interval}秒后重试...")
-                time.sleep(interval)
-                
-    except KeyboardInterrupt:
-        logger.info("\n" + "=" * 80)
-        logger.info("⛔ 用户停止了Cursor任务执行触发器")
-        logger.info("=" * 80)
-
-
-def main():
-    """
-    主函数
-    """
-    parser = argparse.ArgumentParser(
-        description='Cursor任务执行触发器 - 定期检查并触发任务执行'
-    )
-    parser.add_argument(
-        '--once',
-        action='store_true',
-        help='只执行一次检查,不持续运行'
-    )
-    parser.add_argument(
-        '--interval',
-        type=int,
-        default=300,
-        help='检查间隔(秒),默认300秒(5分钟)'
-    )
-    
-    args = parser.parse_args()
-    
-    if args.once:
-        # 执行一次
-        count = execute_tasks_once()
-        logger.info(f"\n✅ 完成!找到 {count} 个待执行任务")
-    else:
-        # 循环执行
-        execute_tasks_loop(interval=args.interval)
-
-
-if __name__ == '__main__':
-    main()
-