@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.
@@ -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
- useEffect(() => {
161
- const list = getModelCatalogModels(modelsFetchState.data);
162
- if (!modelsFetchState.data) return;
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
- setModelsError(list.length > 0 ? null : "No models found");
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
- }, [modelsFetchState.data, setVals, vals.MODEL_KEY]);
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(modelsFetchState.loading && !hasModels);
177
- }, [modelsFetchState.data, modelsFetchState.loading]);
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.4",
30
- preferredKeys: ["openai-codex/gpt-5.4"],
29
+ label: "GPT-5.5",
30
+ preferredKeys: ["openai-codex/gpt-5.5"],
31
31
  },
32
32
  {
33
33
  label: "Gemini 3.1 Pro",
@@ -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 kFallbackOnboardingModels = [
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.4",
116
+ key: "openai-codex/gpt-5.5",
116
117
  provider: "openai-codex",
117
- label: "GPT-5.4",
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;
@@ -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
- ...process.env,
46
- HOME: kRootDir,
47
- OPENCLAW_HOME: kRootDir,
48
- OPENCLAW_CONFIG_PATH: `${OPENCLAW_DIR}/openclaw.json`,
49
- OPENCLAW_STATE_DIR: OPENCLAW_DIR,
50
- XDG_CONFIG_HOME: OPENCLAW_DIR,
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,
@@ -99,6 +99,7 @@ const registerServerRoutes = ({
99
99
  normalizeOnboardingModels,
100
100
  readOpenclawVersion: (options) =>
101
101
  openclawVersionService?.readOpenclawVersion(options),
102
+ isOnboarded,
102
103
  authProfiles,
103
104
  readEnvFile,
104
105
  writeEnvFile,
@@ -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();