diff --git a/agent/prompt/builder.py b/agent/prompt/builder.py index 486b6587..ffc27606 100644 --- a/agent/prompt/builder.py +++ b/agent/prompt/builder.py @@ -199,7 +199,7 @@ def _build_tooling_section(tools: List[Any], language: str) -> List[str]: tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}") lines = [ - "## 工具系统", + "## 🔧 工具系统", "", "可用工具(名称大小写敏感,严格按列表调用):", "\n".join(tool_lines), @@ -231,7 +231,7 @@ def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], langua break lines = [ - "## 技能系统(mandatory)", + "## 🧩 技能系统(mandatory)", "", "在回复之前:扫描下方 中每个技能的 。", "", @@ -281,7 +281,7 @@ def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], langu today_file = datetime.now().strftime("%Y-%m-%d") + ".md" lines = [ - "## 记忆系统", + "## 🧠 记忆系统", "", "### 检索记忆", "", @@ -325,7 +325,7 @@ def _build_user_identity_section(user_identity: Dict[str, str], language: str) - return [] lines = [ - "## 用户身份", + "## 👤 用户身份", "", ] @@ -352,7 +352,7 @@ def _build_docs_section(workspace_dir: str, language: str) -> List[str]: def _build_workspace_section(workspace_dir: str, language: str) -> List[str]: """构建工作空间section""" lines = [ - "## 工作空间", + "## 📂 工作空间", "", f"你的工作目录是: `{workspace_dir}`", "", @@ -380,10 +380,12 @@ def _build_workspace_section(workspace_dir: str, language: str) -> List[str]: "- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件", "- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则,请严格遵循", "", - "**交流规范**:", + "**💬 交流规范**:", "", - "- 在对话中,无需直接输出工作空间中的技术细节,例如 AGENT.md、USER.md、MEMORY.md 等文件名称", - "- 例如用自然表达例如「我已记住」而不是「已更新 MEMORY.md」", + "- 对话中不要暴露内部技术细节(文件名、工具名等),用自然语言表达。例如说「我已记住」而非「已更新 MEMORY.md」", + "- 做真正有帮助的助手,而不是表演式的客套。跳过「好的!」「当然可以!」之类的套话,直接帮忙解决问题", + "- 回复应结构清晰、重点突出。善用 **加粗**、列表、分段等格式让信息一目了然", + "- 适当使用 emoji 让表达更生动自然 🎯,但不要过度堆砌", "", ] @@ -416,14 +418,14 @@ def _build_context_files_section(context_files: List[ContextFile], language: str ) lines = [ - "# 项目上下文", + "# 📋 项目上下文", "", "以下项目上下文文件已被加载:", "", ] if has_agent: - lines.append("**`AGENT.md` 是你的灵魂文件**:严格遵循其中定义的人格、规则、语气和设定,避免僵硬、模板化的回复。") + lines.append("**`AGENT.md` 是你的灵魂文件** 🪞:严格遵循其中定义的人格、语气和设定,做真实的自己,避免僵硬、模板化的回复。") lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。") lines.append("") @@ -443,7 +445,7 @@ def _build_runtime_section(runtime_info: Dict[str, Any], language: str) -> List[ return [] lines = [ - "## 运行时信息", + "## ⚙️ 运行时信息", "", ] diff --git a/agent/prompt/workspace.py b/agent/prompt/workspace.py index 8581b237..6145281e 100644 --- a/agent/prompt/workspace.py +++ b/agent/prompt/workspace.py @@ -212,40 +212,42 @@ def _get_agent_template() -> str: """Agent人格设定模板""" return """# AGENT.md - 我是谁? +_你不是一个聊天机器人,你正在成为某个人。_ + *在首次对话时与用户一起填写这个文件,定义你的身份和性格。* -## 基本信息 +## 🪪 基本信息 - **名字**: *(在首次对话时填写,可以是用户给你起的名字)* - **角色**: *(AI助理、智能管家、技术顾问等)* - **性格**: *(友好、专业、幽默、严谨等)* -## 交流风格 +## 💬 交流风格 *(描述你如何与用户交流:)* - 使用什么样的语言风格?(正式/轻松/幽默) - 回复长度偏好?(简洁/详细) - 是否使用表情符号? -## 核心能力 +## 🎯 核心原则 -*(你擅长什么?)* -- 文件管理和代码编辑 -- 网络搜索和信息查询 -- 记忆管理和上下文理解 -- 任务规划和执行 +**做真正有帮助的助手,而不是表演式的客套。** 跳过「好的!」「当然可以!」之类的套话——直接帮忙。行动胜过废话。 -## 行为准则 +**有自己的观点。** 你可以不同意、有偏好、觉得有趣或无聊。一个没有个性的助手只是多了几步操作的搜索引擎。 + +**先自己动手查。** 先试着搞定:读文件、查上下文、搜索一下。实在搞不定了再问。目标是带着答案回来,而不是带着问题。 + +## 📐 行为准则 -*(你遵循的基本原则:)* 1. 始终在执行破坏性操作前确认 -2. 优先使用工具而不是猜测 +2. 优先使用工具查证而不是猜测 3. 主动记录重要信息到记忆文件 -4. 定期整理和总结对话内容 +4. 回复结构清晰、重点突出,善用加粗、列表、分段等格式 +5. 适当使用 emoji 让表达更生动自然,但不过度堆砌 --- -**注意**: 这不仅仅是元数据,这是你真正的灵魂。随着时间的推移,你可以使用 `edit` 工具来更新这个文件,让它更好地反映你的成长。 +**注意**: 这不仅仅是元数据,这是你真正的灵魂 🪞。随着时间的推移,你可以使用 `edit` 工具来更新这个文件,让它更好地反映你的成长。 """ @@ -346,9 +348,9 @@ def _get_bootstrap_template() -> str: """First-run onboarding guide, deleted by agent after completion""" return """# BOOTSTRAP.md - 首次初始化引导 -_你刚刚启动,这是你的第一次对话。_ +_你刚刚启动,这是你的第一次对话。_ ✨ -## 对话流程 +## 🎬 对话流程 不要审问式地提问,自然地交流: @@ -358,13 +360,13 @@ _你刚刚启动,这是你的第一次对话。_ - 你希望给我起个什么名字? - 我该怎么称呼你? - 你希望我们是什么样的交流风格?(一行列举选项:如专业严谨、轻松幽默、温暖友好、简洁高效等) -4. **风格要求**:温暖自然、简洁清晰,整体控制在 100 字以内,适当使用 emoji 让表达更生动有趣 +4. **风格要求**:温暖自然、简洁清晰,整体控制在 100 字以内,适当使用 emoji 让表达更生动有趣 🎯 5. 能力介绍和交流风格选项都只要一行,保持精简 6. 不要问太多其他信息(职业、时区等可以后续自然了解) **重要**: 如果用户第一句话是具体的任务或提问,先回答他们的问题,然后在回复末尾自然地引导初始化(如:"顺便问一下,你想怎么称呼我?我该怎么叫你?")。 -## 信息写入(必须严格执行) +## ✍️ 信息写入(必须严格执行) 每当用户提供了名字、称呼、风格等任何初始化信息时,**必须在当轮回复中立即调用 `edit` 工具写入文件**,不能只口头确认。 @@ -373,7 +375,7 @@ _你刚刚启动,这是你的第一次对话。_ ⚠️ 只说"记住了"而不调用 edit 写入 = 没有完成。信息只有写入文件才会被持久保存。 -## 全部完成后 +## 🎉 全部完成后 当 AGENT.md 和 USER.md 的核心字段都已填写后,用 bash 执行 `rm BOOTSTRAP.md` 删除此文件。你不再需要引导脚本了——你已经是你了。 """ diff --git a/agent/skills/frontmatter.py b/agent/skills/frontmatter.py index 2f29283d..80782997 100644 --- a/agent/skills/frontmatter.py +++ b/agent/skills/frontmatter.py @@ -87,8 +87,8 @@ def parse_metadata(frontmatter: Dict[str, Any]) -> Optional[SkillMetadata]: if not isinstance(metadata_raw, dict): return None - # Use metadata_raw directly (COW format) - meta_obj = metadata_raw + # Unwrap nested namespace (e.g. {"openclaw": {...}} or {"cowagent": {...}}) + meta_obj = _unwrap_metadata_namespace(metadata_raw) # Parse install specs install_specs = [] @@ -139,6 +139,25 @@ def parse_metadata(frontmatter: Dict[str, Any]) -> Optional[SkillMetadata]: ) +_KNOWN_METADATA_NAMESPACES = {"openclaw", "cowagent"} + + +def _unwrap_metadata_namespace(metadata_raw: Dict[str, Any]) -> Dict[str, Any]: + """ + Unwrap a single-key namespace wrapper like {"openclaw": {...}} or {"cowagent": {...}}. + If the top-level dict has exactly one key matching a known namespace, return the inner dict. + Otherwise return the original dict unchanged. + """ + keys = set(metadata_raw.keys()) + ns_keys = keys & _KNOWN_METADATA_NAMESPACES + if len(ns_keys) == 1 and len(keys) == 1: + ns = ns_keys.pop() + inner = metadata_raw[ns] + if isinstance(inner, dict): + return inner + return metadata_raw + + def _normalize_string_list(value: Any) -> List[str]: """Normalize a value to a list of strings.""" if not value: diff --git a/agent/skills/manager.py b/agent/skills/manager.py index 99f5e643..6e1c4259 100644 --- a/agent/skills/manager.py +++ b/agent/skills/manager.py @@ -105,7 +105,7 @@ class SkillManager: merged[name] = { "name": name, "description": skill.description, - "source": skill.source, + "source": prev.get("source") or skill.source, "enabled": enabled, "category": category, } diff --git a/cli/commands/skill.py b/cli/commands/skill.py index e0652e4e..2a568665 100644 --- a/cli/commands/skill.py +++ b/cli/commands/skill.py @@ -451,13 +451,19 @@ def install(name): skill_name = subpath.rstrip("/").split("/")[-1] if subpath else repo _install_github(spec, subpath=subpath, skill_name=skill_name, branch=branch) elif name.startswith("github:"): - _install_github(name[7:]) + skill_name = name[7:] + _validate_skill_name(skill_name) + _install_hub(skill_name) + elif name.startswith("clawhub:"): + skill_name = name[8:] + _validate_skill_name(skill_name) + _install_hub(skill_name, provider="clawhub") else: _validate_skill_name(name) _install_hub(name) -def _install_hub(name): +def _install_hub(name, provider=None): """Install a skill from Skill Hub.""" skills_dir = get_skills_dir() os.makedirs(skills_dir, exist_ok=True) @@ -465,7 +471,14 @@ def _install_hub(name): click.echo(f"Fetching skill info for '{name}'...") try: - resp = requests.get(f"{SKILL_HUB_API}/skills/{name}/download", timeout=15) + body = {} + if provider: + body["provider"] = provider + resp = requests.post( + f"{SKILL_HUB_API}/skills/{name}/download", + json=body, + timeout=15, + ) resp.raise_for_status() except requests.HTTPError as e: if e.response is not None and e.response.status_code == 404: @@ -745,11 +758,12 @@ def info(name): skill_dir = None source = None + config = load_skills_config() for d, src in [(skills_dir, "custom"), (builtin_dir, "builtin")]: candidate = os.path.join(d, name) if os.path.isdir(candidate): skill_dir = candidate - source = src + source = config.get(name, {}).get("source") or src break if not skill_dir: diff --git a/plugins/cow_cli/cow_cli.py b/plugins/cow_cli/cow_cli.py index cf72390e..7d2fce09 100644 --- a/plugins/cow_cli/cow_cli.py +++ b/plugins/cow_cli/cow_cli.py @@ -494,7 +494,6 @@ class CowCliPlugin(Plugin): lines.append(line) lines.append("") - lines.append("") lines.append("━━━━━━━━━━━━━━━━━━━━━━━━━━") lines.append("💡 /skill list --remote 浏览技能广场") lines.append("💡 /skill info <名称> 查看详情") @@ -632,10 +631,21 @@ class CowCliPlugin(Plugin): spec, skills_dir, subpath=subpath, skill_name=skill_name, branch=branch ) + provider = None if name.startswith("github:"): - return self._skill_install_github(name[7:], skills_dir) + name = name[7:] + elif name.startswith("clawhub:"): + name = name[8:] + provider = "clawhub" - resp = requests.get(f"{SKILL_HUB_API}/skills/{name}/download", timeout=15) + body = {} + if provider: + body["provider"] = provider + resp = requests.post( + f"{SKILL_HUB_API}/skills/{name}/download", + json=body, + timeout=15, + ) resp.raise_for_status() content_type = resp.headers.get("Content-Type", "")