chore(fleet): sync operator memory, hatch menu dialogs, and watchdog alerts
This commit is contained in:
@@ -85,13 +85,152 @@ def _looks_like_shell(task_text):
|
||||
if "/" in first:
|
||||
return os.path.isfile(first) and os.access(first, os.X_OK)
|
||||
# If it's a bare command name, it must exist in standard system bin paths
|
||||
for p in ("/bin", "/usr/bin", "/usr/local/bin"):
|
||||
for p in ("/bin", "/usr/bin", "/usr/local/bin", "/home/super/Projects/NetVM/bin", "/home/super/.local/bin"):
|
||||
candidate = os.path.join(p, first)
|
||||
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def extract_shell_command(task_text):
|
||||
"""Extract an executable shell command from task text if present."""
|
||||
t = (task_text or "").strip()
|
||||
if not t:
|
||||
return None
|
||||
if _looks_like_shell(t):
|
||||
return t
|
||||
# Check for "Run: <cmd>" or "Execute this shell command...: <cmd>"
|
||||
m = re.search(r"(?:Run|Execute)(?:\s+this\s+shell\s+command(?:\s+and\s+report\s+its\s+full\s+output)?)?:\s*[`'\"]?([^`'\n]+)[`'\"]?", t, re.IGNORECASE)
|
||||
if m:
|
||||
candidate = m.group(1).strip()
|
||||
if candidate:
|
||||
return candidate
|
||||
# Check for markdown code blocks ```bash ... ``` or ```sh ... ```
|
||||
m = re.search(r"```(?:bash|sh)?\n(.*?)\n```", t, re.DOTALL)
|
||||
if m:
|
||||
candidate = m.group(1).strip()
|
||||
if candidate:
|
||||
return candidate
|
||||
# Check for single backticked command
|
||||
m = re.search(r"`([^`\n]+)`", t)
|
||||
if m:
|
||||
candidate = m.group(1).strip()
|
||||
if _looks_like_shell(candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
TMUX_SOCKET = "/tmp/tmux-muse.sock"
|
||||
TMUX_LOG_DIR = "/home/super/Projects/NetVM/logs/tmux"
|
||||
|
||||
|
||||
def execute_task_in_tmux(session_name, cmd_str, timeout=300):
|
||||
"""Execute a task inside a dedicated tmux session on /tmp/tmux-muse.sock.
|
||||
|
||||
Captures output to logs/tmux/{session_name}.log, tracks return code via
|
||||
status file, and returns:
|
||||
dict(success=bool, output=str, duration_s=float, error=str|None)
|
||||
"""
|
||||
os.makedirs(TMUX_LOG_DIR, exist_ok=True)
|
||||
started = time.monotonic()
|
||||
log_file = os.path.join(TMUX_LOG_DIR, f"{session_name}.log")
|
||||
exit_file = f"/tmp/{session_name}.exit"
|
||||
script_file = f"/tmp/{session_name}.sh"
|
||||
|
||||
# Clean up prior artifacts
|
||||
for f in (exit_file, script_file):
|
||||
try:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Write wrapper script
|
||||
with open(script_file, "w", encoding="utf-8") as sf:
|
||||
sf.write("#!/usr/bin/env bash\n")
|
||||
sf.write("export PATH=\"/home/super/Projects/NetVM/bin:/home/super/.local/bin:/usr/local/bin:/usr/bin:/bin:$PATH\"\n")
|
||||
sf.write("cd /home/super/Projects/NetVM\n")
|
||||
sf.write(f"{cmd_str}\n")
|
||||
sf.write(f"echo $? > \"{exit_file}\"\n")
|
||||
os.chmod(script_file, 0o755)
|
||||
|
||||
# Kill any existing session with this name
|
||||
subprocess.run(["tmux", "-S", TMUX_SOCKET, "kill-session", "-t", session_name],
|
||||
capture_output=True)
|
||||
|
||||
# Start tmux session
|
||||
tmux_cmd = f"bash \"{script_file}\" > \"{log_file}\" 2>&1"
|
||||
res = subprocess.run(
|
||||
["tmux", "-S", TMUX_SOCKET, "new-session", "-d", "-s", session_name, tmux_cmd],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if res.returncode != 0:
|
||||
dur = round(time.monotonic() - started, 3)
|
||||
return {
|
||||
"success": False,
|
||||
"output": "",
|
||||
"duration_s": dur,
|
||||
"error": f"Failed to create tmux session: {res.stderr.strip()}",
|
||||
}
|
||||
|
||||
# Poll for completion or timeout
|
||||
deadline = started + timeout
|
||||
rc = None
|
||||
while time.monotonic() < deadline:
|
||||
if os.path.exists(exit_file):
|
||||
try:
|
||||
with open(exit_file, "r") as ef:
|
||||
rc = int(ef.read().strip())
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
check = subprocess.run(
|
||||
["tmux", "-S", TMUX_SOCKET, "has-session", "-t", session_name],
|
||||
capture_output=True
|
||||
)
|
||||
if check.returncode != 0 and os.path.exists(exit_file):
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
dur = round(time.monotonic() - started, 3)
|
||||
|
||||
# Clean up tmux session if still running
|
||||
subprocess.run(["tmux", "-S", TMUX_SOCKET, "kill-session", "-t", session_name],
|
||||
capture_output=True)
|
||||
|
||||
# Read output log
|
||||
output = ""
|
||||
if os.path.exists(log_file):
|
||||
try:
|
||||
with open(log_file, "r", encoding="utf-8", errors="replace") as lf:
|
||||
output = lf.read()[:OUTPUT_TRUNCATE]
|
||||
except Exception as e:
|
||||
output = f"Error reading log: {e}"
|
||||
|
||||
# Cleanup temporary script and exit file
|
||||
for f in (exit_file, script_file):
|
||||
try:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if rc is None:
|
||||
return {
|
||||
"success": False,
|
||||
"output": output,
|
||||
"duration_s": dur,
|
||||
"error": f"timeout: exceeded {timeout}s in tmux session",
|
||||
}
|
||||
|
||||
return {
|
||||
"success": (rc == 0),
|
||||
"output": output,
|
||||
"duration_s": dur,
|
||||
"error": None if (rc == 0) else f"exit code {rc}",
|
||||
}
|
||||
|
||||
|
||||
def _refused(task_text):
|
||||
return bool(_REFUSE_RE.search(task_text))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user