From f56aab837001623a85ab84efa66f05d10d67f9a5 Mon Sep 17 00:00:00 2001 From: "neo.webmaster.2@gmail.com" Date: Sun, 16 Nov 2025 23:07:22 +0100 Subject: [PATCH] "Auto-commit via make git" --- CHANGELOG.md | 91 +++++- src/_nfdos/kernel/neurotron/.gitignore | 30 ++ .../neurotron/src/neurotron/__main__.py | 8 +- .../neurotron/src/neurotron/autodiagnostic.py | 195 ++++++------- .../kernel/neurotron/src/neurotron/cortex.py | 266 ++++++++++-------- .../neurotron/src/neurotron/hippocampus.py | 21 +- .../kernel/neurotron/src/neurotron/logbus.py | 83 ++++++ .../kernel/neurotron/src/neurotron/motor.py | 80 +++++- .../kernel/neurotron/src/neurotron/neuron.py | 52 +++- .../src/neurotron/neurotron_config.py | 81 ++---- .../neurotron/src/neurotron/perception.py | 92 ++++-- src/tui/menu_kernel.py | 22 -- 12 files changed, 656 insertions(+), 365 deletions(-) create mode 100644 src/_nfdos/kernel/neurotron/.gitignore create mode 100644 src/_nfdos/kernel/neurotron/src/neurotron/logbus.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ffd3e..4e0e88f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,93 @@ cat -A configure.ac | grep '\^I' nl -ba Makefile | sed -n '770,790p' grep -n "^[ ]" Makefile | head -agora vamos descansar um pouco 😘 ja temos o kernel neurotron nativo sem panicar o kernel linux...ja avancamos bastante. agora chegou a hora de descansarmos 😍😘 +ajustei assim: ``` -``` \ No newline at end of file + # Criar disco (se não existir) + data_disk = nfdos_dir / "nfdos_data.img" + if not data_disk.exists(): + console.print("[cyan]💽 Criando disco persistente (hipocampo físico)...[/cyan]") + safe_run(f"dd if=/dev/zero of={data_disk} bs=1M count=512", shell=True) + else: + console.print("[green]✔ Disco persistente já existe.[/green]") + + time.sleep(5) + + # Testar no QEMU + bz_image = linux_dir / "arch" / "x86" / "boot" / "bzImage" + data_disk = nfdos_dir / "nfdos_data.img" + + if bz_image.exists(): + console.print("\n[bold blue]Iniciando QEMU (modo kernel direto)...[/bold blue]") + + # 🧠 Monta a linha base do QEMU + kernel_params = ( + "console=ttyS0 earlyprintk=serial,ttyS0,115200 " + "keep_bootcon loglevel=8" + ) + + qemu_cmd = ( + f"qemu-system-x86_64 " + f"-machine q35,accel=kvm " + f"-cpu qemu64 " + f"-kernel {bz_image} " + f"-initrd {nfdos_dir}/initramfs.cpio.gz " + f"-append '{kernel_params}' " + f"-drive file={data_disk},if=virtio,format=raw " + f"-m 1024 " + f"-nographic " + f"-no-reboot" + ) + + # 🚀 Executa o QEMU + safe_run(qemu_cmd, shell=True) + + else: + console.print("[red]✗ bzImage não encontrado! Compile o kernel primeiro.[/red]") +``` +creio que agora sim temos uma base solida e limpa. +o ultimo detalhe ... limpar a tree do neurotron: +``` +src/_nfdos/kernel/neurotron +├── aclocal.m4 +├── autom4te.cache +│ ├── output.0 +│ ├── output.1 +│ ├── requests +│ ├── traces.0 +│ └── traces.1 +├── config.log +├── config.status +├── configure +├── configure.ac +├── data # Estamos a usar em algum lado? ou podemos remover? +│ ├── configs # Estamos a usar em algum lado? ou podemos remover? +│ └── logs # Estamos a usar em algum lado? ou podemos remover? +├── install-sh +├── Makefile +├── Makefile.am +├── Makefile.in +├── MANIFEST.in +├── missing +├── neurotron +├── neurotron.in +├── pyproject.toml +├── README.md +├── Setup.py +└── src + └── neurotron + ├── autodiagnostic.py + ├── cortex.py + ├── disk_init.py + ├── hippocampus.py + ├── __init__.py + ├── logbus.py + ├── __main__.py + ├── main_waiting.py # podemos remover + ├── motor.py + ├── neuron.py + ├── neurotron_config.py + ├── perception.py + └── telemetry_tail.py # podemos remover +``` +e ja agora aproveitar e adicionar um .gitignore \ No newline at end of file diff --git a/src/_nfdos/kernel/neurotron/.gitignore b/src/_nfdos/kernel/neurotron/.gitignore new file mode 100644 index 0000000..17d9f64 --- /dev/null +++ b/src/_nfdos/kernel/neurotron/.gitignore @@ -0,0 +1,30 @@ +# Autotools +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +config.log +config.status +configure +install-sh +missing + +# Python build artefacts +__pycache__/ +*.pyc +*.pyo +*.pyd +*.egg-info/ +build/ +dist/ + +# QEMU files +*.img +*.qcow2 + +# Editor junk +*.swp +*.swo +*~ +.idea/ +.vscode/ diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/__main__.py b/src/_nfdos/kernel/neurotron/src/neurotron/__main__.py index 33605ca..aa042f7 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/__main__.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/__main__.py @@ -38,6 +38,7 @@ from .neurotron_config import ( ) from .cortex import Cortex from .autodiagnostic import AutoDiagnostic # se for usado dentro do Cortex, ok mesmo assim +from neurotron.logbus import logbus # ======================================================================================= @@ -66,19 +67,18 @@ def detect_persistent_mode(): if _mounted(mount_point): os.environ["NEUROTRON_MODE"] = "persistent" os.environ["NEUROTRON_RUNTIME"] = "/var/neurotron/data" - os.environ["NEUROTRON_LOG"] = "/var/neurotron/logs" + os.environ["NEUROTRON_LOG_DIR"] = "/var/neurotron/logs" else: os.environ["NEUROTRON_MODE"] = "volatile" os.environ["NEUROTRON_RUNTIME"] = "/tmp/neurotron_data" - os.environ["NEUROTRON_LOG"] = "/tmp/neurotron_logs" + os.environ["NEUROTRON_LOG_DIR"] = "/tmp/neurotron_logs" runtime_dir = Path(os.environ["NEUROTRON_RUNTIME"]) - log_dir = Path(os.environ["NEUROTRON_LOG"]) + log_dir = Path(os.environ["NEUROTRON_LOG_DIR"]) runtime_dir.mkdir(parents=True, exist_ok=True) log_dir.mkdir(parents=True, exist_ok=True) return runtime_dir, log_dir - def read_system_metrics(): """ Lê CPU, memória e loadavg a partir de /proc. diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/autodiagnostic.py b/src/_nfdos/kernel/neurotron/src/neurotron/autodiagnostic.py index 0646a59..be87389 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/autodiagnostic.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/autodiagnostic.py @@ -1,67 +1,87 @@ from __future__ import annotations -import json, os +import json from datetime import datetime, timezone -from rich.console import Console -from rich.table import Table from pathlib import Path +from neurotron.logbus import logbus from .neurotron_config import ( - NEUROTRON_DATASET_PATH, NEUROTRON_HISTORY_KEEP, NEUROTRON_DIAG_SCHEMA, - HOMEOSTASIS_CPU_WARN, HOMEOSTASIS_CPU_ALERT, - HOMEOSTASIS_MEM_WARN, HOMEOSTASIS_MEM_ALERT, - HOMEOSTASIS_LOAD_WARN, HOMEOSTASIS_LOAD_ALERT, + NEUROTRON_DATASET_PATH, + NEUROTRON_HISTORY_KEEP, + NEUROTRON_DIAG_SCHEMA, + HOMEOSTASIS_CPU_WARN, + HOMEOSTASIS_CPU_ALERT, + HOMEOSTASIS_MEM_WARN, + HOMEOSTASIS_MEM_ALERT, + HOMEOSTASIS_LOAD_WARN, + HOMEOSTASIS_LOAD_ALERT, ) from .perception import Perception -console = Console() def _now_iso(): return datetime.now(timezone.utc).isoformat() + class AutoDiagnostic: + """ + Subsistema silencioso de diagnóstico do Neurotron. + + Responsabilidades: + - recolher perceções internas (CPU, MEM, LOAD) + - comparar com diagnóstico anterior + - atualizar last_diagnostic.json + - manter histórico (rolling) + - atualizar telemetria contínua + - emitir eventos para logbus (não para stdout) + """ + def __init__(self, runtime_dir: str, log_dir: str): - self.runtime_dir = runtime_dir - self.log_dir = log_dir + self.runtime_dir = Path(runtime_dir) + self.log_dir = Path(log_dir) + self.data_dir = Path(NEUROTRON_DATASET_PATH) self.data_dir.mkdir(parents=True, exist_ok=True) + self.last_file = self.data_dir / "last_diagnostic.json" + self.telemetry_file = self.data_dir / "telemetry.json" self.perception = Perception() - self.current = None - self.previous = None + + # ---------------------------------------------------------------------- + # Utilitários internos + # ---------------------------------------------------------------------- def _load_previous(self): - if not self.last_file.exists(): - return None try: - with open(self.last_file, "r") as f: - return json.load(f) - except Exception: - return None + if self.last_file.exists(): + return json.loads(self.last_file.read_text()) + except Exception as e: + logbus.emit(f"[diag.warn] falha ao ler último diagnóstico: {e}") + return None - def _save_current(self, payload: dict): + def _save_current(self, payload): history = [] - if self.last_file.exists(): - try: - with open(self.last_file, "r") as f: - prev = json.load(f) - history = prev.get("history", []) - history.append({ - "timestamp": prev.get("timestamp"), - "cpu_percent": prev.get("cpu_percent"), - "mem_percent": prev.get("mem_percent"), - "loadavg": prev.get("loadavg"), - "state": prev.get("state", "UNKNOWN"), - }) - history = history[-NEUROTRON_HISTORY_KEEP:] - except Exception: - history = [] + prev = self._load_previous() + + if prev: + history = prev.get("history", []) + history.append({ + "timestamp": prev.get("timestamp"), + "cpu_percent": prev.get("cpu_percent"), + "mem_percent": prev.get("mem_percent"), + "loadavg": prev.get("loadavg"), + "state": prev.get("state", "UNKNOWN"), + }) + history = history[-NEUROTRON_HISTORY_KEEP:] + payload["history"] = history - with open(self.last_file, "w") as f: - json.dump(payload, f, indent=2) + + try: + self.last_file.write_text(json.dumps(payload, indent=2)) + except Exception as e: + logbus.emit(f"[diag.error] falha ao gravar diagnóstico: {e}") def _classify_state(self, cpu, mem, l1): - # valores podem ser "?" try: cpu = float(cpu) mem = float(mem) @@ -69,81 +89,32 @@ class AutoDiagnostic: except Exception: return "UNKNOWN" - # ALERT/CRITICAL if cpu >= HOMEOSTASIS_CPU_ALERT or mem >= HOMEOSTASIS_MEM_ALERT or l1 >= HOMEOSTASIS_LOAD_ALERT: return "CRITICAL" + if cpu >= HOMEOSTASIS_CPU_WARN or mem >= HOMEOSTASIS_MEM_WARN or l1 >= HOMEOSTASIS_LOAD_WARN: return "ALERT" - # OKs return "STABLE" - def _delta(self, a, b): - try: - if isinstance(a, list) and isinstance(b, list) and len(a) == len(b): - return [round(float(x) - float(y), 2) for x, y in zip(a, b)] - return round(float(a) - float(b), 2) - except Exception: - return "?" - - def _render_mini_trend(self, values, width=24, charset="▁▂▃▄▅▆▇█"): - if not values: - return "" - lo = min(values); hi = max(values) - if not isinstance(lo, (int, float)) or not isinstance(hi, (int, float)): - return "" - span = (hi - lo) or 1.0 - levels = len(charset) - 1 - bars = [] - for v in values[-width:]: - if not isinstance(v, (int, float)): - bars.append("·") - continue - i = int(round((v - lo) / span * levels)) - bars.append(charset[i]) - return "".join(bars) + # ---------------------------------------------------------------------- + # Execução principal + # ---------------------------------------------------------------------- def run_exam(self): - console.print("\n[bold]🤖 Iniciando rotina de Auto-Diagnóstico Evolutivo...[/bold]\n") - + """ + Realiza um diagnóstico silencioso, atualiza os ficheiros, + e retorna (state, payload). + """ snap = self.perception.snapshot() - cpu = snap.get("cpu_percent", "?") - mem = snap.get("mem_percent", "?") + + cpu = snap.get("cpu_percent", "?") + mem = snap.get("mem_percent", "?") load = snap.get("loadavg", ["?", "?", "?"]) + l1 = load[0] if isinstance(load, list) and load else "?" - prev = self._load_previous() - self.previous = prev - - # deltas - cpu_prev = prev.get("cpu_percent") if prev else "?" - mem_prev = prev.get("mem_percent") if prev else "?" - load_prev = prev.get("loadavg") if prev else ["?", "?", "?"] - - d_cpu = self._delta(cpu, cpu_prev) - d_mem = self._delta(mem, mem_prev) - d_load = self._delta(load, load_prev) - - # estado - l1 = load[0] if isinstance(load, list) and load else "?" state = self._classify_state(cpu, mem, l1) - # tabela - table = Table(title="🩺 Exame Clínico Evolutivo", show_lines=True) - table.add_column("Sinal Vital") - table.add_column("Atual", justify="right") - table.add_column("Δ", justify="center") - table.add_column("Anterior", justify="right") - - def fmt(v): - if isinstance(v, list): - return str(v) - return str(v) - - table.add_row("CPU (%)", fmt(cpu), fmt(d_cpu), fmt(cpu_prev)) - table.add_row("Memória (%)", fmt(mem), fmt(d_mem), fmt(mem_prev)) - table.add_row("Carga média (1/5/15)", fmt(load), "≈" if d_load == "?" else fmt(d_load), fmt(load_prev)) - console.print(table) - payload = { "schema": NEUROTRON_DIAG_SCHEMA, "timestamp": _now_iso(), @@ -156,17 +127,22 @@ class AutoDiagnostic: "term": snap.get("env_term"), }, } + self._save_current(payload) - console.print(f"[green]✔ Histórico evolutivo atualizado em:[/green] \n{self.last_file}") + self._update_telemetry(payload) - # Atualiza telemetria contínua + # Falamos apenas através do logbus + logbus.emit(f"[diag] estado={state} cpu={cpu} mem={mem} load1={l1}") + + return state, payload + + # ---------------------------------------------------------------------- + + def _update_telemetry(self, payload): try: - telemetry_file = Path(NEUROTRON_DATASET_PATH) / "telemetry.json" - telemetry_file.parent.mkdir(parents=True, exist_ok=True) - telemetry = [] - if telemetry_file.exists(): - telemetry = json.loads(telemetry_file.read_text() or "[]") + if self.telemetry_file.exists(): + telemetry = json.loads(self.telemetry_file.read_text() or "[]") telemetry.append({ "timestamp": payload["timestamp"], @@ -176,10 +152,9 @@ class AutoDiagnostic: "state": payload.get("state"), }) - telemetry = telemetry[-128:] # manter últimas 128 amostras - telemetry_file.write_text(json.dumps(telemetry, indent=2)) + telemetry = telemetry[-128:] + self.telemetry_file.write_text(json.dumps(telemetry, indent=2)) + except Exception as e: - console.print(f"[yellow]⚠️ Falha ao atualizar telemetria:[/] {e}") + logbus.emit(f"[diag.warn] falha ao atualizar telemetria: {e}") - - return state, payload diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/cortex.py b/src/_nfdos/kernel/neurotron/src/neurotron/cortex.py index acc4510..9a0a192 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/cortex.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/cortex.py @@ -1,27 +1,36 @@ import json import time -from collections import defaultdict, deque from pathlib import Path +from collections import defaultdict, deque from time import sleep -from rich.console import Console + +from neurotron.logbus import logbus from .neuron import Neuron from .hippocampus import Hippocampus from .perception import Perception from .motor import Motor - -from .neurotron_config import ( - NEUROTRON_MODE, NEUROTRON_TICK, NEUROTRON_TICK_MIN, NEUROTRON_TICK_MAX, NEUROTRON_TICK_STEP, - NEUROTRON_DIAG_EVERY_TICKS, NEUROTRON_DATASET_PATH, - HEARTBEAT_ENABLED, HEARTBEAT_STYLE, NEUROTRON_THRESHOLDS, - TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS, -) from .autodiagnostic import AutoDiagnostic +from .neurotron_config import ( + NEUROTRON_MODE, NEUROTRON_TICK, + NEUROTRON_TICK_MIN, NEUROTRON_TICK_MAX, NEUROTRON_TICK_STEP, + NEUROTRON_DIAG_EVERY_TICKS, + NEUROTRON_DATASET_PATH, + HEARTBEAT_ENABLED, HEARTBEAT_STYLE, + NEUROTRON_THRESHOLDS, + TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS, +) + + +# ===================================================================== +# Neurónios básicos +# ===================================================================== class VitalSigns(Neuron): name = "VitalSigns" - def observe(self) -> None: + + def observe(self): snap = self.ctx.perception.snapshot() self.publish("vitals", snap) self.ctx.memory.remember("observe.vitals", snap) @@ -29,123 +38,152 @@ class VitalSigns(Neuron): class EchoAgent(Neuron): name = "EchoAgent" - def think(self) -> None: + + def think(self): msg = self.consume("vitals") if msg: - self.publish("actions", {"action": "echo", "text": f"CPU {msg.get('cpu_percent', '?')}%"}) + self.publish("actions", { + "action": "echo", + "text": f"CPU {msg.get('cpu_percent', '?')}%" + }) +# ===================================================================== +# C O R T E X — versão V6 (Pure LogBus Edition) +# ===================================================================== + class Cortex: """ - Orquestrador: liga neurónios, bus de mensagens, memória, IO e ciclo cognitivo. - Agora com Telemetria Contínua (V5): heartbeat, microalertas e flush periódico. + Orquestrador cognitivo do Neurotron. + Totalmente silencioso — toda saída vai para o logbus. """ + def __init__(self, runtime_dir, log_dir, tick_seconds=NEUROTRON_TICK): - self.runtime_dir = runtime_dir - self.log_dir = log_dir + self.runtime_dir = Path(runtime_dir) + self.log_dir = Path(log_dir) + self.tick = float(tick_seconds) self.mode = NEUROTRON_MODE self._tick_count = 0 - self.diagnostic = AutoDiagnostic(runtime_dir, log_dir) - self.console = Console() + self.diagnostic = AutoDiagnostic(runtime_dir, log_dir) self.memory = Hippocampus(log_dir=log_dir) self.perception = Perception() self.motor = Motor() - # Message bus simples: channels → deque + # Message bus interno self.bus = defaultdict(lambda: deque(maxlen=32)) - # Telemetria em memória (curto prazo) + # Telemetria curta em RAM self.telemetry = deque(maxlen=TELEMETRY_MAXLEN) - # Regista neurónios (podes adicionar mais à medida) - self.neurons: list[Neuron] = [ + # Neurónios ativos + self.neurons = [ VitalSigns(self), EchoAgent(self), ] self._booted = False - # Caminho para gravar a telemetria + # Caminho telemetria longa self.telemetry_path = Path(NEUROTRON_DATASET_PATH) / "telemetry.json" self.telemetry_path.parent.mkdir(parents=True, exist_ok=True) - # ——— ciclo de vida ——— - def boot(self) -> None: + # ================================================================= + # BOOT / SHUTDOWN + # ================================================================= + + def boot(self): if self._booted: return - self.console.print("[bold cyan]🧠 Neurotron[/] — boot") + logbus.emit("🧠 boot() — inicializando Neurotron") self.memory.remember("boot", {"version": "0.1", "tick": self.tick}) self._booted = True - #state, _ = self.diagnostic.run_exam() - #self._apply_homeostasis(state) + + def shutdown(self, reason=""): + logbus.emit(f"⚠️ shutdown — {reason}") + self.memory.remember("shutdown", {"reason": reason}) + + def fatal(self, e: Exception): + logbus.emit(f"🔥 fatal error: {repr(e)}") + self.memory.remember("fatal", {"error": repr(e)}) + raise e + + # ================================================================= + # CICLO COGNITIVO + # ================================================================= + + def observe(self): + for n in self.neurons: + n.observe() + + def think(self): + for n in self.neurons: + n.think() + + def act(self): + action = self.bus_consume("actions") + if not action: + return + + if action.get("action") == "echo": + res = self.motor.run("echo", [action.get("text", "")]) + self.memory.remember("act.echo", res) + + # Redireciona stdout para logbus + if res.get("stdout"): + logbus.emit(f"[motor.echo] {res['stdout'].strip()}") + + if res.get("stderr"): + logbus.emit(f"[motor.echo.err] {res['stderr'].strip()}") + + def rest(self): + # Heartbeat + microalertas + if HEARTBEAT_ENABLED: + self._heartbeat_and_telemetry() + + sleep(self.tick) + self._tick_count += 1 + + # Diagnóstico periódico + if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0: + state, _ = self.diagnostic.run_exam() + self._apply_homeostasis(state) + + # Flush periódico telemetria + if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0: + self._flush_telemetry() + + # ================================================================= + # HOMEOSTASE + # ================================================================= def _apply_homeostasis(self, state): + old = self.tick + if state == "CRITICAL": - self.mode = "diagnostic" self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP) elif state == "ALERT": self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP / 2) elif state == "STABLE": self.tick = max(NEUROTRON_TICK_MIN, self.tick - NEUROTRON_TICK_STEP / 2) - # UNKNOWN → não mexe - def shutdown(self, reason: str = ""): - self.console.print(f"[yellow]shutdown:[/] {reason}") - self.memory.remember("shutdown", {"reason": reason}) + if self.tick != old: + logbus.emit(f"[homeostasis] tick ajustado {old:.2f}s → {self.tick:.2f}s (state={state})") - def fatal(self, e: Exception): - self.console.print(f"[red]fatal:[/] {e!r}") - self.memory.remember("fatal", {"error": repr(e)}) - print(f"fatal: {repr(e)}") - raise + # ================================================================= + # HEARTBEAT + MICROALERTAS + # ================================================================= - # ——— loop ——— - def observe(self) -> None: - for n in self.neurons: - n.observe() - - def think(self) -> None: - for n in self.neurons: - n.think() - - def act(self) -> None: - # Consumir ações agregadas e executar - action = self.bus_consume("actions") - if action and action.get("action") == "echo": - res = self.motor.run("echo", [action.get("text", "")]) - self.memory.remember("act.echo", res) - if res.get("stdout"): - self.console.print(f"[green]{res['stdout'].strip()}[/]") - - def rest(self): - # Heartbeat e microalertas antes de dormir - if HEARTBEAT_ENABLED: - self._heartbeat_and_telemetry() - - # Pausa regulada - sleep(self.tick) - - # Contador e rotinas periódicas - self._tick_count += 1 - - if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0: - #state, _ = self.diagnostic.run_exam() - #self._apply_homeostasis(state) - pass - - if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0: - self._flush_telemetry() - - # ——— telemetria/alertas ——— def _heartbeat_and_telemetry(self): snap = self.perception.snapshot() - cpu = snap.get("cpu_percent", "?") - mem = (snap.get("mem") or {}).get("percent", "?") - load = snap.get("loadavg") or [] - # Adiciona ao buffer de telemetria + cpu = snap.get("cpu_percent") + mem = snap.get("mem_percent") + load = snap.get("loadavg") + load1 = load[0] if isinstance(load, list) and load else None + + # Salva telemetria curta self.telemetry.append({ "ts": time.time(), "cpu": cpu, @@ -154,23 +192,13 @@ class Cortex: "tick": self.tick, }) - # Microalertas com base nos limiares - self._evaluate_microalerts(cpu, mem, load) + self._evaluate_microalerts(cpu, mem, load1) - # Heartbeat visual - color = self._color_for_levels(cpu, mem, load) - if HEARTBEAT_STYLE == "compact": - self.console.print(f"[bold {color}]💓[/] CPU: {cpu}% | MEM: {mem}% | TICK: {self.tick:.2f}s") - else: - self.console.print( - f"[bold {color}]💓 [Heartbeat][/bold {color}] " - f"CPU: {cpu}% | MEM: {mem}% | LOAD: {load} | TICK: {self.tick:.2f}s | MODE: {self.mode}" - ) + # Heartbeat → logbus + logbus.emit(f"💓 cpu={cpu}% mem={mem}% tick={self.tick:.2f}s") - def _evaluate_microalerts(self, cpu, mem, load): + def _evaluate_microalerts(self, cpu, mem, load1): alerts = [] - # Normaliza - load1 = load[0] if (isinstance(load, (list, tuple)) and load) else None try: if isinstance(cpu, (int, float)) and cpu >= NEUROTRON_THRESHOLDS["cpu_high"]: @@ -179,15 +207,15 @@ class Cortex: alerts.append(("mem", mem)) if isinstance(load1, (int, float)) and load1 >= NEUROTRON_THRESHOLDS["load1_high"]: alerts.append(("load1", load1)) - except KeyError: - pass # thresholds incompletos → sem microalertas + except Exception: + return if not alerts: return - for (metric, value) in alerts: - self.console.print(f"[yellow]⚠️ Microalerta:[/] {metric.upper()} {value} — ajustando homeostase (tick +{NEUROTRON_TICK_STEP:.2f}s)") - # Ajuste simples de segurança + for metric, value in alerts: + logbus.emit(f"⚠️ microalerta: {metric}={value} — ajustando tick") + self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP) self.memory.remember("microalert", { @@ -196,36 +224,28 @@ class Cortex: "new_tick": self.tick, }) - def _color_for_levels(self, cpu, mem, load): - # Heurística simples de cor - try: - load1 = load[0] if (isinstance(load, (list, tuple)) and load) else 0.0 - high = ( - (isinstance(cpu, (int, float)) and cpu >= NEUROTRON_THRESHOLDS["cpu_high"]) or - (isinstance(mem, (int, float)) and mem >= NEUROTRON_THRESHOLDS["mem_high"]) or - (isinstance(load1, (int, float)) and load1 >= NEUROTRON_THRESHOLDS["load1_high"]) - ) - if high: - return "yellow" - except Exception: - pass - return "green" + # ================================================================= + # TELEMETRIA LONGA + # ================================================================= def _flush_telemetry(self): - # Grava o buffer de telemetria em JSON (mantendo histórico curto) try: data = list(self.telemetry) - with self.telemetry_path.open("w") as f: - json.dump(data, f) - self.memory.remember("telemetry.flush", {"count": len(data), "path": str(self.telemetry_path)}) + self.telemetry_path.write_text(json.dumps(data)) + self.memory.remember("telemetry.flush", { + "count": len(data), + "path": str(self.telemetry_path) + }) except Exception as e: - self.console.print(f"[red]✖ Falha ao gravar telemetria:[/] {e!r}") - self.memory.remember("telemetry.error", {"error": repr(e)}) + logbus.emit(f"[telemetry.error] {e}") - # ——— bus ——— - def bus_publish(self, channel: str, payload: dict) -> None: + # ================================================================= + # BUS + # ================================================================= + + def bus_publish(self, channel, payload): self.bus[channel].append(payload) - def bus_consume(self, channel: str) -> dict | None: + def bus_consume(self, channel): q = self.bus[channel] - return q.popleft() if q else None \ No newline at end of file + return q.popleft() if q else None diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/hippocampus.py b/src/_nfdos/kernel/neurotron/src/neurotron/hippocampus.py index 073bb5c..efcbdc9 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/hippocampus.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/hippocampus.py @@ -3,7 +3,7 @@ from datetime import datetime try: import orjson as json -except Exception: # fallback leve +except Exception: import json # type: ignore class Hippocampus: @@ -11,9 +11,11 @@ class Hippocampus: Memória contextual simples (JSON Lines): append-only. Guarda perceções, decisões e ações para replays futuros. """ + def __init__(self, log_dir: Path): self.log_dir = log_dir - self.events_file = log_dir / "events.jsonl" + self.log_dir.mkdir(parents=True, exist_ok=True) + self.events_file = self.log_dir / "events.jsonl" def remember(self, kind: str, data: dict) -> None: rec = { @@ -21,14 +23,17 @@ class Hippocampus: "kind": kind, "data": data, } + try: - if "orjson" in json.__name__: - blob = json.dumps(rec) - else: - blob = json.dumps(rec) # type: ignore + # Compatível com orjson e stdlib json + blob = json.dumps(rec) + if isinstance(blob, str): + blob = blob.encode("utf-8") + with self.events_file.open("ab") as f: - f.write(blob if isinstance(blob, bytes) else blob.encode("utf-8")) + f.write(blob) f.write(b"\n") except Exception: - # evitar crash por IO em early boot + # Evitar crash em early boot ou IO quebrado pass + diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/logbus.py b/src/_nfdos/kernel/neurotron/src/neurotron/logbus.py new file mode 100644 index 0000000..d5e58da --- /dev/null +++ b/src/_nfdos/kernel/neurotron/src/neurotron/logbus.py @@ -0,0 +1,83 @@ +""" +Neurotron LogBus — Sistema Centralizado de Logging +================================================== + +Objetivo: + - Centralizar *toda* a saída de logs do Neurotron + - Funcionar tanto no dashboard curses quanto em modo headless + - Manter logs persistentes quando o hipocampo físico está montado + - Fornecer uma API simples: + from neurotron.logbus import log + log("mensagem") + +Componentes: + - LogBus: roteador de mensagens (fila → stdout → ficheiro) + - log(): função global simplificada +""" + +from __future__ import annotations +import json +import time +from queue import Queue +from pathlib import Path + + +class LogBus: + def __init__(self): + self.queue: Queue | None = None # usado pelo dashboard + self.stdout_enabled = True # imprime em modo headless + self.persist_enabled = False # grava em disco + self.persist_file: Path | None = None + + # --------------------------------------------------------- + # Inicialização de destinos + # --------------------------------------------------------- + def enable_dashboard(self, q: Queue): + """Liga o LogBus ao dashboard (curses).""" + self.queue = q + + def enable_persist(self, path: Path): + """Ativa salvamento permanente da stream de logs.""" + self.persist_file = path + self.persist_enabled = True + path.parent.mkdir(parents=True, exist_ok=True) + if not path.exists(): + path.write_text("") # cria ficheiro vazio + + # --------------------------------------------------------- + # Emissão centralizada + # --------------------------------------------------------- + def log(self, message: str): + ts = time.strftime("%H:%M:%S") + line = f"[{ts}] {message}" + + # 1) Dashboard (queue) + if self.queue: + try: + self.queue.put(line) + except Exception: + pass + + # 2) STDOUT + if self.stdout_enabled: + try: + print(line) + except Exception: + pass + + # 3) Persistência + if self.persist_enabled and self.persist_file: + try: + with open(self.persist_file, "a") as f: + f.write(json.dumps({"ts": ts, "msg": message}) + "\n") + except Exception: + pass + + +# Instância global — usada em todo o Neurotron +logbus = LogBus() + + +def log(msg: str): + """Função simples: apenas delega ao LogBus global.""" + logbus.log(msg) diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/motor.py b/src/_nfdos/kernel/neurotron/src/neurotron/motor.py index b6a466a..ac5af9e 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/motor.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/motor.py @@ -1,27 +1,77 @@ import subprocess +from neurotron.logbus import logbus + class Motor: """ - Ator do sistema: executa comandos controlados (whitelist). - Mantém-se minimal até termos política de segurança mais rica. + Subsistema 'ator' do Neurotron. + + Executa comandos controlados (whitelist rígida), + sem side-effects e com logging centralizado. + + FUTURO: + - níveis de permissão + - sandboxes + - ações abstratas (ex: mover, sinalizar, emitir evento) """ + SAFE_CMDS = { "echo": ["echo"], - "sh": ["/bin/sh"], # shell interativo (init) + "sh": ["/bin/sh"], # shell básico — útil para debug interno } + def __init__(self): + pass + def run(self, cmd: str, args: list[str] | None = None) -> dict: - prog = self.SAFE_CMDS.get(cmd) - if not prog: - return {"ok": False, "error": f"cmd '{cmd}' não permitido"} - try: - full = prog + (args or []) - res = subprocess.run(full, capture_output=True, text=True) - return { - "ok": res.returncode == 0, - "code": res.returncode, - "stdout": res.stdout, - "stderr": res.stderr, + """ + Executa um comando permitido e retorna: + { + "ok": bool, + "code": int | None, + "stdout": str, + "stderr": str, } + """ + + if cmd not in self.SAFE_CMDS: + logbus.emit(f"[motor.warn] comando bloqueado: '{cmd}'") + return { + "ok": False, + "code": None, + "stdout": "", + "stderr": f"cmd '{cmd}' não permitido", + } + + try: + full = self.SAFE_CMDS[cmd] + (args or []) + res = subprocess.run( + full, + capture_output=True, + text=True, + env={}, # ambiente neutro → evita leaks + ) + + ok = (res.returncode == 0) + + if not ok: + logbus.emit( + f"[motor.warn] '{cmd}' retornou código {res.returncode}" + ) + + return { + "ok": ok, + "code": res.returncode, + "stdout": res.stdout or "", + "stderr": res.stderr or "", + } + except Exception as e: - return {"ok": False, "error": str(e)} + logbus.emit(f"[motor.error] exceção ao executar '{cmd}': {e}") + return { + "ok": False, + "code": None, + "stdout": "", + "stderr": str(e), + } + diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/neuron.py b/src/_nfdos/kernel/neurotron/src/neurotron/neuron.py index 972102c..a240283 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/neuron.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/neuron.py @@ -1,30 +1,58 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional +from neurotron.logbus import logbus + class Neuron: """ - Classe-base de um “neurónio-agente”. - Cada neurónio pode observar/agir e trocar mensagens via o bus do Cortex. + Classe-base de um neurónio-agente. + + Cada neurónio participa no ciclo cognitivo do Cortex: + observe() → think() → act() + + Pode comunicar via: + - publish(channel, payload) + - consume(channel) + + Implementações devem respeitar esta interface sem causar efeitos colaterais + externos (sem prints, sem IO não controlado). """ - name = "Neuron" + + name: str = "Neuron" def __init__(self, ctx: "Cortex"): self.ctx = ctx + # ---------------------------------------------------------------------- + # Ciclo cognitivo — para override por neurónios concretos + # ---------------------------------------------------------------------- + def observe(self) -> None: - """Ler estado do mundo (sensores, /proc, eventos).""" + """Ler estado do mundo (sensores, perceções, eventos internos).""" return def think(self) -> None: - """Processar/planejar usando o estado atual.""" + """Processar informação, planear, atualizar estado interno.""" return def act(self) -> None: - """Executar uma ação (opcional).""" + """Executar uma ação, enviar comandos, publicar eventos.""" return - # Utilitários - def publish(self, channel: str, payload: Dict[str, Any]) -> None: - self.ctx.bus_publish(channel, payload) + # ---------------------------------------------------------------------- + # Comunicação inter-neurónios via Cortex Bus + # ---------------------------------------------------------------------- - def consume(self, channel: str) -> Dict[str, Any] | None: - return self.ctx.bus_consume(channel) + def publish(self, channel: str, payload: Dict[str, Any]) -> None: + """Publica mensagem num canal do bus cognitivo.""" + try: + self.ctx.bus_publish(channel, payload) + except Exception as e: + logbus.emit(f"[warn] {self.name}.publish falhou: {e}") + + def consume(self, channel: str) -> Optional[Dict[str, Any]]: + """Lê (e remove) última mensagem disponível num canal.""" + try: + return self.ctx.bus_consume(channel) + except Exception as e: + logbus.emit(f"[warn] {self.name}.consume falhou: {e}") + return None diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/neurotron_config.py b/src/_nfdos/kernel/neurotron/src/neurotron/neurotron_config.py index f19f66d..f783a4b 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/neurotron_config.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/neurotron_config.py @@ -1,42 +1,37 @@ """ 🧠 neurotron_config.py -NFDOS — Núcleo de parâmetros vitais do Neurotron ------------------------------------------------- -Nova versão para o layout: -.../neurotron/ - ├── src/ - └── data/ +NFDOS — Núcleo de parâmetros vitais estáticos do Neurotron + +Este módulo define apenas: +- parâmetros cognitivos estáticos +- thresholds +- limites +- caminhos locais relativos ao package (ex: DATA_DIR interno) +Ele NÃO define: +- diretórios de runtime (/var/neurotron, /tmp) +- diretórios de logs dinâmicos +- configurações voláteis +Essas pertencem ao runtime e são avaliadas no __main__. """ from pathlib import Path -# ====================================== -# 🌐 Diretórios e Caminhos -# ====================================== +# ============================================================================ +# 📁 Diretórios internos do package (não confundir com runtime!) +# ============================================================================ -# Diretório deste ficheiro → .../neurotron/src/neurotron_config.py THIS_FILE = Path(__file__).resolve() -SRC_DIR = THIS_FILE.parent # .../neurotron/src -BASE_DIR = SRC_DIR.parent # .../neurotron/ +SRC_DIR = THIS_FILE.parent # .../neurotron/src/neurotron +BASE_DIR = SRC_DIR.parent # .../neurotron/src -# Onde vivem as configs/logs da “instalação” +# Diretórios que fazem parte da instalação do package DATA_DIR = BASE_DIR / "data" CONFIG_DIR = DATA_DIR / "configs" -LOG_DIR = DATA_DIR / "logs" +PACKAGE_LOG_DIR = DATA_DIR / "logs" # logs do próprio package (não runtime!) -# Modo persistente do NFDOS (quando /var/neurotron está montado) -RUNTIME_DIR = Path("/var/run/neurotron") -MOUNT_POINT = "/var/neurotron" - -# Candidatos para disco persistente do hipocampo -DISK_CANDIDATES = [ - "/dev/vda", "/dev/vdb", - "/dev/sda", "/dev/hda" -] - -# ====================================== -# ⚙️ Parâmetros Cognitivos Principais -# ====================================== +# ============================================================================ +# ⚙️ Parâmetros Cognitivos +# ============================================================================ NEUROTRON_TICK = 1.0 NEUROTRON_VERBOSITY = 1 @@ -59,9 +54,9 @@ NEUROTRON_TICK_STEP = 0.25 NEUROTRON_SEED = 42 NEUROTRON_MEMORY_SIZE = 256 # KB -# ====================================== -# 🧩 Parâmetros de Subsistemas -# ====================================== +# ============================================================================ +# 🔌 Subsistemas +# ============================================================================ CORTEX_MAX_THREADS = 1 CORTEX_LOOP_DELAY = 0.1 @@ -76,14 +71,13 @@ PERCEPTION_CPU_SOURCE = "/proc/stat" PERCEPTION_MEM_SOURCE = "/proc/meminfo" PERCEPTION_UPDATE_INTERVAL = 2.0 -# ====================================== -# 🧠 Parâmetros futuros -# ====================================== +# ============================================================================ +# 📊 Telemetria e diagnóstico +# ============================================================================ NEUROTRON_EXPANSION_MODE = "none" NEUROTRON_DATASET_PATH = DATA_DIR NEUROTRON_HISTORY_KEEP = 8 - NEUROTRON_DIAG_SCHEMA = "v4" HEARTBEAT_ENABLED = True @@ -97,22 +91,3 @@ NEUROTRON_THRESHOLDS = { TELEMETRY_MAXLEN = 64 TELEMETRY_FLUSH_EVERY_TICKS = 5 - -# ====================================== -# 🧭 Utilitário -# ====================================== - -def show_config(): - """Mostra a configuração atual do Neurotron (apenas NEUROTRON_*)""" - import json - cfg = { - k: v - for k, v in globals().items() - if k.startswith("NEUROTRON_") - } - print(json.dumps(cfg, indent=2, default=str)) - - -if __name__ == "__main__": - show_config() - diff --git a/src/_nfdos/kernel/neurotron/src/neurotron/perception.py b/src/_nfdos/kernel/neurotron/src/neurotron/perception.py index 3ecbbbd..0dca90c 100644 --- a/src/_nfdos/kernel/neurotron/src/neurotron/perception.py +++ b/src/_nfdos/kernel/neurotron/src/neurotron/perception.py @@ -1,52 +1,75 @@ import os from time import sleep +from neurotron.logbus import logbus + class Perception: """ Sensores internos via /proc: - - CPU % calculado por delta de /proc/stat + - CPU % por delta de /proc/stat - Memória % via /proc/meminfo - - Carga média via /proc/loadavg - Sem dependências externas (psutil). + - Carga média /proc/loadavg ou os.getloadavg() + Totalmente silencioso (sem prints). """ + # ---------------------------------------------------------------------- + # CPU + # ---------------------------------------------------------------------- + def _read_proc_stat(self): + """Lê a linha 'cpu ' de /proc/stat. Retorna dict ou None.""" try: with open("/proc/stat", "r") as f: line = f.readline() if not line.startswith("cpu "): return None + parts = line.strip().split()[1:] - vals = list(map(int, parts[:10])) # user nice system idle iowait irq softirq steal guest guest_nice + vals = list(map(int, parts[:10])) return { "user": vals[0], "nice": vals[1], "system": vals[2], "idle": vals[3], "iowait": vals[4], "irq": vals[5], "softirq": vals[6], "steal": vals[7], "guest": vals[8], "guest_nice": vals[9], } + except Exception: return None def _cpu_percent(self, interval=0.05): + """Computa CPU% entre duas leituras.""" a = self._read_proc_stat() if not a: return "?" - sleep(interval) # micro-janelinha + + sleep(interval) + b = self._read_proc_stat() if not b: return "?" + + # Totais idle_a = a["idle"] + a["iowait"] idle_b = b["idle"] + b["iowait"] + non_a = sum(a.values()) - idle_a non_b = sum(b.values()) - idle_b + total_a = idle_a + non_a total_b = idle_b + non_b + totald = total_b - total_a idled = idle_b - idle_a + if totald <= 0: return "?" + usage = (totald - idled) * 100.0 / totald return round(usage, 1) + # ---------------------------------------------------------------------- + # Memória + # ---------------------------------------------------------------------- + def _mem_percent(self): try: info = {} @@ -54,36 +77,73 @@ class Perception: for line in f: k, v = line.split(":", 1) info[k.strip()] = v.strip() + def kB(key): - if key not in info: return None - return float(info[key].split()[0]) # kB + return float(info[key].split()[0]) if key in info else None + mem_total = kB("MemTotal") mem_avail = kB("MemAvailable") - if not mem_total or mem_avail is None: + + if mem_total is None or mem_avail is None: return "?" + used = mem_total - mem_avail return round(used * 100.0 / mem_total, 1) + except Exception: return "?" + # ---------------------------------------------------------------------- + # Loadavg + # ---------------------------------------------------------------------- + def _loadavg(self): try: if hasattr(os, "getloadavg"): l1, l5, l15 = os.getloadavg() return [round(l1, 2), round(l5, 2), round(l15, 2)] + with open("/proc/loadavg", "r") as f: parts = f.read().strip().split() l1, l5, l15 = map(float, parts[:3]) return [round(l1, 2), round(l5, 2), round(l15, 2)] + except Exception: return ["?", "?", "?"] - def snapshot(self) -> dict: - return { - "env_user": os.environ.get("USER") or "root", - "env_term": os.environ.get("TERM") or "unknown", - "cpu_percent": self._cpu_percent(), - "mem_percent": self._mem_percent(), - "loadavg": self._loadavg(), - } + # ---------------------------------------------------------------------- + # Snapshot principal + # ---------------------------------------------------------------------- + + def snapshot(self) -> dict: + """ + Retorna snapshot interno: + { + "env_user": "...", + "env_term": "...", + "cpu_percent": n | "?", + "mem_percent": n | "?", + "loadavg": [l1, l5, l15] + } + Sempre seguro. Nunca lança exceção. + """ + try: + return { + "env_user": os.environ.get("USER") or "root", + "env_term": os.environ.get("TERM") or "unknown", + "cpu_percent": self._cpu_percent(), + "mem_percent": self._mem_percent(), + "loadavg": self._loadavg(), + } + + except Exception as e: + # fallback extremo — nunca quebrar o Neurotron + logbus.emit(f"[warn] Perception.snapshot falhou: {e}") + return { + "env_user": "unknown", + "env_term": "unknown", + "cpu_percent": "?", + "mem_percent": "?", + "loadavg": ["?", "?", "?"], + } diff --git a/src/tui/menu_kernel.py b/src/tui/menu_kernel.py index b12db87..d3bd187 100644 --- a/src/tui/menu_kernel.py +++ b/src/tui/menu_kernel.py @@ -537,10 +537,8 @@ def run(): else: console.print("[green]✔ Disco persistente já existe.[/green]") - time.sleep(5) - # Testar no QEMU bz_image = linux_dir / "arch" / "x86" / "boot" / "bzImage" data_disk = nfdos_dir / "nfdos_data.img" @@ -548,32 +546,12 @@ def run(): if bz_image.exists(): console.print("\n[bold blue]Iniciando QEMU (modo kernel direto)...[/bold blue]") - # 🔒 Pergunta ao utilizador se deve permitir formatação automática do disco - console.print( - "\n[yellow]O Neurotron detetará o disco físico e poderá formatá-lo se estiver vazio.[/yellow]" - ) - console.print( - "[bright_yellow]Deseja permitir formatação automática do disco?[/bright_yellow] " - "([green]y[/green]/[red]N[/red]): ", - end="" - ) - choice = input().strip().lower() - - allow_format = choice in ("y", "yes", "sim", "s") - # 🧠 Monta a linha base do QEMU kernel_params = ( "console=ttyS0 earlyprintk=serial,ttyS0,115200 " "keep_bootcon loglevel=8" ) - # 🧩 Injeta flag de formatação se permitido - if allow_format: - kernel_params += " nfdos_force_format=1" - console.print("[green]✔ Formatação automática permitida neste boot.[/green]") - else: - console.print("[cyan]ℹ Formatação automática desativada (modo seguro).[/cyan]") - qemu_cmd = ( f"qemu-system-x86_64 " f"-machine q35,accel=kvm "