@chrysb/alphaclaw 0.8.7 → 0.8.8
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 +174 -43
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +2109 -2089
- package/lib/public/js/app.js +3 -0
- package/lib/public/js/components/gateway.js +6 -3
- package/lib/public/js/components/general/index.js +2 -0
- package/lib/public/js/components/onboarding/welcome-form-step.js +29 -4
- package/lib/public/js/components/routes/general-route.js +2 -0
- package/lib/public/js/components/routes/watchdog-route.js +2 -0
- package/lib/public/js/components/sidebar.js +20 -7
- package/lib/public/js/components/update-modal-helpers.js +12 -0
- package/lib/public/js/components/update-modal.js +2 -1
- package/lib/public/js/components/watchdog-tab/index.js +2 -0
- package/lib/public/js/components/welcome/index.js +1 -0
- package/lib/public/js/components/welcome/use-welcome.js +52 -2
- package/lib/public/js/hooks/use-app-shell-controller.js +37 -9
- package/lib/public/js/lib/api.js +36 -0
- package/lib/release/managed-release.js +180 -0
- package/lib/server/alphaclaw-runtime.js +294 -0
- package/lib/server/alphaclaw-version.js +37 -128
- package/lib/server/gateway.js +32 -14
- package/lib/server/init/register-server-routes.js +7 -1
- package/lib/server/openclaw-runtime.js +428 -0
- package/lib/server/openclaw-version.js +76 -136
- package/lib/server/package-fingerprint.js +274 -0
- package/lib/server/pending-alphaclaw-update.js +85 -0
- package/lib/server/pending-openclaw-update.js +86 -0
- package/lib/server/routes/pages.js +9 -1
- package/lib/server/routes/system.js +6 -1
- package/lib/server/usage-tracker-config.js +27 -3
- package/package.json +3 -2
- package/patches/openclaw+2026.4.9.patch +13 -0
- package/patches/openclaw+2026.4.5.patch +0 -13
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const kTemplateDefinitions = {
|
|
5
|
+
railway: {
|
|
6
|
+
id: "railway",
|
|
7
|
+
packageName: "openclaw-railway",
|
|
8
|
+
description:
|
|
9
|
+
"OpenClaw on Railway — one-click deploy, powered by @chrysb/alphaclaw",
|
|
10
|
+
includeCompose: false,
|
|
11
|
+
},
|
|
12
|
+
render: {
|
|
13
|
+
id: "render",
|
|
14
|
+
packageName: "openclaw-render",
|
|
15
|
+
description:
|
|
16
|
+
"OpenClaw on Render — one-click deploy, powered by @chrysb/alphaclaw",
|
|
17
|
+
includeCompose: false,
|
|
18
|
+
},
|
|
19
|
+
apex: {
|
|
20
|
+
id: "apex",
|
|
21
|
+
packageName: "openclaw-apex",
|
|
22
|
+
description:
|
|
23
|
+
"OpenClaw on Apex — managed template, powered by @chrysb/alphaclaw",
|
|
24
|
+
includeCompose: true,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
29
|
+
|
|
30
|
+
const normalizeValue = (value) => String(value || "").trim();
|
|
31
|
+
|
|
32
|
+
const createReleaseId = ({ alphaclawVersion, openclawVersion }) =>
|
|
33
|
+
`alphaclaw-${normalizeValue(alphaclawVersion)}_openclaw-${normalizeValue(openclawVersion)}`;
|
|
34
|
+
|
|
35
|
+
const readCurrentReleaseMetadata = ({ repoRoot }) => {
|
|
36
|
+
const packageJson = readJsonFile(path.join(repoRoot, "package.json"));
|
|
37
|
+
const alphaclawVersion = normalizeValue(packageJson.version);
|
|
38
|
+
const openclawVersion = normalizeValue(packageJson.dependencies?.openclaw);
|
|
39
|
+
if (!alphaclawVersion) {
|
|
40
|
+
throw new Error("AlphaClaw package.json is missing version.");
|
|
41
|
+
}
|
|
42
|
+
if (!openclawVersion) {
|
|
43
|
+
throw new Error("AlphaClaw package.json is missing dependencies.openclaw.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
alphaclawVersion,
|
|
47
|
+
openclawVersion,
|
|
48
|
+
releaseId: createReleaseId({
|
|
49
|
+
alphaclawVersion,
|
|
50
|
+
openclawVersion,
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const resolveReleaseMetadata = ({
|
|
56
|
+
repoRoot,
|
|
57
|
+
alphaclawVersion,
|
|
58
|
+
openclawVersion,
|
|
59
|
+
}) => {
|
|
60
|
+
const normalizedAlphaClawVersion = normalizeValue(alphaclawVersion);
|
|
61
|
+
const normalizedOpenClawVersion = normalizeValue(openclawVersion);
|
|
62
|
+
if (!normalizedAlphaClawVersion && !normalizedOpenClawVersion) {
|
|
63
|
+
return readCurrentReleaseMetadata({ repoRoot });
|
|
64
|
+
}
|
|
65
|
+
if (!normalizedAlphaClawVersion || !normalizedOpenClawVersion) {
|
|
66
|
+
throw new Error("alphaclawVersion and openclawVersion overrides must be provided together.");
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
alphaclawVersion: normalizedAlphaClawVersion,
|
|
70
|
+
openclawVersion: normalizedOpenClawVersion,
|
|
71
|
+
releaseId: createReleaseId({
|
|
72
|
+
alphaclawVersion: normalizedAlphaClawVersion,
|
|
73
|
+
openclawVersion: normalizedOpenClawVersion,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getTemplateDefinition = (templateId = "") => {
|
|
79
|
+
const normalizedTemplateId = normalizeValue(templateId).toLowerCase();
|
|
80
|
+
const definition = kTemplateDefinitions[normalizedTemplateId];
|
|
81
|
+
if (!definition) {
|
|
82
|
+
throw new Error(`Unsupported templateId: ${templateId}`);
|
|
83
|
+
}
|
|
84
|
+
return definition;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const buildManagedBundlePackageJson = ({
|
|
88
|
+
templateId = "apex",
|
|
89
|
+
alphaclawVersion,
|
|
90
|
+
openclawVersion,
|
|
91
|
+
alphaclawPackageSpec = "",
|
|
92
|
+
}) => {
|
|
93
|
+
const definition = getTemplateDefinition(templateId);
|
|
94
|
+
return {
|
|
95
|
+
name: definition.packageName,
|
|
96
|
+
version: "2.0.0",
|
|
97
|
+
private: true,
|
|
98
|
+
description: definition.description,
|
|
99
|
+
scripts: {
|
|
100
|
+
dev: "docker compose up --build",
|
|
101
|
+
"dev:stop": "docker compose down",
|
|
102
|
+
"dev:clean": "docker compose down -v",
|
|
103
|
+
"dev:logs": "docker compose logs -f",
|
|
104
|
+
"dev:shell": "docker compose exec openclaw bash",
|
|
105
|
+
},
|
|
106
|
+
dependencies: {
|
|
107
|
+
"@chrysb/alphaclaw":
|
|
108
|
+
normalizeValue(alphaclawPackageSpec) || normalizeValue(alphaclawVersion),
|
|
109
|
+
openclaw: normalizeValue(openclawVersion),
|
|
110
|
+
},
|
|
111
|
+
engines: {
|
|
112
|
+
node: ">=22.12.0",
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const buildManagedBundleDockerfile = ({
|
|
118
|
+
includeVendorDir = false,
|
|
119
|
+
} = {}) => `FROM node:22-slim
|
|
120
|
+
|
|
121
|
+
WORKDIR /app
|
|
122
|
+
|
|
123
|
+
${includeVendorDir ? "COPY vendor/ ./vendor/\n" : ""}COPY package.json package-lock.json ./
|
|
124
|
+
RUN npm ci --omit=dev --no-audit --no-fund && npm cache clean --force
|
|
125
|
+
|
|
126
|
+
ENV PATH="/app/node_modules/.bin:$PATH"
|
|
127
|
+
ENV ALPHACLAW_ROOT_DIR=/data
|
|
128
|
+
|
|
129
|
+
RUN mkdir -p /data
|
|
130
|
+
|
|
131
|
+
EXPOSE 3000
|
|
132
|
+
|
|
133
|
+
CMD ["alphaclaw", "start"]
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const buildManagedBundleCompose = () => `services:
|
|
137
|
+
openclaw:
|
|
138
|
+
build: .
|
|
139
|
+
restart: unless-stopped
|
|
140
|
+
env_file:
|
|
141
|
+
- /opt/alphaclaw/shared/.env
|
|
142
|
+
volumes:
|
|
143
|
+
- openclaw-data:/data
|
|
144
|
+
- /opt/alphaclaw/shared/.env:/data/.env
|
|
145
|
+
caddy:
|
|
146
|
+
image: caddy:2
|
|
147
|
+
restart: unless-stopped
|
|
148
|
+
extra_hosts:
|
|
149
|
+
- "host.docker.internal:host-gateway"
|
|
150
|
+
volumes:
|
|
151
|
+
- caddy_data:/data
|
|
152
|
+
- caddy_config:/config
|
|
153
|
+
- /opt/alphaclaw/shared/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
154
|
+
ports:
|
|
155
|
+
- "\${ALPHACLAW_HTTP_PORT:-80}:80"
|
|
156
|
+
- "\${ALPHACLAW_HTTPS_PORT:-443}:443"
|
|
157
|
+
volumes:
|
|
158
|
+
openclaw-data:
|
|
159
|
+
caddy_data:
|
|
160
|
+
caddy_config:
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const buildTemplateDependencies = ({
|
|
164
|
+
alphaclawVersion,
|
|
165
|
+
openclawVersion,
|
|
166
|
+
}) => ({
|
|
167
|
+
"@chrysb/alphaclaw": normalizeValue(alphaclawVersion),
|
|
168
|
+
openclaw: normalizeValue(openclawVersion),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
buildManagedBundleCompose,
|
|
173
|
+
buildManagedBundleDockerfile,
|
|
174
|
+
buildManagedBundlePackageJson,
|
|
175
|
+
buildTemplateDependencies,
|
|
176
|
+
createReleaseId,
|
|
177
|
+
getTemplateDefinition,
|
|
178
|
+
readCurrentReleaseMetadata,
|
|
179
|
+
resolveReleaseMetadata,
|
|
180
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
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
|
+
seedRuntimeFromBundledInstall,
|
|
11
|
+
} = require("./package-fingerprint");
|
|
12
|
+
|
|
13
|
+
const getManagedAlphaclawRuntimeDir = ({ rootDir = kRootDir } = {}) =>
|
|
14
|
+
path.join(rootDir, ".alphaclaw-runtime");
|
|
15
|
+
|
|
16
|
+
const getBundledAlphaclawPackageRoot = () => path.resolve(__dirname, "..", "..");
|
|
17
|
+
|
|
18
|
+
const getManagedAlphaclawPackageRoot = ({ runtimeDir } = {}) =>
|
|
19
|
+
path.join(
|
|
20
|
+
runtimeDir || getManagedAlphaclawRuntimeDir(),
|
|
21
|
+
"node_modules",
|
|
22
|
+
"@chrysb",
|
|
23
|
+
"alphaclaw",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const getManagedAlphaclawCliPath = ({ runtimeDir } = {}) =>
|
|
27
|
+
path.join(
|
|
28
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
29
|
+
"bin",
|
|
30
|
+
"alphaclaw.js",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const getManagedAlphaclawPackageJsonPath = ({ runtimeDir } = {}) =>
|
|
34
|
+
path.join(
|
|
35
|
+
getManagedAlphaclawPackageRoot({ runtimeDir }),
|
|
36
|
+
"package.json",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const ensureManagedAlphaclawRuntimeProject = ({
|
|
40
|
+
fsModule = fs,
|
|
41
|
+
runtimeDir,
|
|
42
|
+
} = {}) => {
|
|
43
|
+
const resolvedRuntimeDir = runtimeDir || getManagedAlphaclawRuntimeDir();
|
|
44
|
+
const packageJsonPath = path.join(resolvedRuntimeDir, "package.json");
|
|
45
|
+
fsModule.mkdirSync(resolvedRuntimeDir, { recursive: true });
|
|
46
|
+
if (!fsModule.existsSync(packageJsonPath)) {
|
|
47
|
+
fsModule.writeFileSync(
|
|
48
|
+
packageJsonPath,
|
|
49
|
+
JSON.stringify(
|
|
50
|
+
{
|
|
51
|
+
name: "alphaclaw-runtime",
|
|
52
|
+
private: true,
|
|
53
|
+
},
|
|
54
|
+
null,
|
|
55
|
+
2,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
runtimeDir: resolvedRuntimeDir,
|
|
61
|
+
packageJsonPath,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const readManagedAlphaclawRuntimeVersion = ({
|
|
66
|
+
fsModule = fs,
|
|
67
|
+
runtimeDir,
|
|
68
|
+
} = {}) => {
|
|
69
|
+
try {
|
|
70
|
+
const pkg = JSON.parse(
|
|
71
|
+
fsModule.readFileSync(
|
|
72
|
+
getManagedAlphaclawPackageJsonPath({ runtimeDir }),
|
|
73
|
+
"utf8",
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
return String(pkg?.version || "").trim() || null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const readBundledAlphaclawVersion = ({
|
|
83
|
+
fsModule = fs,
|
|
84
|
+
packageJsonPath = path.resolve(__dirname, "..", "..", "package.json"),
|
|
85
|
+
} = {}) => {
|
|
86
|
+
try {
|
|
87
|
+
const pkg = JSON.parse(fsModule.readFileSync(packageJsonPath, "utf8"));
|
|
88
|
+
return String(pkg?.version || "").trim() || null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const shellQuote = (value) =>
|
|
95
|
+
`'${String(value || "").replace(/'/g, `'\"'\"'`)}'`;
|
|
96
|
+
|
|
97
|
+
const installManagedAlphaclawRuntime = ({
|
|
98
|
+
execSyncImpl,
|
|
99
|
+
fsModule = fs,
|
|
100
|
+
runtimeDir,
|
|
101
|
+
spec,
|
|
102
|
+
sourcePath,
|
|
103
|
+
} = {}) => {
|
|
104
|
+
const normalizedSourcePath = String(sourcePath || "").trim();
|
|
105
|
+
const normalizedSpec = normalizedSourcePath
|
|
106
|
+
? normalizedSourcePath
|
|
107
|
+
: String(spec || "").trim() || "@chrysb/alphaclaw@latest";
|
|
108
|
+
ensureManagedAlphaclawRuntimeProject({
|
|
109
|
+
fsModule,
|
|
110
|
+
runtimeDir,
|
|
111
|
+
});
|
|
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
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
spec: normalizedSpec,
|
|
138
|
+
version: readManagedAlphaclawRuntimeVersion({
|
|
139
|
+
fsModule,
|
|
140
|
+
runtimeDir,
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
};
|
|
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
|
+
|
|
176
|
+
const syncManagedAlphaclawRuntimeWithBundled = ({
|
|
177
|
+
execSyncImpl,
|
|
178
|
+
fsModule = fs,
|
|
179
|
+
logger = console,
|
|
180
|
+
runtimeDir,
|
|
181
|
+
packageRoot = getBundledAlphaclawPackageRoot(),
|
|
182
|
+
packageJsonPath,
|
|
183
|
+
} = {}) => {
|
|
184
|
+
const bundledVersion = readBundledAlphaclawVersion({
|
|
185
|
+
fsModule,
|
|
186
|
+
packageJsonPath: packageJsonPath || path.join(packageRoot, "package.json"),
|
|
187
|
+
});
|
|
188
|
+
if (!bundledVersion) {
|
|
189
|
+
return {
|
|
190
|
+
checked: false,
|
|
191
|
+
synced: false,
|
|
192
|
+
bundledVersion: null,
|
|
193
|
+
runtimeVersion: readManagedAlphaclawRuntimeVersion({
|
|
194
|
+
fsModule,
|
|
195
|
+
runtimeDir,
|
|
196
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const runtimeVersion = readManagedAlphaclawRuntimeVersion({
|
|
201
|
+
fsModule,
|
|
202
|
+
runtimeDir,
|
|
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
|
+
});
|
|
219
|
+
if (runtimeVersion && compareVersionParts(runtimeVersion, bundledVersion) >= 0) {
|
|
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
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const installResult = installManagedAlphaclawRuntime({
|
|
269
|
+
execSyncImpl,
|
|
270
|
+
fsModule,
|
|
271
|
+
runtimeDir,
|
|
272
|
+
sourcePath: packageRoot,
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
checked: true,
|
|
276
|
+
synced: true,
|
|
277
|
+
bundledVersion,
|
|
278
|
+
runtimeVersion: installResult.version || bundledVersion,
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
ensureManagedAlphaclawRuntimeProject,
|
|
284
|
+
getBundledAlphaclawPackageRoot,
|
|
285
|
+
getManagedAlphaclawCliPath,
|
|
286
|
+
getManagedAlphaclawPackageJsonPath,
|
|
287
|
+
getManagedAlphaclawPackageRoot,
|
|
288
|
+
getManagedAlphaclawRuntimeDir,
|
|
289
|
+
installManagedAlphaclawRuntime,
|
|
290
|
+
readBundledAlphaclawVersion,
|
|
291
|
+
readManagedAlphaclawRuntimeVersion,
|
|
292
|
+
seedManagedAlphaclawRuntimeFromBundledInstall,
|
|
293
|
+
syncManagedAlphaclawRuntimeWithBundled,
|
|
294
|
+
};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const childProcess = require("child_process");
|
|
2
1
|
const fs = require("fs");
|
|
3
|
-
const os = require("os");
|
|
4
2
|
const path = require("path");
|
|
5
3
|
const https = require("https");
|
|
6
4
|
const http = require("http");
|
|
@@ -8,7 +6,6 @@ const {
|
|
|
8
6
|
kLatestVersionCacheTtlMs,
|
|
9
7
|
kAlphaclawRegistryUrl,
|
|
10
8
|
kNpmPackageRoot,
|
|
11
|
-
kOpenclawUpdateCopyTimeoutMs,
|
|
12
9
|
kRootDir,
|
|
13
10
|
} = require("./constants");
|
|
14
11
|
|
|
@@ -26,6 +23,9 @@ const isNewerVersion = (latest, current) => {
|
|
|
26
23
|
return l.patch > c.patch;
|
|
27
24
|
};
|
|
28
25
|
|
|
26
|
+
const buildAlphaclawInstallSpec = (version = "latest") =>
|
|
27
|
+
`@chrysb/alphaclaw@${String(version || "").trim() || "latest"}`;
|
|
28
|
+
|
|
29
29
|
const createAlphaclawVersionService = () => {
|
|
30
30
|
let kUpdateStatusCache = {
|
|
31
31
|
latestVersion: null,
|
|
@@ -108,120 +108,6 @@ const createAlphaclawVersionService = () => {
|
|
|
108
108
|
return { latestVersion, hasUpdate };
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
-
const findInstallDir = () => {
|
|
112
|
-
// Walk up from kNpmPackageRoot to find the consuming project's directory
|
|
113
|
-
// (the one with node_modules/@chrysb/alphaclaw). In Docker this is /app.
|
|
114
|
-
let dir = kNpmPackageRoot;
|
|
115
|
-
while (dir !== path.dirname(dir)) {
|
|
116
|
-
const parent = path.dirname(dir);
|
|
117
|
-
if (
|
|
118
|
-
path.basename(parent) === "node_modules" ||
|
|
119
|
-
parent.includes(`${path.sep}node_modules${path.sep}`)
|
|
120
|
-
) {
|
|
121
|
-
dir = parent;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const pkgPath = path.join(parent, "package.json");
|
|
125
|
-
if (fs.existsSync(pkgPath)) {
|
|
126
|
-
try {
|
|
127
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
128
|
-
if (
|
|
129
|
-
pkg.dependencies?.["@chrysb/alphaclaw"] ||
|
|
130
|
-
pkg.devDependencies?.["@chrysb/alphaclaw"] ||
|
|
131
|
-
pkg.optionalDependencies?.["@chrysb/alphaclaw"]
|
|
132
|
-
) {
|
|
133
|
-
return parent;
|
|
134
|
-
}
|
|
135
|
-
} catch {}
|
|
136
|
-
}
|
|
137
|
-
dir = parent;
|
|
138
|
-
}
|
|
139
|
-
// Fallback: if running directly (not from node_modules), use kNpmPackageRoot
|
|
140
|
-
return kNpmPackageRoot;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const installLatestAlphaclaw = () =>
|
|
144
|
-
new Promise((resolve, reject) => {
|
|
145
|
-
const installDir = findInstallDir();
|
|
146
|
-
const tmpDir = fs.mkdtempSync(
|
|
147
|
-
path.join(os.tmpdir(), "alphaclaw-update-"),
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const cleanup = () => {
|
|
151
|
-
try {
|
|
152
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
153
|
-
} catch {}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
fs.writeFileSync(
|
|
157
|
-
path.join(tmpDir, "package.json"),
|
|
158
|
-
JSON.stringify({
|
|
159
|
-
private: true,
|
|
160
|
-
dependencies: { "@chrysb/alphaclaw": "latest" },
|
|
161
|
-
}),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
const npmEnv = {
|
|
165
|
-
...process.env,
|
|
166
|
-
npm_config_update_notifier: "false",
|
|
167
|
-
npm_config_fund: "false",
|
|
168
|
-
npm_config_audit: "false",
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
console.log(
|
|
172
|
-
`[alphaclaw] Running: npm install @chrysb/alphaclaw@latest in temp dir (target: ${installDir})`,
|
|
173
|
-
);
|
|
174
|
-
childProcess.exec(
|
|
175
|
-
"npm install --omit=dev --prefer-online --package-lock=false",
|
|
176
|
-
{
|
|
177
|
-
cwd: tmpDir,
|
|
178
|
-
env: npmEnv,
|
|
179
|
-
timeout: 180000,
|
|
180
|
-
},
|
|
181
|
-
(err, stdout, stderr) => {
|
|
182
|
-
if (err) {
|
|
183
|
-
const message = String(stderr || err.message || "").trim();
|
|
184
|
-
console.log(
|
|
185
|
-
`[alphaclaw] alphaclaw install error: ${message.slice(0, 200)}`,
|
|
186
|
-
);
|
|
187
|
-
cleanup();
|
|
188
|
-
return reject(
|
|
189
|
-
new Error(
|
|
190
|
-
message || "Failed to install @chrysb/alphaclaw@latest",
|
|
191
|
-
),
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
if (stdout?.trim()) {
|
|
195
|
-
console.log(
|
|
196
|
-
`[alphaclaw] alphaclaw install stdout: ${stdout.trim().slice(0, 300)}`,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const src = path.join(tmpDir, "node_modules");
|
|
201
|
-
const dest = path.join(installDir, "node_modules");
|
|
202
|
-
childProcess.exec(
|
|
203
|
-
`cp -af "${src}/." "${dest}/"`,
|
|
204
|
-
{ timeout: kOpenclawUpdateCopyTimeoutMs },
|
|
205
|
-
(copyErr) => {
|
|
206
|
-
cleanup();
|
|
207
|
-
if (copyErr) {
|
|
208
|
-
console.log(
|
|
209
|
-
`[alphaclaw] alphaclaw copy error: ${(copyErr.message || "").slice(0, 200)}`,
|
|
210
|
-
);
|
|
211
|
-
return reject(
|
|
212
|
-
new Error(
|
|
213
|
-
`Failed to copy updated AlphaClaw files: ${copyErr.message}`,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
console.log("[alphaclaw] alphaclaw install completed");
|
|
218
|
-
resolve({ stdout: stdout?.trim(), stderr: stderr?.trim() });
|
|
219
|
-
},
|
|
220
|
-
);
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
111
|
const isContainer = () =>
|
|
226
112
|
process.env.RAILWAY_ENVIRONMENT ||
|
|
227
113
|
process.env.RENDER ||
|
|
@@ -240,9 +126,15 @@ const createAlphaclawVersionService = () => {
|
|
|
240
126
|
// On bare metal / Mac / Linux, spawn a replacement process then exit.
|
|
241
127
|
console.log("[alphaclaw] Spawning new process and exiting...");
|
|
242
128
|
const { spawn } = require("child_process");
|
|
243
|
-
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)], {
|
|
244
135
|
detached: true,
|
|
245
136
|
stdio: "inherit",
|
|
137
|
+
env: nextEnv,
|
|
246
138
|
});
|
|
247
139
|
child.unref();
|
|
248
140
|
process.exit(0);
|
|
@@ -277,18 +169,33 @@ const createAlphaclawVersionService = () => {
|
|
|
277
169
|
kUpdateInProgress = true;
|
|
278
170
|
const previousVersion = readAlphaclawVersion();
|
|
279
171
|
try {
|
|
280
|
-
|
|
281
|
-
// Write marker to persistent volume so the update survives container recreation
|
|
282
|
-
const markerPath = path.join(kRootDir, ".alphaclaw-update-pending");
|
|
172
|
+
let targetVersion = "latest";
|
|
283
173
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
174
|
+
const updateStatus = await readAlphaclawUpdateStatus({ refresh: true });
|
|
175
|
+
if (updateStatus.latestVersion) {
|
|
176
|
+
targetVersion = updateStatus.latestVersion;
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.log(
|
|
180
|
+
`[alphaclaw] Could not resolve exact AlphaClaw version before restart: ${error.message || "unknown error"}`,
|
|
287
181
|
);
|
|
288
|
-
console.log(`[alphaclaw] Update marker written to ${markerPath}`);
|
|
289
|
-
} catch (e) {
|
|
290
|
-
console.log(`[alphaclaw] Could not write update marker: ${e.message}`);
|
|
291
182
|
}
|
|
183
|
+
|
|
184
|
+
const spec = buildAlphaclawInstallSpec(targetVersion);
|
|
185
|
+
// Write marker to persistent volume so the update survives container recreation
|
|
186
|
+
const markerPath = path.join(kRootDir, ".alphaclaw-update-pending");
|
|
187
|
+
fs.writeFileSync(
|
|
188
|
+
markerPath,
|
|
189
|
+
JSON.stringify({
|
|
190
|
+
from: previousVersion,
|
|
191
|
+
to: targetVersion,
|
|
192
|
+
spec,
|
|
193
|
+
ts: Date.now(),
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
console.log(
|
|
197
|
+
`[alphaclaw] Update marker written to ${markerPath} for ${spec}`,
|
|
198
|
+
);
|
|
292
199
|
kUpdateStatusCache = {
|
|
293
200
|
latestVersion: null,
|
|
294
201
|
hasUpdate: false,
|
|
@@ -299,15 +206,17 @@ const createAlphaclawVersionService = () => {
|
|
|
299
206
|
body: {
|
|
300
207
|
ok: true,
|
|
301
208
|
previousVersion,
|
|
209
|
+
targetVersion: targetVersion === "latest" ? null : targetVersion,
|
|
302
210
|
restarting: true,
|
|
303
211
|
},
|
|
304
212
|
};
|
|
305
213
|
} catch (err) {
|
|
306
|
-
kUpdateInProgress = false;
|
|
307
214
|
return {
|
|
308
215
|
status: 500,
|
|
309
216
|
body: { ok: false, error: err.message || "Failed to update AlphaClaw" },
|
|
310
217
|
};
|
|
218
|
+
} finally {
|
|
219
|
+
kUpdateInProgress = false;
|
|
311
220
|
}
|
|
312
221
|
};
|
|
313
222
|
|