@chrysb/alphaclaw 0.8.7-beta.2 → 0.8.7-beta.4
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 +45 -53
- package/lib/server/alphaclaw-runtime.js +142 -28
- package/lib/server/openclaw-runtime.js +173 -26
- package/lib/server/package-fingerprint.js +274 -0
- package/lib/server/pending-alphaclaw-update.js +16 -1
- package/lib/server/pending-openclaw-update.js +16 -1
- package/lib/server/usage-tracker-config.js +27 -3
- package/package.json +1 -1
package/bin/alphaclaw.js
CHANGED
|
@@ -29,14 +29,10 @@ const {
|
|
|
29
29
|
prependManagedOpenclawBinToPath,
|
|
30
30
|
syncManagedOpenclawRuntimeWithBundled,
|
|
31
31
|
} = require("../lib/server/openclaw-runtime");
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"lib",
|
|
37
|
-
"plugin",
|
|
38
|
-
"usage-tracker",
|
|
39
|
-
);
|
|
32
|
+
const {
|
|
33
|
+
ensurePluginsShell,
|
|
34
|
+
ensureUsageTrackerPluginEntry,
|
|
35
|
+
} = require("../lib/server/usage-tracker-config");
|
|
40
36
|
|
|
41
37
|
// ---------------------------------------------------------------------------
|
|
42
38
|
// Parse CLI flags
|
|
@@ -294,36 +290,6 @@ if (fs.existsSync(pendingUpdateMarker)) {
|
|
|
294
290
|
}
|
|
295
291
|
}
|
|
296
292
|
|
|
297
|
-
const pendingOpenclawUpdateMarker = path.join(rootDir, ".openclaw-update-pending");
|
|
298
|
-
const managedOpenclawRuntimeDir = getManagedOpenclawRuntimeDir({ rootDir });
|
|
299
|
-
if (fs.existsSync(pendingOpenclawUpdateMarker)) {
|
|
300
|
-
applyPendingOpenclawUpdate({
|
|
301
|
-
execSyncImpl: execSync,
|
|
302
|
-
fsModule: fs,
|
|
303
|
-
installDir: managedOpenclawRuntimeDir,
|
|
304
|
-
logger: console,
|
|
305
|
-
markerPath: pendingOpenclawUpdateMarker,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
syncManagedOpenclawRuntimeWithBundled({
|
|
310
|
-
execSyncImpl: execSync,
|
|
311
|
-
fsModule: fs,
|
|
312
|
-
logger: console,
|
|
313
|
-
runtimeDir: managedOpenclawRuntimeDir,
|
|
314
|
-
});
|
|
315
|
-
} catch (error) {
|
|
316
|
-
console.log(
|
|
317
|
-
`[alphaclaw] Could not sync managed OpenClaw runtime from bundled install: ${error.message}`,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
prependManagedOpenclawBinToPath({
|
|
321
|
-
env: process.env,
|
|
322
|
-
fsModule: fs,
|
|
323
|
-
logger: console,
|
|
324
|
-
runtimeDir: managedOpenclawRuntimeDir,
|
|
325
|
-
});
|
|
326
|
-
|
|
327
293
|
// ---------------------------------------------------------------------------
|
|
328
294
|
// 3. Symlink ~/.openclaw -> <root>/.openclaw
|
|
329
295
|
// ---------------------------------------------------------------------------
|
|
@@ -630,7 +596,41 @@ if (!kSetupPassword) {
|
|
|
630
596
|
}
|
|
631
597
|
|
|
632
598
|
// ---------------------------------------------------------------------------
|
|
633
|
-
// 7.
|
|
599
|
+
// 7. Prepare managed OpenClaw runtime
|
|
600
|
+
// ---------------------------------------------------------------------------
|
|
601
|
+
|
|
602
|
+
const pendingOpenclawUpdateMarker = path.join(rootDir, ".openclaw-update-pending");
|
|
603
|
+
const managedOpenclawRuntimeDir = getManagedOpenclawRuntimeDir({ rootDir });
|
|
604
|
+
if (fs.existsSync(pendingOpenclawUpdateMarker)) {
|
|
605
|
+
applyPendingOpenclawUpdate({
|
|
606
|
+
execSyncImpl: execSync,
|
|
607
|
+
fsModule: fs,
|
|
608
|
+
installDir: managedOpenclawRuntimeDir,
|
|
609
|
+
logger: console,
|
|
610
|
+
markerPath: pendingOpenclawUpdateMarker,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
syncManagedOpenclawRuntimeWithBundled({
|
|
615
|
+
execSyncImpl: execSync,
|
|
616
|
+
fsModule: fs,
|
|
617
|
+
logger: console,
|
|
618
|
+
runtimeDir: managedOpenclawRuntimeDir,
|
|
619
|
+
});
|
|
620
|
+
} catch (error) {
|
|
621
|
+
console.log(
|
|
622
|
+
`[alphaclaw] Could not sync managed OpenClaw runtime from bundled install: ${error.message}`,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
prependManagedOpenclawBinToPath({
|
|
626
|
+
env: process.env,
|
|
627
|
+
fsModule: fs,
|
|
628
|
+
logger: console,
|
|
629
|
+
runtimeDir: managedOpenclawRuntimeDir,
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// ---------------------------------------------------------------------------
|
|
633
|
+
// 8. Set OPENCLAW_HOME globally so all child processes inherit it
|
|
634
634
|
// ---------------------------------------------------------------------------
|
|
635
635
|
|
|
636
636
|
process.env.OPENCLAW_HOME = rootDir;
|
|
@@ -639,7 +639,7 @@ process.env.GOG_KEYRING_PASSWORD =
|
|
|
639
639
|
process.env.GOG_KEYRING_PASSWORD || "alphaclaw";
|
|
640
640
|
|
|
641
641
|
// ---------------------------------------------------------------------------
|
|
642
|
-
//
|
|
642
|
+
// 9. Install gog (Google Workspace CLI) if not present
|
|
643
643
|
// ---------------------------------------------------------------------------
|
|
644
644
|
|
|
645
645
|
process.env.XDG_CONFIG_HOME = openclawDir;
|
|
@@ -672,7 +672,7 @@ if (!gogInstalled) {
|
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
// ---------------------------------------------------------------------------
|
|
675
|
-
//
|
|
675
|
+
// 10. Install/reconcile system cron entry
|
|
676
676
|
// ---------------------------------------------------------------------------
|
|
677
677
|
|
|
678
678
|
const packagedHourlyGitSyncPath = path.join(setupDir, "hourly-git-sync.sh");
|
|
@@ -738,7 +738,7 @@ if (fs.existsSync(hourlyGitSyncPath)) {
|
|
|
738
738
|
}
|
|
739
739
|
|
|
740
740
|
// ---------------------------------------------------------------------------
|
|
741
|
-
//
|
|
741
|
+
// 11. Start cron daemon if available
|
|
742
742
|
// ---------------------------------------------------------------------------
|
|
743
743
|
|
|
744
744
|
try {
|
|
@@ -752,7 +752,7 @@ try {
|
|
|
752
752
|
} catch {}
|
|
753
753
|
|
|
754
754
|
// ---------------------------------------------------------------------------
|
|
755
|
-
//
|
|
755
|
+
// 12. Reconcile channels if already onboarded
|
|
756
756
|
// ---------------------------------------------------------------------------
|
|
757
757
|
|
|
758
758
|
const configPath = path.join(openclawDir, "openclaw.json");
|
|
@@ -900,10 +900,7 @@ if (fs.existsSync(configPath)) {
|
|
|
900
900
|
try {
|
|
901
901
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
902
902
|
if (!cfg.channels) cfg.channels = {};
|
|
903
|
-
|
|
904
|
-
if (!cfg.plugins.load) cfg.plugins.load = {};
|
|
905
|
-
if (!Array.isArray(cfg.plugins.load.paths)) cfg.plugins.load.paths = [];
|
|
906
|
-
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
903
|
+
ensurePluginsShell(cfg);
|
|
907
904
|
let changed = false;
|
|
908
905
|
|
|
909
906
|
if (process.env.TELEGRAM_BOT_TOKEN && !cfg.channels.telegram) {
|
|
@@ -929,12 +926,7 @@ if (fs.existsSync(configPath)) {
|
|
|
929
926
|
console.log("[alphaclaw] Discord added");
|
|
930
927
|
changed = true;
|
|
931
928
|
}
|
|
932
|
-
if (
|
|
933
|
-
cfg.plugins.load.paths.push(kUsageTrackerPluginPath);
|
|
934
|
-
changed = true;
|
|
935
|
-
}
|
|
936
|
-
if (cfg.plugins.entries["usage-tracker"]?.enabled !== true) {
|
|
937
|
-
cfg.plugins.entries["usage-tracker"] = { enabled: true };
|
|
929
|
+
if (ensureUsageTrackerPluginEntry(cfg)) {
|
|
938
930
|
changed = true;
|
|
939
931
|
}
|
|
940
932
|
|
|
@@ -3,26 +3,36 @@ const path = require("path");
|
|
|
3
3
|
|
|
4
4
|
const { kRootDir } = require("./constants");
|
|
5
5
|
const { compareVersionParts } = require("./helpers");
|
|
6
|
+
const {
|
|
7
|
+
computePackageFingerprint,
|
|
8
|
+
isPackageRootSymlink,
|
|
9
|
+
packLocalPackageForInstall,
|
|
10
|
+
seedRuntimeFromBundledInstall,
|
|
11
|
+
} = require("./package-fingerprint");
|
|
6
12
|
|
|
7
13
|
const getManagedAlphaclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
|
|
8
14
|
path.join(rootDir, ".alphaclaw-runtime");
|
|
9
15
|
|
|
10
|
-
const
|
|
16
|
+
const getBundledAlphaclawPackageRoot = () => path.resolve(__dirname, "..", "..");
|
|
17
|
+
|
|
18
|
+
const getManagedAlphaclawPackageRoot = ({ runtimeDir } = {}) =>
|
|
11
19
|
path.join(
|
|
12
20
|
runtimeDir || getManagedAlphaclawRuntimeDir(),
|
|
13
21
|
"node_modules",
|
|
14
22
|
"@chrysb",
|
|
15
23
|
"alphaclaw",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const getManagedAlphaclawCliPath = ({ runtimeDir } = {}) =>
|
|
27
|
+
path.join(
|
|
28
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
16
29
|
"bin",
|
|
17
30
|
"alphaclaw.js",
|
|
18
31
|
);
|
|
19
32
|
|
|
20
33
|
const getManagedAlphaclawPackageJsonPath = ({ runtimeDir } = {}) =>
|
|
21
34
|
path.join(
|
|
22
|
-
runtimeDir
|
|
23
|
-
"node_modules",
|
|
24
|
-
"@chrysb",
|
|
25
|
-
"alphaclaw",
|
|
35
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
26
36
|
"package.json",
|
|
27
37
|
);
|
|
28
38
|
|
|
@@ -89,21 +99,40 @@ const installManagedAlphaclawRuntime = ({
|
|
|
89
99
|
fsModule = fs,
|
|
90
100
|
runtimeDir,
|
|
91
101
|
spec,
|
|
102
|
+
sourcePath,
|
|
92
103
|
} = {}) => {
|
|
93
|
-
const
|
|
94
|
-
|
|
104
|
+
const normalizedSourcePath = String(sourcePath || "").trim();
|
|
105
|
+
const normalizedSpec = normalizedSourcePath
|
|
106
|
+
? normalizedSourcePath
|
|
107
|
+
: String(spec || "").trim() || "@chrysb/alphaclaw@latest";
|
|
95
108
|
ensureManagedAlphaclawRuntimeProject({
|
|
96
109
|
fsModule,
|
|
97
110
|
runtimeDir,
|
|
98
111
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
let packedSource = null;
|
|
113
|
+
try {
|
|
114
|
+
const installTarget = normalizedSourcePath
|
|
115
|
+
? (() => {
|
|
116
|
+
packedSource = packLocalPackageForInstall({
|
|
117
|
+
execSyncImpl,
|
|
118
|
+
fsModule,
|
|
119
|
+
packageRoot: normalizedSourcePath,
|
|
120
|
+
tempDirPrefix: "alphaclaw-runtime-pack-",
|
|
121
|
+
});
|
|
122
|
+
return packedSource.tarballPath;
|
|
123
|
+
})()
|
|
124
|
+
: normalizedSpec;
|
|
125
|
+
execSyncImpl(
|
|
126
|
+
`npm install ${shellQuote(installTarget)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
|
|
127
|
+
{
|
|
128
|
+
cwd: runtimeDir,
|
|
129
|
+
stdio: "inherit",
|
|
130
|
+
timeout: 180000,
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
} finally {
|
|
134
|
+
packedSource?.cleanup?.();
|
|
135
|
+
}
|
|
107
136
|
return {
|
|
108
137
|
spec: normalizedSpec,
|
|
109
138
|
version: readManagedAlphaclawRuntimeVersion({
|
|
@@ -113,16 +142,48 @@ const installManagedAlphaclawRuntime = ({
|
|
|
113
142
|
};
|
|
114
143
|
};
|
|
115
144
|
|
|
145
|
+
const seedManagedAlphaclawRuntimeFromBundledInstall = ({
|
|
146
|
+
fsModule = fs,
|
|
147
|
+
logger = console,
|
|
148
|
+
runtimeDir,
|
|
149
|
+
packageRoot,
|
|
150
|
+
} = {}) => {
|
|
151
|
+
const seedResult = seedRuntimeFromBundledInstall({
|
|
152
|
+
fsModule,
|
|
153
|
+
packageRoot,
|
|
154
|
+
runtimeDir,
|
|
155
|
+
runtimePackageJson: {
|
|
156
|
+
name: "alphaclaw-runtime",
|
|
157
|
+
private: true,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
if (!seedResult.seeded) {
|
|
161
|
+
return {
|
|
162
|
+
seeded: false,
|
|
163
|
+
version: null,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
logger.log("[alphaclaw] Seeded managed AlphaClaw runtime from bundled node_modules");
|
|
167
|
+
return {
|
|
168
|
+
seeded: true,
|
|
169
|
+
version: readManagedAlphaclawRuntimeVersion({
|
|
170
|
+
fsModule,
|
|
171
|
+
runtimeDir,
|
|
172
|
+
}),
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
116
176
|
const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
117
177
|
execSyncImpl,
|
|
118
178
|
fsModule = fs,
|
|
119
179
|
logger = console,
|
|
120
180
|
runtimeDir,
|
|
181
|
+
packageRoot = getBundledAlphaclawPackageRoot(),
|
|
121
182
|
packageJsonPath,
|
|
122
183
|
} = {}) => {
|
|
123
184
|
const bundledVersion = readBundledAlphaclawVersion({
|
|
124
185
|
fsModule,
|
|
125
|
-
packageJsonPath,
|
|
186
|
+
packageJsonPath: packageJsonPath || path.join(packageRoot, "package.json"),
|
|
126
187
|
});
|
|
127
188
|
if (!bundledVersion) {
|
|
128
189
|
return {
|
|
@@ -140,25 +201,75 @@ const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
|
140
201
|
fsModule,
|
|
141
202
|
runtimeDir,
|
|
142
203
|
});
|
|
204
|
+
const runtimePackageRoot = getManagedAlphaclawPackageRoot({ runtimeDir });
|
|
205
|
+
const runtimePackageRootIsSymlink = isPackageRootSymlink({
|
|
206
|
+
fsModule,
|
|
207
|
+
packageRoot: runtimePackageRoot,
|
|
208
|
+
});
|
|
209
|
+
const bundledFingerprint = computePackageFingerprint({
|
|
210
|
+
fsModule,
|
|
211
|
+
packageRoot,
|
|
212
|
+
packageJsonPath: packageJsonPath || path.join(packageRoot, "package.json"),
|
|
213
|
+
});
|
|
214
|
+
const runtimeFingerprint = computePackageFingerprint({
|
|
215
|
+
fsModule,
|
|
216
|
+
packageRoot: runtimePackageRoot,
|
|
217
|
+
packageJsonPath: getManagedAlphaclawPackageJsonPath({ runtimeDir }),
|
|
218
|
+
});
|
|
143
219
|
if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
220
|
+
if (
|
|
221
|
+
compareVersionParts(runtimeVersion, bundledVersion) > 0 ||
|
|
222
|
+
(!runtimePackageRootIsSymlink &&
|
|
223
|
+
(!bundledFingerprint || runtimeFingerprint === bundledFingerprint))
|
|
224
|
+
) {
|
|
225
|
+
return {
|
|
226
|
+
checked: true,
|
|
227
|
+
synced: false,
|
|
228
|
+
bundledVersion,
|
|
229
|
+
runtimeVersion,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
logger.log(
|
|
233
|
+
runtimePackageRootIsSymlink
|
|
234
|
+
? `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} is symlinked to the bundled package; refreshing runtime...`
|
|
235
|
+
: `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} differs from bundled ${bundledVersion}; refreshing runtime...`,
|
|
236
|
+
);
|
|
237
|
+
} else {
|
|
238
|
+
logger.log(
|
|
239
|
+
runtimeVersion
|
|
240
|
+
? `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} is older than bundled ${bundledVersion}; syncing runtime...`
|
|
241
|
+
: `[alphaclaw] Managed AlphaClaw runtime missing; seeding bundled AlphaClaw ${bundledVersion}...`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!runtimeVersion) {
|
|
246
|
+
try {
|
|
247
|
+
const seedResult = seedManagedAlphaclawRuntimeFromBundledInstall({
|
|
248
|
+
fsModule,
|
|
249
|
+
logger,
|
|
250
|
+
runtimeDir,
|
|
251
|
+
packageRoot,
|
|
252
|
+
});
|
|
253
|
+
if (seedResult.seeded) {
|
|
254
|
+
return {
|
|
255
|
+
checked: true,
|
|
256
|
+
synced: true,
|
|
257
|
+
bundledVersion,
|
|
258
|
+
runtimeVersion: seedResult.version || bundledVersion,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger.log(
|
|
263
|
+
`[alphaclaw] Could not seed managed AlphaClaw runtime from bundled node_modules: ${error.message}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
150
266
|
}
|
|
151
267
|
|
|
152
|
-
logger.log(
|
|
153
|
-
runtimeVersion
|
|
154
|
-
? `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} is older than bundled ${bundledVersion}; syncing runtime...`
|
|
155
|
-
: `[alphaclaw] Managed AlphaClaw runtime missing; seeding bundled AlphaClaw ${bundledVersion}...`,
|
|
156
|
-
);
|
|
157
268
|
const installResult = installManagedAlphaclawRuntime({
|
|
158
269
|
execSyncImpl,
|
|
159
270
|
fsModule,
|
|
160
271
|
runtimeDir,
|
|
161
|
-
|
|
272
|
+
sourcePath: packageRoot,
|
|
162
273
|
});
|
|
163
274
|
return {
|
|
164
275
|
checked: true,
|
|
@@ -170,11 +281,14 @@ const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
|
170
281
|
|
|
171
282
|
module.exports = {
|
|
172
283
|
ensureManagedAlphaclawRuntimeProject,
|
|
284
|
+
getBundledAlphaclawPackageRoot,
|
|
173
285
|
getManagedAlphaclawCliPath,
|
|
174
286
|
getManagedAlphaclawPackageJsonPath,
|
|
287
|
+
getManagedAlphaclawPackageRoot,
|
|
175
288
|
getManagedAlphaclawRuntimeDir,
|
|
176
289
|
installManagedAlphaclawRuntime,
|
|
177
290
|
readBundledAlphaclawVersion,
|
|
178
291
|
readManagedAlphaclawRuntimeVersion,
|
|
292
|
+
seedManagedAlphaclawRuntimeFromBundledInstall,
|
|
179
293
|
syncManagedAlphaclawRuntimeWithBundled,
|
|
180
294
|
};
|
|
@@ -6,10 +6,33 @@ const {
|
|
|
6
6
|
compareVersionParts,
|
|
7
7
|
normalizeOpenclawVersion,
|
|
8
8
|
} = require("./helpers");
|
|
9
|
+
const {
|
|
10
|
+
computePackageFingerprint,
|
|
11
|
+
isPackageRootSymlink,
|
|
12
|
+
packLocalPackageForInstall,
|
|
13
|
+
resolvePackageRootFromEntryPath,
|
|
14
|
+
seedRuntimeFromBundledInstall,
|
|
15
|
+
} = require("./package-fingerprint");
|
|
9
16
|
|
|
10
17
|
const getManagedOpenclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
|
|
11
18
|
path.join(rootDir, ".openclaw-runtime");
|
|
12
19
|
|
|
20
|
+
const getBundledOpenclawPackageRoot = ({
|
|
21
|
+
fsModule = fs,
|
|
22
|
+
resolveImpl = require.resolve,
|
|
23
|
+
} = {}) =>
|
|
24
|
+
resolvePackageRootFromEntryPath({
|
|
25
|
+
fsModule,
|
|
26
|
+
entryPath: resolveImpl("openclaw"),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const getManagedOpenclawPackageRoot = ({ runtimeDir } = {}) =>
|
|
30
|
+
path.join(
|
|
31
|
+
runtimeDir || getManagedOpenclawRuntimeDir(),
|
|
32
|
+
"node_modules",
|
|
33
|
+
"openclaw",
|
|
34
|
+
);
|
|
35
|
+
|
|
13
36
|
const getManagedOpenclawBinDir = ({ runtimeDir } = {}) =>
|
|
14
37
|
path.join(
|
|
15
38
|
runtimeDir || getManagedOpenclawRuntimeDir(),
|
|
@@ -22,9 +45,7 @@ const getManagedOpenclawBinPath = ({ runtimeDir } = {}) =>
|
|
|
22
45
|
|
|
23
46
|
const getManagedOpenclawPackageJsonPath = ({ runtimeDir } = {}) =>
|
|
24
47
|
path.join(
|
|
25
|
-
runtimeDir
|
|
26
|
-
"node_modules",
|
|
27
|
-
"openclaw",
|
|
48
|
+
getManagedOpenclawPackageRoot({ runtimeDir }),
|
|
28
49
|
"package.json",
|
|
29
50
|
);
|
|
30
51
|
|
|
@@ -76,8 +97,14 @@ const readBundledOpenclawVersion = ({
|
|
|
76
97
|
resolveImpl = require.resolve,
|
|
77
98
|
} = {}) => {
|
|
78
99
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
100
|
+
const packageRoot = getBundledOpenclawPackageRoot({
|
|
101
|
+
fsModule,
|
|
102
|
+
resolveImpl,
|
|
103
|
+
});
|
|
104
|
+
if (!packageRoot) return null;
|
|
105
|
+
const pkg = JSON.parse(
|
|
106
|
+
fsModule.readFileSync(path.join(packageRoot, "package.json"), "utf8"),
|
|
107
|
+
);
|
|
81
108
|
return normalizeOpenclawVersion(pkg?.version || "");
|
|
82
109
|
} catch {
|
|
83
110
|
return null;
|
|
@@ -136,21 +163,41 @@ const installManagedOpenclawRuntime = ({
|
|
|
136
163
|
logger = console,
|
|
137
164
|
runtimeDir,
|
|
138
165
|
spec,
|
|
166
|
+
sourcePath,
|
|
139
167
|
alphaclawRoot,
|
|
140
168
|
} = {}) => {
|
|
141
|
-
const
|
|
169
|
+
const normalizedSourcePath = String(sourcePath || "").trim();
|
|
170
|
+
const normalizedSpec = normalizedSourcePath
|
|
171
|
+
? normalizedSourcePath
|
|
172
|
+
: String(spec || "").trim() || "openclaw@latest";
|
|
142
173
|
ensureManagedOpenclawRuntimeProject({
|
|
143
174
|
fsModule,
|
|
144
175
|
runtimeDir,
|
|
145
176
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
177
|
+
let packedSource = null;
|
|
178
|
+
try {
|
|
179
|
+
const installTarget = normalizedSourcePath
|
|
180
|
+
? (() => {
|
|
181
|
+
packedSource = packLocalPackageForInstall({
|
|
182
|
+
execSyncImpl,
|
|
183
|
+
fsModule,
|
|
184
|
+
packageRoot: normalizedSourcePath,
|
|
185
|
+
tempDirPrefix: "openclaw-runtime-pack-",
|
|
186
|
+
});
|
|
187
|
+
return packedSource.tarballPath;
|
|
188
|
+
})()
|
|
189
|
+
: normalizedSpec;
|
|
190
|
+
execSyncImpl(
|
|
191
|
+
`npm install ${shellQuote(installTarget)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
|
|
192
|
+
{
|
|
193
|
+
cwd: runtimeDir,
|
|
194
|
+
stdio: "inherit",
|
|
195
|
+
timeout: 180000,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
} finally {
|
|
199
|
+
packedSource?.cleanup?.();
|
|
200
|
+
}
|
|
154
201
|
const installedVersion = readManagedOpenclawRuntimeVersion({
|
|
155
202
|
fsModule,
|
|
156
203
|
runtimeDir,
|
|
@@ -169,6 +216,48 @@ const installManagedOpenclawRuntime = ({
|
|
|
169
216
|
};
|
|
170
217
|
};
|
|
171
218
|
|
|
219
|
+
const seedManagedOpenclawRuntimeFromBundledInstall = ({
|
|
220
|
+
execSyncImpl,
|
|
221
|
+
fsModule = fs,
|
|
222
|
+
logger = console,
|
|
223
|
+
runtimeDir,
|
|
224
|
+
bundledPackageRoot,
|
|
225
|
+
alphaclawRoot,
|
|
226
|
+
} = {}) => {
|
|
227
|
+
const seedResult = seedRuntimeFromBundledInstall({
|
|
228
|
+
fsModule,
|
|
229
|
+
packageRoot: bundledPackageRoot,
|
|
230
|
+
runtimeDir,
|
|
231
|
+
runtimePackageJson: {
|
|
232
|
+
name: "alphaclaw-openclaw-runtime",
|
|
233
|
+
private: true,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
if (!seedResult.seeded) {
|
|
237
|
+
return {
|
|
238
|
+
seeded: false,
|
|
239
|
+
version: null,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const installedVersion = readManagedOpenclawRuntimeVersion({
|
|
243
|
+
fsModule,
|
|
244
|
+
runtimeDir,
|
|
245
|
+
});
|
|
246
|
+
applyManagedOpenclawPatch({
|
|
247
|
+
execSyncImpl,
|
|
248
|
+
fsModule,
|
|
249
|
+
logger,
|
|
250
|
+
runtimeDir,
|
|
251
|
+
version: installedVersion,
|
|
252
|
+
alphaclawRoot,
|
|
253
|
+
});
|
|
254
|
+
logger.log("[alphaclaw] Seeded managed OpenClaw runtime from bundled node_modules");
|
|
255
|
+
return {
|
|
256
|
+
seeded: true,
|
|
257
|
+
version: installedVersion,
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
172
261
|
const syncManagedOpenclawRuntimeWithBundled = ({
|
|
173
262
|
execSyncImpl,
|
|
174
263
|
fsModule = fs,
|
|
@@ -177,6 +266,10 @@ const syncManagedOpenclawRuntimeWithBundled = ({
|
|
|
177
266
|
resolveImpl,
|
|
178
267
|
alphaclawRoot,
|
|
179
268
|
} = {}) => {
|
|
269
|
+
const bundledPackageRoot = getBundledOpenclawPackageRoot({
|
|
270
|
+
fsModule,
|
|
271
|
+
resolveImpl,
|
|
272
|
+
});
|
|
180
273
|
const bundledVersion = readBundledOpenclawVersion({
|
|
181
274
|
fsModule,
|
|
182
275
|
resolveImpl,
|
|
@@ -194,26 +287,77 @@ const syncManagedOpenclawRuntimeWithBundled = ({
|
|
|
194
287
|
fsModule,
|
|
195
288
|
runtimeDir,
|
|
196
289
|
});
|
|
290
|
+
const runtimePackageRoot = getManagedOpenclawPackageRoot({ runtimeDir });
|
|
291
|
+
const runtimePackageRootIsSymlink = isPackageRootSymlink({
|
|
292
|
+
fsModule,
|
|
293
|
+
packageRoot: runtimePackageRoot,
|
|
294
|
+
});
|
|
295
|
+
const bundledFingerprint = computePackageFingerprint({
|
|
296
|
+
fsModule,
|
|
297
|
+
packageRoot: bundledPackageRoot,
|
|
298
|
+
});
|
|
299
|
+
const runtimeFingerprint = computePackageFingerprint({
|
|
300
|
+
fsModule,
|
|
301
|
+
packageRoot: runtimePackageRoot,
|
|
302
|
+
packageJsonPath: getManagedOpenclawPackageJsonPath({ runtimeDir }),
|
|
303
|
+
});
|
|
197
304
|
if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
305
|
+
if (
|
|
306
|
+
compareVersionParts(runtimeVersion, bundledVersion) > 0 ||
|
|
307
|
+
(!runtimePackageRootIsSymlink &&
|
|
308
|
+
(!bundledFingerprint || runtimeFingerprint === bundledFingerprint))
|
|
309
|
+
) {
|
|
310
|
+
return {
|
|
311
|
+
checked: true,
|
|
312
|
+
synced: false,
|
|
313
|
+
bundledVersion,
|
|
314
|
+
runtimeVersion,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
logger.log(
|
|
318
|
+
runtimePackageRootIsSymlink
|
|
319
|
+
? `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} is symlinked to the bundled package; refreshing runtime...`
|
|
320
|
+
: `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} differs from bundled ${bundledVersion}; refreshing runtime...`,
|
|
321
|
+
);
|
|
322
|
+
} else {
|
|
323
|
+
logger.log(
|
|
324
|
+
runtimeVersion
|
|
325
|
+
? `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} is older than bundled ${bundledVersion}; syncing runtime...`
|
|
326
|
+
: `[alphaclaw] Managed OpenClaw runtime missing; seeding bundled OpenClaw ${bundledVersion}...`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!runtimeVersion) {
|
|
331
|
+
try {
|
|
332
|
+
const seedResult = seedManagedOpenclawRuntimeFromBundledInstall({
|
|
333
|
+
execSyncImpl,
|
|
334
|
+
fsModule,
|
|
335
|
+
logger,
|
|
336
|
+
runtimeDir,
|
|
337
|
+
bundledPackageRoot,
|
|
338
|
+
alphaclawRoot,
|
|
339
|
+
});
|
|
340
|
+
if (seedResult.seeded) {
|
|
341
|
+
return {
|
|
342
|
+
checked: true,
|
|
343
|
+
synced: true,
|
|
344
|
+
bundledVersion,
|
|
345
|
+
runtimeVersion: seedResult.version || bundledVersion,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
logger.log(
|
|
350
|
+
`[alphaclaw] Could not seed managed OpenClaw runtime from bundled node_modules: ${error.message}`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
204
353
|
}
|
|
205
354
|
|
|
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
355
|
const installResult = installManagedOpenclawRuntime({
|
|
212
356
|
execSyncImpl,
|
|
213
357
|
fsModule,
|
|
214
358
|
logger,
|
|
215
359
|
runtimeDir,
|
|
216
|
-
|
|
360
|
+
sourcePath: bundledPackageRoot,
|
|
217
361
|
alphaclawRoot,
|
|
218
362
|
});
|
|
219
363
|
return {
|
|
@@ -248,13 +392,16 @@ const prependManagedOpenclawBinToPath = ({
|
|
|
248
392
|
module.exports = {
|
|
249
393
|
applyManagedOpenclawPatch,
|
|
250
394
|
ensureManagedOpenclawRuntimeProject,
|
|
395
|
+
getBundledOpenclawPackageRoot,
|
|
251
396
|
getManagedOpenclawBinDir,
|
|
252
397
|
getManagedOpenclawBinPath,
|
|
398
|
+
getManagedOpenclawPackageRoot,
|
|
253
399
|
getManagedOpenclawPackageJsonPath,
|
|
254
400
|
getManagedOpenclawRuntimeDir,
|
|
255
401
|
installManagedOpenclawRuntime,
|
|
256
402
|
prependManagedOpenclawBinToPath,
|
|
257
403
|
readBundledOpenclawVersion,
|
|
258
404
|
readManagedOpenclawRuntimeVersion,
|
|
405
|
+
seedManagedOpenclawRuntimeFromBundledInstall,
|
|
259
406
|
syncManagedOpenclawRuntimeWithBundled,
|
|
260
407
|
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const kIgnoredDirectoryNames = new Set([".git", "node_modules"]);
|
|
7
|
+
|
|
8
|
+
const normalizeRelativePath = (packageRoot, absolutePath) =>
|
|
9
|
+
path.relative(packageRoot, absolutePath).split(path.sep).join("/");
|
|
10
|
+
|
|
11
|
+
const addIncludedPath = ({ includeSet, value }) => {
|
|
12
|
+
const normalizedValue = String(value || "").trim();
|
|
13
|
+
if (!normalizedValue) return;
|
|
14
|
+
includeSet.add(normalizedValue.replace(/\/+$/, ""));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const collectIncludedPaths = ({ packageJson = {} } = {}) => {
|
|
18
|
+
const includeSet = new Set(["package.json"]);
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(packageJson.files)) {
|
|
21
|
+
for (const entry of packageJson.files) {
|
|
22
|
+
addIncludedPath({ includeSet, value: entry });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof packageJson.bin === "string") {
|
|
27
|
+
addIncludedPath({ includeSet, value: packageJson.bin });
|
|
28
|
+
} else if (packageJson.bin && typeof packageJson.bin === "object") {
|
|
29
|
+
for (const entry of Object.values(packageJson.bin)) {
|
|
30
|
+
addIncludedPath({ includeSet, value: entry });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Array.from(includeSet).sort((left, right) => left.localeCompare(right));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const walkIncludedFiles = ({
|
|
38
|
+
fsModule = fs,
|
|
39
|
+
packageRoot,
|
|
40
|
+
absolutePath,
|
|
41
|
+
files,
|
|
42
|
+
}) => {
|
|
43
|
+
if (!fsModule.existsSync(absolutePath)) return;
|
|
44
|
+
const relativePath = normalizeRelativePath(packageRoot, absolutePath);
|
|
45
|
+
if (!relativePath || relativePath.startsWith("..")) return;
|
|
46
|
+
|
|
47
|
+
const stat = fsModule.lstatSync(absolutePath);
|
|
48
|
+
if (stat.isSymbolicLink()) {
|
|
49
|
+
files.push({
|
|
50
|
+
relativePath,
|
|
51
|
+
hash: `symlink:${fsModule.readlinkSync(absolutePath)}`,
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (stat.isFile()) {
|
|
56
|
+
files.push({
|
|
57
|
+
relativePath,
|
|
58
|
+
hash: crypto
|
|
59
|
+
.createHash("sha256")
|
|
60
|
+
.update(fsModule.readFileSync(absolutePath))
|
|
61
|
+
.digest("hex"),
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!stat.isDirectory()) return;
|
|
66
|
+
|
|
67
|
+
const entries = fsModule
|
|
68
|
+
.readdirSync(absolutePath, { withFileTypes: true })
|
|
69
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
70
|
+
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.isDirectory() && kIgnoredDirectoryNames.has(entry.name)) continue;
|
|
73
|
+
walkIncludedFiles({
|
|
74
|
+
fsModule,
|
|
75
|
+
packageRoot,
|
|
76
|
+
absolutePath: path.join(absolutePath, entry.name),
|
|
77
|
+
files,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const computePackageFingerprint = ({
|
|
83
|
+
fsModule = fs,
|
|
84
|
+
packageRoot,
|
|
85
|
+
packageJsonPath = path.join(packageRoot, "package.json"),
|
|
86
|
+
} = {}) => {
|
|
87
|
+
const resolvedPackageRoot = path.resolve(String(packageRoot || ""));
|
|
88
|
+
if (!resolvedPackageRoot || !fsModule.existsSync(packageJsonPath)) return null;
|
|
89
|
+
|
|
90
|
+
let packageJson;
|
|
91
|
+
try {
|
|
92
|
+
packageJson = JSON.parse(fsModule.readFileSync(packageJsonPath, "utf8"));
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const files = [];
|
|
98
|
+
for (const includePath of collectIncludedPaths({ packageJson })) {
|
|
99
|
+
walkIncludedFiles({
|
|
100
|
+
fsModule,
|
|
101
|
+
packageRoot: resolvedPackageRoot,
|
|
102
|
+
absolutePath: path.resolve(resolvedPackageRoot, includePath),
|
|
103
|
+
files,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hash = crypto.createHash("sha256");
|
|
108
|
+
hash.update("package-fingerprint-v1");
|
|
109
|
+
for (const entry of files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))) {
|
|
110
|
+
hash.update(entry.relativePath);
|
|
111
|
+
hash.update("\0");
|
|
112
|
+
hash.update(entry.hash);
|
|
113
|
+
hash.update("\0");
|
|
114
|
+
}
|
|
115
|
+
return hash.digest("hex");
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const isPackageRootSymlink = ({
|
|
119
|
+
fsModule = fs,
|
|
120
|
+
packageRoot,
|
|
121
|
+
} = {}) => {
|
|
122
|
+
const resolvedPackageRoot = path.resolve(String(packageRoot || ""));
|
|
123
|
+
if (!resolvedPackageRoot || !fsModule.existsSync(resolvedPackageRoot)) return false;
|
|
124
|
+
try {
|
|
125
|
+
return fsModule.lstatSync(resolvedPackageRoot).isSymbolicLink();
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const resolvePackageRootFromEntryPath = ({
|
|
132
|
+
fsModule = fs,
|
|
133
|
+
entryPath,
|
|
134
|
+
} = {}) => {
|
|
135
|
+
let cursor = path.dirname(path.resolve(String(entryPath || "")));
|
|
136
|
+
while (cursor && cursor !== path.dirname(cursor)) {
|
|
137
|
+
if (fsModule.existsSync(path.join(cursor, "package.json"))) {
|
|
138
|
+
return cursor;
|
|
139
|
+
}
|
|
140
|
+
cursor = path.dirname(cursor);
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const resolveInstallRootFromPackageRoot = ({ packageRoot } = {}) => {
|
|
146
|
+
const resolvedPackageRoot = path.resolve(String(packageRoot || ""));
|
|
147
|
+
if (!resolvedPackageRoot) return "";
|
|
148
|
+
const nodeModulesSegment = `${path.sep}node_modules${path.sep}`;
|
|
149
|
+
const nodeModulesIndex = resolvedPackageRoot.lastIndexOf(nodeModulesSegment);
|
|
150
|
+
if (nodeModulesIndex < 0) {
|
|
151
|
+
return resolvedPackageRoot;
|
|
152
|
+
}
|
|
153
|
+
return resolvedPackageRoot.slice(0, nodeModulesIndex);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const seedRuntimeFromBundledInstall = ({
|
|
157
|
+
fsModule = fs,
|
|
158
|
+
packageRoot,
|
|
159
|
+
runtimeDir,
|
|
160
|
+
runtimePackageJson,
|
|
161
|
+
} = {}) => {
|
|
162
|
+
const installRoot = resolveInstallRootFromPackageRoot({ packageRoot });
|
|
163
|
+
const bundledNodeModulesPath = path.join(installRoot, "node_modules");
|
|
164
|
+
if (!installRoot || !fsModule.existsSync(bundledNodeModulesPath)) {
|
|
165
|
+
return {
|
|
166
|
+
seeded: false,
|
|
167
|
+
installRoot,
|
|
168
|
+
bundledNodeModulesPath,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const resolvedRuntimeDir = path.resolve(String(runtimeDir || ""));
|
|
173
|
+
const runtimeParentDir = path.dirname(resolvedRuntimeDir);
|
|
174
|
+
fsModule.mkdirSync(runtimeParentDir, { recursive: true });
|
|
175
|
+
const tempRuntimeDir = fsModule.mkdtempSync(
|
|
176
|
+
path.join(runtimeParentDir, `${path.basename(resolvedRuntimeDir)}-seed-`),
|
|
177
|
+
);
|
|
178
|
+
let seeded = false;
|
|
179
|
+
try {
|
|
180
|
+
if (runtimePackageJson) {
|
|
181
|
+
fsModule.writeFileSync(
|
|
182
|
+
path.join(tempRuntimeDir, "package.json"),
|
|
183
|
+
JSON.stringify(runtimePackageJson, null, 2),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
fsModule.cpSync(
|
|
187
|
+
bundledNodeModulesPath,
|
|
188
|
+
path.join(tempRuntimeDir, "node_modules"),
|
|
189
|
+
{
|
|
190
|
+
recursive: true,
|
|
191
|
+
dereference: true,
|
|
192
|
+
preserveTimestamps: true,
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
try {
|
|
196
|
+
fsModule.rmSync(resolvedRuntimeDir, { recursive: true, force: true });
|
|
197
|
+
} catch {}
|
|
198
|
+
fsModule.renameSync(tempRuntimeDir, resolvedRuntimeDir);
|
|
199
|
+
seeded = true;
|
|
200
|
+
return {
|
|
201
|
+
seeded: true,
|
|
202
|
+
installRoot,
|
|
203
|
+
bundledNodeModulesPath,
|
|
204
|
+
runtimeDir: resolvedRuntimeDir,
|
|
205
|
+
};
|
|
206
|
+
} finally {
|
|
207
|
+
if (!seeded) {
|
|
208
|
+
try {
|
|
209
|
+
fsModule.rmSync(tempRuntimeDir, { recursive: true, force: true });
|
|
210
|
+
} catch {}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const packLocalPackageForInstall = ({
|
|
216
|
+
execSyncImpl,
|
|
217
|
+
fsModule = fs,
|
|
218
|
+
packageRoot,
|
|
219
|
+
tempDirPrefix = "alphaclaw-package-pack-",
|
|
220
|
+
} = {}) => {
|
|
221
|
+
const resolvedPackageRoot = path.resolve(String(packageRoot || ""));
|
|
222
|
+
const packDir = fsModule.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix));
|
|
223
|
+
try {
|
|
224
|
+
const packStdout = String(
|
|
225
|
+
execSyncImpl(
|
|
226
|
+
`npm pack ${shellQuote(resolvedPackageRoot)} --quiet --ignore-scripts --pack-destination ${shellQuote(packDir)}`,
|
|
227
|
+
{
|
|
228
|
+
encoding: "utf8",
|
|
229
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
230
|
+
timeout: 180000,
|
|
231
|
+
},
|
|
232
|
+
) || "",
|
|
233
|
+
)
|
|
234
|
+
.trim()
|
|
235
|
+
.split(/\r?\n/)
|
|
236
|
+
.map((entry) => entry.trim())
|
|
237
|
+
.filter(Boolean);
|
|
238
|
+
const packFileName =
|
|
239
|
+
packStdout.at(-1) ||
|
|
240
|
+
fsModule.readdirSync(packDir).find((entry) => entry.endsWith(".tgz"));
|
|
241
|
+
if (!packFileName) {
|
|
242
|
+
throw new Error(`npm pack did not produce a tarball for ${resolvedPackageRoot}`);
|
|
243
|
+
}
|
|
244
|
+
const tarballPath = path.join(packDir, packFileName);
|
|
245
|
+
if (!fsModule.existsSync(tarballPath)) {
|
|
246
|
+
throw new Error(`Packed tarball missing at ${tarballPath}`);
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
tarballPath,
|
|
250
|
+
cleanup: () => {
|
|
251
|
+
try {
|
|
252
|
+
fsModule.rmSync(packDir, { recursive: true, force: true });
|
|
253
|
+
} catch {}
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
} catch (error) {
|
|
257
|
+
try {
|
|
258
|
+
fsModule.rmSync(packDir, { recursive: true, force: true });
|
|
259
|
+
} catch {}
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const shellQuote = (value) =>
|
|
265
|
+
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
computePackageFingerprint,
|
|
269
|
+
isPackageRootSymlink,
|
|
270
|
+
packLocalPackageForInstall,
|
|
271
|
+
resolveInstallRootFromPackageRoot,
|
|
272
|
+
resolvePackageRootFromEntryPath,
|
|
273
|
+
seedRuntimeFromBundledInstall,
|
|
274
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
1
3
|
const {
|
|
2
4
|
installManagedAlphaclawRuntime,
|
|
3
5
|
} = require("./alphaclaw-runtime");
|
|
@@ -36,13 +38,23 @@ const applyPendingAlphaclawUpdate = ({
|
|
|
36
38
|
const spec = buildPendingAlphaclawInstallSpec(marker);
|
|
37
39
|
logger.log(`[alphaclaw] Pending update detected, installing ${spec}...`);
|
|
38
40
|
|
|
41
|
+
const resolvedInstallDir = path.resolve(String(installDir || ""));
|
|
42
|
+
const installParentDir = path.dirname(resolvedInstallDir);
|
|
43
|
+
const tempInstallDir = fsModule.mkdtempSync(
|
|
44
|
+
path.join(installParentDir, `${path.basename(resolvedInstallDir)}-pending-`),
|
|
45
|
+
);
|
|
46
|
+
|
|
39
47
|
try {
|
|
40
48
|
installManagedAlphaclawRuntime({
|
|
41
49
|
execSyncImpl,
|
|
42
50
|
fsModule,
|
|
43
|
-
runtimeDir:
|
|
51
|
+
runtimeDir: tempInstallDir,
|
|
44
52
|
spec,
|
|
45
53
|
});
|
|
54
|
+
try {
|
|
55
|
+
fsModule.rmSync(resolvedInstallDir, { recursive: true, force: true });
|
|
56
|
+
} catch {}
|
|
57
|
+
fsModule.renameSync(tempInstallDir, resolvedInstallDir);
|
|
46
58
|
fsModule.unlinkSync(markerPath);
|
|
47
59
|
logger.log("[alphaclaw] Update applied successfully");
|
|
48
60
|
return {
|
|
@@ -52,6 +64,9 @@ const applyPendingAlphaclawUpdate = ({
|
|
|
52
64
|
};
|
|
53
65
|
} catch (error) {
|
|
54
66
|
logger.log(`[alphaclaw] Update install failed: ${error.message}`);
|
|
67
|
+
try {
|
|
68
|
+
fsModule.rmSync(tempInstallDir, { recursive: true, force: true });
|
|
69
|
+
} catch {}
|
|
55
70
|
try {
|
|
56
71
|
fsModule.unlinkSync(markerPath);
|
|
57
72
|
} catch {}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
1
3
|
const {
|
|
2
4
|
installManagedOpenclawRuntime,
|
|
3
5
|
} = require("./openclaw-runtime");
|
|
@@ -36,14 +38,24 @@ const applyPendingOpenclawUpdate = ({
|
|
|
36
38
|
const spec = buildPendingOpenclawInstallSpec(marker);
|
|
37
39
|
logger.log(`[alphaclaw] Pending OpenClaw update detected, installing ${spec}...`);
|
|
38
40
|
|
|
41
|
+
const resolvedInstallDir = path.resolve(String(installDir || ""));
|
|
42
|
+
const installParentDir = path.dirname(resolvedInstallDir);
|
|
43
|
+
const tempInstallDir = fsModule.mkdtempSync(
|
|
44
|
+
path.join(installParentDir, `${path.basename(resolvedInstallDir)}-pending-`),
|
|
45
|
+
);
|
|
46
|
+
|
|
39
47
|
try {
|
|
40
48
|
installManagedOpenclawRuntime({
|
|
41
49
|
execSyncImpl,
|
|
42
50
|
fsModule,
|
|
43
51
|
logger,
|
|
44
|
-
runtimeDir:
|
|
52
|
+
runtimeDir: tempInstallDir,
|
|
45
53
|
spec,
|
|
46
54
|
});
|
|
55
|
+
try {
|
|
56
|
+
fsModule.rmSync(resolvedInstallDir, { recursive: true, force: true });
|
|
57
|
+
} catch {}
|
|
58
|
+
fsModule.renameSync(tempInstallDir, resolvedInstallDir);
|
|
47
59
|
fsModule.unlinkSync(markerPath);
|
|
48
60
|
logger.log("[alphaclaw] OpenClaw update applied successfully");
|
|
49
61
|
return {
|
|
@@ -53,6 +65,9 @@ const applyPendingOpenclawUpdate = ({
|
|
|
53
65
|
};
|
|
54
66
|
} catch (error) {
|
|
55
67
|
logger.log(`[alphaclaw] OpenClaw update install failed: ${error.message}`);
|
|
68
|
+
try {
|
|
69
|
+
fsModule.rmSync(tempInstallDir, { recursive: true, force: true });
|
|
70
|
+
} catch {}
|
|
56
71
|
try {
|
|
57
72
|
fsModule.unlinkSync(markerPath);
|
|
58
73
|
} catch {}
|
|
@@ -8,6 +8,27 @@ 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
|
+
|
|
11
32
|
const ensurePluginsShell = (cfg = {}) => {
|
|
12
33
|
if (!cfg.plugins || typeof cfg.plugins !== "object") cfg.plugins = {};
|
|
13
34
|
if (!Array.isArray(cfg.plugins.allow)) cfg.plugins.allow = [];
|
|
@@ -32,9 +53,11 @@ const ensurePluginAllowed = ({ cfg = {}, pluginKey = "" }) => {
|
|
|
32
53
|
const ensureUsageTrackerPluginEntry = (cfg = {}) => {
|
|
33
54
|
const before = JSON.stringify(cfg);
|
|
34
55
|
ensurePluginAllowed({ cfg, pluginKey: "usage-tracker" });
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
const nextPaths = cfg.plugins.load.paths.filter(
|
|
57
|
+
(entry) => !isUsageTrackerPluginPath(entry),
|
|
58
|
+
);
|
|
59
|
+
nextPaths.push(kUsageTrackerPluginPath);
|
|
60
|
+
cfg.plugins.load.paths = nextPaths;
|
|
38
61
|
cfg.plugins.entries["usage-tracker"] = {
|
|
39
62
|
...(cfg.plugins.entries["usage-tracker"] &&
|
|
40
63
|
typeof cfg.plugins.entries["usage-tracker"] === "object"
|
|
@@ -64,6 +87,7 @@ const ensureUsageTrackerPluginConfig = ({ fsModule, openclawDir }) => {
|
|
|
64
87
|
|
|
65
88
|
module.exports = {
|
|
66
89
|
kUsageTrackerPluginPath,
|
|
90
|
+
isUsageTrackerPluginPath,
|
|
67
91
|
ensurePluginsShell,
|
|
68
92
|
ensurePluginAllowed,
|
|
69
93
|
ensureUsageTrackerPluginEntry,
|