oclaw/runtime/tools/experts/workspace/shell_tools.py
oliver dbbe3add6a 重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。

Made-with: Cursor
2026-04-26 08:34:33 +08:00

113 lines
4.3 KiB
Python

from __future__ import annotations
import subprocess
from typing import Any
from oclaw.platform.config.paths import db_path
from oclaw.platform.persistence.sqlite_store import SqliteStore
from oclaw.runtime.tools.base import ToolSpec
from oclaw.runtime.tools.experts.workspace.workspace_base import resolve_workspace_path, truncate_text
def run_command_tool() -> ToolSpec:
def _run_command_enabled() -> bool:
import os
try:
raw_setting = str(SqliteStore(db_path()).get_setting("AIA_ENABLE_RUN_COMMAND") or "").strip().lower()
if raw_setting in ("0", "false", "no", "off"):
return False
if raw_setting in ("1", "true", "yes", "on"):
return True
except Exception:
pass
raw_env = str(os.getenv("AIA_ENABLE_RUN_COMMAND") or "").strip().lower()
if raw_env in ("0", "false", "no", "off"):
return False
if raw_env in ("1", "true", "yes", "on"):
return True
# Default disabled when unset (explicit opt-in only).
return False
def handler(args: dict[str, Any]) -> dict[str, Any]:
import os
if not _run_command_enabled():
return {
"ok": False,
"error": "disabled",
"hint": "Enable run_command in Admin -> Plugins -> Tool Policy.",
}
command = str(args.get("command") or "").strip()
cwd = str(args.get("cwd") or "").strip()
timeout_s = float(args.get("timeout_s") or 30.0)
max_output_chars = int(args.get("max_output_chars") or 20000)
if not command:
return {"ok": False, "error": "command_required"}
workdir = resolve_workspace_path(cwd or ".")
try:
run_kwargs: dict[str, Any] = {
"cwd": str(workdir),
"shell": True,
"capture_output": True,
"text": True,
"timeout": max(1.0, min(timeout_s, 600.0)),
}
if os.name == "nt":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = 0 # SW_HIDE
run_kwargs["startupinfo"] = startupinfo
run_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
cp = subprocess.run(
command,
**run_kwargs,
)
out = (cp.stdout or "") + (("\n" + cp.stderr) if cp.stderr else "")
out = truncate_text(out, limit=max(1000, min(max_output_chars, 200000)))
return {
"ok": True,
"command": command,
"cwd": str(workdir),
"exit_code": int(cp.returncode),
"output": out,
}
except subprocess.TimeoutExpired as e:
partial = ""
try:
partial = ((e.stdout or "") + ("\n" + (e.stderr or "") if e.stderr else "")).strip()
except Exception:
partial = ""
return {
"ok": False,
"error": "timeout",
"command": command,
"cwd": str(workdir),
"timeout_s": timeout_s,
"output": truncate_text(partial, limit=max_output_chars),
}
except Exception as e:
return {"ok": False, "error": f"{type(e).__name__}: {e}", "command": command, "cwd": str(workdir)}
return ToolSpec(
name="run_command",
description="Run a shell command inside the workspace (captured output, timeout).",
parameters={
"type": "object",
"properties": {
"command": {"type": "string", "description": "Shell command to run."},
"cwd": {"type": "string", "description": "Working directory relative to workspace.", "default": "."},
"timeout_s": {"type": "number", "default": 30.0, "description": "Command timeout in seconds."},
"max_output_chars": {"type": "integer", "default": 20000, "description": "Max characters to return."},
},
"required": ["command"],
"additionalProperties": False,
},
handler=handler,
tags=frozenset({"workspace", "exec"}),
)
__all__ = ["run_command_tool"]