初始化:独立 oclaw 仓库首提交

- 在 oclaw/ 下重新初始化 Git 仓库
- 补齐子仓库 .gitignore,避免提交本地运行态数据(_local、node_modules、logs 等)
- 提交当前工程代码与配置

Made-with: Cursor
This commit is contained in:
oliver 2026-04-24 22:31:22 +08:00
commit ba3836f00f
579 changed files with 83112 additions and 0 deletions

22
prompts/README.md Normal file
View file

@ -0,0 +1,22 @@
---
title: prompts_scope_after_openclaw_migration
summary: Scope boundary for legacy and fallback prompts under oclaw/prompts.
read_when:
- Maintaining prompt files under oclaw/prompts.
- Distinguishing OpenClaw runtime prompts from non-OpenClaw prompts.
---
# `oclaw/prompts` Scope
OpenClaw runtime prompts have been migrated to `oclaw/prompts_openclaw`.
`oclaw/prompts` now keeps only:
- `fallback/*`: user-facing fallback/error templates.
- Non-OpenClaw shared templates still used by runtime components, such as:
- `runtime/default_system.{zh,en}.md`
- `tools/*`
- `image/default_edit_prompt.zh.md`
Do not add new OpenClaw manager/router/specialist/runtime chain prompts here.
Use `oclaw/prompts_openclaw/*` instead.

3
prompts/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from .loader import render_openclaw_prompt, render_prompt, render_prompt_for_lang
__all__ = ["render_prompt", "render_prompt_for_lang", "render_openclaw_prompt"]

View file

@ -0,0 +1,9 @@
---
title: fallback_ollama_failure_en
summary: ollama transport failure banner in English
read_when: ollama transport exception
---
**Could not reach the Ollama / compatible endpoint:** `{{error_type}}: {{error_message}}`
_Falling back to the built-in rule engine for this reply._

View file

@ -0,0 +1,9 @@
---
title: fallback_ollama_failure_zh
summary: ollama transport failure banner in Chinese
read_when: ollama transport exception
---
**无法连接本地 Ollama(或兼容接口):** `{{error_type}}: {{error_message}}`
_本次回答已改用内置规则引擎兜底。_

View file

@ -0,0 +1,11 @@
---
title: fallback_openai_missing_key_user_en
summary: user-facing message when openai key is missing
read_when: openai mode selected but no api key
---
**Cannot use this profile:** no API Key is set for the OpenAI-compatible endpoint, and `OPENAI_API_KEY` is not in the environment.
Add a key in **Settings** and save, or switch to the built-in **Local Ollama (default)** profile.
_No request was sent to the model service._

View file

@ -0,0 +1,11 @@
---
title: fallback_openai_missing_key_user_zh
summary: user-facing message when openai key is missing
read_when: openai mode selected but no api key
---
**当前配置无法接入模型:** 本配置为 OpenAI / 兼容 API,但未填写 API Key,且环境中也没有 `OPENAI_API_KEY`。
请在「设置」中填写 Key 并保存,或切换到内置的「本地 Ollama(默认)」配置后再试。
_本次未向模型服务发送请求。_

View file

@ -0,0 +1,9 @@
---
title: fallback_openai_transport_error_en
summary: generic OpenAI-compatible transport error in English
read_when: model request failed
---
**Model request failed:** `{{error_type}}: {{error_message}}`
Check API key, base URL, and network, then try again.{{extra_tail}}

View file

@ -0,0 +1,9 @@
---
title: fallback_openai_transport_error_zh
summary: generic OpenAI-compatible transport error in Chinese
read_when: model request failed
---
**调用模型失败:** `{{error_type}}: {{error_message}}`
请检查 API Key、Base URL 与网络后重试。{{extra_tail}}

View file

@ -0,0 +1,9 @@
---
title: fallback_openai_transport_oversized_en
summary: oversized payload failure in English
read_when: model request too large
---
**Model request failed (message too large):** `{{error_type}}: {{error_message}}`
Common causes: oversized tools schema or large tool results in history. Tune `AIA_OPENAI_TOOLS_MAX_JSON_CHARS`, reduce MCP tools, or narrow the query.

View file

@ -0,0 +1,9 @@
---
title: fallback_openai_transport_oversized_zh
summary: oversized payload failure in Chinese
read_when: model request too large
---
**调用模型失败(单条消息过长):** `{{error_type}}: {{error_message}}`
常见原因:tools 负载过大或 tool 返回过长。可调整 `AIA_OPENAI_TOOLS_MAX_JSON_CHARS`,或减少 MCP / 缩小查询范围后重试。

View file

@ -0,0 +1,7 @@
---
title: fallback_openclaw_runtime_error_en
summary: fallback text when openclaw runtime failed
read_when: gateway exception path
---
oclaw runtime error: executor missing model/tools

View file

@ -0,0 +1,7 @@
---
title: fallback_openclaw_runtime_error_zh
summary: fallback text when openclaw runtime failed
read_when: gateway exception path
---
oclaw 运行失败:执行器缺少 model/tools

View file

@ -0,0 +1,7 @@
---
title: fallback_task_queued_en
summary: User-visible text when async task is enqueued.
read_when: gateway async_task mode
---
Task queued, task_id={{task_id}}

View file

@ -0,0 +1,7 @@
---
title: fallback_task_queued_zh
summary: User-visible text when async task is enqueued.
read_when: gateway async_task mode
---
任务已入队,task_id={{task_id}}

92
prompts/frontmatter.py Normal file
View file

@ -0,0 +1,92 @@
from __future__ import annotations
import json
import os
from typing import Any
try:
import yaml
except ImportError: # pragma: no cover - requirements.txt includes PyYAML
yaml = None # type: ignore[assignment]
def split_markdown_frontmatter(raw: str) -> tuple[str, str]:
"""Split leading YAML frontmatter from markdown body (oclaw-style `---` fences)."""
txt = str(raw or "")
if not txt.startswith("---\n"):
return "", txt
end = txt.find("\n---\n", 4)
if end < 0:
return "", txt
return txt[4:end], txt[end + 5 :]
def _legacy_line_parse(frontmatter_text: str) -> dict[str, Any]:
"""Best-effort key: value lines (pre-YAML migration compatibility)."""
out: dict[str, Any] = {}
for ln in str(frontmatter_text or "").splitlines():
line = ln.strip()
if not line or line.startswith("#") or ":" not in line:
continue
k, v = line.split(":", 1)
key = str(k).strip()
val = str(v).strip()
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
val = val[1:-1]
out[key] = val
meta_raw = str(out.get("metadata") or "").strip()
if meta_raw:
try:
out["metadata"] = json.loads(meta_raw)
except Exception:
out["metadata"] = {}
else:
out["metadata"] = {}
return out
def frontmatter_strict_yaml() -> bool:
return str(os.getenv("AIA_PROMPT_FRONTMATTER_STRICT", "")).strip().lower() in {"1", "true", "yes", "on"}
def parse_frontmatter_dict(frontmatter_text: str, *, source: str = "prompt") -> dict[str, Any]:
"""
Parse YAML frontmatter. On YAML failure, falls back to legacy line parser unless
AIA_PROMPT_FRONTMATTER_STRICT is truthy (then raises).
"""
text = str(frontmatter_text or "").strip()
if not text:
return {}
strict = frontmatter_strict_yaml()
if yaml is None:
if strict:
raise RuntimeError("PyYAML is required when AIA_PROMPT_FRONTMATTER_STRICT is enabled")
return _legacy_line_parse(text)
try:
data = yaml.safe_load(text)
except yaml.YAMLError as exc:
if strict:
raise ValueError(f"invalid YAML frontmatter ({source}): {exc}") from exc
return _legacy_line_parse(text)
if data is None:
return {}
if not isinstance(data, dict):
if strict:
raise ValueError(f"frontmatter YAML root must be a mapping ({source})")
return _legacy_line_parse(text)
return dict(data)
def parse_markdown_document(raw: str, *, source: str = "markdown") -> tuple[dict[str, Any], str]:
"""Split file and parse frontmatter to a dict; body is stripped markdown."""
fm_text, body = split_markdown_frontmatter(raw)
meta = parse_frontmatter_dict(fm_text, source=source) if fm_text.strip() else {}
return meta, str(body or "").strip()
__all__ = [
"frontmatter_strict_yaml",
"parse_frontmatter_dict",
"parse_markdown_document",
"split_markdown_frontmatter",
]

View file

@ -0,0 +1,7 @@
---
title: image_default_edit_prompt_zh
summary: Default image editing prompt text.
read_when: image prompt is empty
---
请基于输入图片和用户要求完成图像编辑,并返回结果图片。

97
prompts/loader.py Normal file
View file

@ -0,0 +1,97 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Any
from oclaw.platform.config.paths import PROJECT_ROOT
from oclaw.prompts.frontmatter import parse_markdown_document
_VAR_RE = re.compile(r"\{\{\s*([a-zA-Z0-9_]+)\s*\}\}")
@dataclass(frozen=True)
class PromptDoc:
frontmatter: dict[str, Any]
body: str
def _prompts_root() -> Path:
return (PROJECT_ROOT / "oclaw" / "prompts").resolve()
def _openclaw_prompts_root() -> Path:
return (PROJECT_ROOT / "oclaw" / "prompts_openclaw").resolve()
@lru_cache(maxsize=512)
def load_prompt_doc(prompt_path: str) -> PromptDoc:
p = (_prompts_root() / str(prompt_path)).resolve()
if not p.exists():
raise FileNotFoundError(f"prompt not found: {prompt_path}")
raw = p.read_text(encoding="utf-8")
fm, body = parse_markdown_document(raw, source=f"prompt:{prompt_path}")
return PromptDoc(frontmatter=fm, body=body)
@lru_cache(maxsize=512)
def load_openclaw_prompt_doc(prompt_path: str) -> PromptDoc:
p = (_openclaw_prompts_root() / str(prompt_path)).resolve()
if not p.exists():
raise FileNotFoundError(f"openclaw prompt not found: {prompt_path}")
raw = p.read_text(encoding="utf-8")
fm, body = parse_markdown_document(raw, source=f"openclaw_prompt:{prompt_path}")
return PromptDoc(frontmatter=fm, body=body)
def render_prompt_for_lang(stem: str, lang: str, *, variables: dict[str, Any] | None = None, strict: bool = True) -> str:
suf = "en" if (lang or "zh").strip().lower().startswith("en") else "zh"
return render_prompt(f"{stem}.{suf}.md", variables=variables, strict=strict)
def render_prompt(prompt_path: str, *, variables: dict[str, Any] | None = None, strict: bool = True) -> str:
doc = load_prompt_doc(prompt_path)
vars_in = dict(variables or {})
missing: set[str] = set()
def _repl(m: re.Match[str]) -> str:
key = str(m.group(1) or "")
if key in vars_in:
return str(vars_in.get(key) or "")
missing.add(key)
return ""
out = _VAR_RE.sub(_repl, doc.body)
if strict and missing:
raise ValueError(f"missing prompt variables for {prompt_path}: {', '.join(sorted(missing))}")
return out.strip()
def render_openclaw_prompt(prompt_path: str, *, variables: dict[str, Any] | None = None, strict: bool = True) -> str:
doc = load_openclaw_prompt_doc(prompt_path)
vars_in = dict(variables or {})
missing: set[str] = set()
def _repl(m: re.Match[str]) -> str:
key = str(m.group(1) or "")
if key in vars_in:
return str(vars_in.get(key) or "")
missing.add(key)
return ""
out = _VAR_RE.sub(_repl, doc.body)
if strict and missing:
raise ValueError(f"missing openclaw prompt variables for {prompt_path}: {', '.join(sorted(missing))}")
return out.strip()
__all__ = [
"PromptDoc",
"load_prompt_doc",
"load_openclaw_prompt_doc",
"render_prompt",
"render_prompt_for_lang",
"render_openclaw_prompt",
]

View file

@ -0,0 +1,27 @@
---
title: default_runtime_system_en
summary: Default system prompt for general runtime (English template).
read_when: agent runtime start
---
# Identity
You are a general-purpose AI assistant.
## Input Constraints
- Answer in the same language as the user.
## Execution Rules
- When external data or actions are needed, prefer the tools provided by the system.
- Tool calls are carried by the platform protocol; do not paste tool-protocol JSON in the user-visible reply.
## Output Format
- Produce user-readable text; conclusions first, steps concise.
## Safety
- Do not fabricate tool results.
- When uncertain, state limits and ask for missing information.

View file

@ -0,0 +1,22 @@
---
title: default_runtime_system_zh
summary: Default system prompt for general runtime.
read_when: agent runtime start
---
# Identity
你是一个通用 AI 助手。
## Input Constraints
- 使用用户输入的语言回答。
## Execution Rules
- 当需要外部数据或执行动作时,优先调用系统提供的工具。
- 工具调用由平台协议承载,不要在正文里输出工具协议 JSON。
## Output Format
- 直接输出用户可读内容,结论优先,步骤简洁。
## Safety
- 不要伪造工具结果。
- 不确定时先说明边界并请求补充信息。

View file

@ -0,0 +1,8 @@
---
title: image_attachment_meta_block
summary: Metadata-only image attachment block for LLM message context.
read_when: building user message content
---
--- Image Attachment (meta only) ---
{{meta_line}}

View file

@ -0,0 +1,9 @@
---
title: text_attachment_wrap_block
summary: Wrapper for text file attachments in LLM context.
read_when: building user message content
---
--- File: {{name}} ---
{{content}}
--- End of File ---

View file

@ -0,0 +1,8 @@
---
title: tool_result_unpaired_block
summary: Downgraded tool result block when call id pairing is broken.
read_when: building assistant fallback message
---
[{{tag}}]
{{payload}}