pipeline_core/wwwroot/api/skill_proposals.dspy
yumoqing b9998ed00d refactor(skill): 单一技能树——七级 scope 全在全局 skills/ 下,去掉 ensure_org_skills 复制副本
- skill_loader: 加 project_common scope(projects/ 项目通用,不分 project_id) + project 私有改挂 orgs/{org_id}/{project_id}/,SCOPE_PRIORITY 七级
- skill_pack: ensure_org_skills 去复制逻辑改薄封装返回 get_skills_base(),删 _sync_tree/get_org_skills_dir dead code
- skill_pack.dspy/skill_proposals.dspy: base_dir 改全局 skills/,删同步所有机构循环
2026-08-21 19:18:45 +08:00

108 lines
4.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# skill_proposals.dspy - 技能提议管理agent自省提议 + 人工创建生命周期pending→testing→approved→published
# 发布动作:把提议的 SKILL.md 写入 skills/global 并增量同步到所有机构,然后状态置 published
import json
import os
uid = await get_user()
if not uid:
uid = 'user-01'
org_id = await get_userorgid() or '0'
dbname = get_module_dbname('pipeline_core')
action = (params_kw or {}).get('action', 'list')
def _rec_to_dict(r):
out = {}
for k in ('id', 'name', 'description', 'content', 'source', 'status',
'org_id', 'pipeline_id', 'feedback', 'created_by'):
out[k] = getattr(r, k, '') or ''
out['created_at'] = str(getattr(r, 'created_at', '') or '')
out['updated_at'] = str(getattr(r, 'updated_at', '') or '')
return out
async with DBPools().sqlorContext(dbname) as sor:
if action == 'list':
status = (params_kw or {}).get('status', '')
where = "1=1"
p = {}
if status:
where = "status=${status}$"
p["status"] = status
recs = await sor.sqlExe(
"SELECT * FROM skill_proposals WHERE " + where +
" ORDER BY created_at DESC LIMIT 200", p)
proposals = [_rec_to_dict(r) for r in (recs or [])]
return json.dumps({"success": True, "proposals": proposals},
ensure_ascii=False, default=str)
elif action == 'create':
name = (params_kw or {}).get('name', '').strip()
description = (params_kw or {}).get('description', '').strip()
content = (params_kw or {}).get('content', '').strip()
if not name or not content:
return json.dumps({"success": False, "error": "需要 name 和 content"},
ensure_ascii=False)
from appPublic.uniqueID import getID
await sor.C('skill_proposals', {
'id': getID(),
'name': name,
'description': description,
'content': content,
'source': 'manual',
'status': 'pending',
'org_id': org_id,
'pipeline_id': '',
'created_by': uid,
})
return json.dumps({"success": True, "name": name}, ensure_ascii=False)
elif action == 'update_status':
qid = (params_kw or {}).get('id', '').strip()
status = (params_kw or {}).get('status', '').strip()
feedback = (params_kw or {}).get('feedback', '').strip()
if not qid or not status:
return json.dumps({"success": False, "error": "需要 id 和 status"},
ensure_ascii=False)
if status not in ('pending', 'testing', 'approved', 'published', 'rejected'):
return json.dumps({"success": False, "error": f"非法状态: {status}"},
ensure_ascii=False)
recs = await sor.R('skill_proposals', {'id': qid})
if not recs:
return json.dumps({"success": False, "error": "提议不存在"}, ensure_ascii=False)
await sor.U('skill_proposals', {'id': qid, 'status': status, 'feedback': feedback or ''})
return json.dumps({"success": True, "id": qid, "status": status}, ensure_ascii=False)
elif action == 'publish':
# 发布:读提议 → 写 SKILL.md 到 skills/global → 增量同步到所有机构 → 状态 published
qid = (params_kw or {}).get('id', '').strip()
if not qid:
return json.dumps({"success": False, "error": "需要提议ID"}, ensure_ascii=False)
recs = await sor.R('skill_proposals', {'id': qid})
if not recs:
return json.dumps({"success": False, "error": "提议不存在"}, ensure_ascii=False)
r = recs[0]
name = getattr(r, 'name', '') or ''
content = getattr(r, 'content', '') or ''
if not name or not content:
return json.dumps({"success": False, "error": "提议缺少 name 或 content"},
ensure_ascii=False)
# 写 SKILL.md 到全局模板 skills/global/{name}/(单一技能树,所有机构共享读,无需同步副本)
from pipeline_core.skill_pack import get_skills_base
skill_dir = os.path.join(get_skills_base(), "global", name)
os.makedirs(skill_dir, exist_ok=True)
with open(os.path.join(skill_dir, "SKILL.md"), "w", encoding="utf-8") as f:
f.write(content)
await sor.U('skill_proposals', {'id': qid, 'status': 'published'})
return json.dumps({"success": True, "name": name},
ensure_ascii=False)
else:
return json.dumps({"success": False, "error": "Unknown action: " + str(action)},
ensure_ascii=False)