Skip to content
58 changes: 46 additions & 12 deletions scripts/nemoclaw-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2304,7 +2304,7 @@ PYAPPROVEBEFORE
return 0
fi
if [ -n "$_nemoclaw_approve_request_id" ] && [ -n "$_nemoclaw_approve_before" ] && command -v python3 >/dev/null 2>&1; then
if NEMOCLAW_APPROVE_REQUEST_ID="$_nemoclaw_approve_request_id" NEMOCLAW_APPROVE_STATE_DIR="$_nemoclaw_approve_state_dir" NEMOCLAW_APPROVE_BEFORE="$_nemoclaw_approve_before" python3 - <<'PYAPPROVEAFTER'; then
if NEMOCLAW_APPROVE_REQUEST_ID="$_nemoclaw_approve_request_id" NEMOCLAW_APPROVE_STATE_DIR="$_nemoclaw_approve_state_dir" NEMOCLAW_APPROVE_BEFORE="$_nemoclaw_approve_before" NEMOCLAW_APPROVE_OUTPUT="$_nemoclaw_approve_output" python3 - <<'PYAPPROVEAFTER'; then
import json
import os
from pathlib import Path
Expand All @@ -2315,6 +2315,7 @@ try:
before = json.loads(os.environ.get("NEMOCLAW_APPROVE_BEFORE") or "{}")
except Exception:
before = {}
approve_output = os.environ.get("NEMOCLAW_APPROVE_OUTPUT") or ""

def load(name):
try:
Expand All @@ -2323,27 +2324,60 @@ def load(name):
return {}
return value if isinstance(value, dict) else {}

def save(name, value):
(root / name).write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
def norm(value):
return str(value or "").strip()

def scopes(entry):
return {norm(scope) for scope in (entry.get("approvedScopes") or entry.get("scopes") or []) if norm(scope)}
def scope_set(entry, key="scopes"):
return {norm(scope) for scope in (entry.get(key) or []) if norm(scope)}

requested = {norm(scope) for scope in (before.get("scopes") or []) if norm(scope)}
requested = scope_set(before)
device_id = norm(before.get("deviceId"))
pending = load("pending.json")
paired = load("paired.json")
still_pending = any(isinstance(item, dict) and item.get("requestId") == request_id for item in pending.values())
paired_entry = paired.get(device_id) if device_id else None
if request_id and requested and not still_pending and isinstance(paired_entry, dict) and requested.issubset(scopes(paired_entry)):
print(json.dumps({
"requestId": request_id,
"deviceId": device_id,
"approvedScopes": sorted(requested),
"compatibility": "openclaw-approve-applied-after-nonzero",
}, sort_keys=True))
paired_scopes = scope_set(paired_entry or {}, "approvedScopes") or scope_set(paired_entry or {})
if request_id and requested and not still_pending and isinstance(paired_entry, dict) and requested.issubset(paired_scopes):
print(json.dumps({"requestId": request_id, "deviceId": device_id, "approvedScopes": sorted(requested), "compatibility": "openclaw-approve-applied-after-nonzero"}, sort_keys=True))
raise SystemExit(0)
raise SystemExit(1)

if "scope upgrade pending approval" not in approve_output and "pairing required" not in approve_output:
raise SystemExit(1)
allowed = {"operator.pairing", "operator.read", "operator.write"}
if not request_id or not device_id or not requested or not requested.issubset(allowed) or "operator.pairing" not in paired_scopes:
raise SystemExit(1)
replacement_key = replacement = None
for key, item in pending.items():
item_scopes = scope_set(item) if isinstance(item, dict) else set()
if (isinstance(item, dict) and norm(item.get("requestId")) != request_id and norm(item.get("deviceId")) == device_id and
"operator.admin" in item_scopes and requested.issubset(item_scopes) and norm(item.get("requestId")) in approve_output):
replacement_key, replacement = key, item
break
if not replacement:
raise SystemExit(1)
approved = set(paired_scopes) | requested
if "operator.write" in approved:
approved.add("operator.read")
if {"operator.read", "operator.write"} & approved:
approved.add("operator.pairing")
if not approved.issubset(allowed):
raise SystemExit(1)
approved_list = [scope for scope in ("operator.pairing", "operator.read", "operator.write") if scope in approved]
paired_entry["scopes"] = approved_list
paired_entry["approvedScopes"] = approved_list
token = paired_entry.get("tokens", {}).get("operator")
if isinstance(token, dict):
token["scopes"] = approved_list
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
pending.pop(request_id, None)
pending.pop(replacement_key, None)
paired[device_id] = paired_entry
save("pending.json", pending)
save("paired.json", paired)
print(json.dumps({"requestId": request_id, "deviceId": device_id, "approvedScopes": approved_list, "compatibility": "openclaw-approve-recovered-replacement"}, sort_keys=True))
raise SystemExit(0)
PYAPPROVEAFTER
return 0
fi
Expand Down
Loading