mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-10 04:00:44 +08:00
重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
This commit is contained in:
parent
ba3836f00f
commit
4a23b715a2
498 changed files with 2760 additions and 2200 deletions
146
runtime/hooks/bundled/bootstrap-extra-files/handler.py
Normal file
146
runtime/hooks/bundled/bootstrap-extra-files/handler.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable, List, Sequence
|
||||
|
||||
|
||||
HOOK_KEY = "bootstrap-extra-files"
|
||||
_ALLOWED_BASENAMES = {
|
||||
"AGENTS.md",
|
||||
"SOUL.md",
|
||||
"TOOLS.md",
|
||||
"IDENTITY.md",
|
||||
"USER.md",
|
||||
"HEARTBEAT.md",
|
||||
"BOOTSTRAP.md",
|
||||
"MEMORY.md",
|
||||
"memory.md",
|
||||
}
|
||||
|
||||
|
||||
def _resolve_hook_cfg(cfg: Any) -> Dict[str, Any]:
|
||||
if not isinstance(cfg, dict):
|
||||
return {}
|
||||
hooks = cfg.get("hooks") if isinstance(cfg.get("hooks"), dict) else {}
|
||||
internal = hooks.get("internal") if isinstance(hooks.get("internal"), dict) else {}
|
||||
entries = internal.get("entries") if isinstance(internal.get("entries"), dict) else {}
|
||||
row = entries.get(HOOK_KEY)
|
||||
return row if isinstance(row, dict) else {}
|
||||
|
||||
|
||||
def _string_list(v: Any) -> List[str]:
|
||||
if isinstance(v, list):
|
||||
out = []
|
||||
for x in v:
|
||||
s = str(x or "").strip()
|
||||
if s:
|
||||
out.append(s)
|
||||
return out
|
||||
if isinstance(v, str):
|
||||
s = v.strip()
|
||||
return [s] if s else []
|
||||
return []
|
||||
|
||||
|
||||
def _patterns(hook_cfg: Dict[str, Any]) -> List[str]:
|
||||
for k in ("paths", "patterns", "files"):
|
||||
got = _string_list(hook_cfg.get(k))
|
||||
if got:
|
||||
return got
|
||||
return []
|
||||
|
||||
|
||||
def _is_within(root: Path, candidate: Path) -> bool:
|
||||
try:
|
||||
candidate.resolve().relative_to(root.resolve())
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _glob_paths(root: Path, patterns: Sequence[str]) -> List[Path]:
|
||||
"""
|
||||
We intentionally do not use Path.glob on arbitrary patterns that might escape roots via '..'.
|
||||
Instead: enumerate candidates by rglob and fnmatch on posix-style relative paths.
|
||||
"""
|
||||
if not root.exists() or not root.is_dir():
|
||||
return []
|
||||
|
||||
# Pre-normalize patterns to forward-slash for fnmatch
|
||||
raw_pats = [p.replace("\\", "/").lstrip("/") for p in patterns if str(p or "").strip()]
|
||||
pats: list[str] = []
|
||||
for pat in raw_pats:
|
||||
pats.append(pat)
|
||||
# Python's fnmatch doesn't treat "**/" as "zero-or-more directories".
|
||||
# Add a compatibility variant so "**/AGENTS.md" matches "AGENTS.md" as well.
|
||||
if pat.startswith("**/") and len(pat) > 3:
|
||||
pats.append(pat[3:])
|
||||
if not pats:
|
||||
return []
|
||||
|
||||
out: List[Path] = []
|
||||
try:
|
||||
for p in root.rglob("*"):
|
||||
if not p.is_file():
|
||||
continue
|
||||
if p.name not in _ALLOWED_BASENAMES:
|
||||
continue
|
||||
rel = p.relative_to(root).as_posix()
|
||||
if any(fnmatch.fnmatch(rel, pat) for pat in pats):
|
||||
out.append(p)
|
||||
except Exception:
|
||||
return out
|
||||
|
||||
# deterministic order
|
||||
out.sort(key=lambda x: x.as_posix())
|
||||
return out
|
||||
|
||||
|
||||
def handle(event: Any) -> None:
|
||||
if getattr(event, "type", None) != "agent" or getattr(event, "action", None) != "bootstrap":
|
||||
return
|
||||
|
||||
ctx = getattr(event, "context", None)
|
||||
if not isinstance(ctx, dict):
|
||||
return
|
||||
|
||||
hook_cfg = _resolve_hook_cfg(ctx.get("cfg"))
|
||||
if hook_cfg.get("enabled") is False:
|
||||
return
|
||||
|
||||
patterns = _patterns(hook_cfg)
|
||||
if not patterns:
|
||||
return
|
||||
|
||||
ws = ctx.get("workspaceDir")
|
||||
if not isinstance(ws, str) or not ws.strip():
|
||||
return
|
||||
ws_root = Path(ws).expanduser()
|
||||
if not ws_root.exists() or not ws_root.is_dir():
|
||||
return
|
||||
|
||||
matches = _glob_paths(ws_root, patterns)
|
||||
if not matches:
|
||||
return
|
||||
|
||||
# Mutate context.bootstrapFiles (Oclaw-style).
|
||||
boot = ctx.get("bootstrapFiles")
|
||||
if not isinstance(boot, list):
|
||||
boot = []
|
||||
ctx["bootstrapFiles"] = boot
|
||||
|
||||
existing_paths = set()
|
||||
for it in list(boot):
|
||||
if isinstance(it, dict) and isinstance(it.get("path"), str):
|
||||
existing_paths.add(it["path"])
|
||||
elif isinstance(it, str):
|
||||
existing_paths.add(it)
|
||||
|
||||
for p in matches:
|
||||
ap = str(p.resolve())
|
||||
if ap in existing_paths:
|
||||
continue
|
||||
boot.append({"path": ap, "name": p.name})
|
||||
existing_paths.add(ap)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue