@@ -54,7 +54,6 @@ import { defaultRuntime } from "../../runtime.js";
5454import { normalizeOptionalString } from "../../shared/string-coerce.js" ;
5555import { stylePromptMessage } from "../../terminal/prompt-style.js" ;
5656import { theme } from "../../terminal/theme.js" ;
57- import { pathExists } from "../../utils.js" ;
5857import { replaceCliName , resolveCliName } from "../cli-name.js" ;
5958import { formatCliCommand } from "../command-format.js" ;
6059import { installCompletion } from "../completion-runtime.js" ;
@@ -93,6 +92,7 @@ const SERVICE_REFRESH_TIMEOUT_MS = 60_000;
9392const DEFAULT_UPDATE_STEP_TIMEOUT_MS = 30 * 60_000 ;
9493const POST_CORE_UPDATE_ENV = "OPENCLAW_UPDATE_POST_CORE" ;
9594const POST_CORE_UPDATE_CHANNEL_ENV = "OPENCLAW_UPDATE_POST_CORE_CHANNEL" ;
95+ const POST_CORE_UPDATE_REQUESTED_CHANNEL_ENV = "OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL" ;
9696const POST_CORE_UPDATE_RESULT_PATH_ENV = "OPENCLAW_UPDATE_POST_CORE_RESULT_PATH" ;
9797const SERVICE_REFRESH_PATH_ENV_KEYS = [
9898 "OPENCLAW_HOME" ,
@@ -1093,6 +1093,40 @@ async function runPostCorePluginUpdate(params: {
10931093 } ) ;
10941094}
10951095
1096+ async function persistRequestedUpdateChannel ( params : {
1097+ configSnapshot : Awaited < ReturnType < typeof readConfigFileSnapshot > > ;
1098+ requestedChannel : "stable" | "beta" | "dev" | null ;
1099+ } ) : Promise < Awaited < ReturnType < typeof readConfigFileSnapshot > > > {
1100+ if ( ! params . requestedChannel || ! params . configSnapshot . valid ) {
1101+ return params . configSnapshot ;
1102+ }
1103+ const storedChannel = normalizeUpdateChannel ( params . configSnapshot . config . update ?. channel ) ;
1104+ if ( params . requestedChannel === storedChannel ) {
1105+ return params . configSnapshot ;
1106+ }
1107+
1108+ const next = {
1109+ ...params . configSnapshot . sourceConfig ,
1110+ update : {
1111+ ...params . configSnapshot . sourceConfig . update ,
1112+ channel : params . requestedChannel ,
1113+ } ,
1114+ } ;
1115+ await replaceConfigFile ( {
1116+ nextConfig : next ,
1117+ baseHash : params . configSnapshot . hash ,
1118+ } ) ;
1119+ return {
1120+ ...params . configSnapshot ,
1121+ hash : undefined ,
1122+ parsed : next ,
1123+ sourceConfig : asResolvedSourceConfig ( next ) ,
1124+ resolved : asResolvedSourceConfig ( next ) ,
1125+ runtimeConfig : asRuntimeConfig ( next ) ,
1126+ config : asRuntimeConfig ( next ) ,
1127+ } ;
1128+ }
1129+
10961130async function writePostCorePluginUpdateResultFile (
10971131 filePath : string | undefined ,
10981132 result : PostCorePluginUpdateResult ,
@@ -1125,10 +1159,11 @@ async function readPostCorePluginUpdateResultFile(
11251159async function continuePostCoreUpdateInFreshProcess ( params : {
11261160 root : string ;
11271161 channel : "stable" | "beta" | "dev" ;
1162+ requestedChannel : "stable" | "beta" | "dev" | null ;
11281163 opts : UpdateCommandOptions ;
11291164} ) : Promise < { resumed : boolean ; pluginUpdate ?: PostCorePluginUpdateResult } > {
1130- const entryPath = path . join ( params . root , "dist" , "entry.js" ) ;
1131- if ( ! ( await pathExists ( entryPath ) ) ) {
1165+ const entryPath = await resolveGatewayInstallEntrypoint ( params . root ) ;
1166+ if ( ! entryPath ) {
11321167 return { resumed : false } ;
11331168 }
11341169
@@ -1158,6 +1193,9 @@ async function continuePostCoreUpdateInFreshProcess(params: {
11581193 ...disableUpdatedPackageCompileCacheEnv ( process . env ) ,
11591194 [ POST_CORE_UPDATE_ENV ] : "1" ,
11601195 [ POST_CORE_UPDATE_CHANNEL_ENV ] : params . channel ,
1196+ ...( params . requestedChannel
1197+ ? { [ POST_CORE_UPDATE_REQUESTED_CHANNEL_ENV ] : params . requestedChannel }
1198+ : { } ) ,
11611199 ...( resultPath ? { [ POST_CORE_UPDATE_RESULT_PATH_ENV ] : resultPath } : { } ) ,
11621200 } ,
11631201 } ) ;
@@ -1195,14 +1233,32 @@ function shouldResumePostCoreUpdateInFreshProcess(params: {
11951233 result : UpdateRunResult ;
11961234 downgradeRisk : boolean ;
11971235} ) : boolean {
1198- return isPackageManagerUpdateMode ( params . result . mode ) && ! params . downgradeRisk ;
1236+ if ( params . downgradeRisk ) {
1237+ return false ;
1238+ }
1239+ if ( isPackageManagerUpdateMode ( params . result . mode ) ) {
1240+ return true ;
1241+ }
1242+ if ( params . result . mode !== "git" ) {
1243+ return false ;
1244+ }
1245+ const beforeSha = normalizeOptionalString ( params . result . before ?. sha ) ;
1246+ const afterSha = normalizeOptionalString ( params . result . after ?. sha ) ;
1247+ if ( beforeSha && afterSha && beforeSha !== afterSha ) {
1248+ return true ;
1249+ }
1250+ const beforeVersion = normalizeOptionalString ( params . result . before ?. version ) ;
1251+ const afterVersion = normalizeOptionalString ( params . result . after ?. version ) ;
1252+ return Boolean ( beforeVersion && afterVersion && beforeVersion !== afterVersion ) ;
11991253}
12001254
12011255export async function updateCommand ( opts : UpdateCommandOptions ) : Promise < void > {
12021256 suppressDeprecations ( ) ;
12031257 const invocationCwd = tryResolveInvocationCwd ( ) ;
12041258 const postCoreUpdateResume = process . env [ POST_CORE_UPDATE_ENV ] === "1" ;
12051259 const postCoreUpdateChannel = process . env [ POST_CORE_UPDATE_CHANNEL_ENV ] ?. trim ( ) ;
1260+ const postCoreRequestedChannelInput =
1261+ process . env [ POST_CORE_UPDATE_REQUESTED_CHANNEL_ENV ] ?. trim ( ) ?? "" ;
12061262
12071263 const timeoutMs = parseTimeoutMsOrExit ( opts . timeout ) ;
12081264 const shouldRestart = opts . restart !== false ;
@@ -1223,10 +1279,24 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
12231279 return ;
12241280 }
12251281
1282+ const postCoreRequestedChannel = postCoreRequestedChannelInput
1283+ ? normalizeUpdateChannel ( postCoreRequestedChannelInput )
1284+ : null ;
1285+ if ( postCoreRequestedChannelInput && ! postCoreRequestedChannel ) {
1286+ defaultRuntime . error ( "Invalid post-core requested update channel context." ) ;
1287+ defaultRuntime . exit ( 1 ) ;
1288+ return ;
1289+ }
1290+
1291+ const postCoreConfigSnapshot = await persistRequestedUpdateChannel ( {
1292+ configSnapshot : await readConfigFileSnapshot ( ) ,
1293+ requestedChannel : postCoreRequestedChannel ,
1294+ } ) ;
1295+
12261296 const pluginUpdate = await runPostCorePluginUpdate ( {
12271297 root,
12281298 channel : postCoreUpdateChannel ,
1229- configSnapshot : await readConfigFileSnapshot ( ) ,
1299+ configSnapshot : postCoreConfigSnapshot ,
12301300 opts,
12311301 timeoutMs : updateStepTimeoutMs ,
12321302 } ) ;
@@ -1567,53 +1637,58 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
15671637 return ;
15681638 }
15691639
1640+ const shouldResumePostCoreInFreshProcess = shouldResumePostCoreUpdateInFreshProcess ( {
1641+ result,
1642+ downgradeRisk,
1643+ } ) ;
1644+
15701645 let postUpdateConfigSnapshot = configSnapshot ;
1571- if ( requestedChannel && configSnapshot . valid && requestedChannel !== storedChannel ) {
1572- const next = {
1573- ...configSnapshot . sourceConfig ,
1574- update : {
1575- ...configSnapshot . sourceConfig . update ,
1576- channel : requestedChannel ,
1577- } ,
1578- } ;
1579- await replaceConfigFile ( {
1580- nextConfig : next ,
1581- baseHash : configSnapshot . hash ,
1646+ if ( ! shouldResumePostCoreInFreshProcess ) {
1647+ postUpdateConfigSnapshot = await persistRequestedUpdateChannel ( {
1648+ configSnapshot,
1649+ requestedChannel,
15821650 } ) ;
1583- postUpdateConfigSnapshot = {
1584- ...configSnapshot ,
1585- hash : undefined ,
1586- parsed : next ,
1587- sourceConfig : asResolvedSourceConfig ( next ) ,
1588- resolved : asResolvedSourceConfig ( next ) ,
1589- runtimeConfig : asRuntimeConfig ( next ) ,
1590- config : asRuntimeConfig ( next ) ,
1591- } ;
1592- if ( ! opts . json ) {
1593- defaultRuntime . log ( theme . muted ( `Update channel set to ${ requestedChannel } .` ) ) ;
1594- }
1651+ }
1652+ if (
1653+ requestedChannel &&
1654+ configSnapshot . valid &&
1655+ requestedChannel !== storedChannel &&
1656+ ! shouldResumePostCoreInFreshProcess &&
1657+ ! opts . json
1658+ ) {
1659+ defaultRuntime . log ( theme . muted ( `Update channel set to ${ requestedChannel } .` ) ) ;
1660+ } else if (
1661+ requestedChannel &&
1662+ configSnapshot . valid &&
1663+ requestedChannel !== storedChannel &&
1664+ shouldResumePostCoreInFreshProcess &&
1665+ ! opts . json
1666+ ) {
1667+ defaultRuntime . log ( theme . muted ( `Update channel will be set to ${ requestedChannel } .` ) ) ;
15951668 }
15961669
15971670 const postUpdateRoot = result . root ?? root ;
15981671
15991672 let postCorePluginUpdate : PostCorePluginUpdateResult | undefined ;
16001673 let pluginsUpdatedInFreshProcess = false ;
1601- if (
1602- shouldResumePostCoreUpdateInFreshProcess ( {
1603- result,
1604- downgradeRisk,
1605- } )
1606- ) {
1674+ if ( shouldResumePostCoreInFreshProcess ) {
16071675 const freshProcessResult = await continuePostCoreUpdateInFreshProcess ( {
16081676 root : postUpdateRoot ,
16091677 channel,
1678+ requestedChannel,
16101679 opts,
16111680 } ) ;
16121681 pluginsUpdatedInFreshProcess = freshProcessResult . resumed ;
16131682 postCorePluginUpdate = freshProcessResult . pluginUpdate ;
16141683 }
16151684
16161685 if ( ! pluginsUpdatedInFreshProcess ) {
1686+ if ( shouldResumePostCoreInFreshProcess ) {
1687+ postUpdateConfigSnapshot = await persistRequestedUpdateChannel ( {
1688+ configSnapshot,
1689+ requestedChannel,
1690+ } ) ;
1691+ }
16171692 postCorePluginUpdate = await runPostCorePluginUpdate ( {
16181693 root : postUpdateRoot ,
16191694 channel,
0 commit comments