From 1be33d2a4b80485e14ef810db3d8cb86e67b999a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 21 Feb 2026 02:32:13 +0100 Subject: [PATCH 1/5] add autonomous local hardware execution plan and todo runbooks --- specs/03_plan.md | 136 ++++++++++++++++++++++++++++++--- specs/04_tasks.md | 109 ++++++++++++++++++++------ specs/zeroclaw_dual_hw_todo.md | 60 +++++++++------ 3 files changed, 247 insertions(+), 58 deletions(-) diff --git a/specs/03_plan.md b/specs/03_plan.md index 97dc2fe..08686e4 100644 --- a/specs/03_plan.md +++ b/specs/03_plan.md @@ -1,16 +1,128 @@ -# Easter Egg musique expérimentale +# Plan d'execution autonome local (RTC + Zacus) -_« Le plan du projet se transforme, comme un evidence pack modulé par Daphne Oram. »_ -# Plan +Last updated: 2026-02-21 -## Étapes -1) ... -2) ... +## Objectif -## Validation à chaque étape -- Build -- Tests -- Gates hardware (ERC/DRC) +Atteindre une boucle autonome locale stable sur les deux repos branches au hardware: -## Evidence pack -- artifacts/... +- `RTC_BL_PHONE` +- `le-mystere-professeur-zacus` + +avec: + +- stack ZeroClaw local operationnel (`3000`, `8788`, `9090`), +- dev/test reproductible sur chaque repo, +- preflight hardware systematique avant toute action firmware, +- traces et preuves dans `artifacts/zeroclaw/`. + +## Criteres de succes + +- `tools/ai/zeroclaw_stack_up.sh` lance gateway + follow + Prometheus (binary ou docker fallback). +- `http://127.0.0.1:8788/` affiche les conversations et logs en live. +- `http://127.0.0.1:9090/targets` est reachable (Prometheus up). +- `tools/ai/zeroclaw_dual_chat.sh rtc --provider-check` et `... zacus --provider-check` passent. +- Au moins une boucle dev/test complete par repo est executee et tracee. + +## Contraintes + +- Local only (loopback `127.0.0.1`), pas d'exposition publique. +- Secrets hors git (`~/.zeroclaw/env`, mode `600`). +- Cout modele borne par quotas webhook locaux. +- Pas de flash/upload firmware sans detection hardware prealable. + +## Plan par phases + +## Phase 0 - Preflight (auth + outils) + +Actions: + +1. Verifier `gh auth status`. +2. Verifier `zeroclaw auth status`. +3. Verifier `~/.zeroclaw/env` contient `OPENROUTER_API_KEY=...` et permissions `600`. +4. Verifier backend Prometheus local: + - binaire `prometheus`, sinon docker daemon. + +Sortie attendue: + +- environnement provider OK, +- fallback Prometheus disponible. + +## Phase 1 - Stack local ZeroClaw + +Actions: + +1. `tools/ai/zeroclaw_stack_down.sh` +2. `ZEROCLAW_PROM_MODE=auto tools/ai/zeroclaw_stack_up.sh` +3. Verifs: + - `curl -fsS http://127.0.0.1:3000/health` + - `curl -fsS http://127.0.0.1:8788/` + - `curl -fsS http://127.0.0.1:9090/-/ready` + +Sortie attendue: + +- gateway healthy, +- dashboard live actif, +- Prometheus ready. + +## Phase 2 - Boucle autonome RTC + +Actions: + +1. Provider check: `tools/ai/zeroclaw_dual_chat.sh rtc --provider-check` +2. Hardware discover: `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` +3. Build/test firmware repo RTC: + - `cd /Users/cils/Documents/Lelectron_rare/RTC_BL_PHONE` + - `pio run` + - `pio test -e native` (si environnement present) +4. Trace webhook: + - `tools/ai/zeroclaw_webhook_send.sh --repo-hint rtc "rtc loop status ..."` + +Sortie attendue: + +- build RTC passe ou echec documente avec logs, +- ligne JSONL enrichie pour la boucle RTC. + +## Phase 3 - Boucle autonome Zacus + +Actions: + +1. Provider check: `tools/ai/zeroclaw_dual_chat.sh zacus --provider-check` +2. Hardware discover: `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` +3. Build/test firmware repo Zacus: + - `cd /Users/cils/Documents/Lelectron_rare/le-mystere-professeur-zacus/hardware/firmware` + - `pio run` + - `pio test -e native` (si environnement present) +4. Trace webhook: + - `tools/ai/zeroclaw_webhook_send.sh --repo-hint zacus "zacus loop status ..."` + +Sortie attendue: + +- build Zacus passe ou echec documente avec logs, +- ligne JSONL enrichie pour la boucle Zacus. + +## Phase 4 - Stabilisation autonomie + +Actions: + +1. Dry-run budget: `tools/ai/zeroclaw_webhook_send.sh --dry-run "budget probe"` +2. Verifier quota state: `artifacts/zeroclaw/webhook_budget.json` +3. Verifier logs: + - `artifacts/zeroclaw/gateway.log` + - `artifacts/zeroclaw/conversations.jsonl` +4. Mettre a jour TODO (`specs/04_tasks.md` + `specs/zeroclaw_dual_hw_todo.md`). + +Sortie attendue: + +- boucles previsibles, +- cout controle, +- preuves a jour. + +## Cadence autonome quotidienne + +1. Preflight auth + hardware. +2. Stack up + smoke endpoints. +3. RTC loop (dev/test + webhook trace). +4. Zacus loop (dev/test + webhook trace). +5. Review logs + correction ciblée. +6. Commit/PR petit lot. diff --git a/specs/04_tasks.md b/specs/04_tasks.md index 6e18fa1..a01ae8f 100644 --- a/specs/04_tasks.md +++ b/specs/04_tasks.md @@ -1,28 +1,93 @@ -# Axe d’amélioration : badge & conformité +# Tasks autonomie locale (execution) -- Automatiser la génération, vérification et publication des badges (sécurité, SBOM, doc, qualité, communauté, coverage) -- Centraliser guides badge dans docs/badges/ -- Vérifier la fraîcheur des badges à chaque commit (timestamp ≥ commit) -- Inclure badges et rapports dans l’evidence pack -- Ajouter checklist badge en tête du README -- Documenter la politique badge & conformité (docs/COMPLIANCE.md) -- Planifier un audit badge à chaque release majeure +Last updated: 2026-02-21 -## Planification audit badge +Format: -- À chaque release majeure, exécuter un audit badge : - - Vérifier la couleur et l’actualisation de chaque badge - - Vérifier la présence des rapports JSON - - Vérifier l’inclusion dans l’evidence pack - - Documenter les résultats dans docs/badges/audit_.md -# Easter Egg musique concrète +- `[ ]` non fait +- `[x]` fait +- `AC` = acceptance criteria +- `Evidence` = fichier/commande de preuve -_« Les tâches sont des sons trouvés : chaque action, chaque gate, est une pièce du puzzle acousmatique. »_ — Pierre Schaeffer -# Tasks (Backlog exécutable) +## Sprint actuel -Format conseillé (copiable en GitHub Issues) : +- [x] T-001 - Merger la PR autonomie ZeroClaw + - AC: PR mergee sur `main`. + - Evidence: `https://github.com/electron-rare/Kill_LIFE/pull/5` -- [ ] T1 — ... - - AC: ... - - Evidence: ... -- [ ] T2 — ... +- [x] T-002 - Fermer ou merger la PR miroir restante + - AC: plus de PR redondante ouverte pour le meme scope. + - Evidence: `gh pr list --state open` + +- [x] T-003 - Configurer secret fallback OpenRouter local + - AC: `~/.zeroclaw/env` present avec `OPENROUTER_API_KEY` et mode `600`. + - Evidence: `ls -l ~/.zeroclaw/env` + +- [x] T-004 - Installer backend Prometheus local + - AC: commande `prometheus --version` disponible. + - Evidence: sortie shell `prometheus, version ...` + +- [ ] T-005 - Stabiliser pairing bearer auto + - AC: `artifacts/zeroclaw/pair_token.txt` utilisable pour webhook sans override manuel. + - Evidence: `tools/ai/zeroclaw_webhook_send.sh --repo-hint rtc "pairing check"` + +## Boucle hardware RTC + +- [ ] T-101 - Discover hardware RTC + - AC: au moins un port detecte pour carte RTC. + - Evidence: `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` + +- [ ] T-102 - Build firmware RTC + - AC: `pio run` termine sans erreur bloquante. + - Evidence: logs build RTC. + +- [ ] T-103 - Test firmware RTC + - AC: `pio test` (ou fallback test local) execute. + - Evidence: rapport test RTC. + +- [ ] T-104 - Trace webhook RTC + - AC: une ligne JSONL avec `repo_hint=rtc` apparait. + - Evidence: `artifacts/zeroclaw/conversations.jsonl` + +## Boucle hardware Zacus + +- [ ] T-201 - Discover hardware Zacus + - AC: au moins un port detecte pour carte Zacus. + - Evidence: `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` + +- [ ] T-202 - Build firmware Zacus + - AC: `pio run` (dans `hardware/firmware`) termine sans erreur bloquante. + - Evidence: logs build Zacus. + +- [ ] T-203 - Test firmware Zacus + - AC: `pio test` (ou fallback test local) execute. + - Evidence: rapport test Zacus. + +- [ ] T-204 - Trace webhook Zacus + - AC: une ligne JSONL avec `repo_hint=zacus` apparait. + - Evidence: `artifacts/zeroclaw/conversations.jsonl` + +## Observabilite et cout + +- [ ] T-301 - Stack endpoints smoke + - AC: `3000/health`, `8788`, `9090/-/ready` tous OK. + - Evidence: captures `curl`. + +- [ ] T-302 - Dry-run webhook budget + - AC: `--dry-run` passe sans ecriture execution JSONL. + - Evidence: sortie script + diff JSONL. + +- [ ] T-303 - Quota call limiter + - AC: depassement quota bloque avec code non-zero. + - Evidence: message `[budget] hourly call limit reached`. + +- [ ] T-304 - Quota chars limiter + - AC: message trop long bloque avec code non-zero. + - Evidence: message `[budget] message length ... exceeds ...`. + +## Definition of done + +- [ ] Au moins une boucle complete RTC + Zacus executee en local. +- [ ] Dashboard live exploitable pour suivi continu. +- [ ] Prometheus disponible avec target gateway scrapee. +- [ ] Logs et preuves archives dans `artifacts/zeroclaw/`. diff --git a/specs/zeroclaw_dual_hw_todo.md b/specs/zeroclaw_dual_hw_todo.md index 95aed68..2b68c67 100644 --- a/specs/zeroclaw_dual_hw_todo.md +++ b/specs/zeroclaw_dual_hw_todo.md @@ -1,36 +1,48 @@ -# TODO: Dual Hardware Autonomy (RTC + Zacus) +# TODO: Dual hardware autonomy runbook (RTC + Zacus) Last updated: 2026-02-21 -## Phase A: Local Baseline +## Immediate now -- [ ] Run `tools/ai/zeroclaw_dual_bootstrap.sh` and capture hardware snapshot. -- [ ] Validate `tools/ai/zeroclaw_dual_chat.sh rtc -m ""`. -- [ ] Validate `tools/ai/zeroclaw_dual_chat.sh zacus -m ""`. -- [ ] Record detected ports and map preferred role per board. +- [x] I-001 - Merge autonomie stack into `main` (`PR #5`). +- [x] I-002 - Configure local secret file `~/.zeroclaw/env` (`OPENROUTER_API_KEY` placeholder + mode `600`). +- [x] I-003 - Ensure local Prometheus backend exists (`prometheus` binary installed). +- [x] I-004 - Resolve open mirror PR redundancy (`PR #7` merge/close decision). -## Phase B: Repo Specs and PR Cadence +## Daily autonomous sequence -- [ ] Create/refresh one issue in `RTC_BL_PHONE` for ZeroClaw-assisted hardware loop. -- [ ] Create/refresh one issue in `le-mystere-professeur-zacus` for ZeroClaw-assisted hardware loop. -- [ ] Open one small PR per repo focused on one gate (build, tests, hardware smoke, docs). -- [ ] Require code review pass before merge (`gh pr review --approve` only after checks). +- [ ] D-001 - `tools/ai/zeroclaw_stack_down.sh` then `ZEROCLAW_PROM_MODE=auto tools/ai/zeroclaw_stack_up.sh`. +- [ ] D-002 - Smoke endpoints: + - `curl -fsS http://127.0.0.1:3000/health` + - `curl -fsS http://127.0.0.1:8788/` + - `curl -fsS http://127.0.0.1:9090/-/ready` +- [ ] D-003 - RTC loop: + - `tools/ai/zeroclaw_dual_chat.sh rtc --provider-check` + - `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` + - repo build/test + - webhook trace with `--repo-hint rtc` +- [ ] D-004 - Zacus loop: + - `tools/ai/zeroclaw_dual_chat.sh zacus --provider-check` + - `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` + - repo build/test + - webhook trace with `--repo-hint zacus` +- [ ] D-005 - Review `artifacts/zeroclaw/gateway.log` + `conversations.jsonl`. -## Phase C: Autonomy + Cost Optimization +## Hardware safety gates -- [ ] Keep prompts short, target one repo at a time. -- [ ] Use provider auto-fallback (`copilot` -> `openai-codex` -> `openrouter`) to avoid dead sessions. -- [ ] Add repo-level path filters in workflows to avoid expensive irrelevant runs. -- [ ] Use `workflow_dispatch` for hardware-required jobs to avoid noisy CI failures. +- [ ] H-001 - No flash/upload if `--hardware` detect returns no board. +- [ ] H-002 - Resolve stable serial target before upload. +- [ ] H-003 - Keep per-run logs under `artifacts/zeroclaw/`. -## Phase D: Hardware Robustness +## Cost/control gates -- [ ] Add serial-port resolver step before every upload/flash action. -- [ ] Fail fast if no expected USB device is detected. -- [ ] Archive logs per run for replayability (`artifacts//...`). +- [ ] C-001 - Validate `tools/ai/zeroclaw_webhook_send.sh --dry-run`. +- [ ] C-002 - Validate hourly quota guard (`ZEROCLAW_WEBHOOK_MAX_CALLS_PER_HOUR`). +- [ ] C-003 - Validate message length guard (`ZEROCLAW_WEBHOOK_MAX_CHARS`). -## Exit Criteria +## Exit criteria -- [ ] Both repos can be targeted with one command (`rtc` or `zacus`) without workspace leakage. -- [ ] Hardware discovery passes before action on connected boards. -- [ ] At least one successful PR cycle completed per repo with this orchestration path. +- [ ] E-001 - One successful complete loop RTC in local hardware. +- [ ] E-002 - One successful complete loop Zacus in local hardware. +- [ ] E-003 - Dashboard live usable for continuous supervision. +- [ ] E-004 - Prometheus target scrape confirmed on gateway metrics. -- 2.52.0 From 274c24aee8321e96799cdf4178fbf4755349ce96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 21 Feb 2026 02:55:23 +0100 Subject: [PATCH 2/5] add firmware loop helper and enforce pio env constraints in plan --- specs/03_plan.md | 20 +-- tools/ai/zeroclaw_hw_firmware_loop.sh | 172 ++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 7 deletions(-) create mode 100755 tools/ai/zeroclaw_hw_firmware_loop.sh diff --git a/specs/03_plan.md b/specs/03_plan.md index 08686e4..3811105 100644 --- a/specs/03_plan.md +++ b/specs/03_plan.md @@ -29,7 +29,8 @@ avec: - Local only (loopback `127.0.0.1`), pas d'exposition publique. - Secrets hors git (`~/.zeroclaw/env`, mode `600`). - Cout modele borne par quotas webhook locaux. -- Pas de flash/upload firmware sans detection hardware prealable. +- Flash/upload/serial monitor forces par defaut apres detection hardware valide. +- Jamais de commande `pio` sur un env non declare dans `platformio.ini`. ## Plan par phases @@ -73,8 +74,10 @@ Actions: 2. Hardware discover: `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` 3. Build/test firmware repo RTC: - `cd /Users/cils/Documents/Lelectron_rare/RTC_BL_PHONE` - - `pio run` - - `pio test -e native` (si environnement present) + - `pio run -e esp32dev` + - `pio run -e esp32dev -t upload` + - `pio device monitor -p -b 115200` (capture 60s) + - ou commande unique: `tools/ai/zeroclaw_hw_firmware_loop.sh rtc` 4. Trace webhook: - `tools/ai/zeroclaw_webhook_send.sh --repo-hint rtc "rtc loop status ..."` @@ -91,8 +94,11 @@ Actions: 2. Hardware discover: `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` 3. Build/test firmware repo Zacus: - `cd /Users/cils/Documents/Lelectron_rare/le-mystere-professeur-zacus/hardware/firmware` - - `pio run` - - `pio test -e native` (si environnement present) + - `pio run -e esp32dev` + - `pio run -e esp32dev -t upload` + - `pio device monitor -p -b 115200` (capture 60s) + - ou commande unique: `tools/ai/zeroclaw_hw_firmware_loop.sh zacus` + - statut tests: `blocked` tant que dossier `test/` absent 4. Trace webhook: - `tools/ai/zeroclaw_webhook_send.sh --repo-hint zacus "zacus loop status ..."` @@ -122,7 +128,7 @@ Sortie attendue: 1. Preflight auth + hardware. 2. Stack up + smoke endpoints. -3. RTC loop (dev/test + webhook trace). -4. Zacus loop (dev/test + webhook trace). +3. RTC loop (build + upload + monitor + webhook trace). +4. Zacus loop (build + upload + monitor + webhook trace). 5. Review logs + correction ciblée. 6. Commit/PR petit lot. diff --git a/tools/ai/zeroclaw_hw_firmware_loop.sh b/tools/ai/zeroclaw_hw_firmware_loop.sh new file mode 100755 index 0000000..1366192 --- /dev/null +++ b/tools/ai/zeroclaw_hw_firmware_loop.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +set -euo pipefail + +RTC_REPO="${ZEROCLAW_RTC_REPO:-/Users/cils/Documents/Lelectron_rare/RTC_BL_PHONE}" +ZACUS_REPO="${ZEROCLAW_ZACUS_REPO:-/Users/cils/Documents/Lelectron_rare/le-mystere-professeur-zacus}" + +RTC_FW_DIR="$RTC_REPO" +ZACUS_FW_DIR="$ZACUS_REPO/hardware/firmware" + +RTC_ENV_DEFAULT="${ZEROCLAW_RTC_PIO_ENV:-esp32dev}" +ZACUS_ENV_DEFAULT="${ZEROCLAW_ZACUS_PIO_ENV:-esp32dev}" +MONITOR_SECS_DEFAULT="${ZEROCLAW_MONITOR_SECS:-60}" +BAUD_DEFAULT="${ZEROCLAW_SERIAL_BAUD:-115200}" + +usage() { + cat < [--env ] [--port ] [--baud ] [--monitor-secs ] + +Behavior: + - Build is always executed. + - Upload/flash is forced by default. + - Serial monitor is forced by default for monitor-secs (default: ${MONITOR_SECS_DEFAULT}s). +USAGE +} + +if [[ $# -lt 1 ]]; then + usage + exit 1 +fi + +target="$1" +shift + +FW_DIR="" +ENV_NAME="" +case "$target" in + rtc) + FW_DIR="$RTC_FW_DIR" + ENV_NAME="$RTC_ENV_DEFAULT" + ;; + zacus) + FW_DIR="$ZACUS_FW_DIR" + ENV_NAME="$ZACUS_ENV_DEFAULT" + ;; + *) + echo "[fail] unsupported target: $target (expected rtc|zacus)" >&2 + exit 1 + ;; +esac + +PORT="${ZEROCLAW_UPLOAD_PORT:-}" +BAUD="$BAUD_DEFAULT" +MONITOR_SECS="$MONITOR_SECS_DEFAULT" + +while [[ $# -gt 0 ]]; do + case "$1" in + --env) + ENV_NAME="${2:-}" + shift 2 + ;; + --port) + PORT="${2:-}" + shift 2 + ;; + --baud) + BAUD="${2:-}" + shift 2 + ;; + --monitor-secs) + MONITOR_SECS="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "[fail] unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +if [[ ! -d "$FW_DIR" ]]; then + echo "[fail] firmware directory not found: $FW_DIR" >&2 + exit 1 +fi + +if [[ ! -f "$FW_DIR/platformio.ini" ]]; then + echo "[fail] platformio.ini not found in: $FW_DIR" >&2 + exit 1 +fi + +if ! command -v pio >/dev/null 2>&1; then + echo "[fail] platformio (pio) not found in PATH." >&2 + exit 1 +fi + +if ! rg -q "^\[env:${ENV_NAME}\]" "$FW_DIR/platformio.ini"; then + echo "[fail] invalid PlatformIO env '${ENV_NAME}' for $target." >&2 + echo "[info] valid envs:" >&2 + rg "^\[env:" "$FW_DIR/platformio.ini" >&2 || true + exit 1 +fi + +if [[ -z "$PORT" ]]; then + PORT="$(cd "$FW_DIR" && pio device list --json-output 2>/dev/null | python3 -c 'import json,sys +raw=sys.stdin.read().strip() +try: + arr=json.loads(raw) if raw else [] +except Exception: + arr=[] +for d in arr: + p=d.get("port") + if p: + print(p) + raise SystemExit(0) +raise SystemExit(1)' || true)" +fi + +if [[ -z "$PORT" ]]; then + echo "[fail] no serial port detected. Upload/monitor are forced by default." >&2 + exit 2 +fi + +UPLOAD_ARGS=() +MONITOR_ARGS=() +if [[ -n "$PORT" ]]; then + UPLOAD_ARGS+=(--upload-port "$PORT") + MONITOR_ARGS+=(-p "$PORT") +fi +if [[ -n "$BAUD" ]]; then + MONITOR_ARGS+=(-b "$BAUD") +fi + +echo "[info] target=$target fw_dir=$FW_DIR env=$ENV_NAME port=$PORT baud=$BAUD monitor_secs=$MONITOR_SECS" +echo "[step] build" +(cd "$FW_DIR" && pio run -e "$ENV_NAME") + +echo "[step] upload/flash (forced default)" +(cd "$FW_DIR" && pio run -e "$ENV_NAME" -t upload "${UPLOAD_ARGS[@]}") + +echo "[step] serial monitor (forced default)" +python3 - "$FW_DIR" "$MONITOR_SECS" "${MONITOR_ARGS[@]}" <<'PY' +import os +import signal +import subprocess +import sys +import time + +fw_dir = sys.argv[1] +monitor_secs = int(sys.argv[2]) +monitor_args = sys.argv[3:] +cmd = ["pio", "device", "monitor", *monitor_args] +print(f"[info] monitor command: {' '.join(cmd)} (timeout={monitor_secs}s)") + +proc = subprocess.Popen(cmd, cwd=fw_dir, preexec_fn=os.setsid) +try: + time.sleep(monitor_secs) +finally: + if proc.poll() is None: + os.killpg(proc.pid, signal.SIGTERM) + try: + proc.wait(timeout=3) + except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) + proc.wait(timeout=2) +PY + +echo "[ok] loop complete for $target ($ENV_NAME)." -- 2.52.0 From 0cb4c57e0f645a0988b61ccf07683b1919db680b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 21 Feb 2026 02:55:23 +0100 Subject: [PATCH 3/5] add 1min watcher and wire realtime log into zeroclaw stack --- specs/04_tasks.md | 23 +++--- specs/zeroclaw_dual_hw_todo.md | 10 +-- tools/ai/zeroclaw_stack_down.sh | 5 ++ tools/ai/zeroclaw_stack_up.sh | 109 +++++++++++++++++++++++++++- tools/ai/zeroclaw_watch_1min.sh | 123 ++++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 18 deletions(-) create mode 100755 tools/ai/zeroclaw_watch_1min.sh diff --git a/specs/04_tasks.md b/specs/04_tasks.md index a01ae8f..6bcabea 100644 --- a/specs/04_tasks.md +++ b/specs/04_tasks.md @@ -33,17 +33,17 @@ Format: ## Boucle hardware RTC -- [ ] T-101 - Discover hardware RTC +- [x] T-101 - Discover hardware RTC - AC: au moins un port detecte pour carte RTC. - Evidence: `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` - [ ] T-102 - Build firmware RTC - - AC: `pio run` termine sans erreur bloquante. + - AC: `pio run -e esp32dev` termine sans erreur bloquante. - Evidence: logs build RTC. -- [ ] T-103 - Test firmware RTC - - AC: `pio test` (ou fallback test local) execute. - - Evidence: rapport test RTC. +- [ ] T-103 - Upload + monitor RTC (forced default) + - AC: `pio run -e esp32dev -t upload` puis monitor 60s executes. + - Evidence: logs upload/monitor RTC. - [ ] T-104 - Trace webhook RTC - AC: une ligne JSONL avec `repo_hint=rtc` apparait. @@ -51,17 +51,17 @@ Format: ## Boucle hardware Zacus -- [ ] T-201 - Discover hardware Zacus +- [x] T-201 - Discover hardware Zacus - AC: au moins un port detecte pour carte Zacus. - Evidence: `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` - [ ] T-202 - Build firmware Zacus - - AC: `pio run` (dans `hardware/firmware`) termine sans erreur bloquante. + - AC: `pio run -e esp32dev` (dans `hardware/firmware`) termine sans erreur bloquante. - Evidence: logs build Zacus. -- [ ] T-203 - Test firmware Zacus - - AC: `pio test` (ou fallback test local) execute. - - Evidence: rapport test Zacus. +- [ ] T-203 - Upload + monitor Zacus (forced default) + - AC: `pio run -e esp32dev -t upload` puis monitor 60s executes. + - Evidence: logs upload/monitor Zacus. - [ ] T-204 - Trace webhook Zacus - AC: une ligne JSONL avec `repo_hint=zacus` apparait. @@ -69,7 +69,7 @@ Format: ## Observabilite et cout -- [ ] T-301 - Stack endpoints smoke +- [x] T-301 - Stack endpoints smoke - AC: `3000/health`, `8788`, `9090/-/ready` tous OK. - Evidence: captures `curl`. @@ -91,3 +91,4 @@ Format: - [ ] Dashboard live exploitable pour suivi continu. - [ ] Prometheus disponible avec target gateway scrapee. - [ ] Logs et preuves archives dans `artifacts/zeroclaw/`. +- [ ] Aucune commande documentee n'utilise `-e native` ou `-e test`. diff --git a/specs/zeroclaw_dual_hw_todo.md b/specs/zeroclaw_dual_hw_todo.md index 2b68c67..c11ed01 100644 --- a/specs/zeroclaw_dual_hw_todo.md +++ b/specs/zeroclaw_dual_hw_todo.md @@ -11,26 +11,26 @@ Last updated: 2026-02-21 ## Daily autonomous sequence -- [ ] D-001 - `tools/ai/zeroclaw_stack_down.sh` then `ZEROCLAW_PROM_MODE=auto tools/ai/zeroclaw_stack_up.sh`. -- [ ] D-002 - Smoke endpoints: +- [x] D-001 - `tools/ai/zeroclaw_stack_down.sh` then `ZEROCLAW_PROM_MODE=auto tools/ai/zeroclaw_stack_up.sh`. +- [x] D-002 - Smoke endpoints: - `curl -fsS http://127.0.0.1:3000/health` - `curl -fsS http://127.0.0.1:8788/` - `curl -fsS http://127.0.0.1:9090/-/ready` - [ ] D-003 - RTC loop: - `tools/ai/zeroclaw_dual_chat.sh rtc --provider-check` - `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` - - repo build/test + - `tools/ai/zeroclaw_hw_firmware_loop.sh rtc` (build+upload+monitor forced default) - webhook trace with `--repo-hint rtc` - [ ] D-004 - Zacus loop: - `tools/ai/zeroclaw_dual_chat.sh zacus --provider-check` - `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` - - repo build/test + - `tools/ai/zeroclaw_hw_firmware_loop.sh zacus` (build+upload+monitor forced default) - webhook trace with `--repo-hint zacus` - [ ] D-005 - Review `artifacts/zeroclaw/gateway.log` + `conversations.jsonl`. ## Hardware safety gates -- [ ] H-001 - No flash/upload if `--hardware` detect returns no board. +- [ ] H-001 - Flash/upload/monitor are forced by default when a board is detected. - [ ] H-002 - Resolve stable serial target before upload. - [ ] H-003 - Keep per-run logs under `artifacts/zeroclaw/`. diff --git a/tools/ai/zeroclaw_stack_down.sh b/tools/ai/zeroclaw_stack_down.sh index 605ef0a..93e9bc0 100755 --- a/tools/ai/zeroclaw_stack_down.sh +++ b/tools/ai/zeroclaw_stack_down.sh @@ -2,6 +2,7 @@ set -euo pipefail ART_DIR="${ZEROCLAW_ART_DIR:-/Users/cils/Documents/Lelectron_rare/Kill_LIFE/artifacts/zeroclaw}" +ROOT_DIR="/Users/cils/Documents/Lelectron_rare/Kill_LIFE" GW_PID_FILE="$ART_DIR/gateway.pid" FW_PID_FILE="$ART_DIR/follow.pid" PROM_PID_FILE="$ART_DIR/prometheus.pid" @@ -9,6 +10,7 @@ PROM_CONTAINER="${ZEROCLAW_PROM_CONTAINER:-zeroclaw-prometheus}" GW_MANAGED_FILE="$ART_DIR/gateway.managed" FW_MANAGED_FILE="$ART_DIR/follow.managed" PROM_MANAGED_FILE="$ART_DIR/prometheus.managed" +WATCHER_SCRIPT="$ROOT_DIR/tools/ai/zeroclaw_watch_1min.sh" stop_pid_file() { local pid_file="$1" @@ -42,4 +44,7 @@ if [[ -f "$PROM_MANAGED_FILE" ]]; then fi fi rm -f "$GW_MANAGED_FILE" "$FW_MANAGED_FILE" "$PROM_MANAGED_FILE" +if [[ -x "$WATCHER_SCRIPT" ]]; then + "$WATCHER_SCRIPT" stop >/dev/null 2>&1 || true +fi echo "ZeroClaw local stack stopped. Logs preserved in artifacts/zeroclaw/." diff --git a/tools/ai/zeroclaw_stack_up.sh b/tools/ai/zeroclaw_stack_up.sh index 3296069..ee6ce68 100755 --- a/tools/ai/zeroclaw_stack_up.sh +++ b/tools/ai/zeroclaw_stack_up.sh @@ -27,6 +27,7 @@ PROM_SCRAPE_INTERVAL="${ZEROCLAW_PROM_SCRAPE_INTERVAL:-15s}" PROM_CONTAINER="${ZEROCLAW_PROM_CONTAINER:-zeroclaw-prometheus}" DOCKER_WAIT_SECS="${ZEROCLAW_DOCKER_WAIT_SECS:-90}" PROM_READY_WAIT_SECS="${ZEROCLAW_PROM_READY_WAIT_SECS:-15}" +AUTO_REPAIR_ON_INVALID_TOKEN="${ZEROCLAW_AUTO_REPAIR_ON_INVALID_TOKEN:-1}" GW_PID_FILE="$ART_DIR/gateway.pid" FW_PID_FILE="$ART_DIR/follow.pid" @@ -40,13 +41,17 @@ PROM_LOG="$ART_DIR/prometheus.log" PROM_CONFIG_FILE="$ART_DIR/prometheus.yml" PROM_DATA_DIR="$ART_DIR/prometheus-data" PROM_STATUS="disabled" +ZEROCLAW_CONFIG_FILE="${ZEROCLAW_CONFIG_FILE:-$HOME/.zeroclaw/config.toml}" GW_MANAGED_FILE="$ART_DIR/gateway.managed" FW_MANAGED_FILE="$ART_DIR/follow.managed" PROM_MANAGED_FILE="$ART_DIR/prometheus.managed" +WATCHER_SCRIPT="$ROOT_DIR/tools/ai/zeroclaw_watch_1min.sh" +RT_LOG="$ART_DIR/realtime_1min.log" mkdir -p "$ART_DIR" touch "$CONVO_FILE" touch "$GW_LOG" +touch "$RT_LOG" if [[ -f "$HOME/.zeroclaw/config.toml" ]]; then chmod 600 "$HOME/.zeroclaw/config.toml" >/dev/null 2>&1 || true @@ -211,8 +216,44 @@ sys.exit(0 if obj.get("paired") else 1)' >/dev/null 2>&1 } read_pair_token() { - [[ -f "$TOKEN_FILE" ]] || return 1 - cat "$TOKEN_FILE" 2>/dev/null + if [[ -f "$TOKEN_FILE" ]]; then + cat "$TOKEN_FILE" 2>/dev/null + return 0 + fi + + if [[ -f "$ZEROCLAW_CONFIG_FILE" ]]; then + local token_from_config + token_from_config="$(python3 - "$ZEROCLAW_CONFIG_FILE" <<'PY' +import sys +from pathlib import Path + +cfg = Path(sys.argv[1]) +try: + import tomllib +except Exception: + print("", end="") + raise SystemExit(0) + +try: + obj = tomllib.loads(cfg.read_text(encoding="utf-8")) +except Exception: + print("", end="") + raise SystemExit(0) + +tokens = obj.get("gateway", {}).get("paired_tokens", []) +if isinstance(tokens, list) and tokens: + print(str(tokens[0]), end="") +PY +)" + if [[ -n "$token_from_config" ]]; then + printf '%s\n' "$token_from_config" >"$TOKEN_FILE" + chmod 600 "$TOKEN_FILE" + printf '%s\n' "$token_from_config" + return 0 + fi + fi + + return 1 } token_is_usable() { @@ -231,6 +272,34 @@ token_is_usable() { return 0 } +wait_for_gateway_health() { + local waited=0 + while (( waited < 20 )); do + if curl -fsS "http://$GATEWAY_HOST:$GATEWAY_PORT/health" >/dev/null 2>&1; then + return 0 + fi + sleep 0.25 + waited=$((waited + 1)) + done + return 1 +} + +clear_config_paired_tokens() { + [[ -f "$ZEROCLAW_CONFIG_FILE" ]] || return 1 + python3 - "$ZEROCLAW_CONFIG_FILE" <<'PY' +import re +import sys +from pathlib import Path + +cfg = Path(sys.argv[1]) +text = cfg.read_text(encoding="utf-8") +updated, n = re.subn(r'(?m)^paired_tokens\s*=\s*\[.*\]\s*$', 'paired_tokens = []', text) +if n == 0: + raise SystemExit(1) +cfg.write_text(updated, encoding="utf-8") +PY +} + extract_pair_code() { grep -Eo 'X-Pairing-Code: [0-9]{6}' "$GW_LOG" 2>/dev/null | tail -1 | awk '{print $2}' || true } @@ -281,6 +350,36 @@ ensure_gateway_pairing() { current_token="$(read_pair_token || true)" if ! token_is_usable "$current_token"; then rm -f "$TOKEN_FILE" + + if [[ "$AUTO_REPAIR_ON_INVALID_TOKEN" == "1" && -f "$GW_MANAGED_FILE" ]]; then + if clear_config_paired_tokens; then + local gw_pid="" + gw_pid="$(cat "$GW_PID_FILE" 2>/dev/null || true)" + if [[ -n "$gw_pid" ]] && kill -0 "$gw_pid" >/dev/null 2>&1; then + kill "$gw_pid" >/dev/null 2>&1 || true + sleep 0.3 + if kill -0 "$gw_pid" >/dev/null 2>&1; then + kill -9 "$gw_pid" >/dev/null 2>&1 || true + fi + fi + + nohup "$ZEROCLAW_BIN" gateway --port "$GATEWAY_PORT" --host "$GATEWAY_HOST" >"$GW_LOG" 2>&1 & + echo "$!" >"$GW_PID_FILE" + wait_for_gateway_health || true + + for _ in $(seq 1 40); do + pair_code="$(extract_pair_code)" + if [[ -n "$pair_code" ]]; then + break + fi + sleep 0.25 + done + + if [[ -n "${pair_code:-}" ]]; then + write_pair_token_from_code "$pair_code" || true + fi + fi + fi fi } @@ -461,6 +560,7 @@ cat >"$INDEX_FILE" < /conversations.jsonl /gateway.log + /realtime_1min.log /prometheus.yml /health /metrics @@ -576,9 +676,14 @@ cat >"$INDEX_FILE" < EOF +if [[ -x "$WATCHER_SCRIPT" ]]; then + "$WATCHER_SCRIPT" start >/dev/null 2>&1 || true +fi + echo "Gateway: http://$GATEWAY_HOST:$GATEWAY_PORT/health" echo "Follow : http://127.0.0.1:$FOLLOW_PORT/" echo "Prom : http://$PROM_HOST:$PROM_PORT/targets ($PROM_STATUS)" +echo "RT 1m : http://127.0.0.1:$FOLLOW_PORT/realtime_1min.log" echo "Logs : $GW_LOG" echo "Token : $TOKEN_FILE" if [[ ! -s "$TOKEN_FILE" ]]; then diff --git a/tools/ai/zeroclaw_watch_1min.sh b/tools/ai/zeroclaw_watch_1min.sh new file mode 100755 index 0000000..ffa9f13 --- /dev/null +++ b/tools/ai/zeroclaw_watch_1min.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="/Users/cils/Documents/Lelectron_rare/Kill_LIFE" +ART_DIR="${ZEROCLAW_ART_DIR:-$ROOT_DIR/artifacts/zeroclaw}" +HOST="${ZEROCLAW_GATEWAY_HOST:-127.0.0.1}" +PORT="${ZEROCLAW_GATEWAY_PORT:-3000}" +PROM_HOST="${ZEROCLAW_PROM_HOST:-127.0.0.1}" +PROM_PORT="${ZEROCLAW_PROM_PORT:-9090}" +INTERVAL_SECS="${ZEROCLAW_RT_INTERVAL_SECS:-60}" + +PID_FILE="$ART_DIR/realtime_1min.pid" +LOG_FILE="$ART_DIR/realtime_1min.log" +CONVO_FILE="$ART_DIR/conversations.jsonl" +GW_LOG_FILE="$ART_DIR/gateway.log" + +mkdir -p "$ART_DIR" +touch "$LOG_FILE" "$CONVO_FILE" "$GW_LOG_FILE" + +is_running() { + [[ -f "$PID_FILE" ]] || return 1 + local pid + pid="$(cat "$PID_FILE" 2>/dev/null || true)" + [[ -n "$pid" ]] || return 1 + kill -0 "$pid" >/dev/null 2>&1 +} + +collect_once() { + local ts health paired uptime prom last_convo last_gw + ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + + health="$(curl -sS "http://$HOST:$PORT/health" || true)" + if [[ -n "$health" ]]; then + paired="$(printf '%s' "$health" | python3 -c 'import json,sys +raw=sys.stdin.read().strip() +try: + obj=json.loads(raw) if raw else {} +except Exception: + print("unknown") + raise SystemExit +print(str(obj.get("paired","unknown")).lower())' 2>/dev/null || echo "unknown")" + uptime="$(printf '%s' "$health" | python3 -c 'import json,sys +raw=sys.stdin.read().strip() +try: + obj=json.loads(raw) if raw else {} +except Exception: + print("unknown") + raise SystemExit +print(obj.get("runtime",{}).get("uptime_seconds","unknown"))' 2>/dev/null || echo "unknown")" + else + paired="down" + uptime="down" + fi + + if curl -fsS "http://$PROM_HOST:$PROM_PORT/-/ready" >/dev/null 2>&1; then + prom="ready" + else + prom="down" + fi + + last_convo="$(tail -n 1 "$CONVO_FILE" 2>/dev/null || true)" + last_gw="$(tail -n 1 "$GW_LOG_FILE" 2>/dev/null || true)" + last_convo="${last_convo//$'\t'/ }" + last_gw="${last_gw//$'\t'/ }" + + printf '%s | paired=%s | uptime=%s | prom=%s | convo=%s | gateway=%s\n' \ + "$ts" "$paired" "$uptime" "$prom" "$last_convo" "$last_gw" >>"$LOG_FILE" +} + +run_loop() { + while true; do + collect_once + sleep "$INTERVAL_SECS" + done +} + +start() { + if is_running; then + echo "[info] watcher already running (pid $(cat "$PID_FILE"))." + return 0 + fi + nohup "$0" run >>"$LOG_FILE" 2>&1 & + echo "$!" >"$PID_FILE" + echo "[ok] watcher started (pid $(cat "$PID_FILE"))." +} + +stop() { + if ! is_running; then + rm -f "$PID_FILE" + echo "[info] watcher not running." + return 0 + fi + local pid + pid="$(cat "$PID_FILE")" + kill "$pid" >/dev/null 2>&1 || true + sleep 0.2 + if kill -0 "$pid" >/dev/null 2>&1; then + kill -9 "$pid" >/dev/null 2>&1 || true + fi + rm -f "$PID_FILE" + echo "[ok] watcher stopped." +} + +status() { + if is_running; then + echo "[ok] watcher running (pid $(cat "$PID_FILE"))." + else + echo "[info] watcher not running." + fi + tail -n 5 "$LOG_FILE" 2>/dev/null || true +} + +case "${1:-start}" in + start) start ;; + stop) stop ;; + status) status ;; + run) run_loop ;; + once) collect_once ;; + *) + echo "Usage: $(basename "$0") [start|stop|status|run|once]" >&2 + exit 1 + ;; +esac -- 2.52.0 From 94e4da0e67142df51b9393d8b45374cb0b21a11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 21 Feb 2026 02:58:21 +0100 Subject: [PATCH 4/5] [codex] harden dual-hw autonomy defaults and valid pio env CI --- .../workflows/zeroclaw_dual_orchestrator.yml | 4 ++ .../REQUIRED_CHECKS_STRATEGY.md | 2 +- .../patched_workflows/firmware_ci.yml | 64 +++++++++++++++---- gh_setup_and_patches/setup_repo.sh | 4 +- specs/04_tasks.md | 14 ++-- specs/constraints.yaml | 2 +- specs/zeroclaw_dual_hw_orchestration_spec.md | 32 +++++++++- specs/zeroclaw_dual_hw_todo.md | 4 +- tools/ai/zeroclaw_hw_firmware_loop.sh | 29 +++++++-- tools/ai/zeroclaw_webhook_send.sh | 29 +++++++++ 10 files changed, 158 insertions(+), 26 deletions(-) diff --git a/.github/workflows/zeroclaw_dual_orchestrator.yml b/.github/workflows/zeroclaw_dual_orchestrator.yml index 219675e..d6a1f09 100644 --- a/.github/workflows/zeroclaw_dual_orchestrator.yml +++ b/.github/workflows/zeroclaw_dual_orchestrator.yml @@ -35,11 +35,15 @@ jobs: run: | shellcheck tools/ai/zeroclaw_dual_bootstrap.sh shellcheck tools/ai/zeroclaw_dual_chat.sh + shellcheck tools/ai/zeroclaw_hw_firmware_loop.sh shellcheck tools/ai/zeroclaw_stack_up.sh shellcheck tools/ai/zeroclaw_stack_down.sh + shellcheck tools/ai/zeroclaw_watch_1min.sh shellcheck tools/ai/zeroclaw_webhook_send.sh - name: Verify spec files run: | + test -s specs/03_plan.md + test -s specs/04_tasks.md test -s specs/zeroclaw_dual_hw_orchestration_spec.md test -s specs/zeroclaw_dual_hw_todo.md diff --git a/gh_setup_and_patches/REQUIRED_CHECKS_STRATEGY.md b/gh_setup_and_patches/REQUIRED_CHECKS_STRATEGY.md index 0e9351e..59ced4a 100644 --- a/gh_setup_and_patches/REQUIRED_CHECKS_STRATEGY.md +++ b/gh_setup_and_patches/REQUIRED_CHECKS_STRATEGY.md @@ -36,7 +36,7 @@ Then commit. ## Verify required check contexts After your first PR, open **PR → Checks** and note the exact names, e.g. -- `Firmware CI / pio (native)` +- `Firmware CI / pio` - `Scope Guard / guard` Use those exact names in branch protection (the provided `setup_repo.sh` uses the defaults from the template). diff --git a/gh_setup_and_patches/patched_workflows/firmware_ci.yml b/gh_setup_and_patches/patched_workflows/firmware_ci.yml index 76cae16..526d38e 100644 --- a/gh_setup_and_patches/patched_workflows/firmware_ci.yml +++ b/gh_setup_and_patches/patched_workflows/firmware_ci.yml @@ -9,10 +9,6 @@ on: jobs: pio: runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - env: [esp32s3_arduino, esp32_arduino, native] steps: - uses: actions/checkout@v6 @@ -32,7 +28,7 @@ jobs: - name: No relevant changes (fast pass) if: steps.changes.outputs.fw != 'true' run: | - echo "No firmware/spec/standards changes detected. Skipping build/test for ${{ matrix.env }}." + echo "No firmware/spec/standards changes detected. Skipping build/test." - uses: actions/setup-python@v5 if: steps.changes.outputs.fw == 'true' @@ -45,7 +41,7 @@ jobs: path: | ~/.cache/pip ~/.platformio - key: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('firmware/platformio.ini') }} + key: pio-${{ runner.os }}-${{ hashFiles('firmware/platformio.ini') }} - name: Install PlatformIO if: steps.changes.outputs.fw == 'true' @@ -53,12 +49,58 @@ jobs: python -m pip install -U pip python -m pip install -U platformio - - name: Build + - name: Discover PlatformIO envs + id: envs if: steps.changes.outputs.fw == 'true' working-directory: firmware - run: pio run -e ${{ matrix.env }} + run: | + envs="$(pio project config --json-output | python3 -c 'import json,sys +arr=json.load(sys.stdin) +names=[] +for item in arr: + name=item.get("name","") + if name.startswith("env:"): + names.append(name.split("env:",1)[1]) +print(",".join(names))')" + if [[ -z "$envs" ]]; then + echo "has_envs=false" >>"$GITHUB_OUTPUT" + echo "No PlatformIO env found in firmware/platformio.ini." + else + echo "has_envs=true" >>"$GITHUB_OUTPUT" + echo "envs=$envs" >>"$GITHUB_OUTPUT" + echo "Detected envs: $envs" + fi - - name: Test - if: steps.changes.outputs.fw == 'true' && matrix.env == 'native' + - name: Build all declared envs + if: steps.changes.outputs.fw == 'true' && steps.envs.outputs.has_envs == 'true' working-directory: firmware - run: pio test -e native + run: | + IFS=',' read -ra env_arr <<<"${{ steps.envs.outputs.envs }}" + for env_name in "${env_arr[@]}"; do + echo "==> pio run -e ${env_name}" + pio run -e "${env_name}" + done + + - name: Unit tests (valid env only) + if: steps.changes.outputs.fw == 'true' && steps.envs.outputs.has_envs == 'true' + working-directory: firmware + run: | + if [[ ! -d test ]]; then + echo "No firmware/test directory. Skipping pio test." + exit 0 + fi + + IFS=',' read -ra env_arr <<<"${{ steps.envs.outputs.envs }}" + test_env="" + for env_name in "${env_arr[@]}"; do + if [[ "$env_name" == "esp32dev" ]]; then + test_env="$env_name" + break + fi + done + if [[ -z "$test_env" ]]; then + test_env="${env_arr[0]}" + fi + + echo "==> pio test -e ${test_env} --without-uploading" + pio test -e "${test_env}" --without-uploading diff --git a/gh_setup_and_patches/setup_repo.sh b/gh_setup_and_patches/setup_repo.sh index 6532c11..f9dfcb9 100755 --- a/gh_setup_and_patches/setup_repo.sh +++ b/gh_setup_and_patches/setup_repo.sh @@ -141,9 +141,7 @@ setup_branch_protection() { contexts+=("Scope Guard / guard") if [[ "${REQUIRE_BUILD_CHECKS}" == "1" ]]; then - contexts+=("Firmware CI / pio (esp32s3_arduino)") - contexts+=("Firmware CI / pio (esp32_arduino)") - contexts+=("Firmware CI / pio (native)") + contexts+=("Firmware CI / pio") contexts+=("Hardware CI (KiCad) / hw") contexts+=("Compliance Gate / validate") fi diff --git a/specs/04_tasks.md b/specs/04_tasks.md index 6bcabea..3800c0c 100644 --- a/specs/04_tasks.md +++ b/specs/04_tasks.md @@ -27,9 +27,10 @@ Format: - AC: commande `prometheus --version` disponible. - Evidence: sortie shell `prometheus, version ...` -- [ ] T-005 - Stabiliser pairing bearer auto +- [x] T-005 - Stabiliser pairing bearer auto - AC: `artifacts/zeroclaw/pair_token.txt` utilisable pour webhook sans override manuel. - Evidence: `tools/ai/zeroclaw_webhook_send.sh --repo-hint rtc "pairing check"` + - Status: `2026-02-21` done (401 resolved; gateway now accepts bearer, downstream model may still return HTTP 500). ## Boucle hardware RTC @@ -37,13 +38,15 @@ Format: - AC: au moins un port detecte pour carte RTC. - Evidence: `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` -- [ ] T-102 - Build firmware RTC +- [x] T-102 - Build firmware RTC - AC: `pio run -e esp32dev` termine sans erreur bloquante. - Evidence: logs build RTC. + - Status: `2026-02-21` done. - [ ] T-103 - Upload + monitor RTC (forced default) - AC: `pio run -e esp32dev -t upload` puis monitor 60s executes. - Evidence: logs upload/monitor RTC. + - Blocker: `esptool` connect timeout (`No serial data received`) on selected USB serial port. - [ ] T-104 - Trace webhook RTC - AC: une ligne JSONL avec `repo_hint=rtc` apparait. @@ -55,13 +58,15 @@ Format: - AC: au moins un port detecte pour carte Zacus. - Evidence: `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` -- [ ] T-202 - Build firmware Zacus +- [x] T-202 - Build firmware Zacus - AC: `pio run -e esp32dev` (dans `hardware/firmware`) termine sans erreur bloquante. - Evidence: logs build Zacus. + - Status: `2026-02-21` done. - [ ] T-203 - Upload + monitor Zacus (forced default) - AC: `pio run -e esp32dev -t upload` puis monitor 60s executes. - Evidence: logs upload/monitor Zacus. + - Blocker: `esptool` connect timeout (`No serial data received`) on selected USB serial port. - [ ] T-204 - Trace webhook Zacus - AC: une ligne JSONL avec `repo_hint=zacus` apparait. @@ -73,9 +78,10 @@ Format: - AC: `3000/health`, `8788`, `9090/-/ready` tous OK. - Evidence: captures `curl`. -- [ ] T-302 - Dry-run webhook budget +- [x] T-302 - Dry-run webhook budget - AC: `--dry-run` passe sans ecriture execution JSONL. - Evidence: sortie script + diff JSONL. + - Status: `2026-02-21` done. - [ ] T-303 - Quota call limiter - AC: depassement quota bloque avec code non-zero. diff --git a/specs/constraints.yaml b/specs/constraints.yaml index a58fbf7..3e05918 100644 --- a/specs/constraints.yaml +++ b/specs/constraints.yaml @@ -4,7 +4,7 @@ project: targets: - esp32s3 - esp32 - - native + - esp32dev ai: triggers: diff --git a/specs/zeroclaw_dual_hw_orchestration_spec.md b/specs/zeroclaw_dual_hw_orchestration_spec.md index d6f03e8..636653e 100644 --- a/specs/zeroclaw_dual_hw_orchestration_spec.md +++ b/specs/zeroclaw_dual_hw_orchestration_spec.md @@ -9,7 +9,7 @@ Run one orchestration layer that can: - converse against `RTC_BL_PHONE` and `le-mystere-professeur-zacus` independently, - keep workspace boundaries strict per repo, - run low-cost autonomous loops with guarded command allowlists, -- stay ready for connected hardware checks before any upload/flash action. +- enforce upload/flash/serial-monitor loops by default on connected hardware. ## 2) Scope @@ -18,6 +18,7 @@ In scope: - local ZeroClaw profile bootstrap for both repos, - deterministic workspace switch (`rtc` vs `zacus`) from one CLI entrypoint, - hardware discovery/introspection preflight, +- forced-by-default firmware loop (build + upload + monitor) per repo target, - lightweight CI validation for orchestration scripts/spec. Out of scope: @@ -76,6 +77,7 @@ This keeps `autonomy.workspace_only = true` effective on a per-repo boundary. - generates live follow dashboard at `http://127.0.0.1:8788/`, - dashboard includes live polling panels for `/conversations.jsonl` and `/gateway.log` (1s polling), - preserves direct raw links: `/conversations.jsonl` and `/gateway.log`, + - starts `tools/ai/zeroclaw_watch_1min.sh` watcher and exposes `/realtime_1min.log`, - writes `artifacts/zeroclaw/prometheus.yml` scrape config, - supports local Prometheus startup via `ZEROCLAW_PROM_MODE` (`off`, `auto`, `binary`, `docker`) with `auto` fallback `binary -> docker`, - on macOS, auto-attempts Docker Desktop startup before Prometheus docker mode, @@ -83,7 +85,19 @@ This keeps `autonomy.workspace_only = true` effective on a per-repo boundary. - `tools/ai/zeroclaw_stack_down.sh` - stops local gateway/follow processes, - stops local Prometheus process/container if managed by the stack, + - stops `tools/ai/zeroclaw_watch_1min.sh` watcher process, - confirms logs remain in `artifacts/zeroclaw/`. +- `tools/ai/zeroclaw_watch_1min.sh` + - supports `start|stop|status|run|once`, + - appends one status line every 60s by default to `artifacts/zeroclaw/realtime_1min.log`, + - line format: + - ` | paired=<...> | uptime=<...> | prom=<...> | convo= | gateway=` +- `tools/ai/zeroclaw_hw_firmware_loop.sh` + - target switch `rtc|zacus`, + - validates `platformio.ini` env exists before running, + - forces `build -> upload -> serial monitor` by default, + - auto-detects serial port when not specified, + - monitor timeout default 60s. - `tools/ai/zeroclaw_webhook_send.sh` - sends webhook by default (no mandatory allow flag), - keeps `--allow-model-call` as backward-compatible legacy option, @@ -148,6 +162,7 @@ Raw URLs: - `http://127.0.0.1:8788/conversations.jsonl` - `http://127.0.0.1:8788/gateway.log` +- `http://127.0.0.1:8788/realtime_1min.log` - `http://127.0.0.1:8788/prometheus.yml` Conversation JSONL line schema (append-only): @@ -203,3 +218,18 @@ Suggested contents: Behavior: - `zeroclaw_dual_chat.sh`, `zeroclaw_stack_up.sh`, and `zeroclaw_webhook_send.sh` auto-load this file if present. + +## 11) Firmware Loop Defaults (local hardware) + +Default execution policy: + +- with hardware connected, upload/flash and serial monitor are forced by default. +- if no serial port is detected, loop fails fast (non-zero) instead of silently skipping upload. +- only declared PlatformIO envs are allowed; no `-e native` or `-e test` shortcuts. + +Default commands: + +- RTC: + - `tools/ai/zeroclaw_hw_firmware_loop.sh rtc` +- Zacus: + - `tools/ai/zeroclaw_hw_firmware_loop.sh zacus` diff --git a/specs/zeroclaw_dual_hw_todo.md b/specs/zeroclaw_dual_hw_todo.md index c11ed01..817a67e 100644 --- a/specs/zeroclaw_dual_hw_todo.md +++ b/specs/zeroclaw_dual_hw_todo.md @@ -21,11 +21,13 @@ Last updated: 2026-02-21 - `tools/ai/zeroclaw_dual_chat.sh rtc --hardware` - `tools/ai/zeroclaw_hw_firmware_loop.sh rtc` (build+upload+monitor forced default) - webhook trace with `--repo-hint rtc` + - current blocker: serial upload timeout (`No serial data received`) - [ ] D-004 - Zacus loop: - `tools/ai/zeroclaw_dual_chat.sh zacus --provider-check` - `tools/ai/zeroclaw_dual_chat.sh zacus --hardware` - `tools/ai/zeroclaw_hw_firmware_loop.sh zacus` (build+upload+monitor forced default) - webhook trace with `--repo-hint zacus` + - current blocker: serial upload timeout (`No serial data received`) - [ ] D-005 - Review `artifacts/zeroclaw/gateway.log` + `conversations.jsonl`. ## Hardware safety gates @@ -36,7 +38,7 @@ Last updated: 2026-02-21 ## Cost/control gates -- [ ] C-001 - Validate `tools/ai/zeroclaw_webhook_send.sh --dry-run`. +- [x] C-001 - Validate `tools/ai/zeroclaw_webhook_send.sh --dry-run`. - [ ] C-002 - Validate hourly quota guard (`ZEROCLAW_WEBHOOK_MAX_CALLS_PER_HOUR`). - [ ] C-003 - Validate message length guard (`ZEROCLAW_WEBHOOK_MAX_CHARS`). diff --git a/tools/ai/zeroclaw_hw_firmware_loop.sh b/tools/ai/zeroclaw_hw_firmware_loop.sh index 1366192..64251a5 100755 --- a/tools/ai/zeroclaw_hw_firmware_loop.sh +++ b/tools/ai/zeroclaw_hw_firmware_loop.sh @@ -112,11 +112,32 @@ try: arr=json.loads(raw) if raw else [] except Exception: arr=[] +best=None +best_score=-10**9 for d in arr: - p=d.get("port") - if p: - print(p) - raise SystemExit(0) + p=str(d.get("port","")) + desc=str(d.get("description","")) + hwid=str(d.get("hwid","")) + if not p: + continue + score=0 + low=f\"{p} {desc} {hwid}\".lower() + if hwid and hwid.lower() != \"n/a\": + score += 50 + if \"usb\" in low: + score += 30 + if \"cp210\" in low or \"ch340\" in low or \"uart\" in low: + score += 20 + if \"bluetooth\" in low: + score -= 100 + if \"/cu.urt\" in p.lower() and hwid.lower() == \"n/a\": + score -= 120 + if score > best_score: + best_score=score + best=p +if best: + print(best) + raise SystemExit(0) raise SystemExit(1)' || true)" fi diff --git a/tools/ai/zeroclaw_webhook_send.sh b/tools/ai/zeroclaw_webhook_send.sh index 65fb78e..de2d2b9 100755 --- a/tools/ai/zeroclaw_webhook_send.sh +++ b/tools/ai/zeroclaw_webhook_send.sh @@ -17,6 +17,7 @@ ART_DIR="${ZEROCLAW_ART_DIR:-/Users/cils/Documents/Lelectron_rare/Kill_LIFE/arti HOST="${ZEROCLAW_GATEWAY_HOST:-127.0.0.1}" PORT="${ZEROCLAW_GATEWAY_PORT:-3000}" TOKEN_FILE="$ART_DIR/pair_token.txt" +ZEROCLAW_CONFIG_FILE="${ZEROCLAW_CONFIG_FILE:-$HOME/.zeroclaw/config.toml}" CONVO_FILE="$ART_DIR/conversations.jsonl" BUDGET_FILE="$ART_DIR/webhook_budget.json" MAX_CALLS_PER_HOUR="${ZEROCLAW_WEBHOOK_MAX_CALLS_PER_HOUR:-40}" @@ -93,6 +94,34 @@ TOKEN="${ZEROCLAW_BEARER:-}" if [[ -z "$TOKEN" && -f "$TOKEN_FILE" ]]; then TOKEN="$(cat "$TOKEN_FILE")" fi +if [[ -z "$TOKEN" && -f "$ZEROCLAW_CONFIG_FILE" ]]; then + TOKEN="$(python3 - "$ZEROCLAW_CONFIG_FILE" <<'PY' +import sys +from pathlib import Path + +cfg = Path(sys.argv[1]) +try: + import tomllib +except Exception: + print("", end="") + raise SystemExit(0) + +try: + obj = tomllib.loads(cfg.read_text(encoding="utf-8")) +except Exception: + print("", end="") + raise SystemExit(0) + +tokens = obj.get("gateway", {}).get("paired_tokens", []) +if isinstance(tokens, list) and tokens: + print(str(tokens[0]), end="") +PY +)" + if [[ -n "$TOKEN" ]]; then + printf '%s\n' "$TOKEN" >"$TOKEN_FILE" + chmod 600 "$TOKEN_FILE" + fi +fi mkdir -p "$ART_DIR" touch "$CONVO_FILE" -- 2.52.0 From 12b47a761a861574c02724dea584f45b78fd1d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 21 Feb 2026 03:02:29 +0100 Subject: [PATCH 5/5] [codex] fix shellcheck sc2015 in hw firmware loop --- tools/ai/zeroclaw_hw_firmware_loop.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/ai/zeroclaw_hw_firmware_loop.sh b/tools/ai/zeroclaw_hw_firmware_loop.sh index 64251a5..60fe812 100755 --- a/tools/ai/zeroclaw_hw_firmware_loop.sh +++ b/tools/ai/zeroclaw_hw_firmware_loop.sh @@ -106,7 +106,9 @@ if ! rg -q "^\[env:${ENV_NAME}\]" "$FW_DIR/platformio.ini"; then fi if [[ -z "$PORT" ]]; then - PORT="$(cd "$FW_DIR" && pio device list --json-output 2>/dev/null | python3 -c 'import json,sys + if ! PORT="$( + cd "$FW_DIR" + pio device list --json-output 2>/dev/null | python3 -c 'import json,sys raw=sys.stdin.read().strip() try: arr=json.loads(raw) if raw else [] @@ -138,7 +140,10 @@ for d in arr: if best: print(best) raise SystemExit(0) -raise SystemExit(1)' || true)" +raise SystemExit(1)' + )"; then + PORT="" + fi fi if [[ -z "$PORT" ]]; then -- 2.52.0