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)
|
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):
|
def _parse_skill_frontmatter(content):
|
||||||
"""解析技能 SKILL.md 的 frontmatter(--- 之间),返回 dict。无 frontmatter 返回 {}。"""
|
"""解析技能 SKILL.md 的 frontmatter(--- 之间),返回 dict。无 frontmatter 返回 {}。"""
|
||||||
content = (content or "").strip()
|
content = (content or "").strip()
|
||||||
@ -688,23 +701,24 @@ def _parse_skill_frontmatter(content):
|
|||||||
return fm
|
return fm
|
||||||
|
|
||||||
|
|
||||||
def _collect_module_skills(workspace_dir):
|
def _collect_module_skills(space_dir):
|
||||||
"""扫描项目 workspace 的 repos/*/skill/SKILL.md,收集模块技能(模块怎么用)。
|
"""扫描机构工作空间 modules/*/skill/SKILL.md 和 apps/*/skill/SKILL.md,收集模块技能。
|
||||||
|
|
||||||
模块仓库自带 skill/SKILL.md(架构/数据模型/挂载函数/坑位),但这些不进 skill_loader
|
新结构:模块本地仓库在机构工作空间 modules/,应用在 apps/。模块仓库自带
|
||||||
的静态文件树,导致项目角色不知道模块怎么用。这里运行时扫描,作为「项目模块」scope
|
skill/SKILL.md(架构/数据模型/挂载函数/坑位),不进 skill_loader 静态树,
|
||||||
注入技能目录 + 支持 load_skill 加载全文。返回 [{name, description, body, path}]。
|
这里运行时扫描,作为「项目模块」scope 注入技能目录 + 支持 load_skill 加载全文。
|
||||||
其中 body 是剥离 frontmatter 后的正文(对齐 skill_loader.to_prompt_block 的分层导入规范:
|
返回 [{name, description, body, path, repo}]。body 是剥离 frontmatter 后的正文。
|
||||||
目录层只放名字+描述,全文层剥离 frontmatter 只返回正文)。
|
|
||||||
"""
|
"""
|
||||||
modules = []
|
modules = []
|
||||||
repos_dir = os.path.join(workspace_dir, 'repos')
|
skill_files = []
|
||||||
if not os.path.isdir(repos_dir):
|
for sub in ('modules', 'apps'):
|
||||||
return modules
|
base = os.path.join(space_dir, sub)
|
||||||
for entry in sorted(os.listdir(repos_dir)):
|
if os.path.isdir(base):
|
||||||
skill_file = os.path.join(repos_dir, entry, 'skill', 'SKILL.md')
|
for entry in sorted(os.listdir(base)):
|
||||||
if not os.path.isfile(skill_file):
|
sf = os.path.join(base, entry, 'skill', 'SKILL.md')
|
||||||
continue
|
if os.path.isfile(sf):
|
||||||
|
skill_files.append((sf, entry))
|
||||||
|
for skill_file, entry in skill_files:
|
||||||
try:
|
try:
|
||||||
with open(skill_file, 'r', encoding='utf-8') as f:
|
with open(skill_file, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@ -1221,7 +1235,7 @@ async def _build_role_skills_block(sor, project_id, role, org_id=""):
|
|||||||
# 项目模块技能:workspace repos/*/skill/SKILL.md(模块怎么用——架构/数据模型/挂载函数/坑位),
|
# 项目模块技能:workspace repos/*/skill/SKILL.md(模块怎么用——架构/数据模型/挂载函数/坑位),
|
||||||
# 运行时扫描注入,让项目角色知道引用了哪些模块、每个模块怎么挂载(load_xxx 入口/库名/坑)。
|
# 运行时扫描注入,让项目角色知道引用了哪些模块、每个模块怎么挂载(load_xxx 入口/库名/坑)。
|
||||||
try:
|
try:
|
||||||
ws = await _get_workspace_dir(sor, project_id)
|
ws = await _get_space_dir(sor, project_id)
|
||||||
module_skills = _collect_module_skills(ws)
|
module_skills = _collect_module_skills(ws)
|
||||||
if module_skills:
|
if module_skills:
|
||||||
lines.append("\n## 项目模块技能(本项目引用的业务模块,load_skill 加载全文了解模块怎么用)")
|
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 静态树里,运行时补查。
|
# 项目模块技能(workspace repos/*/skill/SKILL.md)不在 skill_loader 静态树里,运行时补查。
|
||||||
# 全文层对齐 skill_loader.to_prompt_block:剥离 frontmatter 只返回正文(body)。
|
# 全文层对齐 skill_loader.to_prompt_block:剥离 frontmatter 只返回正文(body)。
|
||||||
try:
|
try:
|
||||||
ws = await _get_workspace_dir(sor, project_id)
|
ws = await _get_space_dir(sor, project_id)
|
||||||
for m in _collect_module_skills(ws):
|
for m in _collect_module_skills(ws):
|
||||||
if m['name'] == name or m['repo'] == name:
|
if m['name'] == name or m['repo'] == name:
|
||||||
return f"## [项目模块] {m['name']}\n{m['description']}\n\n{m['body']}"
|
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}")
|
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}
|
return {"status": "need_info", "task_id": task_id, "question_id": qid}
|
||||||
workspace_dir = await _get_workspace_dir(sor, project_id)
|
workspace_dir = await _get_workspace_dir(sor, project_id)
|
||||||
|
# 机构工作空间层({space}/):角色工具路径基准,能访问 projects/、apps/、modules/
|
||||||
|
space_dir = await _get_space_dir(sor, project_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await sor.sqlExe("COMMIT", {})
|
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('__ROLE__', role)
|
||||||
.replace('__TITLE__', title)
|
.replace('__TITLE__', title)
|
||||||
.replace('__QNA__', qna_section)
|
.replace('__QNA__', qna_section)
|
||||||
.replace('__WORKSPACE__', workspace_dir)
|
.replace('__WORKSPACE__', space_dir)
|
||||||
.replace('__PROJECT_NAME__', project_name)
|
.replace('__PROJECT_NAME__', project_name)
|
||||||
.replace('__ROLE_SPECIFIC__', role_specific)
|
.replace('__ROLE_SPECIFIC__', role_specific)
|
||||||
.replace('__ROLE_SKILLS__', role_skills)
|
.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":
|
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)
|
result = await _load_skill_by_name(sor, project_id, role, org_id, params.get("name", ""), params.get("file_path") or None)
|
||||||
else:
|
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"):
|
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)})
|
msgs.append({"role": "tool", "tool_call_id": tc.get("id", ""), "content": str(result)})
|
||||||
logger.info(f"role_agent tool_call: {tool} -> {str(result)[:100]}")
|
logger.info(f"role_agent tool_call: {tool} -> {str(result)[:100]}")
|
||||||
if deliverable or ask_question:
|
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':
|
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)
|
result = await _load_skill_by_name(sor, project_id, role, org_id, params.get('name', ''), params.get('file_path') or None)
|
||||||
else:
|
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'):
|
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": "assistant", "content": raw})
|
||||||
msgs.append({"role": "user", "content": f"工具 {tool} 结果:\n{result}"})
|
msgs.append({"role": "user", "content": f"工具 {tool} 结果:\n{result}"})
|
||||||
logger.info(f"role_agent tool_call(text): {tool} -> {str(result)[:100]}")
|
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):
|
if isinstance(code_files, list):
|
||||||
for f in code_files:
|
for f in code_files:
|
||||||
if isinstance(f, dict) and f.get("path") and f.get("content"):
|
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"])
|
ok, msg = await _write_code_file(abs_path, f["content"])
|
||||||
if ok:
|
if ok:
|
||||||
files_written.append(abs_path)
|
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}")
|
path = os.path.join(base_dir, f"{name}_{short}")
|
||||||
return path
|
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 = {
|
TEXT_EXTENSIONS = {
|
||||||
'.md', '.py', '.js', '.html', '.css', '.json', '.txt',
|
'.md', '.py', '.js', '.html', '.css', '.json', '.txt',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user