1- import { spawn } from "node:child_process" ;
1+ import { spawn , type ChildProcess } from "node:child_process" ;
22import fs from "node:fs/promises" ;
33import os from "node:os" ;
44import path from "node:path" ;
@@ -110,6 +110,7 @@ const POST_CORE_UPDATE_ENV = "OPENCLAW_UPDATE_POST_CORE";
110110const POST_CORE_UPDATE_CHANNEL_ENV = "OPENCLAW_UPDATE_POST_CORE_CHANNEL" ;
111111const POST_CORE_UPDATE_REQUESTED_CHANNEL_ENV = "OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL" ;
112112const POST_CORE_UPDATE_RESULT_PATH_ENV = "OPENCLAW_UPDATE_POST_CORE_RESULT_PATH" ;
113+ const POST_CORE_UPDATE_RESULT_POLL_MS = 100 ;
113114const UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV =
114115 "OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE" ;
115116const SERVICE_REFRESH_PATH_ENV_KEYS = [
@@ -1608,6 +1609,25 @@ async function readPostCorePluginUpdateResultFile(
16081609 return undefined ;
16091610}
16101611
1612+ function stopPostCoreUpdateChild ( child : ChildProcess ) : void {
1613+ if ( process . platform === "win32" && child . pid ) {
1614+ try {
1615+ const killer = spawn ( "taskkill" , [ "/PID" , String ( child . pid ) , "/T" , "/F" ] , {
1616+ stdio : "ignore" ,
1617+ windowsHide : true ,
1618+ } ) ;
1619+ killer . once ( "error" , ( ) => {
1620+ child . kill ( ) ;
1621+ } ) ;
1622+ return ;
1623+ } catch {
1624+ child . kill ( ) ;
1625+ return ;
1626+ }
1627+ }
1628+ child . kill ( ) ;
1629+ }
1630+
16111631async function continuePostCoreUpdateInFreshProcess ( params : {
16121632 root : string ;
16131633 channel : "stable" | "beta" | "dev" ;
@@ -1632,11 +1652,8 @@ async function continuePostCoreUpdateInFreshProcess(params: {
16321652 if ( params . opts . timeout ) {
16331653 argv . push ( "--timeout" , params . opts . timeout ) ;
16341654 }
1635- const resultDir =
1636- params . opts . json === true
1637- ? await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-update-post-core-" ) )
1638- : null ;
1639- const resultPath = resultDir ? path . join ( resultDir , "plugins.json" ) : null ;
1655+ const resultDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-update-post-core-" ) ) ;
1656+ const resultPath = path . join ( resultDir , "plugins.json" ) ;
16401657
16411658 try {
16421659 const child = spawn ( resolveNodeRunner ( ) , argv , {
@@ -1648,24 +1665,65 @@ async function continuePostCoreUpdateInFreshProcess(params: {
16481665 ...( params . requestedChannel
16491666 ? { [ POST_CORE_UPDATE_REQUESTED_CHANNEL_ENV ] : params . requestedChannel }
16501667 : { } ) ,
1651- ... ( resultPath ? { [ POST_CORE_UPDATE_RESULT_PATH_ENV ] : resultPath } : { } ) ,
1668+ [ POST_CORE_UPDATE_RESULT_PATH_ENV ] : resultPath ,
16521669 } ,
16531670 } ) ;
16541671
1655- const exitCode = await new Promise < number > ( ( resolve , reject ) => {
1656- child . once ( "error" , reject ) ;
1672+ const childResult = await new Promise <
1673+ | { kind : "exit" ; exitCode : number }
1674+ | { kind : "plugin-update" ; pluginUpdate : PostCorePluginUpdateResult }
1675+ > ( ( resolve , reject ) => {
1676+ let settled = false ;
1677+ const finish = (
1678+ result :
1679+ | { kind : "exit" ; exitCode : number }
1680+ | { kind : "plugin-update" ; pluginUpdate : PostCorePluginUpdateResult } ,
1681+ ) => {
1682+ if ( settled ) {
1683+ return ;
1684+ }
1685+ settled = true ;
1686+ clearInterval ( resultPoll ) ;
1687+ resolve ( result ) ;
1688+ } ;
1689+ const resultPoll = setInterval ( ( ) => {
1690+ void readPostCorePluginUpdateResultFile ( resultPath )
1691+ . then ( ( pluginUpdate ) => {
1692+ if ( ! pluginUpdate ) {
1693+ return ;
1694+ }
1695+ stopPostCoreUpdateChild ( child ) ;
1696+ finish ( { kind : "plugin-update" , pluginUpdate } ) ;
1697+ } )
1698+ . catch ( ( ) => undefined ) ;
1699+ } , POST_CORE_UPDATE_RESULT_POLL_MS ) ;
1700+ child . once ( "error" , ( error ) => {
1701+ if ( settled ) {
1702+ return ;
1703+ }
1704+ settled = true ;
1705+ clearInterval ( resultPoll ) ;
1706+ reject ( error ) ;
1707+ } ) ;
16571708 child . once ( "exit" , ( code , signal ) => {
1709+ if ( settled ) {
1710+ return ;
1711+ }
16581712 if ( signal ) {
1713+ settled = true ;
1714+ clearInterval ( resultPoll ) ;
16591715 reject ( new Error ( `post-update process terminated by signal ${ signal } ` ) ) ;
16601716 return ;
16611717 }
1662- resolve ( code ?? 1 ) ;
1718+ finish ( { kind : "exit" , exitCode : code ?? 1 } ) ;
16631719 } ) ;
16641720 } ) ;
16651721
1666- const pluginUpdate = resultPath
1667- ? await readPostCorePluginUpdateResultFile ( resultPath )
1668- : undefined ;
1722+ const pluginUpdate =
1723+ childResult . kind === "plugin-update"
1724+ ? childResult . pluginUpdate
1725+ : await readPostCorePluginUpdateResultFile ( resultPath ) ;
1726+ const exitCode = childResult . kind === "exit" ? childResult . exitCode : 0 ;
16691727 if ( exitCode !== 0 ) {
16701728 if ( pluginUpdate ) {
16711729 return { resumed : true , pluginUpdate } ;
@@ -1675,9 +1733,7 @@ async function continuePostCoreUpdateInFreshProcess(params: {
16751733 }
16761734 return { resumed : true , ...( pluginUpdate ? { pluginUpdate } : { } ) } ;
16771735 } finally {
1678- if ( resultDir ) {
1679- await fs . rm ( resultDir , { recursive : true , force : true } ) . catch ( ( ) => undefined ) ;
1680- }
1736+ await fs . rm ( resultDir , { recursive : true , force : true } ) . catch ( ( ) => undefined ) ;
16811737 }
16821738}
16831739
@@ -1752,11 +1808,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
17521808 opts,
17531809 timeoutMs : updateStepTimeoutMs ,
17541810 } ) ;
1755- if ( opts . json ) {
1811+ if ( process . env [ POST_CORE_UPDATE_RESULT_PATH_ENV ] ) {
17561812 await writePostCorePluginUpdateResultFile (
17571813 process . env [ POST_CORE_UPDATE_RESULT_PATH_ENV ] ,
17581814 pluginUpdate ,
17591815 ) ;
1816+ }
1817+ if ( opts . json ) {
17601818 if ( ! process . env [ POST_CORE_UPDATE_RESULT_PATH_ENV ] ) {
17611819 const result : UpdateRunResult = {
17621820 status : pluginUpdate . status === "error" ? "error" : "ok" ,
0 commit comments