@chrysb/alphaclaw 0.8.6 → 0.8.7-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,4 @@
1
- const childProcess = require("child_process");
2
1
  const fs = require("fs");
3
- const os = require("os");
4
2
  const path = require("path");
5
3
  const https = require("https");
6
4
  const http = require("http");
@@ -8,7 +6,6 @@ const {
8
6
  kLatestVersionCacheTtlMs,
9
7
  kAlphaclawRegistryUrl,
10
8
  kNpmPackageRoot,
11
- kOpenclawUpdateCopyTimeoutMs,
12
9
  kRootDir,
13
10
  } = require("./constants");
14
11
 
@@ -26,6 +23,9 @@ const isNewerVersion = (latest, current) => {
26
23
  return l.patch > c.patch;
27
24
  };
28
25
 
26
+ const buildAlphaclawInstallSpec = (version = "latest") =>
27
+ `@chrysb/alphaclaw@${String(version || "").trim() || "latest"}`;
28
+
29
29
  const createAlphaclawVersionService = () => {
30
30
  let kUpdateStatusCache = {
31
31
  latestVersion: null,
@@ -108,120 +108,6 @@ const createAlphaclawVersionService = () => {
108
108
  return { latestVersion, hasUpdate };
109
109
  };
110
110
 
111
- const findInstallDir = () => {
112
- // Walk up from kNpmPackageRoot to find the consuming project's directory
113
- // (the one with node_modules/@chrysb/alphaclaw). In Docker this is /app.
114
- let dir = kNpmPackageRoot;
115
- while (dir !== path.dirname(dir)) {
116
- const parent = path.dirname(dir);
117
- if (
118
- path.basename(parent) === "node_modules" ||
119
- parent.includes(`${path.sep}node_modules${path.sep}`)
120
- ) {
121
- dir = parent;
122
- continue;
123
- }
124
- const pkgPath = path.join(parent, "package.json");
125
- if (fs.existsSync(pkgPath)) {
126
- try {
127
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
128
- if (
129
- pkg.dependencies?.["@chrysb/alphaclaw"] ||
130
- pkg.devDependencies?.["@chrysb/alphaclaw"] ||
131
- pkg.optionalDependencies?.["@chrysb/alphaclaw"]
132
- ) {
133
- return parent;
134
- }
135
- } catch {}
136
- }
137
- dir = parent;
138
- }
139
- // Fallback: if running directly (not from node_modules), use kNpmPackageRoot
140
- return kNpmPackageRoot;
141
- };
142
-
143
- const installLatestAlphaclaw = () =>
144
- new Promise((resolve, reject) => {
145
- const installDir = findInstallDir();
146
- const tmpDir = fs.mkdtempSync(
147
- path.join(os.tmpdir(), "alphaclaw-update-"),
148
- );
149
-
150
- const cleanup = () => {
151
- try {
152
- fs.rmSync(tmpDir, { recursive: true, force: true });
153
- } catch {}
154
- };
155
-
156
- fs.writeFileSync(
157
- path.join(tmpDir, "package.json"),
158
- JSON.stringify({
159
- private: true,
160
- dependencies: { "@chrysb/alphaclaw": "latest" },
161
- }),
162
- );
163
-
164
- const npmEnv = {
165
- ...process.env,
166
- npm_config_update_notifier: "false",
167
- npm_config_fund: "false",
168
- npm_config_audit: "false",
169
- };
170
-
171
- console.log(
172
- `[alphaclaw] Running: npm install @chrysb/alphaclaw@latest in temp dir (target: ${installDir})`,
173
- );
174
- childProcess.exec(
175
- "npm install --omit=dev --prefer-online --package-lock=false",
176
- {
177
- cwd: tmpDir,
178
- env: npmEnv,
179
- timeout: 180000,
180
- },
181
- (err, stdout, stderr) => {
182
- if (err) {
183
- const message = String(stderr || err.message || "").trim();
184
- console.log(
185
- `[alphaclaw] alphaclaw install error: ${message.slice(0, 200)}`,
186
- );
187
- cleanup();
188
- return reject(
189
- new Error(
190
- message || "Failed to install @chrysb/alphaclaw@latest",
191
- ),
192
- );
193
- }
194
- if (stdout?.trim()) {
195
- console.log(
196
- `[alphaclaw] alphaclaw install stdout: ${stdout.trim().slice(0, 300)}`,
197
- );
198
- }
199
-
200
- const src = path.join(tmpDir, "node_modules");
201
- const dest = path.join(installDir, "node_modules");
202
- childProcess.exec(
203
- `cp -af "${src}/." "${dest}/"`,
204
- { timeout: kOpenclawUpdateCopyTimeoutMs },
205
- (copyErr) => {
206
- cleanup();
207
- if (copyErr) {
208
- console.log(
209
- `[alphaclaw] alphaclaw copy error: ${(copyErr.message || "").slice(0, 200)}`,
210
- );
211
- return reject(
212
- new Error(
213
- `Failed to copy updated AlphaClaw files: ${copyErr.message}`,
214
- ),
215
- );
216
- }
217
- console.log("[alphaclaw] alphaclaw install completed");
218
- resolve({ stdout: stdout?.trim(), stderr: stderr?.trim() });
219
- },
220
- );
221
- },
222
- );
223
- });
224
-
225
111
  const isContainer = () =>
226
112
  process.env.RAILWAY_ENVIRONMENT ||
227
113
  process.env.RENDER ||
@@ -277,18 +163,33 @@ const createAlphaclawVersionService = () => {
277
163
  kUpdateInProgress = true;
278
164
  const previousVersion = readAlphaclawVersion();
279
165
  try {
280
- await installLatestAlphaclaw();
281
- // Write marker to persistent volume so the update survives container recreation
282
- const markerPath = path.join(kRootDir, ".alphaclaw-update-pending");
166
+ let targetVersion = "latest";
283
167
  try {
284
- fs.writeFileSync(
285
- markerPath,
286
- JSON.stringify({ from: previousVersion, ts: Date.now() }),
168
+ const updateStatus = await readAlphaclawUpdateStatus({ refresh: true });
169
+ if (updateStatus.latestVersion) {
170
+ targetVersion = updateStatus.latestVersion;
171
+ }
172
+ } catch (error) {
173
+ console.log(
174
+ `[alphaclaw] Could not resolve exact AlphaClaw version before restart: ${error.message || "unknown error"}`,
287
175
  );
288
- console.log(`[alphaclaw] Update marker written to ${markerPath}`);
289
- } catch (e) {
290
- console.log(`[alphaclaw] Could not write update marker: ${e.message}`);
291
176
  }
177
+
178
+ const spec = buildAlphaclawInstallSpec(targetVersion);
179
+ // Write marker to persistent volume so the update survives container recreation
180
+ const markerPath = path.join(kRootDir, ".alphaclaw-update-pending");
181
+ fs.writeFileSync(
182
+ markerPath,
183
+ JSON.stringify({
184
+ from: previousVersion,
185
+ to: targetVersion,
186
+ spec,
187
+ ts: Date.now(),
188
+ }),
189
+ );
190
+ console.log(
191
+ `[alphaclaw] Update marker written to ${markerPath} for ${spec}`,
192
+ );
292
193
  kUpdateStatusCache = {
293
194
  latestVersion: null,
294
195
  hasUpdate: false,
@@ -299,15 +200,17 @@ const createAlphaclawVersionService = () => {
299
200
  body: {
300
201
  ok: true,
301
202
  previousVersion,
203
+ targetVersion: targetVersion === "latest" ? null : targetVersion,
302
204
  restarting: true,
303
205
  },
304
206
  };
305
207
  } catch (err) {
306
- kUpdateInProgress = false;
307
208
  return {
308
209
  status: 500,
309
210
  body: { ok: false, error: err.message || "Failed to update AlphaClaw" },
310
211
  };
212
+ } finally {
213
+ kUpdateInProgress = false;
311
214
  }
312
215
  };
313
216
 
@@ -0,0 +1,260 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const { kRootDir } = require("./constants");
5
+ const {
6
+ compareVersionParts,
7
+ normalizeOpenclawVersion,
8
+ } = require("./helpers");
9
+
10
+ const getManagedOpenclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
11
+ path.join(rootDir, ".openclaw-runtime");
12
+
13
+ const getManagedOpenclawBinDir = ({ runtimeDir } = {}) =>
14
+ path.join(
15
+ runtimeDir || getManagedOpenclawRuntimeDir(),
16
+ "node_modules",
17
+ ".bin",
18
+ );
19
+
20
+ const getManagedOpenclawBinPath = ({ runtimeDir } = {}) =>
21
+ path.join(getManagedOpenclawBinDir({ runtimeDir }), "openclaw");
22
+
23
+ const getManagedOpenclawPackageJsonPath = ({ runtimeDir } = {}) =>
24
+ path.join(
25
+ runtimeDir || getManagedOpenclawRuntimeDir(),
26
+ "node_modules",
27
+ "openclaw",
28
+ "package.json",
29
+ );
30
+
31
+ const ensureManagedOpenclawRuntimeProject = ({
32
+ fsModule = fs,
33
+ runtimeDir,
34
+ } = {}) => {
35
+ const resolvedRuntimeDir = runtimeDir || getManagedOpenclawRuntimeDir();
36
+ const packageJsonPath = path.join(resolvedRuntimeDir, "package.json");
37
+ fsModule.mkdirSync(resolvedRuntimeDir, { recursive: true });
38
+ if (!fsModule.existsSync(packageJsonPath)) {
39
+ fsModule.writeFileSync(
40
+ packageJsonPath,
41
+ JSON.stringify(
42
+ {
43
+ name: "alphaclaw-openclaw-runtime",
44
+ private: true,
45
+ },
46
+ null,
47
+ 2,
48
+ ),
49
+ );
50
+ }
51
+ return {
52
+ runtimeDir: resolvedRuntimeDir,
53
+ packageJsonPath,
54
+ };
55
+ };
56
+
57
+ const readManagedOpenclawRuntimeVersion = ({
58
+ fsModule = fs,
59
+ runtimeDir,
60
+ } = {}) => {
61
+ try {
62
+ const pkg = JSON.parse(
63
+ fsModule.readFileSync(
64
+ getManagedOpenclawPackageJsonPath({ runtimeDir }),
65
+ "utf8",
66
+ ),
67
+ );
68
+ return normalizeOpenclawVersion(pkg?.version || "");
69
+ } catch {
70
+ return null;
71
+ }
72
+ };
73
+
74
+ const readBundledOpenclawVersion = ({
75
+ fsModule = fs,
76
+ resolveImpl = require.resolve,
77
+ } = {}) => {
78
+ try {
79
+ const pkgPath = resolveImpl("openclaw/package.json");
80
+ const pkg = JSON.parse(fsModule.readFileSync(pkgPath, "utf8"));
81
+ return normalizeOpenclawVersion(pkg?.version || "");
82
+ } catch {
83
+ return null;
84
+ }
85
+ };
86
+
87
+ const shellQuote = (value) =>
88
+ `'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
89
+
90
+ const applyManagedOpenclawPatch = ({
91
+ execSyncImpl,
92
+ fsModule = fs,
93
+ logger = console,
94
+ runtimeDir,
95
+ version,
96
+ alphaclawRoot = path.resolve(__dirname, "..", ".."),
97
+ } = {}) => {
98
+ const normalizedVersion = normalizeOpenclawVersion(version);
99
+ if (!normalizedVersion) return false;
100
+ const patchesDir = path.join(alphaclawRoot, "patches");
101
+ const patchFileName = `openclaw+${normalizedVersion}.patch`;
102
+ const patchFilePath = path.join(patchesDir, patchFileName);
103
+ if (!fsModule.existsSync(patchFilePath)) {
104
+ return false;
105
+ }
106
+
107
+ const runtimePatchDirName = ".alphaclaw-patches";
108
+ const runtimePatchDirPath = path.join(runtimeDir, runtimePatchDirName);
109
+ try {
110
+ if (fsModule.existsSync(runtimePatchDirPath)) {
111
+ fsModule.rmSync(runtimePatchDirPath, { recursive: true, force: true });
112
+ }
113
+ } catch {}
114
+ fsModule.symlinkSync(patchesDir, runtimePatchDirPath);
115
+
116
+ const patchPackageMain = require.resolve("patch-package/dist/index.js", {
117
+ paths: [alphaclawRoot],
118
+ });
119
+ logger.log(
120
+ `[alphaclaw] Applying bundled OpenClaw patch for ${normalizedVersion}...`,
121
+ );
122
+ execSyncImpl(
123
+ `${shellQuote(process.execPath)} ${shellQuote(patchPackageMain)} --patch-dir ${shellQuote(runtimePatchDirName)}`,
124
+ {
125
+ cwd: runtimeDir,
126
+ stdio: "inherit",
127
+ timeout: 120000,
128
+ },
129
+ );
130
+ return true;
131
+ };
132
+
133
+ const installManagedOpenclawRuntime = ({
134
+ execSyncImpl,
135
+ fsModule = fs,
136
+ logger = console,
137
+ runtimeDir,
138
+ spec,
139
+ alphaclawRoot,
140
+ } = {}) => {
141
+ const normalizedSpec = String(spec || "").trim() || "openclaw@latest";
142
+ ensureManagedOpenclawRuntimeProject({
143
+ fsModule,
144
+ runtimeDir,
145
+ });
146
+ execSyncImpl(
147
+ `npm install ${shellQuote(normalizedSpec)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
148
+ {
149
+ cwd: runtimeDir,
150
+ stdio: "inherit",
151
+ timeout: 180000,
152
+ },
153
+ );
154
+ const installedVersion = readManagedOpenclawRuntimeVersion({
155
+ fsModule,
156
+ runtimeDir,
157
+ });
158
+ applyManagedOpenclawPatch({
159
+ execSyncImpl,
160
+ fsModule,
161
+ logger,
162
+ runtimeDir,
163
+ version: installedVersion,
164
+ alphaclawRoot,
165
+ });
166
+ return {
167
+ spec: normalizedSpec,
168
+ version: installedVersion,
169
+ };
170
+ };
171
+
172
+ const syncManagedOpenclawRuntimeWithBundled = ({
173
+ execSyncImpl,
174
+ fsModule = fs,
175
+ logger = console,
176
+ runtimeDir,
177
+ resolveImpl,
178
+ alphaclawRoot,
179
+ } = {}) => {
180
+ const bundledVersion = readBundledOpenclawVersion({
181
+ fsModule,
182
+ resolveImpl,
183
+ });
184
+ if (!bundledVersion) {
185
+ return {
186
+ checked: false,
187
+ synced: false,
188
+ bundledVersion: null,
189
+ runtimeVersion: readManagedOpenclawRuntimeVersion({ fsModule, runtimeDir }),
190
+ };
191
+ }
192
+
193
+ const runtimeVersion = readManagedOpenclawRuntimeVersion({
194
+ fsModule,
195
+ runtimeDir,
196
+ });
197
+ if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
198
+ return {
199
+ checked: true,
200
+ synced: false,
201
+ bundledVersion,
202
+ runtimeVersion,
203
+ };
204
+ }
205
+
206
+ logger.log(
207
+ runtimeVersion
208
+ ? `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} is older than bundled ${bundledVersion}; syncing runtime...`
209
+ : `[alphaclaw] Managed OpenClaw runtime missing; seeding bundled OpenClaw ${bundledVersion}...`,
210
+ );
211
+ const installResult = installManagedOpenclawRuntime({
212
+ execSyncImpl,
213
+ fsModule,
214
+ logger,
215
+ runtimeDir,
216
+ spec: `openclaw@${bundledVersion}`,
217
+ alphaclawRoot,
218
+ });
219
+ return {
220
+ checked: true,
221
+ synced: true,
222
+ bundledVersion,
223
+ runtimeVersion: installResult.version || bundledVersion,
224
+ };
225
+ };
226
+
227
+ const prependManagedOpenclawBinToPath = ({
228
+ env = process.env,
229
+ fsModule = fs,
230
+ logger = console,
231
+ runtimeDir,
232
+ } = {}) => {
233
+ const resolvedRuntimeDir = runtimeDir || getManagedOpenclawRuntimeDir();
234
+ const binDir = getManagedOpenclawBinDir({ runtimeDir: resolvedRuntimeDir });
235
+ const binPath = getManagedOpenclawBinPath({ runtimeDir: resolvedRuntimeDir });
236
+ if (!fsModule.existsSync(binPath)) {
237
+ return false;
238
+ }
239
+ const currentEntries = String(env.PATH || "")
240
+ .split(path.delimiter)
241
+ .filter(Boolean);
242
+ const nextEntries = [binDir, ...currentEntries.filter((entry) => entry !== binDir)];
243
+ env.PATH = nextEntries.join(path.delimiter);
244
+ logger.log(`[alphaclaw] Using managed OpenClaw runtime from ${resolvedRuntimeDir}`);
245
+ return true;
246
+ };
247
+
248
+ module.exports = {
249
+ applyManagedOpenclawPatch,
250
+ ensureManagedOpenclawRuntimeProject,
251
+ getManagedOpenclawBinDir,
252
+ getManagedOpenclawBinPath,
253
+ getManagedOpenclawPackageJsonPath,
254
+ getManagedOpenclawRuntimeDir,
255
+ installManagedOpenclawRuntime,
256
+ prependManagedOpenclawBinToPath,
257
+ readBundledOpenclawVersion,
258
+ readManagedOpenclawRuntimeVersion,
259
+ syncManagedOpenclawRuntimeWithBundled,
260
+ };
@@ -1,12 +1,10 @@
1
- const { exec, execSync } = require("child_process");
1
+ const { execSync } = require("child_process");
2
2
  const fs = require("fs");
3
- const os = require("os");
4
3
  const path = require("path");
5
4
  const {
6
5
  kVersionCacheTtlMs,
7
6
  kLatestVersionCacheTtlMs,
8
- kNpmPackageRoot,
9
- kOpenclawUpdateCopyTimeoutMs,
7
+ kRootDir,
10
8
  } = require("./constants");
11
9
  const { normalizeOpenclawVersion } = require("./helpers");
12
10
  const { parseJsonObjectFromNoisyOutput } = require("./utils/json");
@@ -24,6 +22,9 @@ const createOpenclawVersionService = ({
24
22
  };
25
23
  let kOpenclawUpdateInProgress = false;
26
24
 
25
+ const buildOpenclawInstallSpec = (version = "latest") =>
26
+ `openclaw@${String(version || "").trim() || "latest"}`;
27
+
27
28
  const readOpenclawVersion = () => {
28
29
  const now = Date.now();
29
30
  if (
@@ -87,118 +88,6 @@ const createOpenclawVersionService = ({
87
88
  }
88
89
  };
89
90
 
90
- const findInstallDir = () => {
91
- // Resolve the consumer app root (for example /app in Docker), not this package directory.
92
- let dir = kNpmPackageRoot;
93
- while (dir !== path.dirname(dir)) {
94
- const parent = path.dirname(dir);
95
- if (
96
- path.basename(parent) === "node_modules" ||
97
- parent.includes(`${path.sep}node_modules${path.sep}`)
98
- ) {
99
- dir = parent;
100
- continue;
101
- }
102
- const pkgPath = path.join(parent, "package.json");
103
- if (fs.existsSync(pkgPath)) {
104
- try {
105
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
106
- if (
107
- pkg.dependencies?.["@chrysb/alphaclaw"] ||
108
- pkg.devDependencies?.["@chrysb/alphaclaw"] ||
109
- pkg.optionalDependencies?.["@chrysb/alphaclaw"]
110
- ) {
111
- return parent;
112
- }
113
- } catch {}
114
- }
115
- dir = parent;
116
- }
117
- return kNpmPackageRoot;
118
- };
119
-
120
- // Install to a temp directory, then copy into the real node_modules.
121
- // Running `npm install` directly in the app dir causes EBUSY on Docker
122
- // because npm tries to rename directories that the running process holds open.
123
- // Copying individual files (cp -af) avoids the rename syscall entirely.
124
- const installLatestOpenclaw = () =>
125
- new Promise((resolve, reject) => {
126
- const installDir = findInstallDir();
127
- const tmpDir = fs.mkdtempSync(
128
- path.join(os.tmpdir(), "openclaw-update-"),
129
- );
130
- const cleanup = () => {
131
- try {
132
- fs.rmSync(tmpDir, { recursive: true, force: true });
133
- } catch {}
134
- };
135
-
136
- fs.writeFileSync(
137
- path.join(tmpDir, "package.json"),
138
- JSON.stringify({
139
- private: true,
140
- dependencies: { openclaw: "latest" },
141
- }),
142
- );
143
-
144
- const npmEnv = {
145
- ...process.env,
146
- npm_config_update_notifier: "false",
147
- npm_config_fund: "false",
148
- npm_config_audit: "false",
149
- };
150
-
151
- console.log(
152
- `[alphaclaw] Running: npm install openclaw@latest in temp dir (target: ${installDir})`,
153
- );
154
- exec(
155
- "npm install --omit=dev --prefer-online --package-lock=false",
156
- { cwd: tmpDir, env: npmEnv, timeout: 180000 },
157
- (installErr, stdout, stderr) => {
158
- if (installErr) {
159
- const message = String(stderr || installErr.message || "").trim();
160
- console.log(
161
- `[alphaclaw] openclaw install error: ${message.slice(0, 200)}`,
162
- );
163
- cleanup();
164
- return reject(
165
- new Error(message || "Failed to install openclaw@latest"),
166
- );
167
- }
168
- if (stdout?.trim()) {
169
- console.log(
170
- `[alphaclaw] openclaw install stdout: ${stdout.trim().slice(0, 300)}`,
171
- );
172
- }
173
-
174
- const src = path.join(tmpDir, "node_modules");
175
- const dest = path.join(installDir, "node_modules");
176
- exec(
177
- `cp -af "${src}/." "${dest}/"`,
178
- { timeout: kOpenclawUpdateCopyTimeoutMs },
179
- (cpErr) => {
180
- cleanup();
181
- if (cpErr) {
182
- console.log(
183
- `[alphaclaw] openclaw copy error: ${(cpErr.message || "").slice(0, 200)}`,
184
- );
185
- return reject(
186
- new Error(
187
- `Failed to copy updated openclaw files: ${cpErr.message}`,
188
- ),
189
- );
190
- }
191
- console.log("[alphaclaw] openclaw install completed");
192
- resolve({
193
- stdout: stdout?.trim() || "",
194
- stderr: stderr?.trim() || "",
195
- });
196
- },
197
- );
198
- },
199
- );
200
- });
201
-
202
91
  const getVersionStatus = async (refresh) => {
203
92
  const currentVersion = readOpenclawVersion();
204
93
  try {
@@ -228,27 +117,67 @@ const createOpenclawVersionService = ({
228
117
  kOpenclawUpdateInProgress = true;
229
118
  const previousVersion = readOpenclawVersion();
230
119
  try {
231
- await installLatestOpenclaw();
232
- kOpenclawVersionCache = { value: null, fetchedAt: 0 };
233
- const currentVersion = readOpenclawVersion();
234
- const { latestVersion, hasUpdate } = readOpenclawUpdateStatus({
235
- refresh: true,
236
- });
237
- let restarted = false;
238
- if (isOnboarded()) {
239
- restartGateway();
240
- restarted = true;
120
+ let latestVersion = null;
121
+ let hasUpdate = false;
122
+ try {
123
+ const updateStatus = readOpenclawUpdateStatus({ refresh: true });
124
+ latestVersion = updateStatus.latestVersion || null;
125
+ hasUpdate = !!updateStatus.hasUpdate;
126
+ } catch (error) {
127
+ console.log(
128
+ `[alphaclaw] Could not resolve exact OpenClaw version before restart: ${error.message || "unknown error"}`,
129
+ );
241
130
  }
131
+
132
+ if (!hasUpdate && latestVersion && latestVersion === previousVersion) {
133
+ return {
134
+ status: 200,
135
+ body: {
136
+ ok: true,
137
+ previousVersion,
138
+ currentVersion: previousVersion,
139
+ latestVersion,
140
+ hasUpdate: false,
141
+ restarted: false,
142
+ restarting: false,
143
+ updated: false,
144
+ },
145
+ };
146
+ }
147
+
148
+ const targetVersion = latestVersion || "latest";
149
+ const spec = buildOpenclawInstallSpec(targetVersion);
150
+ const markerPath = path.join(kRootDir, ".openclaw-update-pending");
151
+ fs.writeFileSync(
152
+ markerPath,
153
+ JSON.stringify({
154
+ from: previousVersion,
155
+ to: targetVersion,
156
+ spec,
157
+ ts: Date.now(),
158
+ }),
159
+ );
160
+ console.log(
161
+ `[alphaclaw] OpenClaw update marker written to ${markerPath} for ${spec}`,
162
+ );
163
+ kOpenclawVersionCache = { value: previousVersion, fetchedAt: 0 };
164
+ kOpenclawUpdateStatusCache = {
165
+ latestVersion,
166
+ hasUpdate,
167
+ fetchedAt: 0,
168
+ };
242
169
  return {
243
170
  status: 200,
244
171
  body: {
245
172
  ok: true,
246
173
  previousVersion,
247
- currentVersion,
174
+ currentVersion: previousVersion,
175
+ targetVersion: targetVersion === "latest" ? null : targetVersion,
248
176
  latestVersion,
249
- hasUpdate,
250
- restarted,
251
- updated: previousVersion !== currentVersion,
177
+ hasUpdate: true,
178
+ restarted: false,
179
+ restarting: true,
180
+ updated: previousVersion !== targetVersion,
252
181
  },
253
182
  };
254
183
  } catch (err) {