@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.
- package/lib/public/dist/app.bundle.js +883 -878
- package/lib/public/js/components/onboarding/welcome-config.js +6 -2
- package/lib/public/js/components/onboarding/welcome-setup-step.js +58 -4
- package/lib/public/js/components/welcome/use-welcome.js +15 -8
- package/lib/public/js/lib/api.js +5 -0
- package/lib/public/js/lib/model-config.js +30 -8
- package/lib/server/onboarding/index.js +11 -0
- package/lib/server/onboarding/validation.js +14 -3
- package/lib/server/routes/onboarding.js +33 -0
- package/package.json +1 -1
|
@@ -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 (
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 =
|
|
216
|
-
|
|
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
|
);
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -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
|
|
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
|
});
|