@chrysb/alphaclaw 0.9.9 → 0.9.11
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/lib/public/dist/app.bundle.js +1773 -1747
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +4 -5
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +11 -5
- package/lib/public/js/components/cron-tab/cron-helpers.js +13 -1
- package/lib/public/js/components/cron-tab/use-cron-tab.js +4 -3
- package/lib/public/js/components/general/index.js +6 -1
- package/lib/public/js/components/general/use-general-tab.js +17 -20
- package/lib/public/js/components/models-tab/index.js +5 -1
- package/lib/public/js/components/models-tab/model-picker.js +52 -0
- package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -1
- package/lib/public/js/components/pairings.js +75 -4
- package/lib/public/js/components/welcome/use-welcome.js +37 -8
- package/lib/public/js/hooks/usePolling.js +46 -13
- package/lib/public/js/lib/model-config.js +6 -2
- package/lib/server/agents/channels.js +53 -9
- package/lib/server/commands.js +4 -1
- package/lib/server/constants.js +14 -3
- package/lib/server/cost-utils.js +9 -0
- package/lib/server/cron-service.js +12 -1
- package/lib/server/db/doctor/index.js +9 -0
- package/lib/server/db/usage/index.js +13 -0
- package/lib/server/db/watchdog/index.js +13 -1
- package/lib/server/db/webhooks/index.js +13 -1
- package/lib/server/gateway.js +119 -8
- package/lib/server/init/register-server-routes.js +3 -0
- package/lib/server/init/runtime-init.js +2 -0
- package/lib/server/internal-files-migration.js +11 -1
- package/lib/server/model-catalog-bootstrap.json +3193 -0
- package/lib/server/model-catalog-cache.js +124 -32
- package/lib/server/onboarding/github.js +79 -2
- package/lib/server/onboarding/index.js +2 -9
- package/lib/server/onboarding/openclaw.js +18 -4
- package/lib/server/openclaw-runtime-env.js +55 -0
- package/lib/server/openclaw-version.js +2 -1
- package/lib/server/routes/models.js +28 -0
- package/lib/server/routes/pairings.js +106 -15
- package/lib/server/usage-tracker-config.js +28 -3
- package/lib/server/utils/command-output.js +11 -0
- package/lib/server.js +4 -0
- package/lib/setup/gitignore +2 -0
- package/package.json +2 -2
- package/patches/openclaw+2026.4.23.patch +63 -0
- package/patches/openclaw+2026.4.15.patch +0 -13
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { ALPHACLAW_DIR, kFallbackOnboardingModels } = require("./constants");
|
|
4
|
+
const { getCommandOutputCandidates } = require("./utils/command-output");
|
|
4
5
|
|
|
5
6
|
const kModelCatalogCacheVersion = 1;
|
|
6
7
|
const kModelCatalogRefreshBackoffMs = 30 * 1000;
|
|
8
|
+
const kModelCatalogLoadTimeoutMs = 120 * 1000;
|
|
9
|
+
const kModelCatalogBootstrapSource = "bootstrap";
|
|
7
10
|
const kDefaultCachePath = path.join(ALPHACLAW_DIR, "cache", "model-catalog.json");
|
|
8
11
|
|
|
9
12
|
const createResponse = ({
|
|
@@ -21,6 +24,12 @@ const createResponse = ({
|
|
|
21
24
|
models,
|
|
22
25
|
});
|
|
23
26
|
|
|
27
|
+
const normalizeOpenclawVersion = (value) => {
|
|
28
|
+
if (typeof value !== "string") return null;
|
|
29
|
+
const normalized = value.trim();
|
|
30
|
+
return normalized || null;
|
|
31
|
+
};
|
|
32
|
+
|
|
24
33
|
const normalizeCachedModels = ({
|
|
25
34
|
models,
|
|
26
35
|
normalizeOnboardingModels = (items) => items,
|
|
@@ -48,10 +57,20 @@ const normalizeCacheEntry = ({
|
|
|
48
57
|
return {
|
|
49
58
|
version: kModelCatalogCacheVersion,
|
|
50
59
|
fetchedAt,
|
|
60
|
+
openclawVersion: normalizeOpenclawVersion(raw.openclawVersion),
|
|
51
61
|
models,
|
|
52
62
|
};
|
|
53
63
|
};
|
|
54
64
|
|
|
65
|
+
const parseCatalogModelsFromOutput = ({
|
|
66
|
+
rawOutput,
|
|
67
|
+
parseJsonFromNoisyOutput = () => ({}),
|
|
68
|
+
normalizeOnboardingModels = (items) => items,
|
|
69
|
+
} = {}) => {
|
|
70
|
+
const parsed = parseJsonFromNoisyOutput(rawOutput);
|
|
71
|
+
return normalizeOnboardingModels(parsed?.models || []);
|
|
72
|
+
};
|
|
73
|
+
|
|
55
74
|
const createModelCatalogCache = ({
|
|
56
75
|
fsModule = fs,
|
|
57
76
|
pathModule = path,
|
|
@@ -59,6 +78,8 @@ const createModelCatalogCache = ({
|
|
|
59
78
|
gatewayEnv = () => ({}),
|
|
60
79
|
parseJsonFromNoisyOutput = () => ({}),
|
|
61
80
|
normalizeOnboardingModels = (items) => items,
|
|
81
|
+
readOpenclawVersion = () => null,
|
|
82
|
+
shouldStartDynamicRefresh = () => true,
|
|
62
83
|
fallbackModels = kFallbackOnboardingModels,
|
|
63
84
|
cachePath = kDefaultCachePath,
|
|
64
85
|
refreshBackoffMs = kModelCatalogRefreshBackoffMs,
|
|
@@ -74,6 +95,23 @@ const createModelCatalogCache = ({
|
|
|
74
95
|
let retryTimer = null;
|
|
75
96
|
let backoffUntilMs = 0;
|
|
76
97
|
|
|
98
|
+
const readCurrentOpenclawVersion = ({ refresh = false } = {}) => {
|
|
99
|
+
try {
|
|
100
|
+
return normalizeOpenclawVersion(readOpenclawVersion({ refresh }));
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const isCompatibleWithCurrentOpenclaw = ({
|
|
107
|
+
entry,
|
|
108
|
+
currentOpenclawVersion,
|
|
109
|
+
} = {}) => {
|
|
110
|
+
if (!entry) return false;
|
|
111
|
+
if (!currentOpenclawVersion) return true;
|
|
112
|
+
return entry.openclawVersion === currentOpenclawVersion;
|
|
113
|
+
};
|
|
114
|
+
|
|
77
115
|
const clearRetryTimer = () => {
|
|
78
116
|
if (!retryTimer) return;
|
|
79
117
|
clearTimeoutFn(retryTimer);
|
|
@@ -82,6 +120,14 @@ const createModelCatalogCache = ({
|
|
|
82
120
|
|
|
83
121
|
const isRefreshPending = () => !!refreshPromise || !!retryTimer;
|
|
84
122
|
|
|
123
|
+
const canStartDynamicRefresh = () => {
|
|
124
|
+
try {
|
|
125
|
+
return shouldStartDynamicRefresh() !== false;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
85
131
|
const setCacheEntry = (entry, { fresh = false } = {}) => {
|
|
86
132
|
memoryCache = entry;
|
|
87
133
|
cacheLoaded = true;
|
|
@@ -121,41 +167,76 @@ const createModelCatalogCache = ({
|
|
|
121
167
|
};
|
|
122
168
|
|
|
123
169
|
const loadFreshCatalog = async () => {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
170
|
+
const openclawVersion = readCurrentOpenclawVersion({ refresh: true });
|
|
171
|
+
let models = [];
|
|
172
|
+
let recoveredFromCommandError = false;
|
|
173
|
+
try {
|
|
174
|
+
const output = await shellCmd("openclaw models list --all --json", {
|
|
175
|
+
env: gatewayEnv(),
|
|
176
|
+
timeout: kModelCatalogLoadTimeoutMs,
|
|
177
|
+
});
|
|
178
|
+
models = parseCatalogModelsFromOutput({
|
|
179
|
+
rawOutput: output,
|
|
180
|
+
parseJsonFromNoisyOutput,
|
|
181
|
+
normalizeOnboardingModels,
|
|
182
|
+
});
|
|
183
|
+
} catch (err) {
|
|
184
|
+
for (const rawOutput of getCommandOutputCandidates(err)) {
|
|
185
|
+
models = parseCatalogModelsFromOutput({
|
|
186
|
+
rawOutput,
|
|
187
|
+
parseJsonFromNoisyOutput,
|
|
188
|
+
normalizeOnboardingModels,
|
|
189
|
+
});
|
|
190
|
+
if (models.length > 0) {
|
|
191
|
+
recoveredFromCommandError = true;
|
|
192
|
+
logger.warn?.(
|
|
193
|
+
`[models] Recovered model catalog from failed command output: ${err.message || String(err)}`,
|
|
194
|
+
);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (models.length === 0) throw err;
|
|
199
|
+
}
|
|
130
200
|
if (models.length === 0) {
|
|
131
201
|
throw new Error("No models found");
|
|
132
202
|
}
|
|
133
203
|
const entry = {
|
|
134
204
|
version: kModelCatalogCacheVersion,
|
|
135
205
|
fetchedAt: now(),
|
|
206
|
+
openclawVersion,
|
|
136
207
|
models,
|
|
137
208
|
};
|
|
138
209
|
writeDiskCache(entry);
|
|
139
210
|
setCacheEntry(entry, { fresh: true });
|
|
211
|
+
if (recoveredFromCommandError) {
|
|
212
|
+
backoffUntilMs = 0;
|
|
213
|
+
clearRetryTimer();
|
|
214
|
+
}
|
|
140
215
|
return entry;
|
|
141
216
|
};
|
|
142
217
|
|
|
143
218
|
const scheduleRetry = () => {
|
|
144
|
-
if (!
|
|
219
|
+
if (!canStartDynamicRefresh()) {
|
|
220
|
+
clearRetryTimer();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (retryTimer) return;
|
|
145
224
|
const delayMs = Math.max(backoffUntilMs - now(), 0);
|
|
146
225
|
retryTimer = setTimeoutFn(() => {
|
|
147
226
|
retryTimer = null;
|
|
148
|
-
if (!
|
|
227
|
+
if (!canStartDynamicRefresh()) return;
|
|
228
|
+
if (refreshPromise) return;
|
|
229
|
+
if (memoryCache && !cacheIsStale) return;
|
|
149
230
|
void startBackgroundRefresh();
|
|
150
231
|
}, delayMs);
|
|
151
232
|
if (typeof retryTimer?.unref === "function") retryTimer.unref();
|
|
152
233
|
};
|
|
153
234
|
|
|
154
235
|
const handleRefreshFailure = (err) => {
|
|
236
|
+
backoffUntilMs = now() + refreshBackoffMs;
|
|
237
|
+
scheduleRetry();
|
|
155
238
|
if (memoryCache) {
|
|
156
239
|
cacheIsStale = true;
|
|
157
|
-
backoffUntilMs = now() + refreshBackoffMs;
|
|
158
|
-
scheduleRetry();
|
|
159
240
|
logger.error?.(
|
|
160
241
|
`[models] Failed to refresh cached models: ${err.message || String(err)}`,
|
|
161
242
|
);
|
|
@@ -167,8 +248,11 @@ const createModelCatalogCache = ({
|
|
|
167
248
|
};
|
|
168
249
|
|
|
169
250
|
const startBackgroundRefresh = () => {
|
|
251
|
+
if (!canStartDynamicRefresh()) {
|
|
252
|
+
clearRetryTimer();
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
170
255
|
readDiskCache();
|
|
171
|
-
if (!memoryCache) return null;
|
|
172
256
|
if (refreshPromise) return refreshPromise;
|
|
173
257
|
if (retryTimer) return null;
|
|
174
258
|
if (backoffUntilMs > now()) {
|
|
@@ -190,6 +274,21 @@ const createModelCatalogCache = ({
|
|
|
190
274
|
return {
|
|
191
275
|
async getCatalogResponse() {
|
|
192
276
|
readDiskCache();
|
|
277
|
+
if (memoryCache && !cacheIsStale) {
|
|
278
|
+
const currentOpenclawVersion = readCurrentOpenclawVersion({
|
|
279
|
+
refresh: true,
|
|
280
|
+
});
|
|
281
|
+
if (
|
|
282
|
+
!isCompatibleWithCurrentOpenclaw({
|
|
283
|
+
entry: memoryCache,
|
|
284
|
+
currentOpenclawVersion,
|
|
285
|
+
})
|
|
286
|
+
) {
|
|
287
|
+
cacheIsStale = true;
|
|
288
|
+
backoffUntilMs = 0;
|
|
289
|
+
clearRetryTimer();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
193
292
|
if (memoryCache && !cacheIsStale) {
|
|
194
293
|
return createResponse({
|
|
195
294
|
source: "openclaw",
|
|
@@ -200,34 +299,25 @@ const createModelCatalogCache = ({
|
|
|
200
299
|
});
|
|
201
300
|
}
|
|
202
301
|
if (memoryCache) {
|
|
203
|
-
startBackgroundRefresh();
|
|
302
|
+
const didStartRefresh = !!startBackgroundRefresh();
|
|
204
303
|
return createResponse({
|
|
205
304
|
source: "cache",
|
|
206
305
|
fetchedAt: memoryCache.fetchedAt,
|
|
207
306
|
stale: true,
|
|
208
|
-
refreshing:
|
|
307
|
+
refreshing:
|
|
308
|
+
canStartDynamicRefresh() && (didStartRefresh || isRefreshPending()),
|
|
209
309
|
models: memoryCache.models,
|
|
210
310
|
});
|
|
211
311
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} catch (err) {
|
|
222
|
-
handleRefreshFailure(err);
|
|
223
|
-
return createResponse({
|
|
224
|
-
source: "fallback",
|
|
225
|
-
fetchedAt: null,
|
|
226
|
-
stale: false,
|
|
227
|
-
refreshing: false,
|
|
228
|
-
models: fallbackModels,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
312
|
+
const didStartRefresh = !!startBackgroundRefresh();
|
|
313
|
+
return createResponse({
|
|
314
|
+
source: kModelCatalogBootstrapSource,
|
|
315
|
+
fetchedAt: null,
|
|
316
|
+
stale: true,
|
|
317
|
+
refreshing:
|
|
318
|
+
canStartDynamicRefresh() && (didStartRefresh || isRefreshPending()),
|
|
319
|
+
models: fallbackModels,
|
|
320
|
+
});
|
|
231
321
|
},
|
|
232
322
|
|
|
233
323
|
markStale() {
|
|
@@ -247,5 +337,7 @@ module.exports = {
|
|
|
247
337
|
normalizeCacheEntry,
|
|
248
338
|
kModelCatalogCacheVersion,
|
|
249
339
|
kModelCatalogRefreshBackoffMs,
|
|
340
|
+
kModelCatalogLoadTimeoutMs,
|
|
341
|
+
kModelCatalogBootstrapSource,
|
|
250
342
|
kDefaultCachePath,
|
|
251
343
|
};
|
|
@@ -113,6 +113,42 @@ const findOwnedRepoByName = async ({
|
|
|
113
113
|
return null;
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
+
const findAccessibleOrgByLogin = async ({ repoOwner, ghHeaders }) => {
|
|
117
|
+
const normalizedRepoOwner = String(repoOwner || "").trim().toLowerCase();
|
|
118
|
+
if (!normalizedRepoOwner) return null;
|
|
119
|
+
|
|
120
|
+
let nextUrl = "https://api.github.com/user/orgs?per_page=100&page=1";
|
|
121
|
+
while (nextUrl) {
|
|
122
|
+
const res = await fetch(nextUrl, { headers: ghHeaders });
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
const details = await parseGithubErrorMessage(res);
|
|
125
|
+
return {
|
|
126
|
+
error:
|
|
127
|
+
`Cannot verify organization "${repoOwner}" access: ${details}. ` +
|
|
128
|
+
"Check the owner name or use a token that can access that organization.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const orgs = await res.json();
|
|
133
|
+
if (!Array.isArray(orgs)) {
|
|
134
|
+
return {
|
|
135
|
+
error: `Cannot verify organization "${repoOwner}" access from GitHub response.`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const org = orgs.find(
|
|
140
|
+
(item) =>
|
|
141
|
+
String(item?.login || "").trim().toLowerCase() ===
|
|
142
|
+
normalizedRepoOwner,
|
|
143
|
+
);
|
|
144
|
+
if (org) return { org };
|
|
145
|
+
|
|
146
|
+
nextUrl = getNextGithubPageUrl(res.headers?.get?.("link"));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
};
|
|
151
|
+
|
|
116
152
|
const isClassicPat = (token) => String(token || "").startsWith("ghp_");
|
|
117
153
|
const isFineGrainedPat = (token) =>
|
|
118
154
|
String(token || "").startsWith("github_pat_");
|
|
@@ -187,7 +223,43 @@ const verifyGithubRepoForOnboarding = async ({
|
|
|
187
223
|
error: `Repository "${repoUrl}" not found. Check the repo name and token permissions.`,
|
|
188
224
|
};
|
|
189
225
|
}
|
|
190
|
-
|
|
226
|
+
if (!viewerLogin) {
|
|
227
|
+
return {
|
|
228
|
+
ok: false,
|
|
229
|
+
status: 400,
|
|
230
|
+
error: "Cannot verify GitHub account owner for this token.",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (repoOwner.toLowerCase() !== viewerLogin.toLowerCase()) {
|
|
234
|
+
const orgLookup = await findAccessibleOrgByLogin({
|
|
235
|
+
repoOwner,
|
|
236
|
+
ghHeaders,
|
|
237
|
+
});
|
|
238
|
+
if (!orgLookup?.org) {
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
status: 400,
|
|
242
|
+
error:
|
|
243
|
+
orgLookup?.error ||
|
|
244
|
+
`Repository owner "${repoOwner}" does not match the authenticated GitHub user "${viewerLogin}" ` +
|
|
245
|
+
"and was not found in the token's accessible organizations. Check the owner name or use a token that can create repositories for that organization.",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
ok: true,
|
|
250
|
+
repoExists: false,
|
|
251
|
+
repoIsEmpty: false,
|
|
252
|
+
createOwnerType: "org",
|
|
253
|
+
viewerLogin,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
ok: true,
|
|
258
|
+
repoExists: false,
|
|
259
|
+
repoIsEmpty: false,
|
|
260
|
+
createOwnerType: "user",
|
|
261
|
+
viewerLogin,
|
|
262
|
+
};
|
|
191
263
|
}
|
|
192
264
|
if (checkRes.ok) {
|
|
193
265
|
const commitsRes = await fetch(
|
|
@@ -250,6 +322,7 @@ const ensureGithubRepoAccessible = async ({
|
|
|
250
322
|
githubToken,
|
|
251
323
|
}) => {
|
|
252
324
|
const ghHeaders = buildGithubHeaders(githubToken);
|
|
325
|
+
const [repoOwner = ""] = String(repoUrl || "").split("/");
|
|
253
326
|
const verification = await verifyGithubRepoForOnboarding({
|
|
254
327
|
repoUrl,
|
|
255
328
|
githubToken,
|
|
@@ -262,7 +335,11 @@ const ensureGithubRepoAccessible = async ({
|
|
|
262
335
|
|
|
263
336
|
try {
|
|
264
337
|
console.log(`[onboard] Creating repo ${repoUrl}...`);
|
|
265
|
-
const
|
|
338
|
+
const createUrl =
|
|
339
|
+
verification.createOwnerType === "org"
|
|
340
|
+
? `https://api.github.com/orgs/${repoOwner}/repos`
|
|
341
|
+
: "https://api.github.com/user/repos";
|
|
342
|
+
const createRes = await fetch(createUrl, {
|
|
266
343
|
method: "POST",
|
|
267
344
|
headers: { ...ghHeaders, "Content-Type": "application/json" },
|
|
268
345
|
body: JSON.stringify({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const { kSetupDir
|
|
2
|
+
const { kSetupDir } = require("../constants");
|
|
3
3
|
const {
|
|
4
4
|
resolveConfigIncludes,
|
|
5
5
|
resolveImportedConfigPaths,
|
|
@@ -491,14 +491,7 @@ const createOnboardingService = ({
|
|
|
491
491
|
await shellCmd(
|
|
492
492
|
`openclaw onboard ${onboardArgs.map((a) => `"${a}"`).join(" ")}`,
|
|
493
493
|
{
|
|
494
|
-
env:
|
|
495
|
-
...process.env,
|
|
496
|
-
HOME: kRootDir,
|
|
497
|
-
OPENCLAW_HOME: kRootDir,
|
|
498
|
-
OPENCLAW_CONFIG_PATH: `${OPENCLAW_DIR}/openclaw.json`,
|
|
499
|
-
OPENCLAW_STATE_DIR: OPENCLAW_DIR,
|
|
500
|
-
XDG_CONFIG_HOME: OPENCLAW_DIR,
|
|
501
|
-
},
|
|
494
|
+
env: gatewayEnv(),
|
|
502
495
|
timeout: 120000,
|
|
503
496
|
},
|
|
504
497
|
);
|
|
@@ -162,7 +162,10 @@ const getSafeImportedDmPolicy = (channelConfig = {}) => {
|
|
|
162
162
|
return channelConfig?.dmPolicy || "pairing";
|
|
163
163
|
};
|
|
164
164
|
|
|
165
|
-
const applyFreshOnboardingChannels = ({
|
|
165
|
+
const applyFreshOnboardingChannels = ({
|
|
166
|
+
cfg,
|
|
167
|
+
varMap,
|
|
168
|
+
}) => {
|
|
166
169
|
if (varMap.TELEGRAM_BOT_TOKEN) {
|
|
167
170
|
cfg.channels.telegram = {
|
|
168
171
|
enabled: true,
|
|
@@ -214,11 +217,18 @@ const applyFreshOnboardingChannels = ({ cfg, varMap }) => {
|
|
|
214
217
|
ensureUsageTrackerPluginEntry(cfg);
|
|
215
218
|
};
|
|
216
219
|
|
|
217
|
-
const writeSanitizedOpenclawConfig = ({
|
|
220
|
+
const writeSanitizedOpenclawConfig = ({
|
|
221
|
+
fs,
|
|
222
|
+
openclawDir,
|
|
223
|
+
varMap,
|
|
224
|
+
}) => {
|
|
218
225
|
const configPath = `${openclawDir}/openclaw.json`;
|
|
219
226
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
220
227
|
ensureManagedConfigShell(cfg);
|
|
221
|
-
applyFreshOnboardingChannels({
|
|
228
|
+
applyFreshOnboardingChannels({
|
|
229
|
+
cfg,
|
|
230
|
+
varMap,
|
|
231
|
+
});
|
|
222
232
|
|
|
223
233
|
let content = JSON.stringify(cfg, null, 2);
|
|
224
234
|
const replacements = buildSecretReplacements(varMap, process.env);
|
|
@@ -239,7 +249,11 @@ const writeSanitizedOpenclawConfig = ({ fs, openclawDir, varMap }) => {
|
|
|
239
249
|
console.log("[onboard] Config sanitized");
|
|
240
250
|
};
|
|
241
251
|
|
|
242
|
-
const writeManagedImportOpenclawConfig = ({
|
|
252
|
+
const writeManagedImportOpenclawConfig = ({
|
|
253
|
+
fs,
|
|
254
|
+
openclawDir,
|
|
255
|
+
varMap,
|
|
256
|
+
}) => {
|
|
243
257
|
const configPath = `${openclawDir}/openclaw.json`;
|
|
244
258
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
245
259
|
ensureManagedConfigShell(cfg);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { kRootDir } = require("./constants");
|
|
4
|
+
|
|
5
|
+
const kDefaultOpenclawCompileCacheDir = path.join(
|
|
6
|
+
kRootDir,
|
|
7
|
+
"cache",
|
|
8
|
+
"openclaw-compile-cache",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const normalizeEnvValue = (value) => String(value || "").trim();
|
|
12
|
+
|
|
13
|
+
const resolveOpenclawCompileCacheDir = (env = process.env) =>
|
|
14
|
+
normalizeEnvValue(env.NODE_COMPILE_CACHE) || kDefaultOpenclawCompileCacheDir;
|
|
15
|
+
|
|
16
|
+
const resolveOpenclawNoRespawn = (env = process.env) =>
|
|
17
|
+
normalizeEnvValue(env.OPENCLAW_NO_RESPAWN) || "1";
|
|
18
|
+
|
|
19
|
+
const withOpenclawStartupEnv = (env = process.env) => ({
|
|
20
|
+
...env,
|
|
21
|
+
NODE_COMPILE_CACHE: resolveOpenclawCompileCacheDir(env),
|
|
22
|
+
OPENCLAW_NO_RESPAWN: resolveOpenclawNoRespawn(env),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const ensureOpenclawStartupEnv = ({
|
|
26
|
+
fsModule = fs,
|
|
27
|
+
env = process.env,
|
|
28
|
+
logger = console,
|
|
29
|
+
} = {}) => {
|
|
30
|
+
const nextEnv = withOpenclawStartupEnv(env);
|
|
31
|
+
try {
|
|
32
|
+
fsModule.mkdirSync(nextEnv.NODE_COMPILE_CACHE, { recursive: true });
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger?.warn?.(
|
|
35
|
+
`[alphaclaw] OpenClaw compile cache directory unavailable: ${err.message}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!normalizeEnvValue(env.NODE_COMPILE_CACHE)) {
|
|
40
|
+
env.NODE_COMPILE_CACHE = nextEnv.NODE_COMPILE_CACHE;
|
|
41
|
+
}
|
|
42
|
+
if (!normalizeEnvValue(env.OPENCLAW_NO_RESPAWN)) {
|
|
43
|
+
env.OPENCLAW_NO_RESPAWN = nextEnv.OPENCLAW_NO_RESPAWN;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return nextEnv;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
kDefaultOpenclawCompileCacheDir,
|
|
51
|
+
ensureOpenclawStartupEnv,
|
|
52
|
+
resolveOpenclawCompileCacheDir,
|
|
53
|
+
resolveOpenclawNoRespawn,
|
|
54
|
+
withOpenclawStartupEnv,
|
|
55
|
+
};
|
|
@@ -24,9 +24,10 @@ const createOpenclawVersionService = ({
|
|
|
24
24
|
};
|
|
25
25
|
let kOpenclawUpdateInProgress = false;
|
|
26
26
|
|
|
27
|
-
const readOpenclawVersion = () => {
|
|
27
|
+
const readOpenclawVersion = ({ refresh = false } = {}) => {
|
|
28
28
|
const now = Date.now();
|
|
29
29
|
if (
|
|
30
|
+
!refresh &&
|
|
30
31
|
kOpenclawVersionCache.value &&
|
|
31
32
|
now - kOpenclawVersionCache.fetchedAt < kVersionCacheTtlMs
|
|
32
33
|
) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { kFallbackOnboardingModels } = require("../constants");
|
|
2
2
|
const { createModelCatalogCache } = require("../model-catalog-cache");
|
|
3
|
+
const { getCommandOutputCandidates } = require("../utils/command-output");
|
|
3
4
|
|
|
4
5
|
const runModelsGitSync = async (shellCmd) => {
|
|
5
6
|
if (typeof shellCmd !== "function") return null;
|
|
@@ -13,12 +14,25 @@ const runModelsGitSync = async (shellCmd) => {
|
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
const parseJsonFromShellError = ({
|
|
18
|
+
error,
|
|
19
|
+
parseJsonFromNoisyOutput = () => null,
|
|
20
|
+
} = {}) => {
|
|
21
|
+
for (const rawOutput of getCommandOutputCandidates(error)) {
|
|
22
|
+
const parsed = parseJsonFromNoisyOutput(rawOutput);
|
|
23
|
+
if (parsed) return parsed;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
16
28
|
const registerModelRoutes = ({
|
|
17
29
|
app,
|
|
18
30
|
shellCmd,
|
|
19
31
|
gatewayEnv,
|
|
20
32
|
parseJsonFromNoisyOutput,
|
|
21
33
|
normalizeOnboardingModels,
|
|
34
|
+
readOpenclawVersion,
|
|
35
|
+
isOnboarded = () => true,
|
|
22
36
|
authProfiles,
|
|
23
37
|
readEnvFile,
|
|
24
38
|
writeEnvFile,
|
|
@@ -28,6 +42,8 @@ const registerModelRoutes = ({
|
|
|
28
42
|
gatewayEnv,
|
|
29
43
|
parseJsonFromNoisyOutput,
|
|
30
44
|
normalizeOnboardingModels,
|
|
45
|
+
readOpenclawVersion,
|
|
46
|
+
shouldStartDynamicRefresh: isOnboarded,
|
|
31
47
|
fallbackModels: kFallbackOnboardingModels,
|
|
32
48
|
}),
|
|
33
49
|
}) => {
|
|
@@ -180,6 +196,18 @@ const registerModelRoutes = ({
|
|
|
180
196
|
imageModel: parsed.imageModel || null,
|
|
181
197
|
});
|
|
182
198
|
} catch (err) {
|
|
199
|
+
const parsed = parseJsonFromShellError({
|
|
200
|
+
error: err,
|
|
201
|
+
parseJsonFromNoisyOutput,
|
|
202
|
+
});
|
|
203
|
+
if (parsed) {
|
|
204
|
+
return res.json({
|
|
205
|
+
ok: true,
|
|
206
|
+
modelKey: parsed.resolvedDefault || parsed.defaultModel || null,
|
|
207
|
+
fallbacks: parsed.fallbacks || [],
|
|
208
|
+
imageModel: parsed.imageModel || null,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
183
211
|
res.json({
|
|
184
212
|
ok: false,
|
|
185
213
|
error: err.message || "Failed to read model status",
|