feat(workspace): 机构工作空间目录重构——新增 build_space_path + _get_space_dir,角色 agent 工具路径基准从项目目录改为产线工作空间{space}/,使 projects/apps/modules 可达;模块技能扫描改扫 modules/ apps/
This commit is contained in:
parent
f8fef7a7d5
commit
b93c1796ca
@ -667,6 +667,19 @@ async def _get_workspace_dir(sor, project_id):
|
||||
return _resolve_workspace(ws)
|
||||
|
||||
|
||||
async def _get_space_dir(sor, project_id):
|
||||
"""返回产线工作空间(机构工作空间层){space}/——角色 agent 工具路径基准。
|
||||
|
||||
新规下角色在 projects/{项目}/docs/、apps/、modules/ 下读写,
|
||||
这些相对路径的基准是 {space}/(不是项目目录 {space}/{项目名})。
|
||||
"""
|
||||
ws = await _get_workspace_dir(sor, project_id)
|
||||
if not ws:
|
||||
return ws
|
||||
# workspace_dir = {base}/{org}/{space}/{项目名},空间目录 = 其父目录
|
||||
return os.path.dirname(ws.rstrip('/'))
|
||||
|
||||
|
||||
def _parse_skill_frontmatter(content):
|
||||
"""解析技能 SKILL.md 的 frontmatter(--- 之间),返回 dict。无 frontmatter 返回 {}。"""
|
||||
content = (content or "").strip()
|
||||
@ -688,23 +701,24 @@ def _parse_skill_frontmatter(content):
|
||||
return fm
|
||||
|
||||
|
||||
def _collect_module_skills(workspace_dir):
|
||||
"""扫描项目 workspace 的 repos/*/skill/SKILL.md,收集模块技能(模块怎么用)。
|
||||
def _collect_module_skills(space_dir):
|
||||
"""扫描机构工作空间 modules/*/skill/SKILL.md 和 apps/*/skill/SKILL.md,收集模块技能。
|
||||
|
||||
模块仓库自带 skill/SKILL.md(架构/数据模型/挂载函数/坑位),但这些不进 skill_loader
|
||||
的静态文件树,导致项目角色不知道模块怎么用。这里运行时扫描,作为「项目模块」scope
|
||||
注入技能目录 + 支持 load_skill 加载全文。返回 [{name, description, body, path}]。
|
||||
其中 body 是剥离 frontmatter 后的正文(对齐 skill_loader.to_prompt_block 的分层导入规范:
|
||||
目录层只放名字+描述,全文层剥离 frontmatter 只返回正文)。
|
||||
新结构:模块本地仓库在机构工作空间 modules/,应用在 apps/。模块仓库自带
|
||||
skill/SKILL.md(架构/数据模型/挂载函数/坑位),不进 skill_loader 静态树,
|
||||
这里运行时扫描,作为「项目模块」scope 注入技能目录 + 支持 load_skill 加载全文。
|
||||
返回 [{name, description, body, path, repo}]。body 是剥离 frontmatter 后的正文。
|
||||
"""
|
||||
modules = []
|
||||
repos_dir = os.path.join(workspace_dir, 'repos')
|
||||
if not os.path.isdir(repos_dir):
|
||||
return modules
|
||||
for entry in sorted(os.listdir(repos_dir)):
|
||||
skill_file = os.path.join(repos_dir, entry, 'skill', 'SKILL.md')
|
||||
if not os.path.isfile(skill_file):
|
||||
continue
|
||||
skill_files = []
|
||||
for sub in ('modules', 'apps'):
|
||||
base = os.path.join(space_dir, sub)
|
||||
if os.path.isdir(base):
|
||||
for entry in sorted(os.listdir(base)):
|
||||
sf = os.path.join(base, entry, 'skill', 'SKILL.md')
|
||||
if os.path.isfile(sf):
|
||||
skill_files.append((sf, entry))
|
||||
for skill_file, entry in skill_files:
|
||||
try:
|
||||
with open(skill_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
@ -1221,7 +1235,7 @@ async def _build_role_skills_block(sor, project_id, role, org_id=""):
|
||||
# 项目模块技能:workspace repos/*/skill/SKILL.md(模块怎么用——架构/数据模型/挂载函数/坑位),
|
||||
# 运行时扫描注入,让项目角色知道引用了哪些模块、每个模块怎么挂载(load_xxx 入口/库名/坑)。
|
||||
try:
|
||||
ws = await _get_workspace_dir(sor, project_id)
|
||||
ws = await _get_space_dir(sor, project_id)
|
||||
module_skills = _collect_module_skills(ws)
|
||||
if module_skills:
|
||||
lines.append("\n## 项目模块技能(本项目引用的业务模块,load_skill 加载全文了解模块怎么用)")
|
||||
@ -1289,7 +1303,7 @@ async def _load_skill_by_name(sor, project_id, role, org_id, name, file_path=Non
|
||||
# 项目模块技能(workspace repos/*/skill/SKILL.md)不在 skill_loader 静态树里,运行时补查。
|
||||
# 全文层对齐 skill_loader.to_prompt_block:剥离 frontmatter 只返回正文(body)。
|
||||
try:
|
||||
ws = await _get_workspace_dir(sor, project_id)
|
||||
ws = await _get_space_dir(sor, project_id)
|
||||
for m in _collect_module_skills(ws):
|
||||
if m['name'] == name or m['repo'] == name:
|
||||
return f"## [项目模块] {m['name']}\n{m['description']}\n\n{m['body']}"
|
||||
@ -1339,6 +1353,8 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
|
||||
logger.warning(f"role_agent_run: org llm missing, task={task_id} org={org_id}")
|
||||
return {"status": "need_info", "task_id": task_id, "question_id": qid}
|
||||
workspace_dir = await _get_workspace_dir(sor, project_id)
|
||||
# 机构工作空间层({space}/):角色工具路径基准,能访问 projects/、apps/、modules/
|
||||
space_dir = await _get_space_dir(sor, project_id)
|
||||
|
||||
try:
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
@ -1386,7 +1402,7 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
|
||||
.replace('__ROLE__', role)
|
||||
.replace('__TITLE__', title)
|
||||
.replace('__QNA__', qna_section)
|
||||
.replace('__WORKSPACE__', workspace_dir)
|
||||
.replace('__WORKSPACE__', space_dir)
|
||||
.replace('__PROJECT_NAME__', project_name)
|
||||
.replace('__ROLE_SPECIFIC__', role_specific)
|
||||
.replace('__ROLE_SKILLS__', role_skills)
|
||||
@ -1453,9 +1469,9 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
|
||||
if tool == "load_skill":
|
||||
result = await _load_skill_by_name(sor, project_id, role, org_id, params.get("name", ""), params.get("file_path") or None)
|
||||
else:
|
||||
result = await _exec_agent_tool(tool, params, workspace_dir, capability_ctx)
|
||||
result = await _exec_agent_tool(tool, params, space_dir, capability_ctx)
|
||||
if tool == "write_file" and params.get("path"):
|
||||
written_files.append(os.path.join(workspace_dir, params["path"]))
|
||||
written_files.append(os.path.join(space_dir, params["path"]))
|
||||
msgs.append({"role": "tool", "tool_call_id": tc.get("id", ""), "content": str(result)})
|
||||
logger.info(f"role_agent tool_call: {tool} -> {str(result)[:100]}")
|
||||
if deliverable or ask_question:
|
||||
@ -1477,9 +1493,9 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
|
||||
if tool == 'load_skill':
|
||||
result = await _load_skill_by_name(sor, project_id, role, org_id, params.get('name', ''), params.get('file_path') or None)
|
||||
else:
|
||||
result = await _exec_agent_tool(tool, params, workspace_dir, capability_ctx)
|
||||
result = await _exec_agent_tool(tool, params, space_dir, capability_ctx)
|
||||
if tool == 'write_file' and params.get('path'):
|
||||
written_files.append(os.path.join(workspace_dir, params['path']))
|
||||
written_files.append(os.path.join(space_dir, params['path']))
|
||||
msgs.append({"role": "assistant", "content": raw})
|
||||
msgs.append({"role": "user", "content": f"工具 {tool} 结果:\n{result}"})
|
||||
logger.info(f"role_agent tool_call(text): {tool} -> {str(result)[:100]}")
|
||||
@ -1519,7 +1535,7 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
|
||||
if isinstance(code_files, list):
|
||||
for f in code_files:
|
||||
if isinstance(f, dict) and f.get("path") and f.get("content"):
|
||||
abs_path = os.path.join(workspace_dir, f["path"])
|
||||
abs_path = os.path.join(space_dir, f["path"])
|
||||
ok, msg = await _write_code_file(abs_path, f["content"])
|
||||
if ok:
|
||||
files_written.append(abs_path)
|
||||
|
||||
@ -31,6 +31,16 @@ def build_workspace_path(workspace_base, org_id, space, name, project_id=''):
|
||||
path = os.path.join(base_dir, f"{name}_{short}")
|
||||
return path
|
||||
|
||||
|
||||
def build_space_path(workspace_base, org_id, space):
|
||||
"""构建产线工作空间路径(机构工作空间层):{base}/{org_id}/{space}。
|
||||
|
||||
开发产线的机构工作空间(projects/apps/modules 三目录所在层)。
|
||||
角色 agent 的工具路径基准用这个,从而能访问 projects/、apps/、modules/。
|
||||
"""
|
||||
space = (space or GENERAL_SPACE).strip() or GENERAL_SPACE
|
||||
return os.path.join(workspace_base, str(org_id or '0'), space)
|
||||
|
||||
# 可编辑的文本文件扩展名
|
||||
TEXT_EXTENSIONS = {
|
||||
'.md', '.py', '.js', '.html', '.css', '.json', '.txt',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user