@chrysb/alphaclaw 0.9.10 → 0.9.12
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 +1383 -1383
- package/lib/public/js/components/welcome/use-welcome.js +37 -8
- package/lib/public/js/lib/model-config.js +2 -2
- package/lib/server/constants.js +9 -3
- package/lib/server/gateway.js +119 -8
- package/lib/server/init/register-server-routes.js +1 -0
- package/lib/server/init/runtime-init.js +2 -0
- package/lib/server/model-catalog-bootstrap.json +4443 -0
- package/lib/server/model-catalog-cache.js +40 -27
- 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/routes/models.js +2 -0
- package/lib/server/usage-tracker-config.js +28 -3
- package/lib/server.js +4 -0
- package/package.json +3 -7
- package/patches/openclaw+2026.4.21.patch +0 -13
- package/scripts/apply-openclaw-patches.js +0 -99
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from "preact/hooks";
|
|
1
|
+
import { useCallback, useEffect, useState } from "preact/hooks";
|
|
2
2
|
import {
|
|
3
3
|
runOnboard,
|
|
4
4
|
verifyGithubOnboardingRepo,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
fetchModels,
|
|
8
8
|
} from "../../lib/api.js";
|
|
9
9
|
import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
|
|
10
|
+
import { usePolling } from "../../hooks/usePolling.js";
|
|
10
11
|
import {
|
|
11
12
|
getModelProvider,
|
|
12
13
|
getAuthProviderFromModelProvider,
|
|
@@ -17,7 +18,9 @@ import {
|
|
|
17
18
|
import {
|
|
18
19
|
getInitialOnboardingModelKey,
|
|
19
20
|
getModelCatalogModels,
|
|
21
|
+
isModelCatalogRefreshing,
|
|
20
22
|
kModelCatalogCacheKey,
|
|
23
|
+
kModelCatalogPollIntervalMs,
|
|
21
24
|
preloadModelCatalog,
|
|
22
25
|
} from "../../lib/model-catalog.js";
|
|
23
26
|
import {
|
|
@@ -100,6 +103,7 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
100
103
|
const [models, setModels] = useState([]);
|
|
101
104
|
const [modelsLoading, setModelsLoading] = useState(true);
|
|
102
105
|
const [modelsError, setModelsError] = useState(null);
|
|
106
|
+
const [modelsRefreshing, setModelsRefreshing] = useState(false);
|
|
103
107
|
const [showAllModels, setShowAllModels] = useState(false);
|
|
104
108
|
const [loading, setLoading] = useState(false);
|
|
105
109
|
const [githubStepLoading, setGithubStepLoading] = useState(false);
|
|
@@ -129,6 +133,11 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
129
133
|
const modelsFetchState = useCachedFetch(kModelCatalogCacheKey, fetchModels, {
|
|
130
134
|
maxAgeMs: 30000,
|
|
131
135
|
});
|
|
136
|
+
const modelsPoll = usePolling(fetchModels, kModelCatalogPollIntervalMs, {
|
|
137
|
+
enabled: modelsRefreshing,
|
|
138
|
+
pauseWhenHidden: true,
|
|
139
|
+
cacheKey: kModelCatalogCacheKey,
|
|
140
|
+
});
|
|
132
141
|
|
|
133
142
|
useEffect(() => {
|
|
134
143
|
// Warm the real catalog immediately so the AI step usually opens ready.
|
|
@@ -157,11 +166,21 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
157
166
|
}));
|
|
158
167
|
};
|
|
159
168
|
|
|
160
|
-
|
|
161
|
-
const list = getModelCatalogModels(
|
|
162
|
-
if (!
|
|
169
|
+
const applyModelCatalog = useCallback((payload) => {
|
|
170
|
+
const list = getModelCatalogModels(payload);
|
|
171
|
+
if (!payload) return;
|
|
172
|
+
const isRefreshing = isModelCatalogRefreshing(payload);
|
|
173
|
+
const isFallbackRefresh =
|
|
174
|
+
String(payload?.source || "") === "fallback" && isRefreshing;
|
|
163
175
|
setModels(list);
|
|
164
|
-
|
|
176
|
+
setModelsRefreshing(isRefreshing);
|
|
177
|
+
setModelsError(
|
|
178
|
+
list.length > 0
|
|
179
|
+
? isFallbackRefresh
|
|
180
|
+
? "Loading full model catalog..."
|
|
181
|
+
: null
|
|
182
|
+
: "No models found",
|
|
183
|
+
);
|
|
165
184
|
const defaultModelKey = getInitialOnboardingModelKey({
|
|
166
185
|
catalog: list,
|
|
167
186
|
currentModelKey: vals.MODEL_KEY,
|
|
@@ -169,12 +188,22 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
169
188
|
if (!vals.MODEL_KEY && defaultModelKey) {
|
|
170
189
|
setVals((prev) => ({ ...prev, MODEL_KEY: defaultModelKey }));
|
|
171
190
|
}
|
|
172
|
-
}, [
|
|
191
|
+
}, [setVals, vals.MODEL_KEY]);
|
|
192
|
+
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
applyModelCatalog(modelsFetchState.data);
|
|
195
|
+
}, [applyModelCatalog, modelsFetchState.data]);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
applyModelCatalog(modelsPoll.data);
|
|
199
|
+
}, [applyModelCatalog, modelsPoll.data]);
|
|
173
200
|
|
|
174
201
|
useEffect(() => {
|
|
175
202
|
const hasModels = getModelCatalogModels(modelsFetchState.data).length > 0;
|
|
176
|
-
setModelsLoading(
|
|
177
|
-
|
|
203
|
+
setModelsLoading(
|
|
204
|
+
(modelsFetchState.loading || modelsPoll.isPolling) && !hasModels,
|
|
205
|
+
);
|
|
206
|
+
}, [modelsFetchState.data, modelsFetchState.loading, modelsPoll.isPolling]);
|
|
178
207
|
|
|
179
208
|
useEffect(() => {
|
|
180
209
|
if (!modelsFetchState.error) return;
|
|
@@ -26,8 +26,8 @@ export const kFeaturedModelDefs = [
|
|
|
26
26
|
preferredKeys: ["openai-codex/gpt-5.3-codex"],
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
label: "GPT-5.
|
|
30
|
-
preferredKeys: ["openai-codex/gpt-5.
|
|
29
|
+
label: "GPT-5.5",
|
|
30
|
+
preferredKeys: ["openai-codex/gpt-5.5"],
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
label: "Gemini 3.1 Pro",
|
package/lib/server/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const os = require("os");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const kBrowseFilePolicies = require("../public/shared/browse-file-policies.json");
|
|
4
|
+
const kBootstrapModelCatalog = require("./model-catalog-bootstrap.json");
|
|
4
5
|
const { parsePositiveInt } = require("./utils/number");
|
|
5
6
|
|
|
6
7
|
// Portable root directory: --root-dir flag sets ALPHACLAW_ROOT_DIR before require
|
|
@@ -90,7 +91,7 @@ const kOnboardingModelProviders = new Set([
|
|
|
90
91
|
"groq",
|
|
91
92
|
"vllm",
|
|
92
93
|
]);
|
|
93
|
-
const
|
|
94
|
+
const kMinimalFallbackOnboardingModels = [
|
|
94
95
|
{
|
|
95
96
|
key: "anthropic/claude-opus-4-7",
|
|
96
97
|
provider: "anthropic",
|
|
@@ -112,9 +113,9 @@ const kFallbackOnboardingModels = [
|
|
|
112
113
|
label: "Claude Haiku 4.6",
|
|
113
114
|
},
|
|
114
115
|
{
|
|
115
|
-
key: "openai-codex/gpt-5.
|
|
116
|
+
key: "openai-codex/gpt-5.5",
|
|
116
117
|
provider: "openai-codex",
|
|
117
|
-
label: "GPT-5.
|
|
118
|
+
label: "GPT-5.5",
|
|
118
119
|
},
|
|
119
120
|
{
|
|
120
121
|
key: "openai-codex/gpt-5.3-codex",
|
|
@@ -137,6 +138,11 @@ const kFallbackOnboardingModels = [
|
|
|
137
138
|
label: "Gemini 3 Flash Preview",
|
|
138
139
|
},
|
|
139
140
|
];
|
|
141
|
+
const kFallbackOnboardingModels =
|
|
142
|
+
Array.isArray(kBootstrapModelCatalog.models) &&
|
|
143
|
+
kBootstrapModelCatalog.models.length > 0
|
|
144
|
+
? kBootstrapModelCatalog.models
|
|
145
|
+
: kMinimalFallbackOnboardingModels;
|
|
140
146
|
|
|
141
147
|
const kVersionCacheTtlMs = 60 * 1000;
|
|
142
148
|
const kLatestVersionCacheTtlMs = 10 * 60 * 1000;
|
package/lib/server/gateway.js
CHANGED
|
@@ -11,11 +11,13 @@ const {
|
|
|
11
11
|
kOnboardingMarkerPath,
|
|
12
12
|
kRootDir,
|
|
13
13
|
} = require("./constants");
|
|
14
|
+
const { withOpenclawStartupEnv } = require("./openclaw-runtime-env");
|
|
14
15
|
|
|
15
16
|
let gatewayChild = null;
|
|
16
17
|
let gatewayExitHandler = null;
|
|
17
18
|
let gatewayLaunchHandler = null;
|
|
18
19
|
const kGatewayStderrTailLines = 50;
|
|
20
|
+
const kPluginRuntimeDepsPreflightTimeoutMs = 120 * 1000;
|
|
19
21
|
let gatewayStderrTail = [];
|
|
20
22
|
const expectedExitPids = new Set();
|
|
21
23
|
|
|
@@ -41,14 +43,117 @@ const setGatewayLaunchHandler = (handler) => {
|
|
|
41
43
|
gatewayLaunchHandler = typeof handler === "function" ? handler : null;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
const gatewayEnv = () =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
const gatewayEnv = () =>
|
|
47
|
+
withOpenclawStartupEnv({
|
|
48
|
+
...process.env,
|
|
49
|
+
HOME: kRootDir,
|
|
50
|
+
OPENCLAW_HOME: kRootDir,
|
|
51
|
+
OPENCLAW_CONFIG_PATH: `${OPENCLAW_DIR}/openclaw.json`,
|
|
52
|
+
OPENCLAW_STATE_DIR: OPENCLAW_DIR,
|
|
53
|
+
XDG_CONFIG_HOME: OPENCLAW_DIR,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const resolveOpenclawExtensionsDir = () => {
|
|
57
|
+
try {
|
|
58
|
+
const entryPath = require.resolve("openclaw");
|
|
59
|
+
const entryDir = path.dirname(entryPath);
|
|
60
|
+
const distDir =
|
|
61
|
+
path.basename(entryDir) === "dist" ? entryDir : path.join(entryDir, "dist");
|
|
62
|
+
return path.join(distDir, "extensions");
|
|
63
|
+
} catch {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const isOpenclawInstallStageDir = (name) =>
|
|
69
|
+
name === ".openclaw-install-stage" ||
|
|
70
|
+
String(name || "").startsWith(".openclaw-install-stage-");
|
|
71
|
+
|
|
72
|
+
const cleanupOpenclawPluginInstallStages = ({
|
|
73
|
+
extensionsDir = resolveOpenclawExtensionsDir(),
|
|
74
|
+
} = {}) => {
|
|
75
|
+
if (!extensionsDir) return 0;
|
|
76
|
+
let removed = 0;
|
|
77
|
+
try {
|
|
78
|
+
for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
|
79
|
+
if (!entry?.isDirectory?.()) continue;
|
|
80
|
+
const pluginDir = path.join(extensionsDir, entry.name);
|
|
81
|
+
for (const child of fs.readdirSync(pluginDir, { withFileTypes: true })) {
|
|
82
|
+
if (!child?.isDirectory?.() || !isOpenclawInstallStageDir(child.name)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const stageDir = path.join(pluginDir, child.name);
|
|
86
|
+
fs.rmSync(stageDir, {
|
|
87
|
+
recursive: true,
|
|
88
|
+
force: true,
|
|
89
|
+
maxRetries: 3,
|
|
90
|
+
retryDelay: 100,
|
|
91
|
+
});
|
|
92
|
+
removed += 1;
|
|
93
|
+
console.log(`[alphaclaw] Removed stale OpenClaw plugin install stage: ${stageDir}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.warn(
|
|
98
|
+
`[alphaclaw] Could not clean OpenClaw plugin install stages: ${err.message}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return removed;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const hasEnabledChannelConfig = () => {
|
|
105
|
+
try {
|
|
106
|
+
const configPath = `${OPENCLAW_DIR}/openclaw.json`;
|
|
107
|
+
if (!fs.existsSync(configPath)) return false;
|
|
108
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
109
|
+
const channels = cfg?.channels && typeof cfg.channels === "object" ? cfg.channels : {};
|
|
110
|
+
return Object.keys(kChannelDefs).some((channel) => channels?.[channel]?.enabled === true);
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const isInstallStageFailure = (err) =>
|
|
117
|
+
/ENOTEMPTY|openclaw-install-stage/i.test(
|
|
118
|
+
[
|
|
119
|
+
err?.message,
|
|
120
|
+
err?.stdout?.toString?.(),
|
|
121
|
+
err?.stderr?.toString?.(),
|
|
122
|
+
]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join("\n"),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const runPluginRuntimeDepsPreflight = () =>
|
|
128
|
+
execSync("openclaw plugins list --json", {
|
|
129
|
+
env: gatewayEnv(),
|
|
130
|
+
timeout: kPluginRuntimeDepsPreflightTimeoutMs,
|
|
131
|
+
encoding: "utf8",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const prepareOpenclawChannelPlugins = () => {
|
|
135
|
+
if (!hasEnabledChannelConfig()) return;
|
|
136
|
+
cleanupOpenclawPluginInstallStages();
|
|
137
|
+
try {
|
|
138
|
+
runPluginRuntimeDepsPreflight();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (!isInstallStageFailure(err)) {
|
|
141
|
+
console.warn(
|
|
142
|
+
`[alphaclaw] OpenClaw plugin preflight failed: ${(err.stderr || err.message || "").toString().trim().slice(0, 300)}`,
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
cleanupOpenclawPluginInstallStages();
|
|
147
|
+
try {
|
|
148
|
+
runPluginRuntimeDepsPreflight();
|
|
149
|
+
console.log("[alphaclaw] OpenClaw plugin preflight recovered after cleaning install stage");
|
|
150
|
+
} catch (retryErr) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[alphaclaw] OpenClaw plugin preflight retry failed: ${(retryErr.stderr || retryErr.message || "").toString().trim().slice(0, 300)}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
52
157
|
|
|
53
158
|
const writeOnboardingMarker = (reason) => {
|
|
54
159
|
fs.mkdirSync(ALPHACLAW_DIR, { recursive: true });
|
|
@@ -123,6 +228,9 @@ const isGatewayRunning = () =>
|
|
|
123
228
|
const runGatewayCmd = (cmd) => {
|
|
124
229
|
console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
|
|
125
230
|
try {
|
|
231
|
+
if (cmd === "--force" || cmd === "restart") {
|
|
232
|
+
prepareOpenclawChannelPlugins();
|
|
233
|
+
}
|
|
126
234
|
const out = execSync(`openclaw gateway ${cmd}`, {
|
|
127
235
|
env: gatewayEnv(),
|
|
128
236
|
timeout: 15000,
|
|
@@ -147,6 +255,7 @@ const launchGatewayProcess = () => {
|
|
|
147
255
|
);
|
|
148
256
|
return gatewayChild;
|
|
149
257
|
}
|
|
258
|
+
prepareOpenclawChannelPlugins();
|
|
150
259
|
gatewayStderrTail = [];
|
|
151
260
|
const child = spawn("openclaw", ["gateway", "run"], {
|
|
152
261
|
env: gatewayEnv(),
|
|
@@ -505,6 +614,8 @@ module.exports = {
|
|
|
505
614
|
isOnboarded,
|
|
506
615
|
isGatewayRunning,
|
|
507
616
|
launchGatewayProcess,
|
|
617
|
+
cleanupOpenclawPluginInstallStages,
|
|
618
|
+
prepareOpenclawChannelPlugins,
|
|
508
619
|
setGatewayExitHandler,
|
|
509
620
|
setGatewayLaunchHandler,
|
|
510
621
|
runGatewayCmd,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const initializeServerRuntime = ({
|
|
2
2
|
fs,
|
|
3
3
|
constants,
|
|
4
|
+
ensureOpenclawStartupEnv,
|
|
4
5
|
startEnvWatcher,
|
|
5
6
|
attachGatewaySignalHandlers,
|
|
6
7
|
cleanupStaleImportTempDirs,
|
|
7
8
|
migrateManagedInternalFiles,
|
|
8
9
|
}) => {
|
|
10
|
+
ensureOpenclawStartupEnv?.({ fsModule: fs });
|
|
9
11
|
startEnvWatcher();
|
|
10
12
|
attachGatewaySignalHandlers();
|
|
11
13
|
cleanupStaleImportTempDirs();
|