mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 05:50:44 +08:00
feat: 路径约定体系 & 自优化管道落地
This commit is contained in:
parent
08df5d580a
commit
df30b5902f
37 changed files with 239 additions and 324 deletions
|
|
@ -42,7 +42,7 @@ from oclaw.runtime.session_auto_title import (
|
|||
finalize_auto_title,
|
||||
)
|
||||
from oclaw.runtime.tools.base import ToolRegistry
|
||||
from oclaw.runtime.tools.local_sdk import local_adapter_startup_self_check
|
||||
from oclaw.runtime.tools.public.local_sdk import local_adapter_startup_self_check
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,8 @@ def _run_dep_install(command: list[str], *, cwd: Path) -> tuple[bool, str]:
|
|||
cwd=str(cwd),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=600,
|
||||
check=False,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,12 +42,13 @@ def _session_bootstrap_content_signature(*, skill_binding_role: str | None, work
|
|||
|
||||
targets: list[Path] = []
|
||||
# session-bootstrap static source files
|
||||
targets.append((repo / "runtime" / "skills" / "session-bootstrap" / "SOUL.md").resolve())
|
||||
targets.append((repo / "runtime" / "skills" / "session-bootstrap" / "IDENTITY.md").resolve())
|
||||
targets.append((repo / "skills" / "session-bootstrap" / "SOUL.md").resolve())
|
||||
targets.append((repo / "skills" / "session-bootstrap" / "IDENTITY.md").resolve())
|
||||
# wiki global memory/improvement sources
|
||||
targets.append((repo / "data" / "wiki" / "improvement" / "learnings.md").resolve())
|
||||
targets.append((repo / "data" / "wiki" / "improvement" / "errors.md").resolve())
|
||||
targets.append((repo / "data" / "wiki" / "improvement" / "feature-requests.md").resolve())
|
||||
targets.append((repo / "data" / "wiki" / "users" / "current.md").resolve())
|
||||
|
||||
core_dir = (repo / "data" / "wiki" / "core").resolve()
|
||||
if core_dir.exists() and core_dir.is_dir():
|
||||
|
|
@ -99,6 +100,7 @@ def _unified_skill_policy_guidance() -> str:
|
|||
# Global policy for all agents (including dynamic/ephemeral) — appended to base_system in build_executor_system_prompt.
|
||||
return (
|
||||
"## 技能使用政策(Skill Usage Policy):\n"
|
||||
"- 每次会话启动后,在进行文件/路径相关操作前,必须先读取 `skills/_workspace/public/path-convention/SKILL.md` 了解当前路径规范。\n"
|
||||
"- 如果用户问你有哪些技能(skill/技能),请直接根据已注入的技能目录及其 description 回答。\n"
|
||||
"- 不要为了“列出技能”而去读取 SKILL.md。只有在你确实需要某个技能的详细使用说明时,才读取对应 SKILL.md。\n"
|
||||
"- 当你需要技能细节时,请按目录中给出的 path 读取对应的 SKILL.md。\n"
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
# Canonical memory expert tools live under this directory.
|
||||
from .wiki_tools import (
|
||||
memory_wiki_apply_tool,
|
||||
memory_wiki_get_tool,
|
||||
memory_wiki_lint_tool,
|
||||
memory_wiki_search_tool,
|
||||
memory_wiki_status_tool,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"memory_wiki_apply_tool",
|
||||
"memory_wiki_get_tool",
|
||||
"memory_wiki_lint_tool",
|
||||
"memory_wiki_search_tool",
|
||||
"memory_wiki_status_tool",
|
||||
]
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .adapter import LocalAdapter, LocalAdapterError, get_local_adapter, local_adapter_startup_self_check
|
||||
|
||||
__all__ = ["LocalAdapter", "LocalAdapterError", "get_local_adapter", "local_adapter_startup_self_check"]
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def cd_tool() -> ToolSpec:
|
||||
def _handler(args: dict[str, Any]) -> dict[str, Any]:
|
||||
cwd = str(args.get("cwd") or "").strip()
|
||||
return get_local_adapter().cd(cwd=cwd)
|
||||
|
||||
return ToolSpec(
|
||||
name="cd",
|
||||
description="Change current working directory for local tools (best-effort, per-process).",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"cwd": {"type": "string", "description": "Directory to change into."}},
|
||||
"required": ["cwd"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
handler=_handler,
|
||||
tags=frozenset({"public", "local", "write"}),
|
||||
risk_level="high",
|
||||
timeout_s=5.0,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["cd_tool"]
|
||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def delete_file_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def edit_file_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def get_cwd_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def get_env_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ def _git(command: str, *, cwd: str) -> dict[str, Any]:
|
|||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=60.0,
|
||||
)
|
||||
out = (cp.stdout or "") + (("\n" + cp.stderr) if cp.stderr else "")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def kill_process_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def list_directory_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def list_processes_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ class LocalAdapter:
|
|||
|
||||
def get_cwd(self) -> dict[str, Any]:
|
||||
try:
|
||||
p = resolve_workspace_path(self._cwd or ".")
|
||||
return {"ok": True, "cwd": str(p)}
|
||||
root = resolve_workspace_path(".")
|
||||
return {"ok": True, "cwd": str(root)}
|
||||
except Exception as exc:
|
||||
return {"ok": False, "error_code": "local_execution_error", "error": f"{type(exc).__name__}: {exc}"}
|
||||
|
||||
|
|
@ -105,8 +105,8 @@ class LocalAdapter:
|
|||
try:
|
||||
timeout_s = max(1, min(int(timeout or 300), 600))
|
||||
# run_command never follows adapter cd state.
|
||||
# It only uses explicit cwd; otherwise defaults to data/workspace.
|
||||
workdir = str(resolve_workspace_path(cwd or "data/workspace"))
|
||||
# It only uses explicit cwd; otherwise defaults to workspace root.
|
||||
workdir = str(resolve_workspace_path(cwd or "."))
|
||||
Path(workdir).mkdir(parents=True, exist_ok=True)
|
||||
# Use UTF-8 for decoded streams: Windows defaults (e.g. GBK) break on UTF-8-only bytes from
|
||||
# curl/wttr.in, git, ripgrep, etc.; subprocess may leave stdout None after UnicodeDecodeError.
|
||||
|
|
@ -370,6 +370,8 @@ class LocalAdapter:
|
|||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=10.0,
|
||||
)
|
||||
out = str(cp.stdout or "")
|
||||
|
|
@ -393,6 +395,8 @@ class LocalAdapter:
|
|||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=10.0,
|
||||
)
|
||||
for line in str(cp.stdout or "").splitlines():
|
||||
|
|
@ -418,7 +422,7 @@ class LocalAdapter:
|
|||
try:
|
||||
if os.name == "nt":
|
||||
cmd = f"taskkill /PID {p} " + ("/F" if force else "")
|
||||
cp = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10.0)
|
||||
cp = subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=10.0)
|
||||
ok = cp.returncode == 0
|
||||
return {"ok": ok, "pid": p, "stdout": str(cp.stdout or ""), "stderr": str(cp.stderr or ""), "exit_code": int(cp.returncode)}
|
||||
import signal
|
||||
|
|
@ -462,5 +466,3 @@ def local_adapter_startup_self_check() -> dict[str, Any]:
|
|||
"error": f"{type(exc).__name__}: {exc}",
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["LocalAdapter", "LocalAdapterError", "get_local_adapter", "local_adapter_startup_self_check"]
|
||||
|
|
@ -31,8 +31,6 @@ def _wiki_handlers() -> dict[str, Callable[[dict[str, Any]], dict[str, Any]]]:
|
|||
if spec is None or spec.loader is None:
|
||||
return {}
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
# Register module before exec so decorators (e.g., @dataclass) can resolve
|
||||
# cls.__module__ via sys.modules during import-time processing.
|
||||
sys.modules[spec.name] = mod
|
||||
spec.loader.exec_module(mod) # type: ignore[assignment]
|
||||
fn = getattr(mod, "build_wiki_tool_specs", None)
|
||||
|
|
@ -67,7 +65,8 @@ def _status_tool(public_name: str, desc: str) -> ToolSpec:
|
|||
description=desc,
|
||||
parameters={"type": "object", "properties": {}, "required": [], "additionalProperties": False},
|
||||
handler=lambda args: _delegate("wiki_status", args),
|
||||
tags=frozenset({"memory", "wiki", "curator"}),
|
||||
tags=frozenset({"wiki", "curator"}),
|
||||
risk_level="low",
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
|
|
@ -87,7 +86,8 @@ def _get_tool(public_name: str, desc: str) -> ToolSpec:
|
|||
"additionalProperties": False,
|
||||
},
|
||||
handler=lambda args: _delegate("wiki_get", args),
|
||||
tags=frozenset({"memory", "wiki", "curator"}),
|
||||
tags=frozenset({"wiki", "curator"}),
|
||||
risk_level="low",
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
|
|
@ -108,7 +108,8 @@ def _search_tool(public_name: str, desc: str) -> ToolSpec:
|
|||
"additionalProperties": False,
|
||||
},
|
||||
handler=lambda args: _delegate("wiki_search", args),
|
||||
tags=frozenset({"memory", "wiki", "curator"}),
|
||||
tags=frozenset({"wiki", "curator"}),
|
||||
risk_level="low",
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
|
|
@ -124,7 +125,8 @@ def _lint_tool(public_name: str, desc: str) -> ToolSpec:
|
|||
"additionalProperties": False,
|
||||
},
|
||||
handler=lambda args: _delegate("wiki_lint", args),
|
||||
tags=frozenset({"memory", "wiki", "curator"}),
|
||||
tags=frozenset({"wiki", "curator"}),
|
||||
risk_level="low",
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
|
|
@ -144,8 +146,8 @@ def _apply_tool(public_name: str, desc: str) -> ToolSpec:
|
|||
"additionalProperties": False,
|
||||
},
|
||||
handler=lambda args: _delegate("wiki_apply", args),
|
||||
tags=frozenset({"memory", "wiki", "curator", "write"}),
|
||||
risk_level="high",
|
||||
tags=frozenset({"wiki", "curator", "write"}),
|
||||
risk_level="low",
|
||||
read_only=False,
|
||||
)
|
||||
|
||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def mkdir_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def move_file_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from oclaw.runtime.tools.base import ToolSpec
|
|||
|
||||
def _ffmpeg_exists() -> bool:
|
||||
try:
|
||||
p = subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True, timeout=3)
|
||||
p = subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=3)
|
||||
return p.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
|
@ -41,6 +41,8 @@ def _ffprobe_json(path: Path) -> dict[str, Any] | None:
|
|||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=8,
|
||||
)
|
||||
if p.returncode != 0:
|
||||
|
|
@ -179,6 +181,8 @@ def query_video_attachment_tool() -> ToolSpec:
|
|||
["ffmpeg", "-y", "-i", str(p), "-vn", "-ac", "1", "-ar", "16000", str(wav)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
timeout=60,
|
||||
)
|
||||
if not wav.exists() or wav.stat().st_size <= 0:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def run_command_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def search_files_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from oclaw.runtime.tools.base import ToolSpec
|
||||
from oclaw.runtime.tools.local_sdk import get_local_adapter
|
||||
from oclaw.runtime.tools.public.local_sdk import get_local_adapter
|
||||
|
||||
|
||||
def set_env_tool() -> ToolSpec:
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ def _run(argv: list[str], *, cwd: Path, stdin_json: dict[str, Any], timeout_s: f
|
|||
argv,
|
||||
input=json.dumps(stdin_json, ensure_ascii=False),
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
capture_output=True,
|
||||
cwd=str(cwd),
|
||||
timeout=max(1, int(timeout_s)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue