@chrysb/alphaclaw 0.8.7-beta.1 → 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 +130 -53
- package/lib/server/alphaclaw-runtime.js +238 -0
- package/lib/server/alphaclaw-version.js +7 -1
- package/lib/server/openclaw-runtime.js +104 -26
- package/lib/server/package-fingerprint.js +202 -0
- package/lib/server/pending-alphaclaw-update.js +10 -11
- package/lib/server/usage-tracker-config.js +27 -3
- package/package.json +1 -1
package/bin/alphaclaw.js
CHANGED
|
@@ -16,6 +16,11 @@ const {
|
|
|
16
16
|
const {
|
|
17
17
|
applyPendingAlphaclawUpdate,
|
|
18
18
|
} = require("../lib/server/pending-alphaclaw-update");
|
|
19
|
+
const {
|
|
20
|
+
getManagedAlphaclawCliPath,
|
|
21
|
+
getManagedAlphaclawRuntimeDir,
|
|
22
|
+
syncManagedAlphaclawRuntimeWithBundled,
|
|
23
|
+
} = require("../lib/server/alphaclaw-runtime");
|
|
19
24
|
const {
|
|
20
25
|
applyPendingOpenclawUpdate,
|
|
21
26
|
} = require("../lib/server/pending-openclaw-update");
|
|
@@ -24,14 +29,10 @@ const {
|
|
|
24
29
|
prependManagedOpenclawBinToPath,
|
|
25
30
|
syncManagedOpenclawRuntimeWithBundled,
|
|
26
31
|
} = require("../lib/server/openclaw-runtime");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"lib",
|
|
32
|
-
"plugin",
|
|
33
|
-
"usage-tracker",
|
|
34
|
-
);
|
|
32
|
+
const {
|
|
33
|
+
ensurePluginsShell,
|
|
34
|
+
ensureUsageTrackerPluginEntry,
|
|
35
|
+
} = require("../lib/server/usage-tracker-config");
|
|
35
36
|
|
|
36
37
|
// ---------------------------------------------------------------------------
|
|
37
38
|
// Parse CLI flags
|
|
@@ -173,10 +174,89 @@ if (portFlag) {
|
|
|
173
174
|
process.env.PORT = portFlag;
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
const kManagedAlphaclawRuntimeEnvFlag = "ALPHACLAW_MANAGED_RUNTIME_ACTIVE";
|
|
178
|
+
const shouldBootstrapManagedAlphaclawRuntime =
|
|
179
|
+
command === "start" &&
|
|
180
|
+
process.env[kManagedAlphaclawRuntimeEnvFlag] !== "1";
|
|
181
|
+
|
|
176
182
|
// ---------------------------------------------------------------------------
|
|
177
183
|
// 2. Create directory structure
|
|
178
184
|
// ---------------------------------------------------------------------------
|
|
179
185
|
|
|
186
|
+
if (shouldBootstrapManagedAlphaclawRuntime) {
|
|
187
|
+
const { spawn } = require("child_process");
|
|
188
|
+
const managedAlphaclawRuntimeDir = getManagedAlphaclawRuntimeDir({ rootDir });
|
|
189
|
+
const pendingUpdateMarker = path.join(rootDir, ".alphaclaw-update-pending");
|
|
190
|
+
if (fs.existsSync(pendingUpdateMarker)) {
|
|
191
|
+
applyPendingAlphaclawUpdate({
|
|
192
|
+
execSyncImpl: execSync,
|
|
193
|
+
fsModule: fs,
|
|
194
|
+
installDir: managedAlphaclawRuntimeDir,
|
|
195
|
+
logger: console,
|
|
196
|
+
markerPath: pendingUpdateMarker,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
syncManagedAlphaclawRuntimeWithBundled({
|
|
201
|
+
execSyncImpl: execSync,
|
|
202
|
+
fsModule: fs,
|
|
203
|
+
logger: console,
|
|
204
|
+
runtimeDir: managedAlphaclawRuntimeDir,
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.log(
|
|
208
|
+
`[alphaclaw] Could not sync managed AlphaClaw runtime from bundled install: ${error.message}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const managedAlphaclawCliPath = getManagedAlphaclawCliPath({
|
|
213
|
+
runtimeDir: managedAlphaclawRuntimeDir,
|
|
214
|
+
});
|
|
215
|
+
if (!fs.existsSync(managedAlphaclawCliPath)) {
|
|
216
|
+
console.error(
|
|
217
|
+
`[alphaclaw] Managed AlphaClaw runtime missing CLI at ${managedAlphaclawCliPath}`,
|
|
218
|
+
);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const runtimeChild = spawn(
|
|
223
|
+
process.argv[0],
|
|
224
|
+
[managedAlphaclawCliPath, ...process.argv.slice(2)],
|
|
225
|
+
{
|
|
226
|
+
stdio: "inherit",
|
|
227
|
+
env: {
|
|
228
|
+
...process.env,
|
|
229
|
+
[kManagedAlphaclawRuntimeEnvFlag]: "1",
|
|
230
|
+
ALPHACLAW_BOOTSTRAP_CLI_PATH: __filename,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const forwardSignal = (signal) => {
|
|
236
|
+
if (runtimeChild.exitCode === null && !runtimeChild.killed) {
|
|
237
|
+
runtimeChild.kill(signal);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
242
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
243
|
+
|
|
244
|
+
runtimeChild.on("error", (error) => {
|
|
245
|
+
console.error(
|
|
246
|
+
`[alphaclaw] Managed AlphaClaw runtime launch failed: ${error.message}`,
|
|
247
|
+
);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
runtimeChild.on("exit", (code, signal) => {
|
|
252
|
+
if (signal) {
|
|
253
|
+
process.kill(process.pid, signal);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
process.exit(Number.isInteger(code) ? code : 0);
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
|
|
180
260
|
const openclawDir = path.join(rootDir, ".openclaw");
|
|
181
261
|
fs.mkdirSync(openclawDir, { recursive: true });
|
|
182
262
|
const { hourlyGitSyncPath } = migrateManagedInternalFiles({
|
|
@@ -210,36 +290,6 @@ if (fs.existsSync(pendingUpdateMarker)) {
|
|
|
210
290
|
}
|
|
211
291
|
}
|
|
212
292
|
|
|
213
|
-
const pendingOpenclawUpdateMarker = path.join(rootDir, ".openclaw-update-pending");
|
|
214
|
-
const managedOpenclawRuntimeDir = getManagedOpenclawRuntimeDir({ rootDir });
|
|
215
|
-
if (fs.existsSync(pendingOpenclawUpdateMarker)) {
|
|
216
|
-
applyPendingOpenclawUpdate({
|
|
217
|
-
execSyncImpl: execSync,
|
|
218
|
-
fsModule: fs,
|
|
219
|
-
installDir: managedOpenclawRuntimeDir,
|
|
220
|
-
logger: console,
|
|
221
|
-
markerPath: pendingOpenclawUpdateMarker,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
syncManagedOpenclawRuntimeWithBundled({
|
|
226
|
-
execSyncImpl: execSync,
|
|
227
|
-
fsModule: fs,
|
|
228
|
-
logger: console,
|
|
229
|
-
runtimeDir: managedOpenclawRuntimeDir,
|
|
230
|
-
});
|
|
231
|
-
} catch (error) {
|
|
232
|
-
console.log(
|
|
233
|
-
`[alphaclaw] Could not sync managed OpenClaw runtime from bundled install: ${error.message}`,
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
prependManagedOpenclawBinToPath({
|
|
237
|
-
env: process.env,
|
|
238
|
-
fsModule: fs,
|
|
239
|
-
logger: console,
|
|
240
|
-
runtimeDir: managedOpenclawRuntimeDir,
|
|
241
|
-
});
|
|
242
|
-
|
|
243
293
|
// ---------------------------------------------------------------------------
|
|
244
294
|
// 3. Symlink ~/.openclaw -> <root>/.openclaw
|
|
245
295
|
// ---------------------------------------------------------------------------
|
|
@@ -546,7 +596,41 @@ if (!kSetupPassword) {
|
|
|
546
596
|
}
|
|
547
597
|
|
|
548
598
|
// ---------------------------------------------------------------------------
|
|
549
|
-
// 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
|
|
550
634
|
// ---------------------------------------------------------------------------
|
|
551
635
|
|
|
552
636
|
process.env.OPENCLAW_HOME = rootDir;
|
|
@@ -555,7 +639,7 @@ process.env.GOG_KEYRING_PASSWORD =
|
|
|
555
639
|
process.env.GOG_KEYRING_PASSWORD || "alphaclaw";
|
|
556
640
|
|
|
557
641
|
// ---------------------------------------------------------------------------
|
|
558
|
-
//
|
|
642
|
+
// 9. Install gog (Google Workspace CLI) if not present
|
|
559
643
|
// ---------------------------------------------------------------------------
|
|
560
644
|
|
|
561
645
|
process.env.XDG_CONFIG_HOME = openclawDir;
|
|
@@ -588,7 +672,7 @@ if (!gogInstalled) {
|
|
|
588
672
|
}
|
|
589
673
|
|
|
590
674
|
// ---------------------------------------------------------------------------
|
|
591
|
-
//
|
|
675
|
+
// 10. Install/reconcile system cron entry
|
|
592
676
|
// ---------------------------------------------------------------------------
|
|
593
677
|
|
|
594
678
|
const packagedHourlyGitSyncPath = path.join(setupDir, "hourly-git-sync.sh");
|
|
@@ -654,7 +738,7 @@ if (fs.existsSync(hourlyGitSyncPath)) {
|
|
|
654
738
|
}
|
|
655
739
|
|
|
656
740
|
// ---------------------------------------------------------------------------
|
|
657
|
-
//
|
|
741
|
+
// 11. Start cron daemon if available
|
|
658
742
|
// ---------------------------------------------------------------------------
|
|
659
743
|
|
|
660
744
|
try {
|
|
@@ -668,7 +752,7 @@ try {
|
|
|
668
752
|
} catch {}
|
|
669
753
|
|
|
670
754
|
// ---------------------------------------------------------------------------
|
|
671
|
-
//
|
|
755
|
+
// 12. Reconcile channels if already onboarded
|
|
672
756
|
// ---------------------------------------------------------------------------
|
|
673
757
|
|
|
674
758
|
const configPath = path.join(openclawDir, "openclaw.json");
|
|
@@ -816,10 +900,7 @@ if (fs.existsSync(configPath)) {
|
|
|
816
900
|
try {
|
|
817
901
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
818
902
|
if (!cfg.channels) cfg.channels = {};
|
|
819
|
-
|
|
820
|
-
if (!cfg.plugins.load) cfg.plugins.load = {};
|
|
821
|
-
if (!Array.isArray(cfg.plugins.load.paths)) cfg.plugins.load.paths = [];
|
|
822
|
-
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
903
|
+
ensurePluginsShell(cfg);
|
|
823
904
|
let changed = false;
|
|
824
905
|
|
|
825
906
|
if (process.env.TELEGRAM_BOT_TOKEN && !cfg.channels.telegram) {
|
|
@@ -845,12 +926,7 @@ if (fs.existsSync(configPath)) {
|
|
|
845
926
|
console.log("[alphaclaw] Discord added");
|
|
846
927
|
changed = true;
|
|
847
928
|
}
|
|
848
|
-
if (
|
|
849
|
-
cfg.plugins.load.paths.push(kUsageTrackerPluginPath);
|
|
850
|
-
changed = true;
|
|
851
|
-
}
|
|
852
|
-
if (cfg.plugins.entries["usage-tracker"]?.enabled !== true) {
|
|
853
|
-
cfg.plugins.entries["usage-tracker"] = { enabled: true };
|
|
929
|
+
if (ensureUsageTrackerPluginEntry(cfg)) {
|
|
854
930
|
changed = true;
|
|
855
931
|
}
|
|
856
932
|
|
|
@@ -952,3 +1028,4 @@ try {
|
|
|
952
1028
|
|
|
953
1029
|
console.log("[alphaclaw] Setup complete -- starting server");
|
|
954
1030
|
require("../lib/server.js");
|
|
1031
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const { kRootDir } = require("./constants");
|
|
5
|
+
const { compareVersionParts } = require("./helpers");
|
|
6
|
+
const {
|
|
7
|
+
computePackageFingerprint,
|
|
8
|
+
isPackageRootSymlink,
|
|
9
|
+
packLocalPackageForInstall,
|
|
10
|
+
} = require("./package-fingerprint");
|
|
11
|
+
|
|
12
|
+
const getManagedAlphaclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
|
|
13
|
+
path.join(rootDir, ".alphaclaw-runtime");
|
|
14
|
+
|
|
15
|
+
const getBundledAlphaclawPackageRoot = () => path.resolve(__dirname, "..", "..");
|
|
16
|
+
|
|
17
|
+
const getManagedAlphaclawPackageRoot = ({ runtimeDir } = {}) =>
|
|
18
|
+
path.join(
|
|
19
|
+
runtimeDir || getManagedAlphaclawRuntimeDir(),
|
|
20
|
+
"node_modules",
|
|
21
|
+
"@chrysb",
|
|
22
|
+
"alphaclaw",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const getManagedAlphaclawCliPath = ({ runtimeDir } = {}) =>
|
|
26
|
+
path.join(
|
|
27
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
28
|
+
"bin",
|
|
29
|
+
"alphaclaw.js",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const getManagedAlphaclawPackageJsonPath = ({ runtimeDir } = {}) =>
|
|
33
|
+
path.join(
|
|
34
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
35
|
+
"package.json",
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const ensureManagedAlphaclawRuntimeProject = ({
|
|
39
|
+
fsModule = fs,
|
|
40
|
+
runtimeDir,
|
|
41
|
+
} = {}) => {
|
|
42
|
+
const resolvedRuntimeDir = runtimeDir || getManagedAlphaclawRuntimeDir();
|
|
43
|
+
const packageJsonPath = path.join(resolvedRuntimeDir, "package.json");
|
|
44
|
+
fsModule.mkdirSync(resolvedRuntimeDir, { recursive: true });
|
|
45
|
+
if (!fsModule.existsSync(packageJsonPath)) {
|
|
46
|
+
fsModule.writeFileSync(
|
|
47
|
+
packageJsonPath,
|
|
48
|
+
JSON.stringify(
|
|
49
|
+
{
|
|
50
|
+
name: "alphaclaw-runtime",
|
|
51
|
+
private: true,
|
|
52
|
+
},
|
|
53
|
+
null,
|
|
54
|
+
2,
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
runtimeDir: resolvedRuntimeDir,
|
|
60
|
+
packageJsonPath,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const readManagedAlphaclawRuntimeVersion = ({
|
|
65
|
+
fsModule = fs,
|
|
66
|
+
runtimeDir,
|
|
67
|
+
} = {}) => {
|
|
68
|
+
try {
|
|
69
|
+
const pkg = JSON.parse(
|
|
70
|
+
fsModule.readFileSync(
|
|
71
|
+
getManagedAlphaclawPackageJsonPath({ runtimeDir }),
|
|
72
|
+
"utf8",
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
return String(pkg?.version || "").trim() || null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const readBundledAlphaclawVersion = ({
|
|
82
|
+
fsModule = fs,
|
|
83
|
+
packageJsonPath = path.resolve(__dirname, "..", "..", "package.json"),
|
|
84
|
+
} = {}) => {
|
|
85
|
+
try {
|
|
86
|
+
const pkg = JSON.parse(fsModule.readFileSync(packageJsonPath, "utf8"));
|
|
87
|
+
return String(pkg?.version || "").trim() || null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const shellQuote = (value) =>
|
|
94
|
+
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
95
|
+
|
|
96
|
+
const installManagedAlphaclawRuntime = ({
|
|
97
|
+
execSyncImpl,
|
|
98
|
+
fsModule = fs,
|
|
99
|
+
runtimeDir,
|
|
100
|
+
spec,
|
|
101
|
+
sourcePath,
|
|
102
|
+
} = {}) => {
|
|
103
|
+
const normalizedSourcePath = String(sourcePath || "").trim();
|
|
104
|
+
const normalizedSpec = normalizedSourcePath
|
|
105
|
+
? normalizedSourcePath
|
|
106
|
+
: String(spec || "").trim() || "@chrysb/alphaclaw@latest";
|
|
107
|
+
ensureManagedAlphaclawRuntimeProject({
|
|
108
|
+
fsModule,
|
|
109
|
+
runtimeDir,
|
|
110
|
+
});
|
|
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
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
spec: normalizedSpec,
|
|
137
|
+
version: readManagedAlphaclawRuntimeVersion({
|
|
138
|
+
fsModule,
|
|
139
|
+
runtimeDir,
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
145
|
+
execSyncImpl,
|
|
146
|
+
fsModule = fs,
|
|
147
|
+
logger = console,
|
|
148
|
+
runtimeDir,
|
|
149
|
+
packageRoot = getBundledAlphaclawPackageRoot(),
|
|
150
|
+
packageJsonPath,
|
|
151
|
+
} = {}) => {
|
|
152
|
+
const bundledVersion = readBundledAlphaclawVersion({
|
|
153
|
+
fsModule,
|
|
154
|
+
packageJsonPath: packageJsonPath || path.join(packageRoot, "package.json"),
|
|
155
|
+
});
|
|
156
|
+
if (!bundledVersion) {
|
|
157
|
+
return {
|
|
158
|
+
checked: false,
|
|
159
|
+
synced: false,
|
|
160
|
+
bundledVersion: null,
|
|
161
|
+
runtimeVersion: readManagedAlphaclawRuntimeVersion({
|
|
162
|
+
fsModule,
|
|
163
|
+
runtimeDir,
|
|
164
|
+
}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const runtimeVersion = readManagedAlphaclawRuntimeVersion({
|
|
169
|
+
fsModule,
|
|
170
|
+
runtimeDir,
|
|
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
|
+
});
|
|
187
|
+
if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
|
|
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
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const installResult = installManagedAlphaclawRuntime({
|
|
214
|
+
execSyncImpl,
|
|
215
|
+
fsModule,
|
|
216
|
+
runtimeDir,
|
|
217
|
+
sourcePath: packageRoot,
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
checked: true,
|
|
221
|
+
synced: true,
|
|
222
|
+
bundledVersion,
|
|
223
|
+
runtimeVersion: installResult.version || bundledVersion,
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
ensureManagedAlphaclawRuntimeProject,
|
|
229
|
+
getBundledAlphaclawPackageRoot,
|
|
230
|
+
getManagedAlphaclawCliPath,
|
|
231
|
+
getManagedAlphaclawPackageJsonPath,
|
|
232
|
+
getManagedAlphaclawPackageRoot,
|
|
233
|
+
getManagedAlphaclawRuntimeDir,
|
|
234
|
+
installManagedAlphaclawRuntime,
|
|
235
|
+
readBundledAlphaclawVersion,
|
|
236
|
+
readManagedAlphaclawRuntimeVersion,
|
|
237
|
+
syncManagedAlphaclawRuntimeWithBundled,
|
|
238
|
+
};
|
|
@@ -126,9 +126,15 @@ const createAlphaclawVersionService = () => {
|
|
|
126
126
|
// On bare metal / Mac / Linux, spawn a replacement process then exit.
|
|
127
127
|
console.log("[alphaclaw] Spawning new process and exiting...");
|
|
128
128
|
const { spawn } = require("child_process");
|
|
129
|
-
const
|
|
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)], {
|
|
130
135
|
detached: true,
|
|
131
136
|
stdio: "inherit",
|
|
137
|
+
env: nextEnv,
|
|
132
138
|
});
|
|
133
139
|
child.unref();
|
|
134
140
|
process.exit(0);
|
|
@@ -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
|
+
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const {
|
|
2
|
+
installManagedAlphaclawRuntime,
|
|
3
|
+
} = require("./alphaclaw-runtime");
|
|
4
|
+
|
|
1
5
|
const buildPendingAlphaclawInstallSpec = (marker = {}) => {
|
|
2
6
|
const explicitSpec = String(marker?.spec || "").trim();
|
|
3
7
|
if (explicitSpec) {
|
|
@@ -7,9 +11,6 @@ const buildPendingAlphaclawInstallSpec = (marker = {}) => {
|
|
|
7
11
|
return `@chrysb/alphaclaw@${targetVersion}`;
|
|
8
12
|
};
|
|
9
13
|
|
|
10
|
-
const shellQuote = (value) =>
|
|
11
|
-
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
12
|
-
|
|
13
14
|
const applyPendingAlphaclawUpdate = ({
|
|
14
15
|
execSyncImpl,
|
|
15
16
|
fsModule,
|
|
@@ -36,14 +37,12 @@ const applyPendingAlphaclawUpdate = ({
|
|
|
36
37
|
logger.log(`[alphaclaw] Pending update detected, installing ${spec}...`);
|
|
37
38
|
|
|
38
39
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
);
|
|
40
|
+
installManagedAlphaclawRuntime({
|
|
41
|
+
execSyncImpl,
|
|
42
|
+
fsModule,
|
|
43
|
+
runtimeDir: installDir,
|
|
44
|
+
spec,
|
|
45
|
+
});
|
|
47
46
|
fsModule.unlinkSync(markerPath);
|
|
48
47
|
logger.log("[alphaclaw] Update applied successfully");
|
|
49
48
|
return {
|
|
@@ -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,
|