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"