artifact_label() { if [[ "$TARGET_PACKAGE_SPEC" == "" && "$MODE" == "upgrade" && "$UPGRADE_FROM_PACKED_MAIN" -eq 0 ]]; then
printf 'Windows smoke artifacts'
return fi if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then
printf 'baseline package tgz'
return fi if [[ "$UPGRADE_FROM_PACKED_MAIN" -eq 1 ]]; then
printf 'packed main tgz'
return fi
printf 'current main tgz'
}
upgrade_summary_label() { if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then
printf 'target-package->dev'
return fi if [[ "$UPGRADE_FROM_PACKED_MAIN" -eq 1 ]]; then
printf 'packed-main->dev'
return fi
printf 'latest->dev'
}
API_KEY_VALUE="${!API_KEY_ENV:-}"
[[ -n "$API_KEY_VALUE" ]] || die "$API_KEY_ENV is required"
ps_single_quote() {
printf "%s""$1" | sed "s/'/''/g"
}
ps_array_literal() {
local arg quoted parts=() for arg in "$@"; do
quoted="$(ps_single_quote "$arg")"
parts+=("'$quoted'") done
local joined=""
local part for part in "${parts[@]}"; do if [[ -n "$joined" ]]; then
joined+=", " fi
joined+="$part" done
printf '@(%s)'"$joined"
}
resolve_snapshot_info() {
local json hint
json="$(prlctl snapshot-list "$VM_NAME" --json)"
hint="$SNAPSHOT_HINT"
SNAPSHOT_JSON="$json" SNAPSHOT_HINT="$hint" python3 - <<'PY' import difflib import json import os import re import sys
def aliases(name: str) -> list[str]:
values = [name] for pattern in (
r"^(.*)-poweroff$",
r"^(.*)-poweroff-\d{4}-\d{2}-\d{2}$",
):
match = re.match(pattern, name) if match:
values.append(match.group(1))
return values
for snapshot_id, meta in payload.items():
name = str(meta.get("name", "")).strip()
lowered = name.lower()
score = 0.0 for alias in aliases(lowered): if alias == hint:
score = max(score, 10.0) elif hint and hint in alias:
score = max(score, 5.0 + len(hint) / max(len(alias), 1)) else:
score = max(score, difflib.SequenceMatcher(None, hint, alias).ratio()) if str(meta.get("state", "")).lower() == "poweroff":
score += 0.5 if score > best_score:
best_score = score
best_id = snapshot_id
best_meta = meta if not best_id:
sys.exit("no snapshot matched")
print( "\t".join(
[
best_id,
str(best_meta.get("state", "")).strip(),
str(best_meta.get("name", "")).strip(),
]
)
)
PY
}
resolve_host_ip() { if [[ -n "$HOST_IP" ]]; then
printf '%s\n'"$HOST_IP"
return fi
local detected
detected="$(ifconfig | awk '/inet 10\.211\./ { print $2; exit }')"
[[ -n "$detected" ]] || die "failed to detect Parallels host IP; pass --host-ip"
printf '%s\n'"$detected"
}
resolve_host_port() { if is_host_port_free "$HOST_PORT"; then
printf '%s\n'"$HOST_PORT"
return fi if [[ "$HOST_PORT_EXPLICIT" -eq 1 ]]; then
die "host port $HOST_PORT already in use" fi
HOST_PORT="$(allocate_host_port)"
warn "host port 18426 busy; using $HOST_PORT"
printf '%s\n'"$HOST_PORT"
}
guest_powershell_poll() {
local timeout_s="$1"
local script="$2"
local encoded if (( timeout_s < 60 )); then
timeout_s=60 fi
encoded="$(
SCRIPT_CONTENT="$script" python3 - <<'PY' import base64 import os
guest_run_openclaw() {
local env_name="${1:-}"
local env_value="${2:-}"
shift 2
local args_literal env_name_q env_value_q
args_literal="$(ps_array_literal "$@")"
env_name_q="$(ps_single_quote "$env_name")"
env_value_q="$(ps_single_quote "$env_value")"
guest_powershell "$(cat <<EOF
\$openclaw = Join-Path \$env:APPDATA 'npm\openclaw.cmd'
\$args = $args_literal if ('${env_name_q}' -ne '') {
Set-Item -Path ('Env:' + '${env_name_q}') -Value '${env_value_q}'
} # openclaw.cmd preserves multi-word --message args reliably here; Start-Process # against the shim can re-split argv and make Commander reject the turn.
\$output = & \$openclaw @args 2>&1 if (\$null -ne \$output) {
\$output | ForEach-Object { \$_ }
}
exit \$LASTEXITCODE
EOF
)"
}
ensure_vm_running_for_retry() {
local status
status="$(prlctl status "$VM_NAME" 2>/dev/null || true)"
case "$status" in
*" suspended") # Some Windows guest transport drops leave the VM suspended between retry # attempts; wake it before the next prlctl exec.
warn "VM suspended during retry path; resuming $VM_NAME"
prlctl resume "$VM_NAME" >/dev/null
;;
*" stopped")
warn "VM stopped during retry path; starting $VM_NAME"
prlctl start "$VM_NAME" >/dev/null
;;
esac
}
run_windows_retry() {
local label="$1"
local max_attempts="$2"
shift 2
local attempt rc
rc=0 for (( attempt = 1; attempt <= max_attempts; attempt++ )); do
printf '%s attempt %d/%d\n'"$label""$attempt""$max_attempts"
set +e "$@"
rc=$?
set -e if [[ $rc -eq 0 ]]; then
return 0 fi
warn "$label attempt $attempt failed (rc=$rc)" if (( attempt < max_attempts )); then if ! ensure_vm_running_for_retry >/dev/null 2>&1; then
: fi if ! wait_for_guest_ready >/dev/null 2>&1; then
: fi
sleep 5 fi done
return "$rc"
}
restore_snapshot() {
local snapshot_id="$1"
say "Restore snapshot $SNAPSHOT_HINT ($snapshot_id)"
prlctl snapshot-switch "$VM_NAME" --id "$snapshot_id" >/dev/null if [[ "$SNAPSHOT_STATE" == "poweroff" ]]; then
wait_for_vm_status "stopped" || die "restored poweroff snapshot did not reach stopped state in $VM_NAME"
say "Start restored poweroff snapshot $SNAPSHOT_NAME"
prlctl start "$VM_NAME" >/dev/null fi
}
wait_for_vm_status() {
local expected="$1"
local deadline status
deadline=$((SECONDS + TIMEOUT_SNAPSHOT_S)) while (( SECONDS < deadline )); do
status="$(prlctl status "$VM_NAME" 2>/dev/null || true)" if [[ "$status" == *" $expected" ]]; then
return 0 fi
sleep 1 done
return 1
}
wait_for_guest_ready() {
local deadline
deadline=$((SECONDS + TIMEOUT_SNAPSHOT_S)) while (( SECONDS < deadline )); do if verify_windows_user_ready >/dev/null 2>&1; then
return 0 fi
sleep 3 done
return 1
}
terminate_process_tree() {
local pid="$1"
local signal_name="${2:-TERM}"
local child
pgrep -P "$pid" 2>/dev/null | while read -r child; do
terminate_process_tree "$child""$signal_name" done
kill "-$signal_name""$pid" >/dev/null 2>&1 || true
}
phase_run() {
local phase_id="$1"
local timeout_s="$2"
shift 2
local log_path pid start rc timed_out next_warn summary
log_path="$(phase_log_path "$phase_id")"
say "$phase_id"
start=$SECONDS
next_warn=$((start + PHASE_STALE_WARN_S))
timed_out=0
( "$@"
) >"$log_path" 2>&1 &
pid=$!
while child_job_running "$pid"; do if (( SECONDS >= next_warn )); then
summary="$(parallels_log_progress_extract python3 "$log_path")"
[[ -n "$summary" ]] || summary="waiting for first log line"
warn "$phase_id still running after $((SECONDS - start))s: $summary"
next_warn=$((SECONDS + PHASE_STALE_WARN_S)) fi if (( SECONDS - start >= timeout_s )); then
timed_out=1
terminate_process_tree "$pid" TERM
sleep 2
terminate_process_tree "$pid" KILL
break fi
sleep 1 done
set +e
wait "$pid"
rc=$?
set -e
if (( timed_out )); then
warn "$phase_id timed out after ${timeout_s}s"
printf 'timeout after %ss\n'"$timeout_s" >>"$log_path"
show_log_excerpt "$log_path"
return 124 fi
if [[ $rc -ne 0 ]]; then
warn "$phase_id failed (rc=$rc)"
show_log_excerpt "$log_path"
return "$rc" fi
return 0
}
extract_last_version() {
local log_path="$1"
python3 - "$log_path" <<'PY' import pathlib import re import sys
text = pathlib.Path(sys.argv[1]).read_text(errors="replace")
matches = re.findall(r"OpenClaw [^\r\n]+ \([0-9a-f]{7,}\)", text)
print(matches[-1] if matches else"")
PY
}
write_summary_json() {
local summary_path="$RUN_DIR/summary.json"
python3 - "$summary_path" <<'PY' import json import os import sys
resolve_latest_version() { if [[ -n "$LATEST_VERSION" ]]; then
printf '%s\n'"$LATEST_VERSION"
return fi
npm view openclaw version --userconfig "$(mktemp)"
}
baseline_install_version() { if [[ -z "$INSTALL_VERSION" ]]; then
printf '%s\n'"$LATEST_VERSION"
return fi
npm view "openclaw@$INSTALL_VERSION" version --userconfig "$(mktemp)"
}
best = None for wanted in preferred_names: for asset in assets: if asset.get("name") == wanted:
best = asset
break if best:
break
if best is None: for asset in assets:
name = asset.get("name", "") if name.startswith("MinGit-") and name.endswith(".zip") and "busybox" not in name:
best = asset
break
if best is None:
raise SystemExit("no MinGit asset found")
ensure_current_build() {
local head build_commit rc lock_owned
lock_owned=0 if [[ "${OPENCLAW_PARALLELS_BUILD_LOCK_HELD:-0}" != "1" ]]; then
acquire_build_lock
lock_owned=1 fi
head="$(git rev-parse HEAD)"
build_commit="$(current_build_commit)" if [[ "$build_commit" == "$head" ]] && ! source_tree_dirty_for_build; then if [[ "$lock_owned" -eq 1 ]]; then
release_build_lock fi
return fi
say "Build dist for current head"
set +e
pnpm build
rc=$? if [[ $rc -eq 0 ]]; then
parallels_package_assert_no_generated_drift
rc=$? fi
build_commit="$(current_build_commit)"
set -e if [[ "$lock_owned" -eq 1 ]]; then
release_build_lock fi
[[ $rc -eq 0 ]] || return "$rc" if [[ "$build_commit" != "$head" ]]; then
warn "dist/build-info.json still does not match HEAD after build"
return 1 fi
}
ensure_mingit_zip() {
local mingit_name mingit_url mingit_meta
mingit_meta="$(resolve_mingit_download)"
mingit_name="${mingit_meta%%$'\n'*}"
mingit_url="${mingit_meta#*$'\n'}"
[[ "$mingit_name" != "$mingit_url" ]] || die "failed to resolve MinGit download metadata"
MINGIT_ZIP_NAME="$mingit_name"
MINGIT_ZIP_PATH="$MAIN_TGZ_DIR/$mingit_name" if [[ ! -f "$MINGIT_ZIP_PATH" ]]; then
say "Download $MINGIT_ZIP_NAME"
curl -fsSL "$mingit_url" -o "$MINGIT_ZIP_PATH" fi
}
pack_main_tgz() {
local short_head pkg packed_commit rc
ensure_mingit_zip if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then
say "Pack target package tgz: $TARGET_PACKAGE_SPEC"
pkg="$(
npm pack "$TARGET_PACKAGE_SPEC" --ignore-scripts --json --pack-destination "$MAIN_TGZ_DIR" \
| python3 -c 'import json, sys; data = json.load(sys.stdin); print(data[-1]["filename"])'
)"
MAIN_TGZ_PATH="$MAIN_TGZ_DIR/$(basename "$pkg")"
TARGET_EXPECT_VERSION="$(tar -xOf "$MAIN_TGZ_PATH" package/package.json | python3 -c "import json, sys; print(json.load(sys.stdin)['version'])")"
say "Packed $MAIN_TGZ_PATH"
say "Target package version: $TARGET_EXPECT_VERSION"
return fi
say "Pack current main tgz"
acquire_build_lock
set +e
{
OPENCLAW_PARALLELS_BUILD_LOCK_HELD=1 ensure_current_build &&
write_package_dist_inventory &&
short_head="$(git rev-parse --short HEAD)" &&
pkg="$(
npm pack --ignore-scripts --json --pack-destination "$MAIN_TGZ_DIR" \
| python3 -c 'import json, sys; data = json.load(sys.stdin); print(data[-1]["filename"])'
)"
}
rc=$?
set -e
release_build_lock
[[ $rc -eq 0 ]] || return "$rc"
MAIN_TGZ_PATH="$MAIN_TGZ_DIR/openclaw-main-$short_head.tgz" cp"$MAIN_TGZ_DIR/$pkg""$MAIN_TGZ_PATH"
packed_commit="$(extract_package_build_commit_from_tgz "$MAIN_TGZ_PATH")"
[[ -n "$packed_commit" ]] || die "failed to read packed build commit from $MAIN_TGZ_PATH"
PACKED_MAIN_COMMIT_SHORT="${packed_commit:0:7}"
say "Packed $MAIN_TGZ_PATH"
tar -xOf "$MAIN_TGZ_PATH" package/dist/build-info.json
}
verify_target_version() { if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then
verify_version_contains "$TARGET_EXPECT_VERSION"
return fi
[[ -n "$PACKED_MAIN_COMMIT_SHORT" ]] || die "packed main commit not captured"
verify_version_contains "$PACKED_MAIN_COMMIT_SHORT"
}
start_server() {
local host_ip="$1"
local artifact probe_url attempt if [[ -n "$MAIN_TGZ_PATH" ]]; then
artifact="$(basename "$MAIN_TGZ_PATH")" else
artifact="$MINGIT_ZIP_NAME" fi
attempt=0 while :; do
attempt=$((attempt + 1))
say "Serve $(artifact_label) on $host_ip:$HOST_PORT"
(
cd "$MAIN_TGZ_DIR"
exec python3 -m http.server "$HOST_PORT" --bind 0.0.0.0
) >/tmp/openclaw-parallels-windows-http.log 2>&1 &
SERVER_PID=$!
sleep 1
probe_url="http://127.0.0.1:$HOST_PORT/$artifact" if kill -0 "$SERVER_PID" >/dev/null 2>&1 && curl -fsSI "$probe_url" >/dev/null 2>&1; then
return 0 fi
kill "$SERVER_PID" >/dev/null 2>&1 || true
wait "$SERVER_PID" >/dev/null 2>&1 || true
SERVER_PID="" if [[ "$HOST_PORT_EXPLICIT" -eq 1 || $attempt -ge 3 ]]; then
die "failed to start reachable host HTTP server on port $HOST_PORT" fi
HOST_PORT="$(allocate_host_port)"
warn "retrying host HTTP server on port $HOST_PORT" done
}
write_latest_install_runner_script() {
local install_url_q="$1"
local version_flag_q="$2"
WINDOWS_LATEST_INSTALL_SCRIPT_PATH="$MAIN_TGZ_DIR/openclaw-install-latest.ps1" cat >"$WINDOWS_LATEST_INSTALL_SCRIPT_PATH" <<EOF
param(
[Parameter(Mandatory = \$true)][string]\$LogPath,
[Parameter(Mandatory = \$true)][string]\$DonePath
)
install_baseline_npm_release() {
local host_ip="$1"
local version="$2"
local script_url
local runner_name log_name done_name done_status launcher_state guest_log
local log_state_path npm_log_state_path
local start_seconds poll_deadline startup_checked poll_rc state_rc log_rc last_npm_log_poll
run_dev_channel_update() {
local host_ip="$1"
local script_url
local runner_name log_name done_name done_status launcher_state guest_log
local log_state_path
local start_seconds poll_deadline startup_checked poll_rc state_rc log_rc
while :; do
set +e
done_status="$(
guest_powershell_poll 20 "\$done = Join-Path \$env:TEMP '$done_name'; if (Test-Path \$done) { (Get-Content \$done -Raw).Trim() }"
)"
poll_rc=$?
set -e
done_status="${done_status//$'\r'/}" if [[ $poll_rc -ne 0 ]]; then
warn "windows dev update helper poll failed; retrying" if (( SECONDS >= poll_deadline )); then
warn "windows dev update helper timed out while polling done file" if verify_windows_dev_update_after_transport_loss; then
warn "windows dev update poll timed out after product verification passed; treating as pass" rm -f "$log_state_path"
return 0 fi rm -f "$log_state_path"
return 1 fi
sleep 2
continue fi
set +e
stream_windows_dev_update_log
log_rc=$?
set -e if [[ $log_rc -ne 0 ]]; then
warn "windows dev update helper live log poll failed; retrying" fi if [[ -n "$done_status" ]]; then if ! stream_windows_dev_update_log; then
warn "windows dev update helper log drain failed after completion" fi rm -f "$log_state_path"
[[ "$done_status" == "0" ]]
return $? fi if [[ "$startup_checked" -eq 0 && $((SECONDS - start_seconds)) -ge 20 ]]; then
set +e
launcher_state="$(
guest_powershell_poll 20 "\$runner = Join-Path \$env:TEMP '$runner_name'; \$log = Join-Path \$env:TEMP '$log_name'; \$done = Join-Path \$env:TEMP '$done_name'; 'runner=' + (Test-Path \$runner) + ' log=' + (Test-Path \$log) + ' done=' + (Test-Path \$done)"
)"
state_rc=$?
set -e
launcher_state="${launcher_state//$'\r'/}"
startup_checked=1 if [[ $state_rc -eq 0 && "$launcher_state" == *"runner=False"* && "$launcher_state" == *"log=False"* && "$launcher_state" == *"done=False"* ]]; then
warn "windows dev update helper failed to materialize guest files" rm -f "$log_state_path"
return 1 fi fi if (( SECONDS >= poll_deadline )); then if ! stream_windows_dev_update_log; then
warn "windows dev update helper log drain failed after timeout" fi
warn "windows dev update helper timed out waiting for done file" if verify_windows_dev_update_after_transport_loss; then
warn "windows dev update transport timed out after product verification passed; treating as pass" rm -f "$log_state_path"
return 0 fi rm -f "$log_state_path"
return 1 fi
sleep 2 done
}
verify_windows_dev_update_after_transport_loss() {
set +e
guest_powershell_poll 90 "$(cat <<'EOF'
$ErrorActionPreference = 'Stop'
$busy = Get-CimInstance Win32_Process |
Where-Object {
$_.CommandLine -and
($_.CommandLine -match 'openclaw update|npm install|pnpm install|pnpm run build')
} if ($busy) {
throw 'dev update still has active npm/pnpm/openclaw processes'
}
$gitEntry = Join-Path $env:USERPROFILE 'openclaw\openclaw.mjs' if (-not (Test-Path $gitEntry)) {
throw "git entry missing after transport loss: $gitEntry"
}
& node.exe $gitEntry --version
& node.exe $gitEntry update status --json
EOF
)"
local rc=$?
set -e
return "$rc"
}
show_gateway_status_compat() { if guest_run_openclaw """" gateway status --help | grep -Fq -- "--require-rpc"; then
guest_run_openclaw """" gateway status --deep --require-rpc
return fi
guest_run_openclaw """" gateway status --deep
}
verify_turn() {
guest_run_openclaw """" models set "$MODEL_ID"
guest_run_openclaw "$API_KEY_ENV""$API_KEY_VALUE" \
agent --agent main --message "Reply with exact ASCII text OK only." --json
}
capture_latest_ref_failure() {
set +e
run_ref_onboard
local rc=$?
set -e if [[ $rc -eq 0 ]]; then
say "Latest release ref-mode onboard passed"
return 0 fi
warn "Latest release ref-mode onboard failed pre-upgrade"
set +e
show_gateway_status_compat || true
set -e
return 1
}
say "VM: $VM_NAME"
say "Snapshot hint: $SNAPSHOT_HINT"
say "Resolved snapshot: $SNAPSHOT_NAME [$SNAPSHOT_STATE]"
say "Latest npm version: $LATEST_VERSION"
say "Current head: $(git rev-parse --short HEAD)"
say "Run logs: $RUN_DIR"
if needs_host_tgz; then
pack_main_tgz else
ensure_mingit_zip fi
start_server "$HOST_IP"
if [[ "$MODE" == "fresh" || "$MODE" == "both" ]]; then
set +e
run_fresh_main_lane "$SNAPSHOT_ID""$HOST_IP"
fresh_rc=$?
set -e if [[ $fresh_rc -eq 0 ]]; then
FRESH_MAIN_STATUS="pass" else
FRESH_MAIN_STATUS="fail" fi fi
if [[ "$MODE" == "upgrade" || "$MODE" == "both" ]]; then
set +e
run_upgrade_lane "$SNAPSHOT_ID""$HOST_IP"
upgrade_rc=$?
set -e if [[ $upgrade_rc -eq 0 ]]; then
UPGRADE_STATUS="pass" else
UPGRADE_STATUS="fail" fi fi
if [[ "$KEEP_SERVER" -eq 0 && -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" >/dev/null 2>&1 || true
SERVER_PID="" fi
if [[ "$JSON_OUTPUT" -eq 1 ]]; then cat"$SUMMARY_JSON_PATH" else
printf '\nSummary:\n' if [[ -n "$TARGET_PACKAGE_SPEC" ]]; then
printf ' target-package: %s\n'"$TARGET_PACKAGE_SPEC" fi if [[ "$UPGRADE_FROM_PACKED_MAIN" -eq 1 ]]; then
printf ' upgrade-from-packed-main: yes\n' fi if [[ -n "$INSTALL_VERSION" ]]; then
printf ' baseline-install-version: %s\n'"$INSTALL_VERSION" fi
printf ' fresh-main: %s (%s)\n'"$FRESH_MAIN_STATUS""$FRESH_MAIN_VERSION"
printf ' %s precheck: %s (%s)\n'"$(upgrade_summary_label)""$UPGRADE_PRECHECK_STATUS""$LATEST_INSTALLED_VERSION"
printf ' %s: %s (%s)\n'"$(upgrade_summary_label)""$UPGRADE_STATUS""$UPGRADE_MAIN_VERSION"
printf ' logs: %s\n'"$RUN_DIR"
printf ' summary: %s\n'"$SUMMARY_JSON_PATH" fi
if [[ "$FRESH_MAIN_STATUS" == "fail" || "$UPGRADE_STATUS" == "fail" ]]; then
exit 1 fi
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.48Bemerkung:
(vorverarbeitet am 2026-04-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.