@chrysb/alphaclaw 0.8.5 → 0.8.7-beta.0
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/bin/alphaclaw.js +56 -20
- package/lib/public/css/explorer.css +48 -0
- package/lib/public/css/shell.css +149 -0
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/css/theme.css +265 -0
- package/lib/public/dist/app.bundle.js +2441 -2352
- package/lib/public/js/app.js +7 -0
- package/lib/public/js/components/gateway.js +6 -3
- package/lib/public/js/components/general/index.js +2 -0
- package/lib/public/js/components/icons.js +38 -0
- package/lib/public/js/components/models-tab/provider-auth-card.js +60 -49
- package/lib/public/js/components/models-tab/use-models.js +74 -9
- package/lib/public/js/components/models.js +52 -37
- package/lib/public/js/components/onboarding/use-welcome-codex.js +34 -24
- package/lib/public/js/components/onboarding/welcome-config.js +76 -10
- package/lib/public/js/components/onboarding/welcome-form-step.js +31 -11
- package/lib/public/js/components/onboarding/welcome-header.js +12 -14
- package/lib/public/js/components/onboarding/welcome-setup-step.js +3 -3
- package/lib/public/js/components/providers.js +53 -42
- package/lib/public/js/components/routes/general-route.js +2 -0
- package/lib/public/js/components/routes/watchdog-route.js +2 -0
- package/lib/public/js/components/sidebar.js +29 -8
- package/lib/public/js/components/theme-toggle.js +113 -0
- package/lib/public/js/components/update-modal-helpers.js +12 -0
- package/lib/public/js/components/update-modal.js +2 -1
- package/lib/public/js/components/watchdog-tab/index.js +2 -0
- package/lib/public/js/components/welcome/index.js +1 -2
- package/lib/public/js/components/welcome/use-welcome.js +153 -38
- package/lib/public/js/hooks/use-app-shell-controller.js +33 -9
- package/lib/public/js/lib/api.js +35 -0
- package/lib/public/js/lib/codex-oauth-window.js +22 -0
- package/lib/public/js/lib/model-catalog.js +20 -0
- package/lib/public/js/lib/storage-keys.js +1 -1
- package/lib/public/login.html +8 -4
- package/lib/public/setup.html +9 -0
- package/lib/server/alphaclaw-version.js +30 -127
- package/lib/server/db/webhooks/index.js +48 -8
- package/lib/server/model-catalog-cache.js +251 -0
- package/lib/server/openclaw-version.js +59 -130
- package/lib/server/pending-alphaclaw-update.js +71 -0
- package/lib/server/pending-openclaw-update.js +71 -0
- package/lib/server/routes/models.js +14 -23
- package/lib/server/routes/system.js +6 -1
- package/lib/server/routes/webhooks.js +12 -1
- package/package.json +1 -1
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
exchangeCodexOAuth,
|
|
5
5
|
fetchCodexStatus,
|
|
6
6
|
} from "../../lib/api.js";
|
|
7
|
+
import {
|
|
8
|
+
isCodexAuthCallbackMessage,
|
|
9
|
+
openCodexAuthWindow,
|
|
10
|
+
} from "../../lib/codex-oauth-window.js";
|
|
7
11
|
|
|
8
12
|
export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
9
13
|
const [codexStatus, setCodexStatus] = useState({ connected: false });
|
|
@@ -12,6 +16,7 @@ export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
|
12
16
|
const [codexExchanging, setCodexExchanging] = useState(false);
|
|
13
17
|
const [codexAuthStarted, setCodexAuthStarted] = useState(false);
|
|
14
18
|
const [codexAuthWaiting, setCodexAuthWaiting] = useState(false);
|
|
19
|
+
const codexExchangeInFlightRef = useRef(false);
|
|
15
20
|
const codexPopupPollRef = useRef(null);
|
|
16
21
|
|
|
17
22
|
const refreshCodexStatus = async () => {
|
|
@@ -33,10 +38,36 @@ export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
|
33
38
|
refreshCodexStatus();
|
|
34
39
|
}, []);
|
|
35
40
|
|
|
41
|
+
const submitCodexAuthInput = async (input) => {
|
|
42
|
+
const normalizedInput = String(input || "").trim();
|
|
43
|
+
if (!normalizedInput || codexExchangeInFlightRef.current) return;
|
|
44
|
+
codexExchangeInFlightRef.current = true;
|
|
45
|
+
setCodexManualInput(normalizedInput);
|
|
46
|
+
setCodexExchanging(true);
|
|
47
|
+
setFormError(null);
|
|
48
|
+
try {
|
|
49
|
+
const result = await exchangeCodexOAuth(normalizedInput);
|
|
50
|
+
if (!result.ok)
|
|
51
|
+
throw new Error(result.error || "Codex OAuth exchange failed");
|
|
52
|
+
setCodexManualInput("");
|
|
53
|
+
setCodexAuthStarted(false);
|
|
54
|
+
setCodexAuthWaiting(false);
|
|
55
|
+
await refreshCodexStatus();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
setCodexAuthWaiting(false);
|
|
58
|
+
setFormError(err.message || "Codex OAuth exchange failed");
|
|
59
|
+
} finally {
|
|
60
|
+
codexExchangeInFlightRef.current = false;
|
|
61
|
+
setCodexExchanging(false);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
36
65
|
useEffect(() => {
|
|
37
66
|
const onMessage = async (e) => {
|
|
38
67
|
if (e.data?.codex === "success") {
|
|
39
68
|
await refreshCodexStatus();
|
|
69
|
+
} else if (isCodexAuthCallbackMessage(e.data)) {
|
|
70
|
+
await submitCodexAuthInput(e.data.input);
|
|
40
71
|
}
|
|
41
72
|
if (e.data?.codex === "error") {
|
|
42
73
|
setFormError(`Codex auth failed: ${e.data.message || "unknown error"}`);
|
|
@@ -44,7 +75,7 @@ export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
|
44
75
|
};
|
|
45
76
|
window.addEventListener("message", onMessage);
|
|
46
77
|
return () => window.removeEventListener("message", onMessage);
|
|
47
|
-
}, [setFormError]);
|
|
78
|
+
}, [setFormError, submitCodexAuthInput]);
|
|
48
79
|
|
|
49
80
|
useEffect(
|
|
50
81
|
() => () => {
|
|
@@ -60,15 +91,9 @@ export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
|
60
91
|
if (codexStatus.connected) return;
|
|
61
92
|
setCodexAuthStarted(true);
|
|
62
93
|
setCodexAuthWaiting(true);
|
|
63
|
-
const
|
|
64
|
-
const popup = window.open(
|
|
65
|
-
authUrl,
|
|
66
|
-
"codex-auth",
|
|
67
|
-
"popup=yes,width=640,height=780",
|
|
68
|
-
);
|
|
94
|
+
const popup = openCodexAuthWindow();
|
|
69
95
|
if (!popup || popup.closed) {
|
|
70
96
|
setCodexAuthWaiting(false);
|
|
71
|
-
window.location.href = authUrl;
|
|
72
97
|
return;
|
|
73
98
|
}
|
|
74
99
|
if (codexPopupPollRef.current) {
|
|
@@ -84,22 +109,7 @@ export const useWelcomeCodex = ({ setFormError } = {}) => {
|
|
|
84
109
|
};
|
|
85
110
|
|
|
86
111
|
const completeCodexAuth = async () => {
|
|
87
|
-
|
|
88
|
-
setCodexExchanging(true);
|
|
89
|
-
setFormError(null);
|
|
90
|
-
try {
|
|
91
|
-
const result = await exchangeCodexOAuth(codexManualInput.trim());
|
|
92
|
-
if (!result.ok)
|
|
93
|
-
throw new Error(result.error || "Codex OAuth exchange failed");
|
|
94
|
-
setCodexManualInput("");
|
|
95
|
-
setCodexAuthStarted(false);
|
|
96
|
-
setCodexAuthWaiting(false);
|
|
97
|
-
await refreshCodexStatus();
|
|
98
|
-
} catch (err) {
|
|
99
|
-
setFormError(err.message || "Codex OAuth exchange failed");
|
|
100
|
-
} finally {
|
|
101
|
-
setCodexExchanging(false);
|
|
102
|
-
}
|
|
112
|
+
await submitCodexAuthInput(codexManualInput);
|
|
103
113
|
};
|
|
104
114
|
|
|
105
115
|
const handleCodexDisconnect = async () => {
|
|
@@ -11,6 +11,8 @@ export const kGithubFlowImport = "import";
|
|
|
11
11
|
export const kGithubTargetRepoModeCreate = "create";
|
|
12
12
|
export const kGithubTargetRepoModeExistingEmpty = "existing-empty";
|
|
13
13
|
|
|
14
|
+
const hasValue = (value) => !!String(value || "").trim();
|
|
15
|
+
|
|
14
16
|
export const normalizeGithubRepoInput = (repoInput) =>
|
|
15
17
|
String(repoInput || "")
|
|
16
18
|
.trim()
|
|
@@ -25,6 +27,74 @@ export const isValidGithubRepoInput = (repoInput) => {
|
|
|
25
27
|
return parts.length === 2 && !parts.some((part) => /\s/.test(part));
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
const getGithubGroupError = (vals) => {
|
|
31
|
+
const githubFlow = vals._GITHUB_FLOW || kGithubFlowFresh;
|
|
32
|
+
if (!hasValue(vals.GITHUB_TOKEN)) {
|
|
33
|
+
return "Enter a GitHub personal access token to continue.";
|
|
34
|
+
}
|
|
35
|
+
if (!hasValue(vals.GITHUB_WORKSPACE_REPO)) {
|
|
36
|
+
return 'Enter the target repo as "owner/repo".';
|
|
37
|
+
}
|
|
38
|
+
if (!isValidGithubRepoInput(vals.GITHUB_WORKSPACE_REPO)) {
|
|
39
|
+
return 'Target repo must be in "owner/repo" format.';
|
|
40
|
+
}
|
|
41
|
+
if (githubFlow === kGithubFlowImport) {
|
|
42
|
+
if (!hasValue(vals._GITHUB_SOURCE_REPO)) {
|
|
43
|
+
return 'Enter the source repo as "owner/repo".';
|
|
44
|
+
}
|
|
45
|
+
if (!isValidGithubRepoInput(vals._GITHUB_SOURCE_REPO)) {
|
|
46
|
+
return 'Source repo must be in "owner/repo" format.';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return "";
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getAiGroupError = (vals, ctx = {}) => {
|
|
53
|
+
if (!hasValue(vals.MODEL_KEY) || !String(vals.MODEL_KEY).includes("/")) {
|
|
54
|
+
return "Choose a model to continue.";
|
|
55
|
+
}
|
|
56
|
+
if (ctx.selectedProvider === "openai-codex" && ctx.codexLoading) {
|
|
57
|
+
return "Checking Codex OAuth status. Try Next again in a moment.";
|
|
58
|
+
}
|
|
59
|
+
if (!ctx.hasAi) {
|
|
60
|
+
return ctx.selectedProvider === "openai-codex"
|
|
61
|
+
? "Connect Codex OAuth to continue."
|
|
62
|
+
: "Add credentials for the selected model provider to continue.";
|
|
63
|
+
}
|
|
64
|
+
return "";
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getChannelsGroupError = (vals) => {
|
|
68
|
+
const hasTelegram = hasValue(vals.TELEGRAM_BOT_TOKEN);
|
|
69
|
+
const hasDiscord = hasValue(vals.DISCORD_BOT_TOKEN);
|
|
70
|
+
const hasSlackBot = hasValue(vals.SLACK_BOT_TOKEN);
|
|
71
|
+
const hasSlackApp = hasValue(vals.SLACK_APP_TOKEN);
|
|
72
|
+
|
|
73
|
+
if (hasSlackBot && !hasSlackApp) {
|
|
74
|
+
return "Add the Slack app token to continue with Slack.";
|
|
75
|
+
}
|
|
76
|
+
if (!hasSlackBot && hasSlackApp) {
|
|
77
|
+
return "Add the Slack bot token to continue with Slack.";
|
|
78
|
+
}
|
|
79
|
+
if (!hasTelegram && !hasDiscord && !(hasSlackBot && hasSlackApp)) {
|
|
80
|
+
return "Add at least one channel to continue.";
|
|
81
|
+
}
|
|
82
|
+
return "";
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const getWelcomeGroupError = (groupId, vals, ctx = {}) => {
|
|
86
|
+
switch (groupId) {
|
|
87
|
+
case "github":
|
|
88
|
+
return getGithubGroupError(vals);
|
|
89
|
+
case "ai":
|
|
90
|
+
return getAiGroupError(vals, ctx);
|
|
91
|
+
case "channels":
|
|
92
|
+
return getChannelsGroupError(vals);
|
|
93
|
+
default:
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
28
98
|
export const kWelcomeGroups = [
|
|
29
99
|
{
|
|
30
100
|
id: "github",
|
|
@@ -64,21 +134,14 @@ export const kWelcomeGroups = [
|
|
|
64
134
|
placeholder: "ghp_... or github_pat_...",
|
|
65
135
|
},
|
|
66
136
|
],
|
|
67
|
-
validate: (vals) =>
|
|
68
|
-
const githubFlow = vals._GITHUB_FLOW || kGithubFlowFresh;
|
|
69
|
-
const hasTarget = isValidGithubRepoInput(vals.GITHUB_WORKSPACE_REPO);
|
|
70
|
-
const hasSource =
|
|
71
|
-
githubFlow !== kGithubFlowImport ||
|
|
72
|
-
isValidGithubRepoInput(vals._GITHUB_SOURCE_REPO);
|
|
73
|
-
return !!(vals.GITHUB_TOKEN && hasTarget && hasSource);
|
|
74
|
-
},
|
|
137
|
+
validate: (vals, ctx = {}) => !getWelcomeGroupError("github", vals, ctx),
|
|
75
138
|
},
|
|
76
139
|
{
|
|
77
140
|
id: "ai",
|
|
78
141
|
title: "Primary Agent Model",
|
|
79
142
|
description: "Choose your main model and authenticate its provider",
|
|
80
143
|
fields: kAllAiAuthFields,
|
|
81
|
-
validate: (vals, ctx = {}) =>
|
|
144
|
+
validate: (vals, ctx = {}) => !getWelcomeGroupError("ai", vals, ctx),
|
|
82
145
|
},
|
|
83
146
|
{
|
|
84
147
|
id: "channels",
|
|
@@ -152,7 +215,7 @@ export const kWelcomeGroups = [
|
|
|
152
215
|
placeholder: "xapp-...",
|
|
153
216
|
},
|
|
154
217
|
],
|
|
155
|
-
validate: (vals) =>
|
|
218
|
+
validate: (vals, ctx = {}) => !getWelcomeGroupError("channels", vals, ctx),
|
|
156
219
|
},
|
|
157
220
|
{
|
|
158
221
|
id: "tools",
|
|
@@ -175,3 +238,6 @@ export const kWelcomeGroups = [
|
|
|
175
238
|
validate: () => true,
|
|
176
239
|
},
|
|
177
240
|
];
|
|
241
|
+
|
|
242
|
+
export const findFirstInvalidWelcomeGroup = (vals, ctx = {}) =>
|
|
243
|
+
kWelcomeGroups.find((group) => getWelcomeGroupError(group.id, vals, ctx)) || null;
|
|
@@ -50,12 +50,11 @@ export const WelcomeFormStep = ({
|
|
|
50
50
|
error,
|
|
51
51
|
step,
|
|
52
52
|
totalGroups,
|
|
53
|
-
|
|
53
|
+
importApplied,
|
|
54
54
|
goBack,
|
|
55
55
|
goNext,
|
|
56
56
|
loading,
|
|
57
57
|
githubStepLoading,
|
|
58
|
-
allValid,
|
|
59
58
|
handleSubmit,
|
|
60
59
|
}) => {
|
|
61
60
|
const [showOptionalOpenai, setShowOptionalOpenai] = useState(false);
|
|
@@ -90,18 +89,28 @@ export const WelcomeFormStep = ({
|
|
|
90
89
|
});
|
|
91
90
|
}, [activeGroup.id]);
|
|
92
91
|
|
|
93
|
-
const renderStandardField = (field) =>
|
|
92
|
+
const renderStandardField = (field) => {
|
|
93
|
+
const isLockedImportSourceField =
|
|
94
|
+
activeGroup.id === "github" &&
|
|
95
|
+
githubFlow === kGithubFlowImport &&
|
|
96
|
+
importApplied &&
|
|
97
|
+
field.key === "_GITHUB_SOURCE_REPO";
|
|
98
|
+
|
|
99
|
+
return html`
|
|
94
100
|
<div class="space-y-1" key=${field.key}>
|
|
95
101
|
<label class="text-xs font-medium text-fg-muted">${field.label}</label>
|
|
96
102
|
<${SecretInput}
|
|
97
103
|
key=${field.key}
|
|
98
104
|
value=${vals[field.key] || ""}
|
|
99
105
|
onInput=${(e) => setValue(field.key, e.target.value)}
|
|
106
|
+
disabled=${isLockedImportSourceField}
|
|
100
107
|
placeholder=${activeGroup.id === "github" && field.key === "GITHUB_TOKEN"
|
|
101
108
|
? githubTokenPlaceholder
|
|
102
109
|
: field.placeholder || ""}
|
|
103
110
|
isSecret=${!field.isText}
|
|
104
|
-
inputClass
|
|
111
|
+
inputClass=${`flex-1 bg-field border border-border rounded-lg px-3 py-2 text-sm text-body outline-none focus:border-fg-muted font-mono ${
|
|
112
|
+
isLockedImportSourceField ? "opacity-60 cursor-not-allowed" : ""
|
|
113
|
+
}`}
|
|
105
114
|
/>
|
|
106
115
|
<p class="text-xs text-fg-dim">
|
|
107
116
|
${activeGroup.id === "github" &&
|
|
@@ -112,7 +121,9 @@ export const WelcomeFormStep = ({
|
|
|
112
121
|
? "Enter the owner/repo of an existing empty repository"
|
|
113
122
|
: "A new private repo will be created for you"
|
|
114
123
|
: activeGroup.id === "github" && field.key === "_GITHUB_SOURCE_REPO"
|
|
115
|
-
?
|
|
124
|
+
? importApplied
|
|
125
|
+
? "This source repo is already imported locally. You can still change the target repo below."
|
|
126
|
+
: "The repo to import from"
|
|
116
127
|
: activeGroup.id === "github" && field.key === "GITHUB_TOKEN"
|
|
117
128
|
? githubFlow === kGithubFlowImport
|
|
118
129
|
? freshRepoMode === kGithubTargetRepoModeCreate
|
|
@@ -141,7 +152,8 @@ export const WelcomeFormStep = ({
|
|
|
141
152
|
: field.hint}
|
|
142
153
|
</p>
|
|
143
154
|
</div>
|
|
144
|
-
|
|
155
|
+
`;
|
|
156
|
+
};
|
|
145
157
|
const fieldLookup = new Map((activeGroup.fields || []).map((field) => [field.key, field]));
|
|
146
158
|
const toggleChannelSection = (channelId) =>
|
|
147
159
|
setExpandedChannels((current) => {
|
|
@@ -294,13 +306,12 @@ export const WelcomeFormStep = ({
|
|
|
294
306
|
/>
|
|
295
307
|
`}
|
|
296
308
|
</div>
|
|
297
|
-
${
|
|
298
|
-
codexAuthStarted &&
|
|
309
|
+
${codexAuthStarted &&
|
|
299
310
|
html`
|
|
300
311
|
<div class="space-y-1 pt-1">
|
|
301
312
|
<p class="text-xs text-fg-muted">
|
|
302
313
|
${codexAuthWaiting
|
|
303
|
-
? "Complete login in the popup,
|
|
314
|
+
? "Complete login in the popup. AlphaClaw should finish automatically, but if it doesn't, paste the full redirect URL from the address bar (starts with "
|
|
304
315
|
: "Paste the full redirect URL from the address bar (starts with "}
|
|
305
316
|
<code class="text-xs bg-field px-1 rounded"
|
|
306
317
|
>http://localhost:1455/auth/callback</code
|
|
@@ -330,6 +341,17 @@ export const WelcomeFormStep = ({
|
|
|
330
341
|
${activeGroup.id === "github" &&
|
|
331
342
|
html`
|
|
332
343
|
<div class="space-y-3">
|
|
344
|
+
${githubFlow === kGithubFlowImport && importApplied
|
|
345
|
+
? html`
|
|
346
|
+
<div
|
|
347
|
+
class="bg-status-info-bg border border-status-info-border rounded-lg p-3 text-xs text-status-info"
|
|
348
|
+
>
|
|
349
|
+
The import source is already applied locally. You can still
|
|
350
|
+
change the target repo before finishing setup, but we will not
|
|
351
|
+
re-import the source repo a second time.
|
|
352
|
+
</div>
|
|
353
|
+
`
|
|
354
|
+
: null}
|
|
333
355
|
${githubFlow === kGithubFlowFresh
|
|
334
356
|
? html`
|
|
335
357
|
<div class="space-y-1">
|
|
@@ -442,7 +464,6 @@ export const WelcomeFormStep = ({
|
|
|
442
464
|
: html`<div class="w-full"></div>`}
|
|
443
465
|
<${ActionButton}
|
|
444
466
|
onClick=${goNext}
|
|
445
|
-
disabled=${!currentGroupValid}
|
|
446
467
|
loading=${activeGroup.id === "github" && githubStepLoading}
|
|
447
468
|
tone="primary"
|
|
448
469
|
size="md"
|
|
@@ -466,7 +487,6 @@ export const WelcomeFormStep = ({
|
|
|
466
487
|
: html`<div class="w-full"></div>`}
|
|
467
488
|
<${ActionButton}
|
|
468
489
|
onClick=${handleSubmit}
|
|
469
|
-
disabled=${!allValid}
|
|
470
490
|
loading=${loading}
|
|
471
491
|
tone="primary"
|
|
472
492
|
size="md"
|
|
@@ -20,13 +20,11 @@ export const WelcomeHeader = ({
|
|
|
20
20
|
|
|
21
21
|
return html`
|
|
22
22
|
<div class="text-center mb-1">
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
height="33"
|
|
29
|
-
/>
|
|
23
|
+
<span
|
|
24
|
+
class="ac-logo-mark block mx-auto mb-3"
|
|
25
|
+
style="--ac-logo-width: 32px; --ac-logo-height: 33px;"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
></span>
|
|
30
28
|
<h1 class="text-2xl font-semibold mb-2">Setup</h1>
|
|
31
29
|
<p style="color: var(--text-muted)" class="text-sm">
|
|
32
30
|
Let's get your agent running
|
|
@@ -34,7 +32,7 @@ export const WelcomeHeader = ({
|
|
|
34
32
|
<div class="mt-4 mb-2 flex items-center justify-center">
|
|
35
33
|
<span
|
|
36
34
|
class="text-[11px] px-2.5 py-1 rounded-full border border-border font-medium"
|
|
37
|
-
style="background:
|
|
35
|
+
style="background: var(--field-bg-contrast); color: var(--text-muted)"
|
|
38
36
|
>
|
|
39
37
|
${isPreStep
|
|
40
38
|
? "Choose your destiny"
|
|
@@ -51,16 +49,16 @@ export const WelcomeHeader = ({
|
|
|
51
49
|
const isPairingComplete =
|
|
52
50
|
idx < step || (isPairingStep && group.id === "pairing");
|
|
53
51
|
const bg = isPreStep
|
|
54
|
-
? "
|
|
52
|
+
? "var(--border-strong)"
|
|
55
53
|
: isActive
|
|
56
|
-
? "
|
|
54
|
+
? "var(--accent)"
|
|
57
55
|
: group.id === "pairing"
|
|
58
56
|
? isPairingComplete
|
|
59
|
-
? "
|
|
60
|
-
: "
|
|
57
|
+
? "var(--accent-dim)"
|
|
58
|
+
: "var(--border-strong)"
|
|
61
59
|
: isComplete
|
|
62
|
-
? "
|
|
63
|
-
: "
|
|
60
|
+
? "var(--accent-dim)"
|
|
61
|
+
: "var(--border-strong)";
|
|
64
62
|
return html`
|
|
65
63
|
<div
|
|
66
64
|
class="h-1 flex-1 rounded-full transition-colors ${isActive ? "ac-step-pill-pulse" : ""}"
|
|
@@ -45,7 +45,7 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
|
|
|
45
45
|
if (error) {
|
|
46
46
|
return html`
|
|
47
47
|
<div class="py-4 flex flex-col items-center text-center gap-3">
|
|
48
|
-
<h3 class="text-lg font-semibold text-
|
|
48
|
+
<h3 class="text-lg font-semibold text-body">Setup failed</h3>
|
|
49
49
|
<p class="text-sm text-fg-muted">Fix the values and try again.</p>
|
|
50
50
|
</div>
|
|
51
51
|
<div
|
|
@@ -83,8 +83,8 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
|
|
|
83
83
|
<div
|
|
84
84
|
class="flex-1 flex flex-col items-center justify-center text-center gap-4"
|
|
85
85
|
>
|
|
86
|
-
<${LoadingSpinner} className="h-8 w-8 text-
|
|
87
|
-
<h3 class="text-lg font-semibold text-
|
|
86
|
+
<${LoadingSpinner} className="h-8 w-8 text-body" />
|
|
87
|
+
<h3 class="text-lg font-semibold text-body">
|
|
88
88
|
Initializing OpenClaw...
|
|
89
89
|
</h3>
|
|
90
90
|
<p class="text-sm text-fg-muted">This could take 10-15 seconds</p>
|
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
kProviderFeatures,
|
|
28
28
|
kCoreProviders,
|
|
29
29
|
} from "../lib/model-config.js";
|
|
30
|
+
import {
|
|
31
|
+
isCodexAuthCallbackMessage,
|
|
32
|
+
openCodexAuthWindow,
|
|
33
|
+
} from "../lib/codex-oauth-window.js";
|
|
30
34
|
|
|
31
35
|
const html = htm.bind(h);
|
|
32
36
|
|
|
@@ -89,6 +93,7 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
89
93
|
() => kProvidersTabCache?.savedAiValues || {},
|
|
90
94
|
);
|
|
91
95
|
const [showMoreProviders, setShowMoreProviders] = useState(false);
|
|
96
|
+
const codexExchangeInFlightRef = useRef(false);
|
|
92
97
|
const codexPopupPollRef = useRef(null);
|
|
93
98
|
|
|
94
99
|
const refresh = async () => {
|
|
@@ -171,11 +176,37 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
171
176
|
[],
|
|
172
177
|
);
|
|
173
178
|
|
|
179
|
+
const submitCodexAuthInput = async (input) => {
|
|
180
|
+
const normalizedInput = String(input || "").trim();
|
|
181
|
+
if (!normalizedInput || codexExchangeInFlightRef.current) return;
|
|
182
|
+
codexExchangeInFlightRef.current = true;
|
|
183
|
+
setCodexManualInput(normalizedInput);
|
|
184
|
+
setCodexExchanging(true);
|
|
185
|
+
try {
|
|
186
|
+
const result = await exchangeCodexOAuth(normalizedInput);
|
|
187
|
+
if (!result.ok)
|
|
188
|
+
throw new Error(result.error || "Codex OAuth exchange failed");
|
|
189
|
+
setCodexManualInput("");
|
|
190
|
+
showToast("Codex connected", "success");
|
|
191
|
+
setCodexAuthStarted(false);
|
|
192
|
+
setCodexAuthWaiting(false);
|
|
193
|
+
await refreshCodexConnection();
|
|
194
|
+
} catch (err) {
|
|
195
|
+
setCodexAuthWaiting(false);
|
|
196
|
+
showToast(err.message || "Codex OAuth exchange failed", "error");
|
|
197
|
+
} finally {
|
|
198
|
+
codexExchangeInFlightRef.current = false;
|
|
199
|
+
setCodexExchanging(false);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
174
203
|
useEffect(() => {
|
|
175
204
|
const onMessage = async (e) => {
|
|
176
205
|
if (e.data?.codex === "success") {
|
|
177
206
|
showToast("Codex connected", "success");
|
|
178
207
|
await refreshCodexConnection();
|
|
208
|
+
} else if (isCodexAuthCallbackMessage(e.data)) {
|
|
209
|
+
await submitCodexAuthInput(e.data.input);
|
|
179
210
|
} else if (e.data?.codex === "error") {
|
|
180
211
|
showToast(
|
|
181
212
|
`Codex auth failed: ${e.data.message || "unknown error"}`,
|
|
@@ -185,7 +216,7 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
185
216
|
};
|
|
186
217
|
window.addEventListener("message", onMessage);
|
|
187
218
|
return () => window.removeEventListener("message", onMessage);
|
|
188
|
-
}, []);
|
|
219
|
+
}, [submitCodexAuthInput]);
|
|
189
220
|
|
|
190
221
|
const setEnvValue = (key, value) => {
|
|
191
222
|
setEnvVars((prev) => {
|
|
@@ -296,14 +327,9 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
296
327
|
if (codexStatus.connected) return;
|
|
297
328
|
setCodexAuthStarted(true);
|
|
298
329
|
setCodexAuthWaiting(true);
|
|
299
|
-
const popup =
|
|
300
|
-
"/auth/codex/start",
|
|
301
|
-
"codex-auth",
|
|
302
|
-
"popup=yes,width=640,height=780",
|
|
303
|
-
);
|
|
330
|
+
const popup = openCodexAuthWindow();
|
|
304
331
|
if (!popup || popup.closed) {
|
|
305
332
|
setCodexAuthWaiting(false);
|
|
306
|
-
window.location.href = "/auth/codex/start";
|
|
307
333
|
return;
|
|
308
334
|
}
|
|
309
335
|
if (codexPopupPollRef.current) {
|
|
@@ -319,22 +345,7 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
319
345
|
};
|
|
320
346
|
|
|
321
347
|
const completeCodexAuth = async () => {
|
|
322
|
-
|
|
323
|
-
setCodexExchanging(true);
|
|
324
|
-
try {
|
|
325
|
-
const result = await exchangeCodexOAuth(codexManualInput.trim());
|
|
326
|
-
if (!result.ok)
|
|
327
|
-
throw new Error(result.error || "Codex OAuth exchange failed");
|
|
328
|
-
setCodexManualInput("");
|
|
329
|
-
showToast("Codex connected", "success");
|
|
330
|
-
setCodexAuthStarted(false);
|
|
331
|
-
setCodexAuthWaiting(false);
|
|
332
|
-
await refreshCodexConnection();
|
|
333
|
-
} catch (err) {
|
|
334
|
-
showToast(err.message || "Codex OAuth exchange failed", "error");
|
|
335
|
-
} finally {
|
|
336
|
-
setCodexExchanging(false);
|
|
337
|
-
}
|
|
348
|
+
await submitCodexAuthInput(codexManualInput);
|
|
338
349
|
};
|
|
339
350
|
|
|
340
351
|
const handleCodexDisconnect = async () => {
|
|
@@ -385,7 +396,23 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
385
396
|
? html`<${Badge} tone="success">Connected</${Badge}>`
|
|
386
397
|
: html`<${Badge} tone="warning">Not connected</${Badge}>`}
|
|
387
398
|
</div>
|
|
388
|
-
${
|
|
399
|
+
${codexAuthStarted
|
|
400
|
+
? html`
|
|
401
|
+
<div class="flex items-center justify-between gap-2">
|
|
402
|
+
<p class="text-xs text-fg-muted">
|
|
403
|
+
${codexAuthWaiting
|
|
404
|
+
? "Complete login in the popup. AlphaClaw should finish automatically, but you can paste the redirect URL below if it doesn't."
|
|
405
|
+
: "Paste the redirect URL from your browser to finish connecting."}
|
|
406
|
+
</p>
|
|
407
|
+
<button
|
|
408
|
+
onclick=${startCodexAuth}
|
|
409
|
+
class="text-xs font-medium px-3 py-1.5 rounded-lg ac-btn-secondary shrink-0"
|
|
410
|
+
>
|
|
411
|
+
Restart
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
`
|
|
415
|
+
: codexStatus.connected
|
|
389
416
|
? html`
|
|
390
417
|
<div class="flex gap-2">
|
|
391
418
|
<button
|
|
@@ -402,31 +429,15 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
402
429
|
</button>
|
|
403
430
|
</div>
|
|
404
431
|
`
|
|
405
|
-
:
|
|
406
|
-
? html`
|
|
432
|
+
: html`
|
|
407
433
|
<button
|
|
408
434
|
onclick=${startCodexAuth}
|
|
409
435
|
class="text-xs font-medium px-3 py-1.5 rounded-lg ac-btn-cyan"
|
|
410
436
|
>
|
|
411
437
|
Connect Codex OAuth
|
|
412
438
|
</button>
|
|
413
|
-
`
|
|
414
|
-
: html`
|
|
415
|
-
<div class="flex items-center justify-between gap-2">
|
|
416
|
-
<p class="text-xs text-fg-muted">
|
|
417
|
-
${codexAuthWaiting
|
|
418
|
-
? "Complete login in the popup, then paste the redirect URL."
|
|
419
|
-
: "Paste the redirect URL from your browser to finish connecting."}
|
|
420
|
-
</p>
|
|
421
|
-
<button
|
|
422
|
-
onclick=${startCodexAuth}
|
|
423
|
-
class="text-xs font-medium px-3 py-1.5 rounded-lg ac-btn-secondary shrink-0"
|
|
424
|
-
>
|
|
425
|
-
Restart
|
|
426
|
-
</button>
|
|
427
|
-
</div>
|
|
428
439
|
`}
|
|
429
|
-
${
|
|
440
|
+
${codexAuthStarted
|
|
430
441
|
? html`
|
|
431
442
|
<p class="text-xs text-fg-muted">
|
|
432
443
|
After login, copy the full redirect URL (starts with
|
|
@@ -16,6 +16,7 @@ export const GeneralRoute = ({
|
|
|
16
16
|
restartingGateway = false,
|
|
17
17
|
onRestartGateway = () => {},
|
|
18
18
|
restartSignal = 0,
|
|
19
|
+
openclawRestarting = false,
|
|
19
20
|
openclawUpdateInProgress = false,
|
|
20
21
|
onOpenclawVersionActionComplete = () => {},
|
|
21
22
|
onOpenclawUpdate = () => {},
|
|
@@ -37,6 +38,7 @@ export const GeneralRoute = ({
|
|
|
37
38
|
restartingGateway=${restartingGateway}
|
|
38
39
|
onRestartGateway=${onRestartGateway}
|
|
39
40
|
restartSignal=${restartSignal}
|
|
41
|
+
openclawRestarting=${openclawRestarting}
|
|
40
42
|
openclawUpdateInProgress=${openclawUpdateInProgress}
|
|
41
43
|
onOpenclawVersionActionComplete=${onOpenclawVersionActionComplete}
|
|
42
44
|
onOpenclawUpdate=${onOpenclawUpdate}
|
|
@@ -11,6 +11,7 @@ export const WatchdogRoute = ({
|
|
|
11
11
|
restartingGateway = false,
|
|
12
12
|
onRestartGateway = () => {},
|
|
13
13
|
restartSignal = 0,
|
|
14
|
+
openclawRestarting = false,
|
|
14
15
|
openclawUpdateInProgress = false,
|
|
15
16
|
onOpenclawVersionActionComplete = () => {},
|
|
16
17
|
onOpenclawUpdate = () => {},
|
|
@@ -24,6 +25,7 @@ export const WatchdogRoute = ({
|
|
|
24
25
|
restartingGateway=${restartingGateway}
|
|
25
26
|
onRestartGateway=${onRestartGateway}
|
|
26
27
|
restartSignal=${restartSignal}
|
|
28
|
+
openclawRestarting=${openclawRestarting}
|
|
27
29
|
openclawUpdateInProgress=${openclawUpdateInProgress}
|
|
28
30
|
onOpenclawVersionActionComplete=${onOpenclawVersionActionComplete}
|
|
29
31
|
onOpenclawUpdate=${onOpenclawUpdate}
|