oclaw/runtime/tools/experts/workspace/search_tools.py
oliver 4a23b715a2 重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。

Made-with: Cursor
2026-04-25 01:24:23 +08:00

92 lines
3.7 KiB
Python

from __future__ import annotations
import re
from typing import Any
from oclaw.runtime.tools.base import ToolSpec
from oclaw.runtime.tools.experts.workspace.workspace_base import resolve_workspace_path
def grep_tool() -> ToolSpec:
def handler(args: dict[str, Any]) -> dict[str, Any]:
pattern = str(args.get("pattern") or "").strip()
file_glob = str(args.get("file_glob") or "**/*").strip() or "**/*"
max_matches = int(args.get("max_matches") or 200)
if not pattern:
return {"ok": False, "error": "pattern_required"}
root = resolve_workspace_path(".")
try:
rx = re.compile(pattern)
except re.error as e:
return {"ok": False, "error": "invalid_regex", "detail": str(e)}
matches: list[dict[str, Any]] = []
for p in root.glob(file_glob):
if p.is_dir():
continue
try:
text = p.read_text(encoding="utf-8", errors="replace").splitlines()
except Exception:
continue
for i, line in enumerate(text, start=1):
if rx.search(line):
matches.append({"file": str(p.relative_to(root)), "line": i, "text": line[:400]})
if len(matches) >= max(1, min(max_matches, 5000)):
return {"ok": True, "pattern": pattern, "count": len(matches), "matches": matches}
return {"ok": True, "pattern": pattern, "count": len(matches), "matches": matches}
return ToolSpec(
name="grep",
description="Search files in the workspace for a regex pattern.",
parameters={
"type": "object",
"properties": {
"pattern": {"type": "string", "description": "Regex pattern."},
"file_glob": {"type": "string", "default": "**/*", "description": "Glob of files to search."},
"max_matches": {"type": "integer", "default": 200, "description": "Max number of matches."},
},
"required": ["pattern"],
"additionalProperties": False,
},
handler=handler,
tags=frozenset({"workspace"}),
read_only=True,
)
def index_workspace_tool() -> ToolSpec:
def handler(args: dict[str, Any]) -> dict[str, Any]:
max_files = int(args.get("max_files") or 120)
try:
# Lazy import to avoid heavy deps during tool discovery.
from oclaw.platform.config.paths import db_path
except Exception:
pass
# Indexer uses store passed via closure? ToolSpec doesn't carry store.
# We index using the global SqliteStore path (same as app runtime).
try:
from oclaw.platform.persistence.sqlite_store import SqliteStore
from oclaw.platform.config.paths import db_path
from oclaw.runtime.tools.workspace_indexer import index_workspace
store = SqliteStore(db_path())
st = index_workspace(store, max_files=max(1, min(max_files, 800)))
return {"ok": True, "files_seen": st.files_seen, "chunks_upserted": st.chunks_upserted, "embeddings_upserted": st.embeddings_upserted}
except Exception as e:
return {"ok": False, "error": f"{type(e).__name__}: {e}"}
return ToolSpec(
name="index_workspace",
description="Index workspace files into the vector knowledge base for RAG (may be slow).",
parameters={
"type": "object",
"properties": {"max_files": {"type": "integer", "default": 120, "description": "Max files to index."}},
"required": [],
"additionalProperties": False,
},
handler=handler,
tags=frozenset({"workspace", "rag"}),
)
__all__ = ["grep_tool", "index_workspace_tool"]