@chrysb/alphaclaw 0.8.6 → 0.8.7-beta.0
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.
- package/bin/alphaclaw.js +56 -20
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +2041 -2021
- package/lib/public/js/app.js +3 -0
- package/lib/public/js/components/gateway.js +6 -3
- package/lib/public/js/components/general/index.js +2 -0
- package/lib/public/js/components/onboarding/welcome-form-step.js +29 -4
- package/lib/public/js/components/routes/general-route.js +2 -0
- package/lib/public/js/components/routes/watchdog-route.js +2 -0
- package/lib/public/js/components/sidebar.js +20 -7
- package/lib/public/js/components/update-modal-helpers.js +12 -0
- package/lib/public/js/components/update-modal.js +2 -1
- package/lib/public/js/components/watchdog-tab/index.js +2 -0
- package/lib/public/js/components/welcome/index.js +1 -0
- package/lib/public/js/components/welcome/use-welcome.js +52 -2
- package/lib/public/js/hooks/use-app-shell-controller.js +33 -9
- package/lib/public/js/lib/api.js +35 -0
- package/lib/server/alphaclaw-version.js +30 -127
- package/lib/server/openclaw-version.js +59 -130
- package/lib/server/pending-alphaclaw-update.js +71 -0
- package/lib/server/pending-openclaw-update.js +71 -0
- package/lib/server/routes/system.js +6 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
const {
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
177
|
+
hasUpdate: true,
|
|
178
|
+
restarted: false,
|
|
179
|
+
restarting: true,
|
|
180
|
+
updated: previousVersion !== targetVersion,
|
|
252
181
|
},
|
|
253
182
|
};
|
|
254
183
|
} catch (err) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const buildPendingAlphaclawInstallSpec = (marker = {}) => {
|
|
2
|
+
const explicitSpec = String(marker?.spec || "").trim();
|
|
3
|
+
if (explicitSpec) {
|
|
4
|
+
return explicitSpec;
|
|
5
|
+
}
|
|
6
|
+
const targetVersion = String(marker?.to || "").trim() || "latest";
|
|
7
|
+
return `@chrysb/alphaclaw@${targetVersion}`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const shellQuote = (value) =>
|
|
11
|
+
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
12
|
+
|
|
13
|
+
const applyPendingAlphaclawUpdate = ({
|
|
14
|
+
execSyncImpl,
|
|
15
|
+
fsModule,
|
|
16
|
+
installDir,
|
|
17
|
+
logger = console,
|
|
18
|
+
markerPath,
|
|
19
|
+
}) => {
|
|
20
|
+
if (!fsModule.existsSync(markerPath)) {
|
|
21
|
+
return {
|
|
22
|
+
attempted: false,
|
|
23
|
+
installed: false,
|
|
24
|
+
spec: "",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let marker = {};
|
|
29
|
+
try {
|
|
30
|
+
marker = JSON.parse(fsModule.readFileSync(markerPath, "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
marker = {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const spec = buildPendingAlphaclawInstallSpec(marker);
|
|
36
|
+
logger.log(`[alphaclaw] Pending update detected, installing ${spec}...`);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
execSyncImpl(
|
|
40
|
+
`npm install ${shellQuote(spec)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
|
|
41
|
+
{
|
|
42
|
+
cwd: installDir,
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
timeout: 180000,
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
fsModule.unlinkSync(markerPath);
|
|
48
|
+
logger.log("[alphaclaw] Update applied successfully");
|
|
49
|
+
return {
|
|
50
|
+
attempted: true,
|
|
51
|
+
installed: true,
|
|
52
|
+
spec,
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.log(`[alphaclaw] Update install failed: ${error.message}`);
|
|
56
|
+
try {
|
|
57
|
+
fsModule.unlinkSync(markerPath);
|
|
58
|
+
} catch {}
|
|
59
|
+
return {
|
|
60
|
+
attempted: true,
|
|
61
|
+
installed: false,
|
|
62
|
+
spec,
|
|
63
|
+
error,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
applyPendingAlphaclawUpdate,
|
|
70
|
+
buildPendingAlphaclawInstallSpec,
|
|
71
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const buildPendingOpenclawInstallSpec = (marker = {}) => {
|
|
2
|
+
const explicitSpec = String(marker?.spec || "").trim();
|
|
3
|
+
if (explicitSpec) {
|
|
4
|
+
return explicitSpec;
|
|
5
|
+
}
|
|
6
|
+
const targetVersion = String(marker?.to || "").trim() || "latest";
|
|
7
|
+
return `openclaw@${targetVersion}`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const shellQuote = (value) =>
|
|
11
|
+
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
12
|
+
|
|
13
|
+
const applyPendingOpenclawUpdate = ({
|
|
14
|
+
execSyncImpl,
|
|
15
|
+
fsModule,
|
|
16
|
+
installDir,
|
|
17
|
+
logger = console,
|
|
18
|
+
markerPath,
|
|
19
|
+
}) => {
|
|
20
|
+
if (!fsModule.existsSync(markerPath)) {
|
|
21
|
+
return {
|
|
22
|
+
attempted: false,
|
|
23
|
+
installed: false,
|
|
24
|
+
spec: "",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let marker = {};
|
|
29
|
+
try {
|
|
30
|
+
marker = JSON.parse(fsModule.readFileSync(markerPath, "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
marker = {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const spec = buildPendingOpenclawInstallSpec(marker);
|
|
36
|
+
logger.log(`[alphaclaw] Pending OpenClaw update detected, installing ${spec}...`);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
execSyncImpl(
|
|
40
|
+
`npm install ${shellQuote(spec)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
|
|
41
|
+
{
|
|
42
|
+
cwd: installDir,
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
timeout: 180000,
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
fsModule.unlinkSync(markerPath);
|
|
48
|
+
logger.log("[alphaclaw] OpenClaw update applied successfully");
|
|
49
|
+
return {
|
|
50
|
+
attempted: true,
|
|
51
|
+
installed: true,
|
|
52
|
+
spec,
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.log(`[alphaclaw] OpenClaw update install failed: ${error.message}`);
|
|
56
|
+
try {
|
|
57
|
+
fsModule.unlinkSync(markerPath);
|
|
58
|
+
} catch {}
|
|
59
|
+
return {
|
|
60
|
+
attempted: true,
|
|
61
|
+
installed: false,
|
|
62
|
+
spec,
|
|
63
|
+
error,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
applyPendingOpenclawUpdate,
|
|
70
|
+
buildPendingOpenclawInstallSpec,
|
|
71
|
+
};
|
|
@@ -588,7 +588,12 @@ const registerSystemRoutes = ({
|
|
|
588
588
|
console.log(
|
|
589
589
|
`[alphaclaw] /api/openclaw/update result: status=${result.status} ok=${result.body?.ok === true}`,
|
|
590
590
|
);
|
|
591
|
-
|
|
591
|
+
if (result.status === 200 && result.body?.ok && result.body?.restarting) {
|
|
592
|
+
res.json(result.body);
|
|
593
|
+
setTimeout(() => alphaclawVersionService.restartProcess(), 1000);
|
|
594
|
+
} else {
|
|
595
|
+
res.status(result.status).json(result.body);
|
|
596
|
+
}
|
|
592
597
|
});
|
|
593
598
|
|
|
594
599
|
app.get("/api/alphaclaw/version", async (req, res) => {
|