fix(server/tasks): 修复 skill 创建任务失败并补充调试日志
- 补全从 api.py 拆到 skills.py 时遗漏的 _build_skill_context_messages 导入 - 捕获 skill 文件读取时的 UnicodeDecodeError/OSError,转为 400 错误 - 在 skill 路径解析与读取流程中增加详细 debug_log
This commit is contained in:
parent
8e1a339102
commit
2bfbddb0dd
@ -6,7 +6,7 @@ import json
|
||||
import time
|
||||
import threading
|
||||
import uuid
|
||||
import re
|
||||
import traceback
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional, List
|
||||
@ -23,14 +23,11 @@ from utils.host_workspace_debug import write_host_workspace_debug
|
||||
from config import DATA_DIR, WORKSPACE_SKILLS_DIRNAME
|
||||
from modules.goal_state_manager import GoalStateManager, REASON_USER_CANCEL
|
||||
from server.tasks import task_manager
|
||||
from server.tasks.skills import _build_skill_context_messages
|
||||
from server.tasks.helpers import _task_public_payload
|
||||
from server.tasks.media import _normalize_media_payload
|
||||
|
||||
|
||||
SKILL_FRONTMATTER_RE = re.compile(r"^---\s*\n(?P<body>.*?)\n---\s*\n?", re.S)
|
||||
SKILL_FIELD_RE = re.compile(r"^(?P<key>name|description)\s*:\s*(?P<value>.*)$")
|
||||
|
||||
|
||||
|
||||
@tasks_bp.route("/api/tasks", methods=["GET"])
|
||||
@api_login_required
|
||||
@ -87,14 +84,27 @@ def create_task_api():
|
||||
raw_skill_refs = payload.get("skill_refs")
|
||||
if raw_skill_refs:
|
||||
try:
|
||||
debug_log(
|
||||
f"[SkillsAPI] create_task skill_refs type={type(raw_skill_refs).__name__} "
|
||||
f"count={len(raw_skill_refs) if isinstance(raw_skill_refs, list) else 'N/A'} "
|
||||
f"refs={raw_skill_refs!r}"
|
||||
)
|
||||
_terminal, workspace = get_user_resources(username, workspace_id)
|
||||
if not workspace:
|
||||
debug_log(f"[SkillsAPI] create_task workspace unavailable user={username} ws={workspace_id}")
|
||||
return jsonify({"success": False, "error": "工作区不可用"}), 400
|
||||
debug_log(
|
||||
f"[SkillsAPI] create_task workspace OK project_path={getattr(workspace, 'project_path', None)!r} "
|
||||
f"data_dir={getattr(workspace, 'data_dir', None)!r}"
|
||||
)
|
||||
skill_context_messages = _build_skill_context_messages(workspace, raw_skill_refs)
|
||||
debug_log(f"[SkillsAPI] create_task built {len(skill_context_messages)} skill context messages")
|
||||
except ValueError as exc:
|
||||
debug_log(f"[SkillsAPI] create_task ValueError: {exc}")
|
||||
return jsonify({"success": False, "error": str(exc)}), 400
|
||||
except Exception as exc:
|
||||
debug_log(f"[SkillsAPI] 读取技能上下文失败: {exc}")
|
||||
debug_log(f"[SkillsAPI] 读取技能上下文失败 traceback:\n{traceback.format_exc()}")
|
||||
return jsonify({"success": False, "error": "读取 skill 失败"}), 500
|
||||
try:
|
||||
debug_log(
|
||||
|
||||
@ -1,27 +1,17 @@
|
||||
"""简单任务 API:将聊天任务与 WebSocket 解耦,支持后台运行与轮询。"""
|
||||
"""Workspace skill 读取与 /api/skills 接口。"""
|
||||
from __future__ import annotations
|
||||
from server.tasks import tasks_bp
|
||||
import mimetypes
|
||||
import json
|
||||
import time
|
||||
import threading
|
||||
import uuid
|
||||
import re
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional, List
|
||||
from typing import Dict, Any, List
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask import current_app, session
|
||||
from flask import request, jsonify
|
||||
from flask import session
|
||||
|
||||
from server.auth_helpers import api_login_required, get_current_username
|
||||
from server.context import get_user_resources, ensure_conversation_loaded
|
||||
from server.chat_flow import run_chat_task_sync
|
||||
from server.state import stop_flags
|
||||
from server.utils_common import debug_log, log_conn_diag
|
||||
from utils.host_workspace_debug import write_host_workspace_debug
|
||||
from config import DATA_DIR, WORKSPACE_SKILLS_DIRNAME
|
||||
from modules.goal_state_manager import GoalStateManager, REASON_USER_CANCEL
|
||||
from server.context import get_user_resources
|
||||
from server.utils_common import debug_log
|
||||
from config import WORKSPACE_SKILLS_DIRNAME
|
||||
|
||||
|
||||
SKILL_FRONTMATTER_RE = re.compile(r"^---\s*\n(?P<body>.*?)\n---\s*\n?", re.S)
|
||||
@ -48,18 +38,30 @@ def _workspace_skills_dir(workspace) -> Path:
|
||||
return (Path(workspace.project_path).expanduser().resolve() / WORKSPACE_SKILLS_DIRNAME).resolve()
|
||||
|
||||
def _resolve_workspace_skill_path(workspace, raw_path: str) -> Path:
|
||||
skills_dir = _workspace_skills_dir(workspace)
|
||||
target = Path(str(raw_path or "")).expanduser()
|
||||
if not target.is_absolute():
|
||||
target = Path(workspace.project_path).expanduser().resolve() / target
|
||||
target = target.resolve()
|
||||
try:
|
||||
skills_dir = _workspace_skills_dir(workspace)
|
||||
except Exception as exc:
|
||||
debug_log(f"[SkillsAPI] resolve skill cannot compute skills_dir: {exc}")
|
||||
raise ValueError(f"无法定位工作区 skills 目录: {exc}") from exc
|
||||
debug_log(f"[SkillsAPI] resolve skill skills_dir={skills_dir!r} raw_path={raw_path!r}")
|
||||
try:
|
||||
target = Path(str(raw_path or "")).expanduser()
|
||||
if not target.is_absolute():
|
||||
target = Path(workspace.project_path).expanduser().resolve() / target
|
||||
target = target.resolve()
|
||||
except Exception as exc:
|
||||
debug_log(f"[SkillsAPI] resolve skill cannot resolve path: {exc}")
|
||||
raise ValueError(f"skill 路径解析失败: {exc}") from exc
|
||||
debug_log(f"[SkillsAPI] resolve skill resolved_target={target!r} name={target.name!r}")
|
||||
if target.name != "SKILL.md":
|
||||
raise ValueError("skill 路径必须指向 SKILL.md")
|
||||
raise ValueError(f"skill 路径必须指向 SKILL.md: {target.name}")
|
||||
try:
|
||||
target.relative_to(skills_dir)
|
||||
except ValueError as exc:
|
||||
debug_log(f"[SkillsAPI] resolve skill path outside skills_dir: target={target!r} skills_dir={skills_dir!r}")
|
||||
raise ValueError("skill 路径必须位于当前工作区 .agents/skills/ 内") from exc
|
||||
if not target.is_file():
|
||||
debug_log(f"[SkillsAPI] resolve skill file not found: {target!r}")
|
||||
raise ValueError("skill 文件不存在")
|
||||
return target
|
||||
|
||||
@ -83,21 +85,30 @@ def _list_workspace_skills(workspace) -> List[Dict[str, str]]:
|
||||
|
||||
def _build_skill_context_messages(workspace, raw_refs: Any) -> List[Dict[str, str]]:
|
||||
if not isinstance(raw_refs, list):
|
||||
debug_log(f"[SkillsAPI] build_skill_context raw_refs is not list: {type(raw_refs).__name__}")
|
||||
return []
|
||||
messages: List[Dict[str, str]] = []
|
||||
seen_paths = set()
|
||||
for item in raw_refs[:10]:
|
||||
for idx, item in enumerate(raw_refs[:10]):
|
||||
if not isinstance(item, dict):
|
||||
debug_log(f"[SkillsAPI] build_skill_context item[{idx}] not dict: {item!r}")
|
||||
continue
|
||||
raw_path = str(item.get("path") or "").strip()
|
||||
debug_log(f"[SkillsAPI] build_skill_context item[{idx}] raw_path={raw_path!r} name={item.get('name')!r}")
|
||||
if not raw_path:
|
||||
continue
|
||||
skill_file = _resolve_workspace_skill_path(workspace, raw_path)
|
||||
path_key = str(skill_file)
|
||||
if path_key in seen_paths:
|
||||
debug_log(f"[SkillsAPI] build_skill_context duplicate path skipped: {path_key}")
|
||||
continue
|
||||
seen_paths.add(path_key)
|
||||
content = skill_file.read_text(encoding="utf-8")
|
||||
try:
|
||||
content = skill_file.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ValueError(f"skill 文件编码错误,无法读取: {skill_file.name}") from exc
|
||||
except OSError as exc:
|
||||
raise ValueError(f"读取 skill 文件失败: {exc}") from exc
|
||||
metadata = _parse_skill_metadata(content, skill_file.parent.name)
|
||||
messages.append({
|
||||
"name": metadata.get("name") or skill_file.parent.name,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user