feat: add shell_exec tool and fix orchestrator model name resolution
- Add shell_exec to orchestrator tool suite (system.py + __init__.py) Runs arbitrary shell commands on the Cortex host with timeout (1–120s), combined stdout/stderr output, optional working_dir, and exit code reporting. Enables system diagnostics (df, ls, ps, journalctl, etc.) from Agent mode. - Fix orchestrator_engine.run() to use model_name from resolved registry entry Previously used settings.orchestrator_model (.env hardcode) regardless of what model was assigned to the orchestrator role. Now accepts model_name param and falls back to settings value only when registry has no model_name. - Update ARCH__FUTURE.md: date, running host, local orchestrator status, model registry V2 progress, added Cortex Mesh concept (section 9) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ These tools affect the host system directly. Use with care.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,3 +43,43 @@ async def claude_allow_dir(path: str, mode: str = "rw") -> str:
|
||||
except Exception as e:
|
||||
logger.error("claude_allow_dir error: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
async def shell_exec(command: str, working_dir: str | None = None, timeout: int = 30) -> str:
|
||||
"""Execute a shell command on the Cortex host and return combined stdout/stderr."""
|
||||
timeout = min(max(timeout, 1), 120)
|
||||
|
||||
cwd = None
|
||||
if working_dir:
|
||||
cwd = os.path.expanduser(working_dir)
|
||||
if not os.path.isdir(cwd):
|
||||
return f"Error: working_dir '{working_dir}' does not exist or is not a directory"
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=cwd,
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
|
||||
out = stdout.decode(errors="replace").strip()
|
||||
err = stderr.decode(errors="replace").strip()
|
||||
|
||||
parts = []
|
||||
if out:
|
||||
parts.append(out)
|
||||
if err:
|
||||
parts.append(f"[stderr]\n{err}")
|
||||
combined = "\n".join(parts) if parts else "(no output)"
|
||||
|
||||
if proc.returncode != 0:
|
||||
return f"Exit {proc.returncode}:\n{combined}"
|
||||
return combined
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return f"Error: command timed out after {timeout}s"
|
||||
except Exception as e:
|
||||
logger.error("shell_exec error: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
Reference in New Issue
Block a user