@chrysb/alphaclaw 0.8.7-beta.4 → 0.8.7

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.
Files changed (29) hide show
  1. package/bin/alphaclaw.js +43 -174
  2. package/lib/public/css/tailwind.generated.css +1 -1
  3. package/lib/public/dist/app.bundle.js +2021 -2041
  4. package/lib/public/js/app.js +0 -3
  5. package/lib/public/js/components/gateway.js +3 -6
  6. package/lib/public/js/components/general/index.js +0 -2
  7. package/lib/public/js/components/onboarding/welcome-form-step.js +4 -29
  8. package/lib/public/js/components/routes/general-route.js +0 -2
  9. package/lib/public/js/components/routes/watchdog-route.js +0 -2
  10. package/lib/public/js/components/sidebar.js +7 -20
  11. package/lib/public/js/components/update-modal.js +1 -2
  12. package/lib/public/js/components/watchdog-tab/index.js +0 -2
  13. package/lib/public/js/components/welcome/index.js +0 -1
  14. package/lib/public/js/components/welcome/use-welcome.js +2 -52
  15. package/lib/public/js/hooks/use-app-shell-controller.js +9 -33
  16. package/lib/public/js/lib/api.js +0 -35
  17. package/lib/server/alphaclaw-version.js +128 -37
  18. package/lib/server/openclaw-version.js +130 -59
  19. package/lib/server/routes/system.js +1 -6
  20. package/lib/server/usage-tracker-config.js +3 -27
  21. package/package.json +2 -2
  22. package/patches/openclaw+2026.4.5.patch +13 -0
  23. package/lib/public/js/components/update-modal-helpers.js +0 -12
  24. package/lib/server/alphaclaw-runtime.js +0 -294
  25. package/lib/server/openclaw-runtime.js +0 -407
  26. package/lib/server/package-fingerprint.js +0 -274
  27. package/lib/server/pending-alphaclaw-update.js +0 -85
  28. package/lib/server/pending-openclaw-update.js +0 -86
  29. package/patches/openclaw+2026.4.1.patch +0 -13
@@ -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 nextEnv = { ...process.env };
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
- let targetVersion = "latest";
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
- const updateStatus = await readAlphaclawUpdateStatus({ refresh: true });
175
- if (updateStatus.latestVersion) {
176
- targetVersion = updateStatus.latestVersion;
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
 
@@ -1,10 +1,12 @@
1
- const { execSync } = require("child_process");
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
- kRootDir,
8
+ kNpmPackageRoot,
9
+ kOpenclawUpdateCopyTimeoutMs,
8
10
  } = require("./constants");
9
11
  const { normalizeOpenclawVersion } = require("./helpers");
10
12
  const { parseJsonObjectFromNoisyOutput } = require("./utils/json");
@@ -22,9 +24,6 @@ const createOpenclawVersionService = ({
22
24
  };
23
25
  let kOpenclawUpdateInProgress = false;
24
26
 
25
- const buildOpenclawInstallSpec = (version = "latest") =>
26
- `openclaw@${String(version || "").trim() || "latest"}`;
27
-
28
27
  const readOpenclawVersion = () => {
29
28
  const now = Date.now();
30
29
  if (
@@ -88,6 +87,118 @@ const createOpenclawVersionService = ({
88
87
  }
89
88
  };
90
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
+
91
202
  const getVersionStatus = async (refresh) => {
92
203
  const currentVersion = readOpenclawVersion();
93
204
  try {
@@ -117,67 +228,27 @@ const createOpenclawVersionService = ({
117
228
  kOpenclawUpdateInProgress = true;
118
229
  const previousVersion = readOpenclawVersion();
119
230
  try {
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
- );
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
- };
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;
146
241
  }
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
- };
169
242
  return {
170
243
  status: 200,
171
244
  body: {
172
245
  ok: true,
173
246
  previousVersion,
174
- currentVersion: previousVersion,
175
- targetVersion: targetVersion === "latest" ? null : targetVersion,
247
+ currentVersion,
176
248
  latestVersion,
177
- hasUpdate: true,
178
- restarted: false,
179
- restarting: true,
180
- updated: previousVersion !== targetVersion,
249
+ hasUpdate,
250
+ restarted,
251
+ updated: previousVersion !== currentVersion,
181
252
  },
182
253
  };
183
254
  } catch (err) {
@@ -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
- 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
- }
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
- const nextPaths = cfg.plugins.load.paths.filter(
57
- (entry) => !isUsageTrackerPluginPath(entry),
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.8.7-beta.4",
3
+ "version": "0.8.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "express": "^4.21.0",
38
38
  "http-proxy": "^1.18.1",
39
- "openclaw": "2026.4.1",
39
+ "openclaw": "2026.4.5",
40
40
  "patch-package": "^8.0.1",
41
41
  "ws": "^8.19.0"
42
42
  },
@@ -0,0 +1,13 @@
1
+ diff --git a/node_modules/openclaw/dist/server-Cv5hzFG4.js b/node_modules/openclaw/dist/server-Cv5hzFG4.js
2
+ index 926102f391c94b0e8bb52c2d322c107636e267f3..9e04644fcb8cc406d9b50f2590c5e12788cb7b6e 100644
3
+ --- a/node_modules/openclaw/dist/server-Cv5hzFG4.js
4
+ +++ b/node_modules/openclaw/dist/server-Cv5hzFG4.js
5
+ @@ -26710,7 +26710,7 @@
6
+ close(1008, truncateCloseReason(authMessage));
7
+ };
8
+ const clearUnboundScopes = () => {
9
+ - if (scopes.length > 0) {
10
+ + if (scopes.length > 0 && !sharedAuthOk) {
11
+ scopes = [];
12
+ connectParams.scopes = scopes;
13
+ }
@@ -1,12 +0,0 @@
1
- export const createUpdateModalSubmitHandler = ({
2
- onClose = () => {},
3
- onUpdate = async () => ({ ok: false }),
4
- }) => {
5
- return async () => {
6
- const result = await onUpdate();
7
- if (result?.ok) {
8
- onClose();
9
- }
10
- return result;
11
- };
12
- };