@chrysb/alphaclaw 0.9.28 → 0.9.29

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.
@@ -53,12 +53,16 @@ const getAiGroupError = (vals, ctx = {}) => {
53
53
  if (!hasValue(vals.MODEL_KEY) || !String(vals.MODEL_KEY).includes("/")) {
54
54
  return "Choose a model to continue.";
55
55
  }
56
- if (ctx.selectedProvider === "openai-codex" && ctx.codexLoading) {
56
+ if (
57
+ ctx.selectedProvider === "openai-codex" &&
58
+ ctx.codexLoading &&
59
+ !ctx.hasAi
60
+ ) {
57
61
  return "Checking Codex OAuth status. Try Next again in a moment.";
58
62
  }
59
63
  if (!ctx.hasAi) {
60
64
  return ctx.selectedProvider === "openai-codex"
61
- ? "Connect Codex OAuth to continue."
65
+ ? "Connect Codex OAuth or enter an OpenAI API key to continue."
62
66
  : "Add credentials for the selected model provider to continue.";
63
67
  }
64
68
  return "";
@@ -2,6 +2,7 @@ import { h } from "preact";
2
2
  import { useEffect, useState } from "preact/hooks";
3
3
  import htm from "htm";
4
4
  import { LoadingSpinner } from "../loading-spinner.js";
5
+ import { fetchOnboardProgress } from "../../lib/api.js";
5
6
 
6
7
  const html = htm.bind(h);
7
8
  const kSetupTips = [
@@ -30,9 +31,24 @@ const kSetupTips = [
30
31
  text: "Be incredibly careful installing skills from the internet - they may contain malicious code.",
31
32
  },
32
33
  ];
34
+ const kDefaultProgress = {
35
+ stage: "creating_repo",
36
+ message: "Creating repo...",
37
+ };
38
+ const kProgressStepNumbers = {
39
+ creating_repo: 1,
40
+ running_openclaw_onboard: 2,
41
+ initial_git_push: 3,
42
+ starting_gateway: 4,
43
+ };
44
+ const kProgressPollIntervalMs = 500;
45
+ const kProgressDotsIntervalMs = 450;
46
+ const kProgressDotSlots = [1, 2, 3];
33
47
 
34
48
  export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
35
49
  const [tipIndex, setTipIndex] = useState(0);
50
+ const [progress, setProgress] = useState(kDefaultProgress);
51
+ const [progressDotCount, setProgressDotCount] = useState(1);
36
52
 
37
53
  useEffect(() => {
38
54
  if (error || !loading) return;
@@ -42,6 +58,35 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
42
58
  return () => clearInterval(timer);
43
59
  }, [error, loading]);
44
60
 
61
+ useEffect(() => {
62
+ if (error || !loading) return;
63
+ let cancelled = false;
64
+ const refreshProgress = async () => {
65
+ try {
66
+ const progress = await fetchOnboardProgress();
67
+ if (!cancelled && progress?.message) {
68
+ setProgress(progress);
69
+ }
70
+ } catch {}
71
+ };
72
+ setProgress(kDefaultProgress);
73
+ refreshProgress();
74
+ const timer = setInterval(refreshProgress, kProgressPollIntervalMs);
75
+ return () => {
76
+ cancelled = true;
77
+ clearInterval(timer);
78
+ };
79
+ }, [error, loading]);
80
+
81
+ useEffect(() => {
82
+ if (error || !loading) return;
83
+ setProgressDotCount(1);
84
+ const timer = setInterval(() => {
85
+ setProgressDotCount((count) => (count % 3) + 1);
86
+ }, kProgressDotsIntervalMs);
87
+ return () => clearInterval(timer);
88
+ }, [error, loading]);
89
+
45
90
  if (error) {
46
91
  return html`
47
92
  <div class="py-4 flex flex-col items-center text-center gap-3">
@@ -77,6 +122,8 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
77
122
  }
78
123
 
79
124
  const currentTip = kSetupTips[tipIndex];
125
+ const progressStep = kProgressStepNumbers[progress.stage] || 1;
126
+ const progressLabel = (progress.message || kDefaultProgress.message).replace(/\.{1,3}$/, "");
80
127
 
81
128
  return html`
82
129
  <div class="relative min-h-[320px] pt-4 pb-20 flex">
@@ -84,10 +131,17 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
84
131
  class="flex-1 flex flex-col items-center justify-center text-center gap-4"
85
132
  >
86
133
  <${LoadingSpinner} className="h-8 w-8 text-body" />
87
- <h3 class="text-lg font-semibold text-body">
88
- Initializing OpenClaw...
89
- </h3>
90
- <p class="text-sm text-fg-muted">This could take 10-15 seconds</p>
134
+ <h3 class="text-lg font-semibold text-body">Initializing AlphaClaw...</h3>
135
+ <p class="text-sm text-fg-muted">
136
+ ${progressStep} / 4: ${progressLabel}<span class="inline-flex" aria-hidden="true"
137
+ >${kProgressDotSlots.map(
138
+ (slot) => html`<span style=${{ visibility: slot <= progressDotCount ? "visible" : "hidden" }}
139
+ >.</span
140
+ >`,
141
+ )}</span
142
+ >
143
+ </p>
144
+ <p class="text-xs text-fg-muted">This could take up to 30 seconds</p>
91
145
  </div>
92
146
  <div
93
147
  class="absolute bottom-3 left-3 right-3 bg-field border border-border rounded-lg px-3 py-2 text-xs text-fg-muted"
@@ -9,9 +9,11 @@ import {
9
9
  import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
10
10
  import { usePolling } from "../../hooks/usePolling.js";
11
11
  import {
12
- getModelProvider,
13
12
  getAuthProviderFromModelProvider,
14
13
  getFeaturedModels,
14
+ getOnboardingModelProvider,
15
+ getOnboardingModels,
16
+ isDeprecatedOnboardingModelKey,
15
17
  getVisibleAiFieldKeys,
16
18
  kProviderAuthFields,
17
19
  } from "../../lib/model-config.js";
@@ -167,7 +169,7 @@ export const useWelcome = ({ onComplete }) => {
167
169
  };
168
170
 
169
171
  const applyModelCatalog = useCallback((payload) => {
170
- const list = getModelCatalogModels(payload);
172
+ const list = getOnboardingModels(getModelCatalogModels(payload));
171
173
  if (!payload) return;
172
174
  const isRefreshing = isModelCatalogRefreshing(payload);
173
175
  const isFallbackRefresh =
@@ -181,11 +183,14 @@ export const useWelcome = ({ onComplete }) => {
181
183
  : null
182
184
  : "No models found",
183
185
  );
186
+ const currentModelIsDeprecated = isDeprecatedOnboardingModelKey(
187
+ vals.MODEL_KEY,
188
+ );
184
189
  const defaultModelKey = getInitialOnboardingModelKey({
185
190
  catalog: list,
186
- currentModelKey: vals.MODEL_KEY,
191
+ currentModelKey: currentModelIsDeprecated ? "" : vals.MODEL_KEY,
187
192
  });
188
- if (!vals.MODEL_KEY && defaultModelKey) {
193
+ if ((!vals.MODEL_KEY || currentModelIsDeprecated) && defaultModelKey) {
189
194
  setVals((prev) => ({ ...prev, MODEL_KEY: defaultModelKey }));
190
195
  }
191
196
  }, [setVals, vals.MODEL_KEY]);
@@ -212,16 +217,18 @@ export const useWelcome = ({ onComplete }) => {
212
217
  }, [modelsFetchState.error]);
213
218
 
214
219
  const getValidationContext = (currentVals = {}) => {
215
- const currentSelectedProvider = getModelProvider(
216
- String(currentVals.MODEL_KEY || "").trim(),
217
- );
220
+ const currentSelectedProvider = getOnboardingModelProvider({
221
+ modelKey: currentVals.MODEL_KEY,
222
+ models,
223
+ });
218
224
  const currentSelectedAuthProvider =
219
225
  getAuthProviderFromModelProvider(currentSelectedProvider);
220
226
  const currentProviderAuthFields =
221
227
  kProviderAuthFields[currentSelectedAuthProvider] || [];
222
228
  const currentHasAi =
223
229
  currentSelectedProvider === "openai-codex"
224
- ? !!codexStatus.connected
230
+ ? !!codexStatus.connected ||
231
+ !!String(currentVals.OPENAI_API_KEY || "").trim()
225
232
  : currentProviderAuthFields.some((field) =>
226
233
  !!String(currentVals[field.key] || "").trim(),
227
234
  );
@@ -818,6 +818,11 @@ export async function fetchOnboardStatus() {
818
818
  return res.json();
819
819
  }
820
820
 
821
+ export async function fetchOnboardProgress() {
822
+ const res = await authFetch("/api/onboard/progress");
823
+ return res.json();
824
+ }
825
+
821
826
  export async function runOnboard(vars, modelKey, { importMode = false } = {}) {
822
827
  const res = await authFetch("/api/onboard", {
823
828
  method: "POST",
@@ -25,13 +25,6 @@ export const kFeaturedModelDefs = [
25
25
  label: "Sonnet 4.6",
26
26
  preferredKeys: ["anthropic/claude-sonnet-4-6"],
27
27
  },
28
- {
29
- label: "Codex 5.3",
30
- preferredKeys: [
31
- "openai/gpt-5.3-codex",
32
- "openai-codex/gpt-5.3-codex",
33
- ],
34
- },
35
28
  {
36
29
  label: "GPT-5.5",
37
30
  preferredKeys: ["openai/gpt-5.5", "openai-codex/gpt-5.5"],
@@ -42,6 +35,36 @@ export const kFeaturedModelDefs = [
42
35
  },
43
36
  ];
44
37
 
38
+ const kDeprecatedOnboardingModelKeys = new Set([
39
+ "openai/gpt-5.3-codex",
40
+ "openai-codex/gpt-5.3-codex",
41
+ ]);
42
+ const kCanonicalCodexOauthModelKeys = new Set([
43
+ "openai/gpt-5.4-mini",
44
+ "openai/gpt-5.5",
45
+ ]);
46
+
47
+ export const isDeprecatedOnboardingModelKey = (modelKey) =>
48
+ kDeprecatedOnboardingModelKeys.has(String(modelKey || "").trim());
49
+
50
+ export const getOnboardingModels = (models = []) =>
51
+ models.filter(
52
+ (model) => !isDeprecatedOnboardingModelKey(model?.key),
53
+ );
54
+
55
+ export const getOnboardingModelProvider = ({ modelKey, models = [] } = {}) => {
56
+ const normalizedKey = String(modelKey || "").trim();
57
+ const model = models.find((candidate) => candidate?.key === normalizedKey);
58
+ if (
59
+ getModelProvider(normalizedKey) === "openai-codex" ||
60
+ kCanonicalCodexOauthModelKeys.has(normalizedKey) ||
61
+ model?.agentRuntime?.id === "codex"
62
+ ) {
63
+ return "openai-codex";
64
+ }
65
+ return getModelProvider(normalizedKey);
66
+ };
67
+
45
68
  export const kAlwaysAvailableModelDefs = [
46
69
  {
47
70
  key: "openai/gpt-5.4-mini",
@@ -345,7 +368,6 @@ export const kFeatureDefs = [
345
368
  ];
346
369
 
347
370
  export const getVisibleAiFieldKeys = (provider) => {
348
- if (provider === "openai-codex") return new Set();
349
371
  const authProvider = getAuthProviderFromModelProvider(provider);
350
372
  const fields = kProviderAuthFields[authProvider] || [];
351
373
  return new Set(fields.map((field) => field.key));
@@ -369,7 +369,14 @@ const createOnboardingService = ({
369
369
  vars,
370
370
  modelKey,
371
371
  importMode = false,
372
+ onProgress,
372
373
  }) => {
374
+ const reportProgress = (stage) => {
375
+ if (typeof onProgress !== "function") return;
376
+ try {
377
+ onProgress(stage);
378
+ } catch {}
379
+ };
373
380
  const validation = validateOnboardingInput({
374
381
  vars,
375
382
  modelKey,
@@ -425,6 +432,7 @@ const createOnboardingService = ({
425
432
  syncApiKeyAuthProfilesFromEnvVars(authProfiles, varsToSave);
426
433
 
427
434
  const [, repoName] = repoUrl.split("/");
435
+ reportProgress("creating_repo");
428
436
  const repoCheck = await ensureGithubRepoAccessible({
429
437
  repoUrl,
430
438
  repoName,
@@ -481,6 +489,7 @@ const createOnboardingService = ({
481
489
  );
482
490
  }
483
491
 
492
+ reportProgress("running_openclaw_onboard");
484
493
  if (!existingConfigPresent) {
485
494
  const onboardArgs = buildOnboardArgs({
486
495
  varMap,
@@ -551,6 +560,7 @@ const createOnboardingService = ({
551
560
 
552
561
  ensureGatewayProxyConfig(getBaseUrl(req));
553
562
 
563
+ reportProgress("initial_git_push");
554
564
  try {
555
565
  const commitMsg = importMode
556
566
  ? "imported existing setup via AlphaClaw"
@@ -567,6 +577,7 @@ const createOnboardingService = ({
567
577
  console.error("[onboard] Git push error:", e.message);
568
578
  }
569
579
 
580
+ reportProgress("starting_gateway");
570
581
  runOnboardedBootSequence();
571
582
  return { status: 200, body: { ok: true } };
572
583
  };
@@ -2,6 +2,14 @@ const { getEnvVarForApiKeyProvider } = require("../auth-profiles");
2
2
 
3
3
  const kAnthropicSetupTokenPrefix = "sk-ant-oat01-";
4
4
  const kAnthropicApiKeyPrefix = "sk-ant-api";
5
+ const kCanonicalCodexOauthModelKeys = new Set([
6
+ "openai/gpt-5.4-mini",
7
+ "openai/gpt-5.5",
8
+ ]);
9
+
10
+ const usesCodexOauth = (modelKey, provider) =>
11
+ provider === "openai-codex" ||
12
+ kCanonicalCodexOauthModelKeys.has(String(modelKey || "").trim());
5
13
 
6
14
  const validateAnthropicCredentialShape = (varMap) => {
7
15
  const anthropicToken = String(varMap.ANTHROPIC_TOKEN || "").trim();
@@ -71,7 +79,10 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
71
79
  if (!anthropicValidation.ok) return anthropicValidation;
72
80
  const githubToken = String(varMap.GITHUB_TOKEN || "");
73
81
  const githubRepoInput = String(varMap.GITHUB_WORKSPACE_REPO || "").trim();
74
- const selectedProvider = resolveModelProvider(modelKey);
82
+ const resolvedProvider = resolveModelProvider(modelKey);
83
+ const selectedProvider = usesCodexOauth(modelKey, resolvedProvider)
84
+ ? "openai-codex"
85
+ : resolvedProvider;
75
86
  const hasCodexOauth = hasCodexOauthProfile();
76
87
  const hasAnyAi = !!(
77
88
  varMap.ANTHROPIC_API_KEY ||
@@ -82,7 +93,7 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
82
93
  );
83
94
  const hasAi = (() => {
84
95
  if (selectedProvider === "openai-codex") {
85
- return hasCodexOauth;
96
+ return hasCodexOauth || !!String(varMap.OPENAI_API_KEY || "").trim();
86
97
  }
87
98
  if (selectedProvider === "anthropic") {
88
99
  return !!(varMap.ANTHROPIC_API_KEY || varMap.ANTHROPIC_TOKEN);
@@ -101,7 +112,7 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
101
112
  return {
102
113
  ok: false,
103
114
  status: 400,
104
- error: "Connect OpenAI Codex OAuth before continuing",
115
+ error: "Connect OpenAI Codex OAuth or add an OpenAI API key before continuing",
105
116
  };
106
117
  }
107
118
  return {
@@ -95,6 +95,28 @@ const registerOnboardingRoutes = ({
95
95
  getBaseUrl,
96
96
  runOnboardedBootSequence,
97
97
  }) => {
98
+ const kOnboardingProgressMessages = {
99
+ creating_repo: "Creating repo...",
100
+ running_openclaw_onboard: "Running openclaw onboard...",
101
+ initial_git_push: "Initial git commit/push...",
102
+ starting_gateway: "Starting gateway...",
103
+ };
104
+ let onboardingProgress = {
105
+ active: false,
106
+ stage: "idle",
107
+ message: "",
108
+ updatedAt: null,
109
+ };
110
+ const setOnboardingProgress = (stage, { active = true } = {}) => {
111
+ const normalizedStage = String(stage || "").trim();
112
+ onboardingProgress = {
113
+ active,
114
+ stage: normalizedStage,
115
+ message: kOnboardingProgressMessages[normalizedStage] || "",
116
+ updatedAt: new Date().toISOString(),
117
+ };
118
+ };
119
+
98
120
  // Keep mutating onboarding routes marker-gated so in-progress imports
99
121
  // can promote files before the final completion marker is written.
100
122
  const hasExplicitOnboardingMarker = () =>
@@ -157,21 +179,32 @@ const registerOnboardingRoutes = ({
157
179
  res.json({ onboarded: hasExplicitOnboardingMarker() });
158
180
  });
159
181
 
182
+ app.get("/api/onboard/progress", (req, res) => {
183
+ res.json(onboardingProgress);
184
+ });
185
+
160
186
  app.post("/api/onboard", async (req, res) => {
161
187
  if (hasExplicitOnboardingMarker())
162
188
  return res.json({ ok: false, error: "Already onboarded" });
163
189
 
164
190
  try {
191
+ setOnboardingProgress("creating_repo");
165
192
  const { vars, modelKey, importMode } = req.body;
166
193
  const result = await onboardingService.completeOnboarding({
167
194
  req,
168
195
  vars,
169
196
  modelKey,
170
197
  importMode: !!importMode,
198
+ onProgress: (stage) => setOnboardingProgress(stage),
171
199
  });
200
+ setOnboardingProgress(
201
+ result.body?.ok ? "starting_gateway" : "failed",
202
+ { active: false },
203
+ );
172
204
  res.status(result.status).json(result.body);
173
205
  } catch (err) {
174
206
  console.error("[onboard] Error:", err);
207
+ setOnboardingProgress("failed", { active: false });
175
208
  res.status(500).json({ ok: false, error: sanitizeOnboardingError(err) });
176
209
  }
177
210
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.9.28",
3
+ "version": "0.9.29",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },