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:
Scott Idem
2026-04-28 20:29:46 -04:00
parent 8baab874f1
commit 1cc7988953
5 changed files with 101 additions and 6 deletions

View File

@@ -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}"