Split monolithic tai.py into tai_pkg package, keep tai-orig.py as original
tai.py (8643 lines) is now a thin facade over tai_pkg/ (33 feature modules, one class or feature per file). tai-orig.py preserves the pre-split single-file version byte for byte (sha256 verified against snapshot); import tai and ./tai.py behavior unchanged. Equivalence proof, all run on this tree immediately pre-commit: differential battery tai-orig.py vs facade 3718 checks 0 failures (surface, pure fuzz, tool dispatch, store/audit, release ops, fake-HOME install roundtrip, CLI runs); test_tai.py 261 OK; test_seal.py 16 OK; --version/--help byte-identical; dir(tai) zero removals. Fix included: opencode wire-protocol schema echo leaked into replies; opencode_parse now scans with JSONDecoder.raw_decode capped at 200 scans (13 OpencodeParseTests). Test count 261 = 237 HEAD + 8 pre-existing uncommitted ToolArgumentsTests (now committed with this change, disclosed) + 13 OpencodeParseTests + 3 EmbeddedStringsTests. Patch-target-only test migration (24 hunks), zero assertion changes. tai_pkg clean-regenerated after mid-verification tamper was found and reverted.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Host inspection probes and report assembly."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from tai_pkg.consts import Consts
|
||||
from tai_pkg.sandbox import Sandbox
|
||||
from tai_pkg.text import Text
|
||||
|
||||
__all__ = ["SysInfo", "SYSINFO_CHECKS"]
|
||||
|
||||
|
||||
class SysInfo:
|
||||
"""SysInfo."""
|
||||
|
||||
@staticmethod
|
||||
def tmux_pane_header():
|
||||
try:
|
||||
info = subprocess.run(["tmux", "display-message", "-p", "#{session_name}:#{window_index}.#{pane_index} #{pane_current_command}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=10)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return ""
|
||||
if info.returncode == 0 and info.stdout.strip():
|
||||
return info.stdout.strip()
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_os():
|
||||
try:
|
||||
uname = os.uname()
|
||||
base = "%s %s %s" % (uname.sysname, uname.release, uname.machine)
|
||||
except (AttributeError, OSError):
|
||||
base = sys.platform
|
||||
try:
|
||||
with open("/etc/os-release", "r", encoding="utf-8") as handle:
|
||||
fields = dict(line.strip().split("=", 1) for line in handle if "=" in line)
|
||||
pretty = fields.get("PRETTY_NAME", "").strip('"')
|
||||
if pretty:
|
||||
return "%s (%s)" % (pretty, base)
|
||||
except OSError:
|
||||
pass
|
||||
return base
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_python():
|
||||
return "%s (%s)" % (sys.version.split()[0], sys.executable)
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_venv():
|
||||
env_path = os.environ.get("VIRTUAL_ENV") or ""
|
||||
if env_path:
|
||||
return "yes (%s)" % env_path
|
||||
if sys.prefix != sys.base_prefix:
|
||||
return "yes (%s)" % sys.prefix
|
||||
return "no (system python at %s)" % sys.prefix
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_root():
|
||||
geteuid = getattr(os, "geteuid", None)
|
||||
if geteuid is None:
|
||||
return "unknown (no geteuid on %s)" % sys.platform
|
||||
uid = geteuid()
|
||||
return "yes (uid 0)" if uid == 0 else "no (uid %d)" % uid
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_container():
|
||||
engine = Sandbox.container_engine()
|
||||
if not engine:
|
||||
return "no engine (install podman or docker)"
|
||||
return "%s, box %s" % (engine, Sandbox.box_state(engine))
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_binaries():
|
||||
found = []
|
||||
lost = []
|
||||
for name in Consts.SYSINFO_BINARIES:
|
||||
(found if shutil.which(name) else lost).append(name)
|
||||
return "present: %s; missing: %s" % (", ".join(found) or "none", ", ".join(lost) or "none")
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_cpu():
|
||||
count = os.cpu_count() or 0
|
||||
try:
|
||||
one, five, fifteen = os.getloadavg()
|
||||
return "%d cores, load %.2f/%.2f/%.2f" % (count, one, five, fifteen)
|
||||
except OSError:
|
||||
return "%d cores" % count
|
||||
|
||||
@staticmethod
|
||||
def sysinfo_check_disk():
|
||||
usage = shutil.disk_usage(os.getcwd())
|
||||
return "%.1f GiB free of %.1f GiB at %s" % (usage.free / 2 ** 30, usage.total / 2 ** 30, os.getcwd())
|
||||
|
||||
@staticmethod
|
||||
def collect_sysinfo(names=None):
|
||||
wanted = [pair for pair in SYSINFO_CHECKS if names is None or pair[0] in names]
|
||||
results = {}
|
||||
|
||||
def target(name, func):
|
||||
started = time.time()
|
||||
try:
|
||||
value = func()
|
||||
except Exception as exc:
|
||||
value = "error: " + Text.short_error(exc)
|
||||
results[name] = (value, int((time.time() - started) * 1000))
|
||||
|
||||
workers = [threading.Thread(target=target, args=(name, func), daemon=True) for name, func in wanted]
|
||||
for worker in workers:
|
||||
worker.start()
|
||||
deadline = time.time() + Consts.SYSINFO_TIMEOUT
|
||||
for worker in workers:
|
||||
worker.join(timeout=max(0, deadline - time.time()))
|
||||
lines = []
|
||||
for name, _func in wanted:
|
||||
if name in results:
|
||||
value, elapsed = results[name]
|
||||
lines.append("%s: %s (%dms)" % (name, value, elapsed))
|
||||
else:
|
||||
lines.append("%s: timed out after %ds" % (name, Consts.SYSINFO_TIMEOUT))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
SYSINFO_CHECKS = (
|
||||
("os", SysInfo.sysinfo_check_os),
|
||||
("python", SysInfo.sysinfo_check_python),
|
||||
("venv", SysInfo.sysinfo_check_venv),
|
||||
("root", SysInfo.sysinfo_check_root),
|
||||
("container", SysInfo.sysinfo_check_container),
|
||||
("binaries", SysInfo.sysinfo_check_binaries),
|
||||
("cpu", SysInfo.sysinfo_check_cpu),
|
||||
("disk", SysInfo.sysinfo_check_disk),
|
||||
)
|
||||
Reference in New Issue
Block a user