@chrysb/alphaclaw 0.8.8 → 0.8.10
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 +43 -174
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +2089 -2109
- package/lib/public/js/app.js +0 -3
- package/lib/public/js/components/gateway.js +3 -6
- package/lib/public/js/components/general/index.js +0 -2
- package/lib/public/js/components/onboarding/welcome-form-step.js +4 -29
- package/lib/public/js/components/routes/general-route.js +0 -2
- package/lib/public/js/components/routes/watchdog-route.js +0 -2
- package/lib/public/js/components/sidebar.js +7 -20
- package/lib/public/js/components/update-modal.js +1 -2
- package/lib/public/js/components/watchdog-tab/index.js +0 -2
- package/lib/public/js/components/welcome/index.js +0 -1
- package/lib/public/js/components/welcome/use-welcome.js +2 -52
- package/lib/public/js/hooks/use-app-shell-controller.js +9 -37
- package/lib/public/js/lib/api.js +0 -36
- package/lib/server/alphaclaw-version.js +128 -37
- package/lib/server/gateway.js +14 -32
- package/lib/server/init/register-server-routes.js +1 -7
- package/lib/server/openclaw-version.js +136 -76
- package/lib/server/routes/pages.js +1 -9
- package/lib/server/routes/system.js +1 -6
- package/lib/server/usage-tracker-config.js +3 -27
- package/package.json +1 -2
- package/lib/public/js/components/update-modal-helpers.js +0 -12
- package/lib/release/managed-release.js +0 -180
- package/lib/server/alphaclaw-runtime.js +0 -294
- package/lib/server/openclaw-runtime.js +0 -428
- package/lib/server/package-fingerprint.js +0 -274
- package/lib/server/pending-alphaclaw-update.js +0 -85
- package/lib/server/pending-openclaw-update.js +0 -86
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
const childProcess = require("child_process");
|
|
1
2
|
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
2
4
|
const path = require("path");
|
|
3
5
|
const https = require("https");
|
|
4
6
|
const http = require("http");
|
|
@@ -6,6 +8,7 @@ const {
|
|
|
6
8
|
kLatestVersionCacheTtlMs,
|
|
7
9
|
kAlphaclawRegistryUrl,
|
|
8
10
|
kNpmPackageRoot,
|
|
11
|
+
kOpenclawUpdateCopyTimeoutMs,
|
|
9
12
|
kRootDir,
|
|
10
13
|
} = require("./constants");
|
|
11
14
|
|
|
@@ -23,9 +26,6 @@ const isNewerVersion = (latest, current) => {
|
|
|
23
26
|
return l.patch > c.patch;
|
|
24
27
|
};
|
|
25
28
|
|
|
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,6 +108,120 @@ 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
|
+
|
|
111
225
|
const isContainer = () =>
|
|
112
226
|
process.env.RAILWAY_ENVIRONMENT ||
|
|
113
227
|
process.env.RENDER ||
|
|
@@ -126,15 +240,9 @@ const createAlphaclawVersionService = () => {
|
|
|
126
240
|
// On bare metal / Mac / Linux, spawn a replacement process then exit.
|
|
127
241
|
console.log("[alphaclaw] Spawning new process and exiting...");
|
|
128
242
|
const { spawn } = require("child_process");
|
|
129
|
-
const
|
|
130
|
-
delete nextEnv.ALPHACLAW_MANAGED_RUNTIME_ACTIVE;
|
|
131
|
-
const bootstrapCliPath =
|
|
132
|
-
String(process.env.ALPHACLAW_BOOTSTRAP_CLI_PATH || "").trim() ||
|
|
133
|
-
process.argv[1];
|
|
134
|
-
const child = spawn(process.argv[0], [bootstrapCliPath, ...process.argv.slice(2)], {
|
|
243
|
+
const child = spawn(process.argv[0], process.argv.slice(1), {
|
|
135
244
|
detached: true,
|
|
136
245
|
stdio: "inherit",
|
|
137
|
-
env: nextEnv,
|
|
138
246
|
});
|
|
139
247
|
child.unref();
|
|
140
248
|
process.exit(0);
|
|
@@ -169,33 +277,18 @@ const createAlphaclawVersionService = () => {
|
|
|
169
277
|
kUpdateInProgress = true;
|
|
170
278
|
const previousVersion = readAlphaclawVersion();
|
|
171
279
|
try {
|
|
172
|
-
|
|
280
|
+
await installLatestAlphaclaw();
|
|
281
|
+
// Write marker to persistent volume so the update survives container recreation
|
|
282
|
+
const markerPath = path.join(kRootDir, ".alphaclaw-update-pending");
|
|
173
283
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
console.log(
|
|
180
|
-
`[alphaclaw] Could not resolve exact AlphaClaw version before restart: ${error.message || "unknown error"}`,
|
|
284
|
+
fs.writeFileSync(
|
|
285
|
+
markerPath,
|
|
286
|
+
JSON.stringify({ from: previousVersion, ts: Date.now() }),
|
|
181
287
|
);
|
|
288
|
+
console.log(`[alphaclaw] Update marker written to ${markerPath}`);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
console.log(`[alphaclaw] Could not write update marker: ${e.message}`);
|
|
182
291
|
}
|
|
183
|
-
|
|
184
|
-
const spec = buildAlphaclawInstallSpec(targetVersion);
|
|
185
|
-
// Write marker to persistent volume so the update survives container recreation
|
|
186
|
-
const markerPath = path.join(kRootDir, ".alphaclaw-update-pending");
|
|
187
|
-
fs.writeFileSync(
|
|
188
|
-
markerPath,
|
|
189
|
-
JSON.stringify({
|
|
190
|
-
from: previousVersion,
|
|
191
|
-
to: targetVersion,
|
|
192
|
-
spec,
|
|
193
|
-
ts: Date.now(),
|
|
194
|
-
}),
|
|
195
|
-
);
|
|
196
|
-
console.log(
|
|
197
|
-
`[alphaclaw] Update marker written to ${markerPath} for ${spec}`,
|
|
198
|
-
);
|
|
199
292
|
kUpdateStatusCache = {
|
|
200
293
|
latestVersion: null,
|
|
201
294
|
hasUpdate: false,
|
|
@@ -206,17 +299,15 @@ const createAlphaclawVersionService = () => {
|
|
|
206
299
|
body: {
|
|
207
300
|
ok: true,
|
|
208
301
|
previousVersion,
|
|
209
|
-
targetVersion: targetVersion === "latest" ? null : targetVersion,
|
|
210
302
|
restarting: true,
|
|
211
303
|
},
|
|
212
304
|
};
|
|
213
305
|
} catch (err) {
|
|
306
|
+
kUpdateInProgress = false;
|
|
214
307
|
return {
|
|
215
308
|
status: 500,
|
|
216
309
|
body: { ok: false, error: err.message || "Failed to update AlphaClaw" },
|
|
217
310
|
};
|
|
218
|
-
} finally {
|
|
219
|
-
kUpdateInProgress = false;
|
|
220
311
|
}
|
|
221
312
|
};
|
|
222
313
|
|
package/lib/server/gateway.js
CHANGED
|
@@ -2,7 +2,6 @@ const path = require("path");
|
|
|
2
2
|
const { spawn, execSync } = require("child_process");
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const net = require("net");
|
|
5
|
-
const { getManagedOpenclawBinPath } = require("./openclaw-runtime");
|
|
6
5
|
const {
|
|
7
6
|
ALPHACLAW_DIR,
|
|
8
7
|
OPENCLAW_DIR,
|
|
@@ -49,16 +48,6 @@ const gatewayEnv = () => ({
|
|
|
49
48
|
XDG_CONFIG_HOME: OPENCLAW_DIR,
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
const shellQuote = (value) =>
|
|
53
|
-
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
54
|
-
|
|
55
|
-
const getOpenclawCommandPath = () => {
|
|
56
|
-
const managedBinPath = getManagedOpenclawBinPath();
|
|
57
|
-
return fs.existsSync(managedBinPath) ? managedBinPath : "openclaw";
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const getOpenclawCommandPrefix = () => shellQuote(getOpenclawCommandPath());
|
|
61
|
-
|
|
62
51
|
const writeOnboardingMarker = (reason) => {
|
|
63
52
|
fs.mkdirSync(ALPHACLAW_DIR, { recursive: true });
|
|
64
53
|
fs.writeFileSync(
|
|
@@ -130,10 +119,9 @@ const isGatewayRunning = () =>
|
|
|
130
119
|
});
|
|
131
120
|
|
|
132
121
|
const runGatewayCmd = (cmd) => {
|
|
133
|
-
|
|
134
|
-
console.log(`[alphaclaw] Running: ${openclawCommandPrefix} gateway ${cmd}`);
|
|
122
|
+
console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
|
|
135
123
|
try {
|
|
136
|
-
const out = execSync(
|
|
124
|
+
const out = execSync(`openclaw gateway ${cmd}`, {
|
|
137
125
|
env: gatewayEnv(),
|
|
138
126
|
timeout: 15000,
|
|
139
127
|
encoding: "utf8",
|
|
@@ -158,7 +146,7 @@ const launchGatewayProcess = () => {
|
|
|
158
146
|
return gatewayChild;
|
|
159
147
|
}
|
|
160
148
|
gatewayStderrTail = [];
|
|
161
|
-
const child = spawn(
|
|
149
|
+
const child = spawn("openclaw", ["gateway", "run"], {
|
|
162
150
|
env: gatewayEnv(),
|
|
163
151
|
stdio: ["pipe", "pipe", "pipe"],
|
|
164
152
|
});
|
|
@@ -318,7 +306,7 @@ const syncChannelConfig = (savedVars, mode = "all") => {
|
|
|
318
306
|
const appToken = savedMap[def.extraEnvKeys?.[0]];
|
|
319
307
|
if (!appToken) continue;
|
|
320
308
|
execSync(
|
|
321
|
-
|
|
309
|
+
`openclaw channels add --channel slack --bot-token "${token}" --app-token "${appToken}"`,
|
|
322
310
|
{ env, timeout: 15000, encoding: "utf8" },
|
|
323
311
|
);
|
|
324
312
|
let raw = fs.readFileSync(configPath, "utf8");
|
|
@@ -330,14 +318,11 @@ const syncChannelConfig = (savedVars, mode = "all") => {
|
|
|
330
318
|
}
|
|
331
319
|
fs.writeFileSync(configPath, raw);
|
|
332
320
|
} else {
|
|
333
|
-
execSync(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
encoding: "utf8",
|
|
339
|
-
},
|
|
340
|
-
);
|
|
321
|
+
execSync(`openclaw channels add --channel ${ch} --token "${token}"`, {
|
|
322
|
+
env,
|
|
323
|
+
timeout: 15000,
|
|
324
|
+
encoding: "utf8",
|
|
325
|
+
});
|
|
341
326
|
const raw = fs.readFileSync(configPath, "utf8");
|
|
342
327
|
if (raw.includes(token)) {
|
|
343
328
|
fs.writeFileSync(
|
|
@@ -359,14 +344,11 @@ const syncChannelConfig = (savedVars, mode = "all") => {
|
|
|
359
344
|
) {
|
|
360
345
|
console.log(`[alphaclaw] Removing channel: ${ch}`);
|
|
361
346
|
try {
|
|
362
|
-
execSync(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
encoding: "utf8",
|
|
368
|
-
},
|
|
369
|
-
);
|
|
347
|
+
execSync(`openclaw channels remove --channel ${ch} --delete`, {
|
|
348
|
+
env,
|
|
349
|
+
timeout: 15000,
|
|
350
|
+
encoding: "utf8",
|
|
351
|
+
});
|
|
370
352
|
console.log(`[alphaclaw] Channel ${ch} removed`);
|
|
371
353
|
} catch (e) {
|
|
372
354
|
console.error(
|
|
@@ -90,13 +90,7 @@ const registerServerRoutes = ({
|
|
|
90
90
|
loginThrottle,
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
registerPageRoutes({
|
|
94
|
-
app,
|
|
95
|
-
requireAuth,
|
|
96
|
-
isGatewayRunning,
|
|
97
|
-
alphaclawVersionService,
|
|
98
|
-
openclawVersionService,
|
|
99
|
-
});
|
|
93
|
+
registerPageRoutes({ app, requireAuth, isGatewayRunning });
|
|
100
94
|
registerModelRoutes({
|
|
101
95
|
app,
|
|
102
96
|
shellCmd,
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { exec, execSync } = require("child_process");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
3
4
|
const path = require("path");
|
|
4
5
|
const {
|
|
5
6
|
kVersionCacheTtlMs,
|
|
6
7
|
kLatestVersionCacheTtlMs,
|
|
7
|
-
|
|
8
|
+
kNpmPackageRoot,
|
|
9
|
+
kOpenclawUpdateCopyTimeoutMs,
|
|
8
10
|
} = require("./constants");
|
|
9
11
|
const { normalizeOpenclawVersion } = require("./helpers");
|
|
10
|
-
const { getManagedOpenclawBinPath } = require("./openclaw-runtime");
|
|
11
12
|
const { parseJsonObjectFromNoisyOutput } = require("./utils/json");
|
|
12
13
|
|
|
13
14
|
const createOpenclawVersionService = ({
|
|
@@ -23,14 +24,6 @@ const createOpenclawVersionService = ({
|
|
|
23
24
|
};
|
|
24
25
|
let kOpenclawUpdateInProgress = false;
|
|
25
26
|
|
|
26
|
-
const buildOpenclawInstallSpec = (version = "latest") =>
|
|
27
|
-
`openclaw@${String(version || "").trim() || "latest"}`;
|
|
28
|
-
|
|
29
|
-
const getOpenclawCommandPath = () => {
|
|
30
|
-
const managedBinPath = getManagedOpenclawBinPath();
|
|
31
|
-
return fs.existsSync(managedBinPath) ? managedBinPath : "openclaw";
|
|
32
|
-
};
|
|
33
|
-
|
|
34
27
|
const readOpenclawVersion = () => {
|
|
35
28
|
const now = Date.now();
|
|
36
29
|
if (
|
|
@@ -40,9 +33,9 @@ const createOpenclawVersionService = ({
|
|
|
40
33
|
return kOpenclawVersionCache.value;
|
|
41
34
|
}
|
|
42
35
|
try {
|
|
43
|
-
const raw =
|
|
36
|
+
const raw = execSync("openclaw --version", {
|
|
44
37
|
env: gatewayEnv(),
|
|
45
|
-
timeout:
|
|
38
|
+
timeout: 5000,
|
|
46
39
|
encoding: "utf8",
|
|
47
40
|
}).trim();
|
|
48
41
|
const version = normalizeOpenclawVersion(raw);
|
|
@@ -66,16 +59,11 @@ const createOpenclawVersionService = ({
|
|
|
66
59
|
};
|
|
67
60
|
}
|
|
68
61
|
try {
|
|
69
|
-
const raw =
|
|
70
|
-
getOpenclawCommandPath(),
|
|
71
|
-
["update", "status", "--json"],
|
|
72
|
-
{
|
|
62
|
+
const raw = execSync("openclaw update status --json", {
|
|
73
63
|
env: gatewayEnv(),
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
).trim();
|
|
64
|
+
timeout: 8000,
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
}).trim();
|
|
79
67
|
const parsed = parseJsonObjectFromNoisyOutput(raw);
|
|
80
68
|
if (!parsed) {
|
|
81
69
|
throw new Error("openclaw update status returned invalid JSON payload");
|
|
@@ -99,6 +87,118 @@ const createOpenclawVersionService = ({
|
|
|
99
87
|
}
|
|
100
88
|
};
|
|
101
89
|
|
|
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
|
+
|
|
102
202
|
const getVersionStatus = async (refresh) => {
|
|
103
203
|
const currentVersion = readOpenclawVersion();
|
|
104
204
|
try {
|
|
@@ -128,67 +228,27 @@ const createOpenclawVersionService = ({
|
|
|
128
228
|
kOpenclawUpdateInProgress = true;
|
|
129
229
|
const previousVersion = readOpenclawVersion();
|
|
130
230
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!hasUpdate && latestVersion && latestVersion === previousVersion) {
|
|
144
|
-
return {
|
|
145
|
-
status: 200,
|
|
146
|
-
body: {
|
|
147
|
-
ok: true,
|
|
148
|
-
previousVersion,
|
|
149
|
-
currentVersion: previousVersion,
|
|
150
|
-
latestVersion,
|
|
151
|
-
hasUpdate: false,
|
|
152
|
-
restarted: false,
|
|
153
|
-
restarting: false,
|
|
154
|
-
updated: false,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
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;
|
|
157
241
|
}
|
|
158
|
-
|
|
159
|
-
const targetVersion = latestVersion || "latest";
|
|
160
|
-
const spec = buildOpenclawInstallSpec(targetVersion);
|
|
161
|
-
const markerPath = path.join(kRootDir, ".openclaw-update-pending");
|
|
162
|
-
fs.writeFileSync(
|
|
163
|
-
markerPath,
|
|
164
|
-
JSON.stringify({
|
|
165
|
-
from: previousVersion,
|
|
166
|
-
to: targetVersion,
|
|
167
|
-
spec,
|
|
168
|
-
ts: Date.now(),
|
|
169
|
-
}),
|
|
170
|
-
);
|
|
171
|
-
console.log(
|
|
172
|
-
`[alphaclaw] OpenClaw update marker written to ${markerPath} for ${spec}`,
|
|
173
|
-
);
|
|
174
|
-
kOpenclawVersionCache = { value: previousVersion, fetchedAt: 0 };
|
|
175
|
-
kOpenclawUpdateStatusCache = {
|
|
176
|
-
latestVersion,
|
|
177
|
-
hasUpdate,
|
|
178
|
-
fetchedAt: 0,
|
|
179
|
-
};
|
|
180
242
|
return {
|
|
181
243
|
status: 200,
|
|
182
244
|
body: {
|
|
183
245
|
ok: true,
|
|
184
246
|
previousVersion,
|
|
185
|
-
currentVersion
|
|
186
|
-
targetVersion: targetVersion === "latest" ? null : targetVersion,
|
|
247
|
+
currentVersion,
|
|
187
248
|
latestVersion,
|
|
188
|
-
hasUpdate
|
|
189
|
-
restarted
|
|
190
|
-
|
|
191
|
-
updated: previousVersion !== targetVersion,
|
|
249
|
+
hasUpdate,
|
|
250
|
+
restarted,
|
|
251
|
+
updated: previousVersion !== currentVersion,
|
|
192
252
|
},
|
|
193
253
|
};
|
|
194
254
|
} catch (err) {
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
|
|
3
|
-
const registerPageRoutes = ({
|
|
4
|
-
app,
|
|
5
|
-
requireAuth,
|
|
6
|
-
isGatewayRunning,
|
|
7
|
-
alphaclawVersionService,
|
|
8
|
-
openclawVersionService,
|
|
9
|
-
}) => {
|
|
3
|
+
const registerPageRoutes = ({ app, requireAuth, isGatewayRunning }) => {
|
|
10
4
|
app.get("/health", async (req, res) => {
|
|
11
5
|
const running = await isGatewayRunning();
|
|
12
6
|
res.json({
|
|
13
7
|
status: running ? "healthy" : "starting",
|
|
14
8
|
gateway: running ? "running" : "starting",
|
|
15
|
-
alphaclawVersion: alphaclawVersionService?.readAlphaclawVersion?.() || null,
|
|
16
|
-
openclawVersion: openclawVersionService?.readOpenclawVersion?.() || null,
|
|
17
9
|
});
|
|
18
10
|
});
|
|
19
11
|
|
|
@@ -588,12 +588,7 @@ const registerSystemRoutes = ({
|
|
|
588
588
|
console.log(
|
|
589
589
|
`[alphaclaw] /api/openclaw/update result: status=${result.status} ok=${result.body?.ok === true}`,
|
|
590
590
|
);
|
|
591
|
-
|
|
592
|
-
res.json(result.body);
|
|
593
|
-
setTimeout(() => alphaclawVersionService.restartProcess(), 1000);
|
|
594
|
-
} else {
|
|
595
|
-
res.status(result.status).json(result.body);
|
|
596
|
-
}
|
|
591
|
+
res.status(result.status).json(result.body);
|
|
597
592
|
});
|
|
598
593
|
|
|
599
594
|
app.get("/api/alphaclaw/version", async (req, res) => {
|
|
@@ -8,27 +8,6 @@ const kUsageTrackerPluginPath = path.resolve(
|
|
|
8
8
|
"usage-tracker",
|
|
9
9
|
);
|
|
10
10
|
|
|
11
|
-
const normalizePluginPath = (value = "") =>
|
|
12
|
-
String(value || "")
|
|
13
|
-
.trim()
|
|
14
|
-
.replace(/\\/g, "/")
|
|
15
|
-
.replace(/\/+$/, "");
|
|
16
|
-
|
|
17
|
-
const isUsageTrackerPluginPath = (value = "") => {
|
|
18
|
-
const normalizedValue = normalizePluginPath(value);
|
|
19
|
-
const normalizedCanonicalPath = normalizePluginPath(kUsageTrackerPluginPath);
|
|
20
|
-
if (!normalizedValue) return false;
|
|
21
|
-
if (
|
|
22
|
-
normalizedValue === normalizedCanonicalPath ||
|
|
23
|
-
normalizedValue.startsWith(`${normalizedCanonicalPath}/`)
|
|
24
|
-
) {
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
return /(?:^|\/)(?:node_modules\/@chrysb\/alphaclaw\/lib|lib)\/plugin\/usage-tracker(?:\/.*)?$/.test(
|
|
28
|
-
normalizedValue,
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
11
|
const ensurePluginsShell = (cfg = {}) => {
|
|
33
12
|
if (!cfg.plugins || typeof cfg.plugins !== "object") cfg.plugins = {};
|
|
34
13
|
if (!Array.isArray(cfg.plugins.allow)) cfg.plugins.allow = [];
|
|
@@ -53,11 +32,9 @@ const ensurePluginAllowed = ({ cfg = {}, pluginKey = "" }) => {
|
|
|
53
32
|
const ensureUsageTrackerPluginEntry = (cfg = {}) => {
|
|
54
33
|
const before = JSON.stringify(cfg);
|
|
55
34
|
ensurePluginAllowed({ cfg, pluginKey: "usage-tracker" });
|
|
56
|
-
|
|
57
|
-
(
|
|
58
|
-
|
|
59
|
-
nextPaths.push(kUsageTrackerPluginPath);
|
|
60
|
-
cfg.plugins.load.paths = nextPaths;
|
|
35
|
+
if (!cfg.plugins.load.paths.includes(kUsageTrackerPluginPath)) {
|
|
36
|
+
cfg.plugins.load.paths.push(kUsageTrackerPluginPath);
|
|
37
|
+
}
|
|
61
38
|
cfg.plugins.entries["usage-tracker"] = {
|
|
62
39
|
...(cfg.plugins.entries["usage-tracker"] &&
|
|
63
40
|
typeof cfg.plugins.entries["usage-tracker"] === "object"
|
|
@@ -87,7 +64,6 @@ const ensureUsageTrackerPluginConfig = ({ fsModule, openclawDir }) => {
|
|
|
87
64
|
|
|
88
65
|
module.exports = {
|
|
89
66
|
kUsageTrackerPluginPath,
|
|
90
|
-
isUsageTrackerPluginPath,
|
|
91
67
|
ensurePluginsShell,
|
|
92
68
|
ensurePluginAllowed,
|
|
93
69
|
ensureUsageTrackerPluginEntry,
|