[codex] harden dual-hw autonomy defaults and valid pio env CI
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-4
@@ -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.
|
||||
|
||||
@@ -4,7 +4,7 @@ project:
|
||||
targets:
|
||||
- esp32s3
|
||||
- esp32
|
||||
- native
|
||||
- esp32dev
|
||||
|
||||
ai:
|
||||
triggers:
|
||||
|
||||
@@ -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:
|
||||
- `<ts> | paired=<...> | uptime=<...> | prom=<...> | convo=<last line> | gateway=<last line>`
|
||||
- `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`
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user