From 08f22edf44ca6da65cee9dad80325dc16dc2c550 Mon Sep 17 00:00:00 2001 From: zheng <18760530726@163.com> Date: Tue, 18 Aug 2026 18:47:41 +0800 Subject: [PATCH] fix(ci): align Node v22.22.3 pins and unblock pre-push tests Sync install scripts and CI container image with NODE_VERSION, regenerate Electron Config.html, and skip gitignored portable/app in origin scan. Co-authored-by: Cursor --- .gitea/workflows/tests.yml | 2 +- .github/workflows/tests.yml | 2 +- bootable/linux-setup/setup-openclaw.sh | 2 +- bootable/linux-setup/test-installation.sh | 4 +- install/install.ps1 | 2 +- install/install.sh | 2 +- portable/advanced/Mac-Install.command | 2 +- portable/advanced/Windows-Install.bat | 2 +- portable/advanced/setup.bat | 2 +- portable/advanced/setup.ps1 | 2 +- portable/advanced/setup.sh | 2 +- tests/origin.test.mjs | 4 + u-claw-app/resources/Config.html | 194 ++++++++++++++++++++-- u-claw-app/setup.bat | 2 +- u-claw-app/setup.sh | 2 +- 15 files changed, 201 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/tests.yml b/.gitea/workflows/tests.yml index 9c1a44e..8d652d2 100644 --- a/.gitea/workflows/tests.yml +++ b/.gitea/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: # Self-hosted Gitea runners are registered as internal_docker, not ubuntu-latest. runs-on: internal_docker container: - image: node:22.22.1 + image: node:22.22.3 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d058b2e..0e66ae6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: # Self-hosted Gitea runners are registered as internal_docker, not ubuntu-latest. runs-on: internal_docker container: - image: node:22.22.1 + image: node:22.22.3 steps: - uses: actions/checkout@v4 diff --git a/bootable/linux-setup/setup-openclaw.sh b/bootable/linux-setup/setup-openclaw.sh index e9d6269..90dd49e 100644 --- a/bootable/linux-setup/setup-openclaw.sh +++ b/bootable/linux-setup/setup-openclaw.sh @@ -7,7 +7,7 @@ set -euo pipefail # ── All constants defined here (independent of any other project files) ── -NODE_VERSION="v22.22.1" +NODE_VERSION="v22.22.3" NODE_MIRROR="https://nodejs.org/dist" NODE_OFFICIAL="https://nodejs.org/dist" NPM_MIRROR="https://registry.npmjs.org" diff --git a/bootable/linux-setup/test-installation.sh b/bootable/linux-setup/test-installation.sh index b16654f..9fffc51 100755 --- a/bootable/linux-setup/test-installation.sh +++ b/bootable/linux-setup/test-installation.sh @@ -59,8 +59,8 @@ if [ -x "$NODE_BIN" ]; then NODE_VERSION=$("$NODE_BIN" --version 2>/dev/null || echo "") echo -e "[OK] Node.js ${GREEN}$NODE_VERSION${NC}" - if [ "$NODE_VERSION" != "v22.22.1" ]; then - warn "Node.js version" "Expected v22.22.1, found $NODE_VERSION" + if [ "$NODE_VERSION" != "v22.22.3" ]; then + warn "Node.js version" "Expected v22.22.3, found $NODE_VERSION" fi else echo -e "${RED}[!!] Node.js is missing or will not run${NC}" diff --git a/install/install.ps1 b/install/install.ps1 index aaf1e29..213d44d 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -22,7 +22,7 @@ $RUNTIME_DIR = "$UCLAW_DIR\runtime" $CORE_DIR = "$UCLAW_DIR\core" $DATA_DIR = "$UCLAW_DIR\data" $CONFIG_PATH = "$DATA_DIR\.openclaw\openclaw.json" -$NODE_VERSION = "v22.22.1" +$NODE_VERSION = "v22.22.3" $MIRROR = "https://registry.npmjs.org" $NODE_MIRROR = "https://nodejs.org/dist" $OPENCLAW_VERSION = "2026.7.1-2" diff --git a/install/install.sh b/install/install.sh index a7edcae..402f8a3 100644 --- a/install/install.sh +++ b/install/install.sh @@ -25,7 +25,7 @@ RUNTIME_DIR="$UCLAW_DIR/runtime" CORE_DIR="$UCLAW_DIR/core" DATA_DIR="$UCLAW_DIR/data" CONFIG_PATH="$DATA_DIR/.openclaw/openclaw.json" -NODE_VERSION="v22.22.1" +NODE_VERSION="v22.22.3" MIRROR="https://registry.npmjs.org" NODE_MIRROR="https://nodejs.org/dist" OPENCLAW_VERSION="2026.7.1-2" diff --git a/portable/advanced/Mac-Install.command b/portable/advanced/Mac-Install.command index 964ef81..54637a0 100755 --- a/portable/advanced/Mac-Install.command +++ b/portable/advanced/Mac-Install.command @@ -18,7 +18,7 @@ NC='\033[0m' BOLD='\033[1m' DIM='\033[2m' -NODE_VER="$(tr -d "[:space:]" < "$UCLAW_DIR/NODE_VERSION" 2>/dev/null || tr -d "[:space:]" < "$UCLAW_DIR/../NODE_VERSION" 2>/dev/null || echo v22.22.1)" +NODE_VER="$(tr -d "[:space:]" < "$UCLAW_DIR/NODE_VERSION" 2>/dev/null || tr -d "[:space:]" < "$UCLAW_DIR/../NODE_VERSION" 2>/dev/null || echo v22.22.3)" MIRROR="https://registry.npmjs.org" NODE_MIRROR="https://nodejs.org/dist" diff --git a/portable/advanced/Windows-Install.bat b/portable/advanced/Windows-Install.bat index 0f55d61..2ad509b 100644 --- a/portable/advanced/Windows-Install.bat +++ b/portable/advanced/Windows-Install.bat @@ -15,7 +15,7 @@ set "APP_DIR=%UCLAW_DIR%app" set "INSTALL_TARGET=%USERPROFILE%\.uclaw" set "MIRROR=https://registry.npmjs.org" set "NODE_MIRROR=https://nodejs.org/dist" -set "NODE_VER=v22.22.1" +set "NODE_VER=v22.22.3" REM ---- Step 1: Check environment ---- echo [1/4] Checking diff --git a/portable/advanced/setup.bat b/portable/advanced/setup.bat index 55fc85a..d65454a 100644 --- a/portable/advanced/setup.bat +++ b/portable/advanced/setup.bat @@ -9,7 +9,7 @@ set "CORE_DIR=%APP_DIR%\core" set "RUNTIME_DIR=%APP_DIR%\runtime" set "MIRROR=https://registry.npmjs.org" set "NODE_MIRROR=https://nodejs.org/dist" -set "NODE_VERSION=v22.22.1" +set "NODE_VERSION=v22.22.3" set "ALL_PLATFORMS=false" if "%~1"=="--all-platforms" set "ALL_PLATFORMS=true" diff --git a/portable/advanced/setup.ps1 b/portable/advanced/setup.ps1 index d4c48b9..901e9cb 100644 --- a/portable/advanced/setup.ps1 +++ b/portable/advanced/setup.ps1 @@ -11,7 +11,7 @@ $runtimeDir = Join-Path $appDir "runtime" $mirror = "https://registry.npmjs.org" $nodeMirror = "https://nodejs.org/dist" -$nodeVersion = "v22.22.1" +$nodeVersion = "v22.22.3" function Write-Step { param( diff --git a/portable/advanced/setup.sh b/portable/advanced/setup.sh index 3af17c8..75c7583 100755 --- a/portable/advanced/setup.sh +++ b/portable/advanced/setup.sh @@ -13,7 +13,7 @@ CORE_DIR="$APP_DIR/core" RUNTIME_DIR="$APP_DIR/runtime" MIRROR="https://registry.npmjs.org" NODE_MIRROR="https://nodejs.org/dist" -NODE_VERSION="$(tr -d "[:space:]" < "$SCRIPT_DIR/NODE_VERSION" 2>/dev/null || tr -d "[:space:]" < "$SCRIPT_DIR/../NODE_VERSION" 2>/dev/null || echo v22.22.1)" +NODE_VERSION="$(tr -d "[:space:]" < "$SCRIPT_DIR/NODE_VERSION" 2>/dev/null || tr -d "[:space:]" < "$SCRIPT_DIR/../NODE_VERSION" 2>/dev/null || echo v22.22.3)" ALL_PLATFORMS=false [ "$1" = "--all-platforms" ] && ALL_PLATFORMS=true diff --git a/tests/origin.test.mjs b/tests/origin.test.mjs index 4232997..e99c438 100644 --- a/tests/origin.test.mjs +++ b/tests/origin.test.mjs @@ -8,6 +8,8 @@ const repoRoot = fileURLToPath(new URL('..', import.meta.url)); const origin = JSON.parse(readFileSync(join(repoRoot, 'origin.json'), 'utf8')); const SKIPPED_DIRS = new Set(['.git', 'node_modules', 'dist', '.download-cache', 'tests']); +// portable/app/ is gitignored — setup downloads Node/OpenClaw there for local runs. +const SKIPPED_PATH_PREFIXES = ['portable/app/']; // Lockfiles are full of third-party funding and repository links. They say // nothing about where *this* build fetches from. const SKIPPED_FILES = new Set(['package-lock.json']); @@ -17,6 +19,8 @@ function* walk(dir) { for (const entry of readdirSync(dir)) { if (SKIPPED_DIRS.has(entry)) continue; const full = join(dir, entry); + const rel = relative(repoRoot, full); + if (rel === 'portable/app' || SKIPPED_PATH_PREFIXES.some((p) => rel.startsWith(p))) continue; if (statSync(full).isDirectory()) yield* walk(full); else yield full; } diff --git a/u-claw-app/resources/Config.html b/u-claw-app/resources/Config.html index dc00b6d..5c784f7 100644 --- a/u-claw-app/resources/Config.html +++ b/u-claw-app/resources/Config.html @@ -59,7 +59,9 @@ h1 .lobster { color: #ff6b35; } } .form-group input, input[type="text"], -input[type="password"] { +input[type="password"], +.form-group select, +select { display: block; width: 100%; min-width: 0; @@ -74,6 +76,20 @@ input[type="password"] { outline: none; transition: border-color 0.2s; } +.form-group select, +select { + padding-right: 36px; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23888' d='M1 1l5 5 5-5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 14px center; +} +.form-group select:focus, +select:focus { border-color: #ff6b35; } +.form-group select option, +select option { background: #222; color: #fff; } input:focus { border-color: #ff6b35; } .input-wrap { position: relative; display: block; width: 100%; min-width: 0; } .input-wrap input { padding-right: 70px; } @@ -114,6 +130,37 @@ input:focus { border-color: #ff6b35; } .btn-secondary:hover { background: #444; } .btn-row { display: flex; gap: 10px; margin-top: 20px; justify-content: flex-end; } +/* Key verified: model picker + continue on one row */ +.key-actions { + display: flex; + align-items: flex-end; + gap: 12px; + margin-top: 16px; + margin-bottom: 4px; +} +.key-actions .model-picker { + flex: 1; + min-width: 0; + margin-bottom: 0; +} +.key-actions .model-picker label { + margin-bottom: 6px; + font-size: 0.85em; + color: #aaa; +} +.key-actions .btn-primary { + flex-shrink: 0; + min-width: 128px; + min-height: 46px; + padding: 12px 22px; + margin-left: auto; +} +.key-actions.has-model .btn-primary { margin-left: 0; } +@media (max-width: 520px) { + .key-actions { flex-direction: column; align-items: stretch; } + .key-actions .btn-primary { margin-left: 0; width: 100%; } +} + /* Result */ .result { text-align: center; padding: 40px 20px; } .result .icon { font-size: 3em; margin-bottom: 12px; } @@ -126,15 +173,29 @@ input:focus { border-color: #ff6b35; } .toast.show { opacity: 1; transform: translateY(0); } .toast.error { background: #f44336; } -/* Channel form */ +/* Channel form — open state must scroll (Telegram pairing steps are tall) */ .channel-card { transition: all 0.2s; } .channel-card.selected { border-color: #ff6b35; background: rgba(255,107,53,0.06); } -.channel-form { padding-top: 0; margin-top: 10px; border-top: 1px solid #333; max-height: 0; overflow: hidden; transition: max-height 0.3s ease, padding 0.3s ease; } -.channel-form.open { max-height: 300px; padding-top: 12px; } +.channel-form { padding-top: 0; margin-top: 10px; border-top: 1px solid #333; max-height: 0; overflow: hidden; transition: max-height 0.35s ease, padding 0.35s ease; } +.channel-form.open { + max-height: min(72vh, 560px); + overflow-x: hidden; + overflow-y: auto; + padding-top: 12px; + padding-bottom: 4px; + -webkit-overflow-scrolling: touch; + scrollbar-gutter: stable; +} +.channel-form.open::-webkit-scrollbar { width: 8px; } +.channel-form.open::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; } +.channel-form.open::-webkit-scrollbar-thumb:hover { background: #555; } +/* Telegram needs the full row when expanded */ +.channel-card.channel-expanded { grid-column: 1 / -1; } .channel-form .form-group { margin-bottom: 10px; } .channel-form label { font-size: 0.82em; color: #aaa; margin-bottom: 4px; } .channel-form input { font-size: 0.88em; padding: 9px 12px; } .channel-form .hint { font-size: 0.75em; margin-top: 4px; } +.tg-steps code { background: #2a2a2a; padding: 1px 5px; border-radius: 4px; color: #ffb84d; font-size: 0.95em; } /* Status bar */ .status-bar { @@ -203,12 +264,11 @@ body[data-tier="standard"] [data-tier-min="expert"] { display: none !important; - - -
+
+
@@ -268,6 +328,16 @@ body[data-tier="standard"] [data-tier-min="expert"] { display: none !important;

→ Get one from @BotFather

+
+

After saving, connect your Telegram account:

+
    +
  1. Save the bot token above (click “Open dashboard” or save channels).
  2. +
  3. On your phone, open Telegram, find your bot, and send /start.
  4. +
  5. Come back here — a button will appear. Click it once. No command line.
  6. +
+
+ +
@@ -495,11 +565,16 @@ async function checkKey() { const modelPicker = document.getElementById('modelPicker'); const modelChoice = document.getElementById('modelChoice'); +const keyActions = document.getElementById('keyActions'); function populateModels(provider, current) { var options = (provider && provider.models) ? provider.models.slice() : []; if (current && options.indexOf(current) === -1) options.unshift(current); - if (options.length < 2) { modelPicker.style.display = 'none'; return; } + if (options.length < 2) { + modelPicker.style.display = 'none'; + keyActions.classList.remove('has-model'); + return; + } modelChoice.innerHTML = ''; options.forEach(function (name) { var opt = document.createElement('option'); @@ -509,6 +584,7 @@ function populateModels(provider, current) { modelChoice.appendChild(opt); }); modelPicker.style.display = 'block'; + keyActions.classList.add('has-model'); } // Check as they paste, but wait for typing to settle first. @@ -581,6 +657,14 @@ async function saveConfig() { var config = buildConfig(detected.id, detected.baseUrl, model, apiKey); try { + var existingRes = await fetch('/api/config'); + var existing = existingRes.ok ? await existingRes.json() : {}; + delete existing.agent; + config = Object.assign(existing, config); + if (existing.channels) config.channels = existing.channels; + if (existing.commands) { + config.commands = Object.assign({}, existing.commands, config.commands || {}); + } var res = await fetch('/api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -601,11 +685,93 @@ function toggleChannel(card, type) { if (isOpen) { form.classList.remove('open'); card.classList.remove('selected'); + card.classList.remove('channel-expanded'); + if (type === 'telegram') stopTelegramPairingPoll(); } else { form.classList.add('open'); card.classList.add('selected'); + if (type === 'telegram') card.classList.add('channel-expanded'); var firstInput = form.querySelector('input'); if (firstInput) setTimeout(function() { firstInput.focus(); }, 100); + if (type === 'telegram') startTelegramPairingPoll(); + } +} + +// --- Telegram pairing (Control Panel, no CLI) --- +var telegramPairingTimer = null; +var telegramPendingCode = null; + +function stopTelegramPairingPoll() { + if (telegramPairingTimer) { + clearTimeout(telegramPairingTimer); + telegramPairingTimer = null; + } +} + +function formatTelegramRequestLabel(req) { + if (req.username) return '@' + req.username; + if (req.firstName) return req.firstName; + return req.id || ''; +} + +async function pollTelegramPairing() { + var statusEl = document.getElementById('telegram-pairing-status'); + var btn = document.getElementById('telegram-approve-btn'); + if (!statusEl || !btn) return; + + try { + var res = await fetch('/api/telegram/pairing'); + var data = res.ok ? await res.json() : { requests: [] }; + var requests = data.requests || []; + + if (requests.length > 0) { + var req = requests[0]; + telegramPendingCode = req.code; + var who = formatTelegramRequestLabel(req); + statusEl.innerHTML = '' + t('tg.pending', { who: who || req.id }) + ''; + btn.style.display = 'block'; + btn.disabled = false; + } else { + telegramPendingCode = null; + btn.style.display = 'none'; + btn.disabled = true; + statusEl.textContent = t('tg.waiting'); + } + } catch (e) { + statusEl.textContent = t('tg.poll_failed'); + } + + telegramPairingTimer = setTimeout(pollTelegramPairing, 2000); +} + +function startTelegramPairingPoll() { + stopTelegramPairingPoll(); + pollTelegramPairing(); +} + +async function approveTelegramPairing() { + if (!telegramPendingCode) return; + var btn = document.getElementById('telegram-approve-btn'); + var statusEl = document.getElementById('telegram-pairing-status'); + btn.disabled = true; + statusEl.textContent = t('tg.approving'); + + try { + var res = await fetch('/api/telegram/pairing/approve', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code: telegramPendingCode }) + }); + var data = await res.json(); + if (!res.ok) throw new Error(data.error || String(res.status)); + telegramPendingCode = null; + btn.style.display = 'none'; + statusEl.innerHTML = '' + t('tg.approved') + ''; + showToast(t('tg.approved_toast')); + } catch (e) { + statusEl.textContent = t('tg.approve_failed', { reason: e.message }); + btn.disabled = false; + showToast(t('tg.approve_failed', { reason: e.message }), true); } } @@ -748,6 +914,7 @@ async function saveChannelsAndOpen() { body: JSON.stringify(existing) }); showToast(t('ch.saved')); + if (channels.telegram) startTelegramPairingPoll(); } catch (e) { showToast(t('ch.err_save'), true); } @@ -865,6 +1032,11 @@ async function loadConfig() { if (modelChoice.options[i].value === model) { modelChoice.selectedIndex = i; break; } } } + + if (cfg.channels && cfg.channels.telegram && cfg.channels.telegram.botToken) { + var tgInput = document.getElementById('ch-telegram-token'); + if (tgInput) tgInput.value = cfg.channels.telegram.botToken; + } } catch (e) { /* first run: nothing configured yet */ } } diff --git a/u-claw-app/setup.bat b/u-claw-app/setup.bat index 39d8f04..a1cbc90 100644 --- a/u-claw-app/setup.bat +++ b/u-claw-app/setup.bat @@ -10,7 +10,7 @@ echo ======================================== echo. set "APP_DIR=%~dp0" -set "NODE_VER=v22.22.1" +set "NODE_VER=v22.22.3" set "MIRROR=https://registry.npmjs.org" set "NODE_MIRROR=https://nodejs.org/dist" diff --git a/u-claw-app/setup.sh b/u-claw-app/setup.sh index 0009330..80b6680 100755 --- a/u-claw-app/setup.sh +++ b/u-claw-app/setup.sh @@ -14,7 +14,7 @@ NC='\033[0m' BOLD='\033[1m' APP_DIR="$(cd "$(dirname "$0")" && pwd)" -NODE_VER="$(tr -d "[:space:]" < "$(dirname "$0")/../NODE_VERSION" 2>/dev/null || echo v22.22.1)" +NODE_VER="$(tr -d "[:space:]" < "$(dirname "$0")/../NODE_VERSION" 2>/dev/null || echo v22.22.3)" MIRROR="https://registry.npmjs.org" NODE_MIRROR="https://nodejs.org/dist"