mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 17:50:47 +08:00
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
92 lines
3.7 KiB
Python
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"]
|
|
|