Merge branch 'master' into ji
This commit is contained in:
commit
d6f018e581
2 files changed
+57
No files matched your search
@@ -153,6 +153,43 @@ def _resolve_skills_path(path: str) -> str:
|
||||
return _join_path_preserving_style(skills_host, relative)
|
||||
|
||||
|
||||
def _custom_skill_name_from_path(path: str) -> str | None:
|
||||
"""从虚拟技能路径里解析出 custom 技能名,用于"被使用"埋点。
|
||||
|
||||
只识别 ``{skills_container}/custom/{name}/...`` 形式 —— public 技能不参与
|
||||
策展,给它们埋点只会往 ``.usage.json`` 写无用条目。读技能目录下任意文件
|
||||
(SKILL.md 或 references/templates/scripts 支持文件)都算一次该技能的使用。
|
||||
解析不出技能名时返回 None。
|
||||
"""
|
||||
skills_container = _get_skills_container_path()
|
||||
if not (path == skills_container or path.startswith(f"{skills_container}/")):
|
||||
return None
|
||||
relative = path[len(skills_container) :].lstrip("/")
|
||||
parts = [segment for segment in relative.split("/") if segment]
|
||||
if len(parts) >= 2 and parts[0] == "custom":
|
||||
return parts[1]
|
||||
return None
|
||||
|
||||
|
||||
def _record_skill_read(virtual_path: str) -> None:
|
||||
"""技能文件被 ``read_file`` 读取时记一次 use —— 技能"被使用"的真实信号。
|
||||
|
||||
技能正文不进 system prompt(prompt 里只有 name/description/location),Agent
|
||||
按 Progressive Loading 用 ``read_file`` 加载 SKILL.md 才算真正用到该技能。
|
||||
curator 的年龄流转据此判断闲置,没有这个埋点会把每天都在用的技能误判为陈旧。
|
||||
best-effort:埋点失败绝不影响 ``read_file`` 本身。
|
||||
"""
|
||||
try:
|
||||
name = _custom_skill_name_from_path(virtual_path)
|
||||
if name is None:
|
||||
return
|
||||
from deerflow.skills.usage import bump_use
|
||||
|
||||
bump_use(name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _is_acp_workspace_path(path: str) -> bool:
|
||||
"""Check if a path is under the ACP workspace virtual path."""
|
||||
return path == _ACP_WORKSPACE_VIRTUAL_PATH or path.startswith(f"{_ACP_WORKSPACE_VIRTUAL_PATH}/")
|
||||
@@ -1467,6 +1504,9 @@ def read_file_tool(
|
||||
path = _resolve_and_validate_user_data_path(path, thread_data)
|
||||
# Custom mount paths are resolved by LocalSandbox._resolve_path()
|
||||
content = sandbox.read_file(path)
|
||||
# 读成功即记一次技能使用 —— requested_path 仍是虚拟路径(本地/容器沙箱都一样),
|
||||
# 非技能路径会被 _record_skill_read 自行忽略。
|
||||
_record_skill_read(requested_path)
|
||||
if not content:
|
||||
return "(empty)"
|
||||
if start_line is not None and end_line is not None:
|
||||
|
||||
@@ -66,6 +66,21 @@ async def _to_thread(func, /, *args, **kwargs):
|
||||
return await asyncio.to_thread(func, *args, **kwargs)
|
||||
|
||||
|
||||
async def _bump_patch_best_effort(name: str) -> None:
|
||||
"""记一次技能维护活动(patch / edit)。
|
||||
|
||||
刷新 ``.usage.json`` 的 ``patch_count`` 与 ``last_patched_at``,让 curator 的
|
||||
年龄流转把"Agent 正在主动维护的技能"算作活跃,而不是误判为闲置后归档。
|
||||
best-effort:埋点失败只记 DEBUG 日志,绝不影响技能写入主流程。
|
||||
"""
|
||||
try:
|
||||
from deerflow.skills.usage import bump_patch
|
||||
|
||||
await _to_thread(bump_patch, name)
|
||||
except Exception:
|
||||
logger.debug("Failed to bump patch count for %r", name, exc_info=True)
|
||||
|
||||
|
||||
async def _ensure_skill_db_registered(store: SkillStore, name: str, user_id: str) -> None:
|
||||
"""Idempotently record an agent-created skill's ownership in the DB.
|
||||
|
||||
@@ -229,6 +244,7 @@ async def _skill_manage_impl(
|
||||
name,
|
||||
_history_record(action="edit", file_path=SKILL_MD_FILE, prev_content=prev_content, new_content=content, thread_id=thread_id, scanner=scan),
|
||||
)
|
||||
await _bump_patch_best_effort(name)
|
||||
await refresh_skills_system_prompt_cache_async()
|
||||
return f"Updated custom skill '{name}'."
|
||||
|
||||
@@ -253,6 +269,7 @@ async def _skill_manage_impl(
|
||||
name,
|
||||
_history_record(action="patch", file_path=SKILL_MD_FILE, prev_content=prev_content, new_content=new_content, thread_id=thread_id, scanner=scan),
|
||||
)
|
||||
await _bump_patch_best_effort(name)
|
||||
await refresh_skills_system_prompt_cache_async()
|
||||
return f"Patched custom skill '{name}' ({replacement_count} replacement(s) applied, {occurrences} match(es) found)."
|
||||
|
||||
|
||||
Reference in new issue
Block a user