@chrysb/alphaclaw 0.8.7-beta.2 → 0.8.7-beta.3
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 +86 -28
- package/lib/server/openclaw-runtime.js +104 -26
- package/lib/server/package-fingerprint.js +202 -0
- 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,35 @@ 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
|
+
} = require("./package-fingerprint");
|
|
6
11
|
|
|
7
12
|
const getManagedAlphaclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
|
|
8
13
|
path.join(rootDir, ".alphaclaw-runtime");
|
|
9
14
|
|
|
10
|
-
const
|
|
15
|
+
const getBundledAlphaclawPackageRoot = () => path.resolve(__dirname, "..", "..");
|
|
16
|
+
|
|
17
|
+
const getManagedAlphaclawPackageRoot = ({ runtimeDir } = {}) =>
|
|
11
18
|
path.join(
|
|
12
19
|
runtimeDir || getManagedAlphaclawRuntimeDir(),
|
|
13
20
|
"node_modules",
|
|
14
21
|
"@chrysb",
|
|
15
22
|
"alphaclaw",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const getManagedAlphaclawCliPath = ({ runtimeDir } = {}) =>
|
|
26
|
+
path.join(
|
|
27
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
16
28
|
"bin",
|
|
17
29
|
"alphaclaw.js",
|
|
18
30
|
);
|
|
19
31
|
|
|
20
32
|
const getManagedAlphaclawPackageJsonPath = ({ runtimeDir } = {}) =>
|
|
21
33
|
path.join(
|
|
22
|
-
runtimeDir
|
|
23
|
-
"node_modules",
|
|
24
|
-
"@chrysb",
|
|
25
|
-
"alphaclaw",
|
|
34
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
26
35
|
"package.json",
|
|
27
36
|
);
|
|
28
37
|
|
|
@@ -89,21 +98,40 @@ const installManagedAlphaclawRuntime = ({
|
|
|
89
98
|
fsModule = fs,
|
|
90
99
|
runtimeDir,
|
|
91
100
|
spec,
|
|
101
|
+
sourcePath,
|
|
92
102
|
} = {}) => {
|
|
93
|
-
const
|
|
94
|
-
|
|
103
|
+
const normalizedSourcePath = String(sourcePath || "").trim();
|
|
104
|
+
const normalizedSpec = normalizedSourcePath
|
|
105
|
+
? normalizedSourcePath
|
|
106
|
+
: String(spec || "").trim() || "@chrysb/alphaclaw@latest";
|
|
95
107
|
ensureManagedAlphaclawRuntimeProject({
|
|
96
108
|
fsModule,
|
|
97
109
|
runtimeDir,
|
|
98
110
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
let packedSource = null;
|
|
112
|
+
try {
|
|
113
|
+
const installTarget = normalizedSourcePath
|
|
114
|
+
? (() => {
|
|
115
|
+
packedSource = packLocalPackageForInstall({
|
|
116
|
+
execSyncImpl,
|
|
117
|
+
fsModule,
|
|
118
|
+
packageRoot: normalizedSourcePath,
|
|
119
|
+
tempDirPrefix: "alphaclaw-runtime-pack-",
|
|
120
|
+
});
|
|
121
|
+
return packedSource.tarballPath;
|
|
122
|
+
})()
|
|
123
|
+
: normalizedSpec;
|
|
124
|
+
execSyncImpl(
|
|
125
|
+
`npm install ${shellQuote(installTarget)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
|
|
126
|
+
{
|
|
127
|
+
cwd: runtimeDir,
|
|
128
|
+
stdio: "inherit",
|
|
129
|
+
timeout: 180000,
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
} finally {
|
|
133
|
+
packedSource?.cleanup?.();
|
|
134
|
+
}
|
|
107
135
|
return {
|
|
108
136
|
spec: normalizedSpec,
|
|
109
137
|
version: readManagedAlphaclawRuntimeVersion({
|
|
@@ -118,11 +146,12 @@ const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
|
118
146
|
fsModule = fs,
|
|
119
147
|
logger = console,
|
|
120
148
|
runtimeDir,
|
|
149
|
+
packageRoot = getBundledAlphaclawPackageRoot(),
|
|
121
150
|
packageJsonPath,
|
|
122
151
|
} = {}) => {
|
|
123
152
|
const bundledVersion = readBundledAlphaclawVersion({
|
|
124
153
|
fsModule,
|
|
125
|
-
packageJsonPath,
|
|
154
|
+
packageJsonPath: packageJsonPath || path.join(packageRoot, "package.json"),
|
|
126
155
|
});
|
|
127
156
|
if (!bundledVersion) {
|
|
128
157
|
return {
|
|
@@ -140,25 +169,52 @@ const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
|
140
169
|
fsModule,
|
|
141
170
|
runtimeDir,
|
|
142
171
|
});
|
|
172
|
+
const runtimePackageRoot = getManagedAlphaclawPackageRoot({ runtimeDir });
|
|
173
|
+
const runtimePackageRootIsSymlink = isPackageRootSymlink({
|
|
174
|
+
fsModule,
|
|
175
|
+
packageRoot: runtimePackageRoot,
|
|
176
|
+
});
|
|
177
|
+
const bundledFingerprint = computePackageFingerprint({
|
|
178
|
+
fsModule,
|
|
179
|
+
packageRoot,
|
|
180
|
+
packageJsonPath: packageJsonPath || path.join(packageRoot, "package.json"),
|
|
181
|
+
});
|
|
182
|
+
const runtimeFingerprint = computePackageFingerprint({
|
|
183
|
+
fsModule,
|
|
184
|
+
packageRoot: runtimePackageRoot,
|
|
185
|
+
packageJsonPath: getManagedAlphaclawPackageJsonPath({ runtimeDir }),
|
|
186
|
+
});
|
|
143
187
|
if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
188
|
+
if (
|
|
189
|
+
compareVersionParts(runtimeVersion, bundledVersion) > 0 ||
|
|
190
|
+
(!runtimePackageRootIsSymlink &&
|
|
191
|
+
(!bundledFingerprint || runtimeFingerprint === bundledFingerprint))
|
|
192
|
+
) {
|
|
193
|
+
return {
|
|
194
|
+
checked: true,
|
|
195
|
+
synced: false,
|
|
196
|
+
bundledVersion,
|
|
197
|
+
runtimeVersion,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
logger.log(
|
|
201
|
+
runtimePackageRootIsSymlink
|
|
202
|
+
? `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} is symlinked to the bundled package; refreshing runtime...`
|
|
203
|
+
: `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} differs from bundled ${bundledVersion}; refreshing runtime...`,
|
|
204
|
+
);
|
|
205
|
+
} else {
|
|
206
|
+
logger.log(
|
|
207
|
+
runtimeVersion
|
|
208
|
+
? `[alphaclaw] Managed AlphaClaw runtime ${runtimeVersion} is older than bundled ${bundledVersion}; syncing runtime...`
|
|
209
|
+
: `[alphaclaw] Managed AlphaClaw runtime missing; seeding bundled AlphaClaw ${bundledVersion}...`,
|
|
210
|
+
);
|
|
150
211
|
}
|
|
151
212
|
|
|
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
213
|
const installResult = installManagedAlphaclawRuntime({
|
|
158
214
|
execSyncImpl,
|
|
159
215
|
fsModule,
|
|
160
216
|
runtimeDir,
|
|
161
|
-
|
|
217
|
+
sourcePath: packageRoot,
|
|
162
218
|
});
|
|
163
219
|
return {
|
|
164
220
|
checked: true,
|
|
@@ -170,8 +226,10 @@ const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
|
170
226
|
|
|
171
227
|
module.exports = {
|
|
172
228
|
ensureManagedAlphaclawRuntimeProject,
|
|
229
|
+
getBundledAlphaclawPackageRoot,
|
|
173
230
|
getManagedAlphaclawCliPath,
|
|
174
231
|
getManagedAlphaclawPackageJsonPath,
|
|
232
|
+
getManagedAlphaclawPackageRoot,
|
|
175
233
|
getManagedAlphaclawRuntimeDir,
|
|
176
234
|
installManagedAlphaclawRuntime,
|
|
177
235
|
readBundledAlphaclawVersion,
|
|
@@ -6,10 +6,32 @@ const {
|
|
|
6
6
|
compareVersionParts,
|
|
7
7
|
normalizeOpenclawVersion,
|
|
8
8
|
} = require("./helpers");
|
|
9
|
+
const {
|
|
10
|
+
computePackageFingerprint,
|
|
11
|
+
isPackageRootSymlink,
|
|
12
|
+
packLocalPackageForInstall,
|
|
13
|
+
resolvePackageRootFromEntryPath,
|
|
14
|
+
} = require("./package-fingerprint");
|
|
9
15
|
|
|
10
16
|
const getManagedOpenclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
|
|
11
17
|
path.join(rootDir, ".openclaw-runtime");
|
|
12
18
|
|
|
19
|
+
const getBundledOpenclawPackageRoot = ({
|
|
20
|
+
fsModule = fs,
|
|
21
|
+
resolveImpl = require.resolve,
|
|
22
|
+
} = {}) =>
|
|
23
|
+
resolvePackageRootFromEntryPath({
|
|
24
|
+
fsModule,
|
|
25
|
+
entryPath: resolveImpl("openclaw"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const getManagedOpenclawPackageRoot = ({ runtimeDir } = {}) =>
|
|
29
|
+
path.join(
|
|
30
|
+
runtimeDir || getManagedOpenclawRuntimeDir(),
|
|
31
|
+
"node_modules",
|
|
32
|
+
"openclaw",
|
|
33
|
+
);
|
|
34
|
+
|
|
13
35
|
const getManagedOpenclawBinDir = ({ runtimeDir } = {}) =>
|
|
14
36
|
path.join(
|
|
15
37
|
runtimeDir || getManagedOpenclawRuntimeDir(),
|
|
@@ -22,9 +44,7 @@ const getManagedOpenclawBinPath = ({ runtimeDir } = {}) =>
|
|
|
22
44
|
|
|
23
45
|
const getManagedOpenclawPackageJsonPath = ({ runtimeDir } = {}) =>
|
|
24
46
|
path.join(
|
|
25
|
-
runtimeDir
|
|
26
|
-
"node_modules",
|
|
27
|
-
"openclaw",
|
|
47
|
+
getManagedOpenclawPackageRoot({ runtimeDir }),
|
|
28
48
|
"package.json",
|
|
29
49
|
);
|
|
30
50
|
|
|
@@ -76,8 +96,14 @@ const readBundledOpenclawVersion = ({
|
|
|
76
96
|
resolveImpl = require.resolve,
|
|
77
97
|
} = {}) => {
|
|
78
98
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
99
|
+
const packageRoot = getBundledOpenclawPackageRoot({
|
|
100
|
+
fsModule,
|
|
101
|
+
resolveImpl,
|
|
102
|
+
});
|
|
103
|
+
if (!packageRoot) return null;
|
|
104
|
+
const pkg = JSON.parse(
|
|
105
|
+
fsModule.readFileSync(path.join(packageRoot, "package.json"), "utf8"),
|
|
106
|
+
);
|
|
81
107
|
return normalizeOpenclawVersion(pkg?.version || "");
|
|
82
108
|
} catch {
|
|
83
109
|
return null;
|
|
@@ -136,21 +162,41 @@ const installManagedOpenclawRuntime = ({
|
|
|
136
162
|
logger = console,
|
|
137
163
|
runtimeDir,
|
|
138
164
|
spec,
|
|
165
|
+
sourcePath,
|
|
139
166
|
alphaclawRoot,
|
|
140
167
|
} = {}) => {
|
|
141
|
-
const
|
|
168
|
+
const normalizedSourcePath = String(sourcePath || "").trim();
|
|
169
|
+
const normalizedSpec = normalizedSourcePath
|
|
170
|
+
? normalizedSourcePath
|
|
171
|
+
: String(spec || "").trim() || "openclaw@latest";
|
|
142
172
|
ensureManagedOpenclawRuntimeProject({
|
|
143
173
|
fsModule,
|
|
144
174
|
runtimeDir,
|
|
145
175
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
let packedSource = null;
|
|
177
|
+
try {
|
|
178
|
+
const installTarget = normalizedSourcePath
|
|
179
|
+
? (() => {
|
|
180
|
+
packedSource = packLocalPackageForInstall({
|
|
181
|
+
execSyncImpl,
|
|
182
|
+
fsModule,
|
|
183
|
+
packageRoot: normalizedSourcePath,
|
|
184
|
+
tempDirPrefix: "openclaw-runtime-pack-",
|
|
185
|
+
});
|
|
186
|
+
return packedSource.tarballPath;
|
|
187
|
+
})()
|
|
188
|
+
: normalizedSpec;
|
|
189
|
+
execSyncImpl(
|
|
190
|
+
`npm install ${shellQuote(installTarget)} --omit=dev --no-save --save=false --package-lock=false --prefer-online`,
|
|
191
|
+
{
|
|
192
|
+
cwd: runtimeDir,
|
|
193
|
+
stdio: "inherit",
|
|
194
|
+
timeout: 180000,
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
} finally {
|
|
198
|
+
packedSource?.cleanup?.();
|
|
199
|
+
}
|
|
154
200
|
const installedVersion = readManagedOpenclawRuntimeVersion({
|
|
155
201
|
fsModule,
|
|
156
202
|
runtimeDir,
|
|
@@ -177,6 +223,10 @@ const syncManagedOpenclawRuntimeWithBundled = ({
|
|
|
177
223
|
resolveImpl,
|
|
178
224
|
alphaclawRoot,
|
|
179
225
|
} = {}) => {
|
|
226
|
+
const bundledPackageRoot = getBundledOpenclawPackageRoot({
|
|
227
|
+
fsModule,
|
|
228
|
+
resolveImpl,
|
|
229
|
+
});
|
|
180
230
|
const bundledVersion = readBundledOpenclawVersion({
|
|
181
231
|
fsModule,
|
|
182
232
|
resolveImpl,
|
|
@@ -194,26 +244,52 @@ const syncManagedOpenclawRuntimeWithBundled = ({
|
|
|
194
244
|
fsModule,
|
|
195
245
|
runtimeDir,
|
|
196
246
|
});
|
|
247
|
+
const runtimePackageRoot = getManagedOpenclawPackageRoot({ runtimeDir });
|
|
248
|
+
const runtimePackageRootIsSymlink = isPackageRootSymlink({
|
|
249
|
+
fsModule,
|
|
250
|
+
packageRoot: runtimePackageRoot,
|
|
251
|
+
});
|
|
252
|
+
const bundledFingerprint = computePackageFingerprint({
|
|
253
|
+
fsModule,
|
|
254
|
+
packageRoot: bundledPackageRoot,
|
|
255
|
+
});
|
|
256
|
+
const runtimeFingerprint = computePackageFingerprint({
|
|
257
|
+
fsModule,
|
|
258
|
+
packageRoot: runtimePackageRoot,
|
|
259
|
+
packageJsonPath: getManagedOpenclawPackageJsonPath({ runtimeDir }),
|
|
260
|
+
});
|
|
197
261
|
if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
262
|
+
if (
|
|
263
|
+
compareVersionParts(runtimeVersion, bundledVersion) > 0 ||
|
|
264
|
+
(!runtimePackageRootIsSymlink &&
|
|
265
|
+
(!bundledFingerprint || runtimeFingerprint === bundledFingerprint))
|
|
266
|
+
) {
|
|
267
|
+
return {
|
|
268
|
+
checked: true,
|
|
269
|
+
synced: false,
|
|
270
|
+
bundledVersion,
|
|
271
|
+
runtimeVersion,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
logger.log(
|
|
275
|
+
runtimePackageRootIsSymlink
|
|
276
|
+
? `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} is symlinked to the bundled package; refreshing runtime...`
|
|
277
|
+
: `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} differs from bundled ${bundledVersion}; refreshing runtime...`,
|
|
278
|
+
);
|
|
279
|
+
} else {
|
|
280
|
+
logger.log(
|
|
281
|
+
runtimeVersion
|
|
282
|
+
? `[alphaclaw] Managed OpenClaw runtime ${runtimeVersion} is older than bundled ${bundledVersion}; syncing runtime...`
|
|
283
|
+
: `[alphaclaw] Managed OpenClaw runtime missing; seeding bundled OpenClaw ${bundledVersion}...`,
|
|
284
|
+
);
|
|
204
285
|
}
|
|
205
286
|
|
|
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
287
|
const installResult = installManagedOpenclawRuntime({
|
|
212
288
|
execSyncImpl,
|
|
213
289
|
fsModule,
|
|
214
290
|
logger,
|
|
215
291
|
runtimeDir,
|
|
216
|
-
|
|
292
|
+
sourcePath: bundledPackageRoot,
|
|
217
293
|
alphaclawRoot,
|
|
218
294
|
});
|
|
219
295
|
return {
|
|
@@ -248,8 +324,10 @@ const prependManagedOpenclawBinToPath = ({
|
|
|
248
324
|
module.exports = {
|
|
249
325
|
applyManagedOpenclawPatch,
|
|
250
326
|
ensureManagedOpenclawRuntimeProject,
|
|
327
|
+
getBundledOpenclawPackageRoot,
|
|
251
328
|
getManagedOpenclawBinDir,
|
|
252
329
|
getManagedOpenclawBinPath,
|
|
330
|
+
getManagedOpenclawPackageRoot,
|
|
253
331
|
getManagedOpenclawPackageJsonPath,
|
|
254
332
|
getManagedOpenclawRuntimeDir,
|
|
255
333
|
installManagedOpenclawRuntime,
|
|
@@ -0,0 +1,202 @@
|
|
|
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 packLocalPackageForInstall = ({
|
|
146
|
+
execSyncImpl,
|
|
147
|
+
fsModule = fs,
|
|
148
|
+
packageRoot,
|
|
149
|
+
tempDirPrefix = "alphaclaw-package-pack-",
|
|
150
|
+
} = {}) => {
|
|
151
|
+
const resolvedPackageRoot = path.resolve(String(packageRoot || ""));
|
|
152
|
+
const packDir = fsModule.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix));
|
|
153
|
+
try {
|
|
154
|
+
const packStdout = String(
|
|
155
|
+
execSyncImpl(
|
|
156
|
+
`npm pack ${shellQuote(resolvedPackageRoot)} --quiet --ignore-scripts --pack-destination ${shellQuote(packDir)}`,
|
|
157
|
+
{
|
|
158
|
+
encoding: "utf8",
|
|
159
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
160
|
+
timeout: 180000,
|
|
161
|
+
},
|
|
162
|
+
) || "",
|
|
163
|
+
)
|
|
164
|
+
.trim()
|
|
165
|
+
.split(/\r?\n/)
|
|
166
|
+
.map((entry) => entry.trim())
|
|
167
|
+
.filter(Boolean);
|
|
168
|
+
const packFileName =
|
|
169
|
+
packStdout.at(-1) ||
|
|
170
|
+
fsModule.readdirSync(packDir).find((entry) => entry.endsWith(".tgz"));
|
|
171
|
+
if (!packFileName) {
|
|
172
|
+
throw new Error(`npm pack did not produce a tarball for ${resolvedPackageRoot}`);
|
|
173
|
+
}
|
|
174
|
+
const tarballPath = path.join(packDir, packFileName);
|
|
175
|
+
if (!fsModule.existsSync(tarballPath)) {
|
|
176
|
+
throw new Error(`Packed tarball missing at ${tarballPath}`);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
tarballPath,
|
|
180
|
+
cleanup: () => {
|
|
181
|
+
try {
|
|
182
|
+
fsModule.rmSync(packDir, { recursive: true, force: true });
|
|
183
|
+
} catch {}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
try {
|
|
188
|
+
fsModule.rmSync(packDir, { recursive: true, force: true });
|
|
189
|
+
} catch {}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const shellQuote = (value) =>
|
|
195
|
+
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
computePackageFingerprint,
|
|
199
|
+
isPackageRootSymlink,
|
|
200
|
+
packLocalPackageForInstall,
|
|
201
|
+
resolvePackageRootFromEntryPath,
|
|
202
|
+
};
|
|
@@ -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,
|