@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.
- package/bin/alphaclaw.js +74 -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-runtime.js +260 -0
- 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
|
|
|
@@ -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 {
|
|
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) {
|