

























@@ -14,6 +14,7 @@ import {
1414resolveLatestVersion,
1515resolveProviderAuth,
1616run,
17+runStreaming,
1718say,
1819startHostServer,
1920writeJson,
@@ -318,7 +319,7 @@ class NpmUpdateSmoke {
318319}
319320}
320321321-private spawnUpdate(label: string, platform: Platform, fn: () => void): Job {
322+private spawnUpdate(label: string, platform: Platform, fn: () => Promise<void> | void): Job {
322323const logPath = path.join(this.runDir, `${platform}-update.log`);
323324const job: Job = {
324325done: false,
@@ -343,7 +344,7 @@ class NpmUpdateSmoke {
343344append(chunk)) as typeof process.stdout.write;
344345process.stderr.write = ((chunk: string | Uint8Array) =>
345346append(chunk)) as typeof process.stderr.write;
346-fn();
347+await fn();
347348await writeFile(logPath, log, "utf8");
348349return 0;
349350} catch (error) {
@@ -365,8 +366,8 @@ class NpmUpdateSmoke {
365366this.guestMacos(this.updateScript("macos"), updateTimeoutSeconds * 1000);
366367}
367368368-private runWindowsUpdate(): void {
369-this.guestWindows(this.updateScript("windows"), updateTimeoutSeconds * 1000);
369+private runWindowsUpdate(): Promise<void> {
370+return this.guestWindows(this.updateScript("windows"), updateTimeoutSeconds * 1000);
370371}
371372372373private runLinuxUpdate(): void {
@@ -452,7 +453,27 @@ class NpmUpdateSmoke {
452453);
453454}
454455455-private guestWindows(script: string, timeoutMs: number): void {
456+private async guestWindows(script: string, timeoutMs: number): Promise<void> {
457+const fileBase = `openclaw-parallels-npm-update-windows-${process.pid}-${Date.now()}`;
458+const pathsScript = `$base = Join-Path $env:TEMP '${fileBase}'
459+$scriptPath = "$base.ps1"
460+$logPath = "$base.log"
461+$donePath = "$base.done"
462+$exitPath = "$base.exit"`;
463+const payload = `$ErrorActionPreference = 'Stop'
464+$PSNativeCommandUseErrorActionPreference = $false
465+${pathsScript}
466+try {
467+ & {
468+${script}
469+ } *>&1 | ForEach-Object { $_ | Out-String | Add-Content -Path $logPath -Encoding UTF8 }
470+ Set-Content -Path $exitPath -Value '0' -Encoding UTF8
471+} catch {
472+ $_ | Out-String | Add-Content -Path $logPath -Encoding UTF8
473+ Set-Content -Path $exitPath -Value '1' -Encoding UTF8
474+} finally {
475+ Set-Content -Path $donePath -Value 'done' -Encoding UTF8
476+}`;
456477run(
457478"prlctl",
458479[
@@ -464,10 +485,107 @@ class NpmUpdateSmoke {
464485"-ExecutionPolicy",
465486"Bypass",
466487"-EncodedCommand",
467-encodePowerShell(script),
488+encodePowerShell(`${pathsScript}
489+Remove-Item -Path $scriptPath, $logPath, $donePath, $exitPath -Force -ErrorAction SilentlyContinue
490+[System.IO.File]::WriteAllText($scriptPath, [Console]::In.ReadToEnd(), [System.Text.UTF8Encoding]::new($false))
491+if (!(Test-Path $scriptPath)) { throw "background update script was not written" }`),
468492],
469-{ timeoutMs },
493+{ input: payload, timeoutMs: Math.min(timeoutMs, 120_000) },
470494);
495+496+const launchLogPath = path.join(this.runDir, `${fileBase}-launch.log`);
497+const launchStatus = await runStreaming(
498+"prlctl",
499+[
500+"exec",
501+windowsVm,
502+"--current-user",
503+"cmd.exe",
504+"/d",
505+"/s",
506+"/c",
507+`start "" /min powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "%TEMP%\\${fileBase}.ps1"`,
508+],
509+{ logPath: launchLogPath, quiet: true, timeoutMs: 20_000 },
510+);
511+const launchLog = await readFile(launchLogPath, "utf8").catch(() => "");
512+if (launchLog) {
513+process.stdout.write(launchLog);
514+}
515+if (launchStatus !== 0 && launchStatus !== 124) {
516+throw new Error(`Windows update background launch failed with exit code ${launchStatus}`);
517+}
518+519+const deadline = Date.now() + timeoutMs;
520+let lastLogOffset = 0;
521+while (Date.now() < deadline) {
522+const poll = run(
523+"prlctl",
524+[
525+"exec",
526+windowsVm,
527+"--current-user",
528+"powershell.exe",
529+"-NoProfile",
530+"-ExecutionPolicy",
531+"Bypass",
532+"-EncodedCommand",
533+encodePowerShell(`${pathsScript}
534+$offset = ${lastLogOffset}
535+if (Test-Path $logPath) {
536+ $bytes = [System.IO.File]::ReadAllBytes($logPath)
537+ if ($bytes.Length -gt $offset) {
538+ "__OPENCLAW_LOG_OFFSET__:$($bytes.Length)"
539+ [System.Text.Encoding]::UTF8.GetString($bytes, $offset, $bytes.Length - $offset)
540+ }
541+}
542+if (Test-Path $donePath) {
543+ $backgroundExit = if (Test-Path $exitPath) { (Get-Content -Path $exitPath -Raw).Trim() } else { '0' }
544+ "__OPENCLAW_BACKGROUND_EXIT__:$backgroundExit"
545+ '__OPENCLAW_BACKGROUND_DONE__'
546+ if ($backgroundExit -ne '0') { exit 23 }
547+ exit 0
548+}`),
549+],
550+{ check: false, timeoutMs: Math.min(30_000, Math.max(1_000, deadline - Date.now())) },
551+);
552+if (poll.stdout) {
553+process.stdout.write(poll.stdout);
554+}
555+if (poll.stderr) {
556+process.stderr.write(poll.stderr);
557+}
558+const offsetMatch = poll.stdout.match(/__OPENCLAW_LOG_OFFSET__:(\d+)/);
559+if (offsetMatch) {
560+lastLogOffset = Number(offsetMatch[1]);
561+}
562+if (poll.stdout.includes("__OPENCLAW_BACKGROUND_DONE__")) {
563+const exitMatch = poll.stdout.match(/__OPENCLAW_BACKGROUND_EXIT__:(\S+)/);
564+const backgroundExit = exitMatch?.[1] ?? "0";
565+if (backgroundExit !== "0" || (poll.status !== 0 && poll.status !== 124)) {
566+throw new Error("Windows update failed");
567+}
568+run(
569+"prlctl",
570+[
571+"exec",
572+windowsVm,
573+"--current-user",
574+"powershell.exe",
575+"-NoProfile",
576+"-ExecutionPolicy",
577+"Bypass",
578+"-EncodedCommand",
579+encodePowerShell(`${pathsScript}
580+Remove-Item -Path $scriptPath, $logPath, $donePath, $exitPath -Force -ErrorAction SilentlyContinue`),
581+],
582+{ check: false, timeoutMs: 30_000 },
583+);
584+return;
585+}
586+await new Promise((resolve) => setTimeout(resolve, 5_000));
587+}
588+throw new Error(`Windows update timed out after ${updateTimeoutSeconds}s`);
471589}
472590473591private guestLinux(script: string, timeoutMs: number): void {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。