@chrysb/alphaclaw 0.4.6-beta.4 → 0.4.6-beta.6

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.
Files changed (52) hide show
  1. package/lib/public/js/app.js +158 -1073
  2. package/lib/public/js/components/envars.js +146 -29
  3. package/lib/public/js/components/features.js +1 -1
  4. package/lib/public/js/components/general/index.js +155 -0
  5. package/lib/public/js/components/icons.js +52 -0
  6. package/lib/public/js/components/info-tooltip.js +4 -7
  7. package/lib/public/js/components/models-tab/index.js +286 -0
  8. package/lib/public/js/components/models-tab/provider-auth-card.js +369 -0
  9. package/lib/public/js/components/models-tab/use-models.js +262 -0
  10. package/lib/public/js/components/models.js +1 -1
  11. package/lib/public/js/components/providers.js +1 -1
  12. package/lib/public/js/components/routes/browse-route.js +35 -0
  13. package/lib/public/js/components/routes/doctor-route.js +21 -0
  14. package/lib/public/js/components/routes/envars-route.js +11 -0
  15. package/lib/public/js/components/routes/general-route.js +45 -0
  16. package/lib/public/js/components/routes/index.js +11 -0
  17. package/lib/public/js/components/routes/models-route.js +11 -0
  18. package/lib/public/js/components/routes/providers-route.js +11 -0
  19. package/lib/public/js/components/routes/route-redirect.js +10 -0
  20. package/lib/public/js/components/routes/telegram-route.js +11 -0
  21. package/lib/public/js/components/routes/usage-route.js +15 -0
  22. package/lib/public/js/components/routes/watchdog-route.js +32 -0
  23. package/lib/public/js/components/routes/webhooks-route.js +43 -0
  24. package/lib/public/js/components/sidebar.js +2 -3
  25. package/lib/public/js/components/tooltip.js +106 -0
  26. package/lib/public/js/components/usage-tab/constants.js +1 -1
  27. package/lib/public/js/components/usage-tab/overview-section.js +124 -50
  28. package/lib/public/js/components/usage-tab/use-usage-tab.js +42 -11
  29. package/lib/public/js/components/welcome.js +1 -1
  30. package/lib/public/js/hooks/use-app-shell-controller.js +230 -0
  31. package/lib/public/js/hooks/use-app-shell-ui.js +112 -0
  32. package/lib/public/js/hooks/use-browse-navigation.js +193 -0
  33. package/lib/public/js/hooks/use-hash-location.js +32 -0
  34. package/lib/public/js/lib/api.js +35 -0
  35. package/lib/public/js/lib/app-navigation.js +39 -0
  36. package/lib/public/js/lib/browse-restart-policy.js +28 -0
  37. package/lib/public/js/lib/browse-route.js +57 -0
  38. package/lib/public/js/lib/format.js +12 -0
  39. package/lib/public/js/lib/model-config.js +1 -0
  40. package/lib/server/auth-profiles.js +291 -53
  41. package/lib/server/constants.js +24 -8
  42. package/lib/server/doctor/service.js +0 -3
  43. package/lib/server/gateway.js +50 -31
  44. package/lib/server/onboarding/index.js +2 -0
  45. package/lib/server/onboarding/validation.js +2 -2
  46. package/lib/server/routes/models.js +214 -2
  47. package/lib/server/routes/onboarding.js +2 -0
  48. package/lib/server/routes/system.js +42 -1
  49. package/lib/server/watchdog.js +14 -1
  50. package/lib/server.js +6 -0
  51. package/lib/setup/env.template +1 -0
  52. package/package.json +1 -1
@@ -1,62 +1,278 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const { AUTH_PROFILES_PATH, CODEX_PROFILE_ID } = require("./constants");
3
+ const { AUTH_PROFILES_PATH, CODEX_PROFILE_ID, OPENCLAW_DIR } = require("./constants");
4
4
 
5
- const createAuthProfiles = () => {
6
- const ensureAuthProfilesStore = () => {
7
- let store = { version: 1, profiles: {} };
8
- try {
9
- if (fs.existsSync(AUTH_PROFILES_PATH)) {
10
- const parsed = JSON.parse(fs.readFileSync(AUTH_PROFILES_PATH, "utf8"));
11
- if (
12
- parsed &&
13
- typeof parsed === "object" &&
14
- parsed.profiles &&
15
- typeof parsed.profiles === "object"
16
- ) {
17
- store = {
18
- version: Number(parsed.version || 1),
19
- profiles: parsed.profiles,
20
- order: parsed.order,
21
- lastGood: parsed.lastGood,
22
- usageStats: parsed.usageStats,
23
- };
24
- }
5
+ const kDefaultAgentId = "main";
6
+ const kApiKeyEnvVarByProvider = {
7
+ anthropic: "ANTHROPIC_API_KEY",
8
+ openai: "OPENAI_API_KEY",
9
+ google: "GEMINI_API_KEY",
10
+ mistral: "MISTRAL_API_KEY",
11
+ voyage: "VOYAGE_API_KEY",
12
+ groq: "GROQ_API_KEY",
13
+ deepgram: "DEEPGRAM_API_KEY",
14
+ };
15
+
16
+ const normalizeSecret = (raw) =>
17
+ String(raw ?? "")
18
+ .replace(/[\r\n\u2028\u2029]/g, "")
19
+ .trim();
20
+
21
+ const credentialMode = (credential) => {
22
+ if (credential.type === "api_key") return "api_key";
23
+ if (credential.type === "token") return "token";
24
+ return "oauth";
25
+ };
26
+
27
+ const getEnvVarForApiKeyProvider = (provider) =>
28
+ kApiKeyEnvVarByProvider[String(provider || "").trim()] || "";
29
+
30
+ const getDefaultProfileIdForApiKeyProvider = (provider) => {
31
+ const normalized = String(provider || "").trim();
32
+ return normalized ? `${normalized}:default` : "";
33
+ };
34
+
35
+ const resolveAgentDir = (agentId = kDefaultAgentId) =>
36
+ path.join(OPENCLAW_DIR, "agents", agentId, "agent");
37
+
38
+ const resolveAuthProfilesPath = (agentId = kDefaultAgentId) =>
39
+ path.join(resolveAgentDir(agentId), "auth-profiles.json");
40
+
41
+ const resolveOpenclawConfigPath = () =>
42
+ path.join(OPENCLAW_DIR, "openclaw.json");
43
+
44
+ const hasCompletedOnboardingConfig = (cfg) =>
45
+ String(cfg?.agents?.defaults?.model?.primary || "").trim().includes("/");
46
+
47
+ const loadAuthStore = (agentId = kDefaultAgentId) => {
48
+ const storePath = resolveAuthProfilesPath(agentId);
49
+ let store = { version: 1, profiles: {} };
50
+ try {
51
+ if (fs.existsSync(storePath)) {
52
+ const parsed = JSON.parse(fs.readFileSync(storePath, "utf8"));
53
+ if (
54
+ parsed &&
55
+ typeof parsed === "object" &&
56
+ parsed.profiles &&
57
+ typeof parsed.profiles === "object"
58
+ ) {
59
+ store = {
60
+ version: Number(parsed.version || 1),
61
+ profiles: parsed.profiles,
62
+ order: parsed.order,
63
+ lastGood: parsed.lastGood,
64
+ usageStats: parsed.usageStats,
65
+ };
25
66
  }
26
- } catch {}
27
- return store;
28
- };
29
-
30
- const saveAuthProfilesStore = (store) => {
31
- fs.mkdirSync(path.dirname(AUTH_PROFILES_PATH), { recursive: true });
32
- fs.writeFileSync(
33
- AUTH_PROFILES_PATH,
34
- JSON.stringify(
35
- {
36
- version: Number(store.version || 1),
37
- profiles: store.profiles || {},
38
- order: store.order,
39
- lastGood: store.lastGood,
40
- usageStats: store.usageStats,
41
- },
42
- null,
43
- 2,
44
- ),
67
+ }
68
+ } catch {}
69
+ return store;
70
+ };
71
+
72
+ const saveAuthStore = (agentId, store) => {
73
+ const storePath = resolveAuthProfilesPath(agentId);
74
+ fs.mkdirSync(path.dirname(storePath), { recursive: true });
75
+ fs.writeFileSync(
76
+ storePath,
77
+ JSON.stringify(
78
+ {
79
+ version: Number(store.version || 1),
80
+ profiles: store.profiles || {},
81
+ ...(store.order !== undefined ? { order: store.order } : {}),
82
+ ...(store.lastGood !== undefined ? { lastGood: store.lastGood } : {}),
83
+ ...(store.usageStats !== undefined
84
+ ? { usageStats: store.usageStats }
85
+ : {}),
86
+ },
87
+ null,
88
+ 2,
89
+ ),
90
+ );
91
+ };
92
+
93
+ const loadOpenclawConfig = () => {
94
+ const configPath = resolveOpenclawConfigPath();
95
+ try {
96
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
97
+ } catch {
98
+ return {};
99
+ }
100
+ };
101
+
102
+ const canSyncOpenclawAuthReferences = () => {
103
+ const configPath = resolveOpenclawConfigPath();
104
+ if (!fs.existsSync(configPath)) return false;
105
+ try {
106
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
107
+ return hasCompletedOnboardingConfig(cfg);
108
+ } catch {
109
+ return false;
110
+ }
111
+ };
112
+
113
+ const saveOpenclawConfig = (cfg) => {
114
+ const configPath = resolveOpenclawConfigPath();
115
+ fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
116
+ };
117
+
118
+ const syncConfigAuthReference = (cfg, profileId, credential) => {
119
+ const next = { ...cfg };
120
+ if (!next.auth) next.auth = {};
121
+ if (!next.auth.profiles) next.auth.profiles = {};
122
+ next.auth = { ...next.auth, profiles: { ...next.auth.profiles } };
123
+ next.auth.profiles[profileId] = {
124
+ provider: credential.provider,
125
+ mode: credentialMode(credential),
126
+ };
127
+ return next;
128
+ };
129
+
130
+ const removeConfigAuthReference = (cfg, profileId) => {
131
+ if (!cfg.auth?.profiles?.[profileId]) return cfg;
132
+ const next = { ...cfg };
133
+ next.auth = { ...next.auth, profiles: { ...next.auth.profiles } };
134
+ delete next.auth.profiles[profileId];
135
+ if (Object.keys(next.auth.profiles).length === 0) {
136
+ delete next.auth.profiles;
137
+ }
138
+ if (Object.keys(next.auth).length === 0) {
139
+ delete next.auth;
140
+ }
141
+ return next;
142
+ };
143
+
144
+ const createAuthProfiles = () => {
145
+ // ── Generic profile operations ──
146
+
147
+ const listProfiles = (agentId = kDefaultAgentId) => {
148
+ const store = loadAuthStore(agentId);
149
+ return Object.entries(store.profiles || {}).map(([id, cred]) => ({
150
+ id,
151
+ ...cred,
152
+ }));
153
+ };
154
+
155
+ const listProfilesByProvider = (provider, agentId = kDefaultAgentId) =>
156
+ listProfiles(agentId).filter((p) => p.provider === provider);
157
+
158
+ const getProfile = (profileId, agentId = kDefaultAgentId) => {
159
+ const store = loadAuthStore(agentId);
160
+ const cred = store.profiles?.[profileId];
161
+ if (!cred) return null;
162
+ return { id: profileId, ...cred };
163
+ };
164
+
165
+ const upsertProfile = (profileId, credential, agentId = kDefaultAgentId) => {
166
+ const store = loadAuthStore(agentId);
167
+ const sanitized = { ...credential };
168
+ if (sanitized.key) sanitized.key = normalizeSecret(sanitized.key);
169
+ if (sanitized.token) sanitized.token = normalizeSecret(sanitized.token);
170
+ if (sanitized.access) sanitized.access = normalizeSecret(sanitized.access);
171
+ if (sanitized.refresh)
172
+ sanitized.refresh = normalizeSecret(sanitized.refresh);
173
+ store.profiles[profileId] = sanitized;
174
+ saveAuthStore(agentId, store);
175
+
176
+ if (!canSyncOpenclawAuthReferences()) return;
177
+ const cfg = loadOpenclawConfig();
178
+ const updated = syncConfigAuthReference(cfg, profileId, sanitized);
179
+ saveOpenclawConfig(updated);
180
+ };
181
+
182
+ const removeProfile = (profileId, agentId = kDefaultAgentId) => {
183
+ const store = loadAuthStore(agentId);
184
+ if (!store.profiles[profileId]) return false;
185
+ delete store.profiles[profileId];
186
+ saveAuthStore(agentId, store);
187
+
188
+ if (!canSyncOpenclawAuthReferences()) return true;
189
+ const cfg = loadOpenclawConfig();
190
+ const updated = removeConfigAuthReference(cfg, profileId);
191
+ saveOpenclawConfig(updated);
192
+ return true;
193
+ };
194
+
195
+ const setAuthOrder = (provider, orderedProfileIds, agentId = kDefaultAgentId) => {
196
+ const store = loadAuthStore(agentId);
197
+ if (!store.order) store.order = {};
198
+ store.order[provider] = orderedProfileIds;
199
+ saveAuthStore(agentId, store);
200
+ };
201
+
202
+ const syncConfigAuthReferencesForAgent = (agentId = kDefaultAgentId) => {
203
+ if (!canSyncOpenclawAuthReferences()) return;
204
+ const store = loadAuthStore(agentId);
205
+ let cfg = loadOpenclawConfig();
206
+ for (const [profileId, credential] of Object.entries(store.profiles || {})) {
207
+ if (!credential?.type || !credential?.provider) continue;
208
+ cfg = syncConfigAuthReference(cfg, profileId, credential);
209
+ }
210
+ saveOpenclawConfig(cfg);
211
+ };
212
+
213
+ const upsertApiKeyProfileForEnvVar = (
214
+ provider,
215
+ rawValue,
216
+ agentId = kDefaultAgentId,
217
+ ) => {
218
+ const key = normalizeSecret(rawValue);
219
+ if (!provider || !key) return false;
220
+ upsertProfile(
221
+ getDefaultProfileIdForApiKeyProvider(provider),
222
+ {
223
+ type: "api_key",
224
+ provider,
225
+ key,
226
+ },
227
+ agentId,
45
228
  );
229
+ return true;
230
+ };
231
+
232
+ const removeApiKeyProfileForEnvVar = (provider, agentId = kDefaultAgentId) => {
233
+ const profileId = getDefaultProfileIdForApiKeyProvider(provider);
234
+ if (!profileId) return false;
235
+ const existing = getProfile(profileId, agentId);
236
+ if (!existing) return false;
237
+ if (existing.type !== "api_key" || existing.provider !== provider) return false;
238
+ return removeProfile(profileId, agentId);
46
239
  };
47
240
 
48
- const listCodexProfiles = () => {
49
- const store = ensureAuthProfilesStore();
50
- return Object.entries(store.profiles || {})
51
- .filter(([, cred]) => cred?.provider === "openai-codex")
52
- .map(([id, cred]) => ({ id, cred }));
241
+ // ── Model config operations ──
242
+
243
+ const getModelConfig = () => {
244
+ const cfg = loadOpenclawConfig();
245
+ const defaults = cfg.agents?.defaults || {};
246
+ return {
247
+ primary: defaults.model?.primary || null,
248
+ configuredModels: defaults.models || {},
249
+ };
53
250
  };
54
251
 
252
+ const setModelConfig = ({ primary, configuredModels }) => {
253
+ const cfg = loadOpenclawConfig();
254
+ if (!cfg.agents) cfg.agents = {};
255
+ if (!cfg.agents.defaults) cfg.agents.defaults = {};
256
+ if (!cfg.agents.defaults.model) cfg.agents.defaults.model = {};
257
+ if (primary !== undefined) {
258
+ cfg.agents.defaults.model.primary = primary;
259
+ }
260
+ if (configuredModels !== undefined) {
261
+ cfg.agents.defaults.models = configuredModels;
262
+ }
263
+ saveOpenclawConfig(cfg);
264
+ };
265
+
266
+ // ── Legacy Codex-specific wrappers ──
267
+
268
+ const listCodexProfiles = () => listProfilesByProvider("openai-codex");
269
+
55
270
  const getCodexProfile = () => {
56
271
  const profiles = listCodexProfiles();
57
272
  if (profiles.length === 0) return null;
58
- const preferred = profiles.find((p) => p.id === CODEX_PROFILE_ID) || profiles[0];
59
- return { profileId: preferred.id, ...preferred.cred };
273
+ const preferred =
274
+ profiles.find((p) => p.id === CODEX_PROFILE_ID) || profiles[0];
275
+ return { profileId: preferred.id, ...preferred };
60
276
  };
61
277
 
62
278
  const hasCodexOauthProfile = () => {
@@ -65,20 +281,18 @@ const createAuthProfiles = () => {
65
281
  };
66
282
 
67
283
  const upsertCodexProfile = ({ access, refresh, expires, accountId }) => {
68
- const store = ensureAuthProfilesStore();
69
- store.profiles[CODEX_PROFILE_ID] = {
284
+ upsertProfile(CODEX_PROFILE_ID, {
70
285
  type: "oauth",
71
286
  provider: "openai-codex",
72
287
  access,
73
288
  refresh,
74
289
  expires,
75
290
  ...(accountId ? { accountId } : {}),
76
- };
77
- saveAuthProfilesStore(store);
291
+ });
78
292
  };
79
293
 
80
294
  const removeCodexProfiles = () => {
81
- const store = ensureAuthProfilesStore();
295
+ const store = loadAuthStore();
82
296
  let changed = false;
83
297
  for (const [id, cred] of Object.entries(store.profiles || {})) {
84
298
  if (cred?.provider === "openai-codex") {
@@ -86,15 +300,39 @@ const createAuthProfiles = () => {
86
300
  changed = true;
87
301
  }
88
302
  }
89
- if (changed) saveAuthProfilesStore(store);
303
+ if (changed) {
304
+ saveAuthStore(kDefaultAgentId, store);
305
+ if (!canSyncOpenclawAuthReferences()) return changed;
306
+ let cfg = loadOpenclawConfig();
307
+ for (const [id, cred] of Object.entries(cfg.auth?.profiles || {})) {
308
+ if (cred?.provider === "openai-codex") {
309
+ cfg = removeConfigAuthReference(cfg, id);
310
+ }
311
+ }
312
+ saveOpenclawConfig(cfg);
313
+ }
90
314
  return changed;
91
315
  };
92
316
 
93
317
  return {
318
+ listProfiles,
319
+ listProfilesByProvider,
320
+ getProfile,
321
+ upsertProfile,
322
+ removeProfile,
323
+ setAuthOrder,
324
+ syncConfigAuthReferencesForAgent,
325
+ upsertApiKeyProfileForEnvVar,
326
+ removeApiKeyProfileForEnvVar,
327
+ getEnvVarForApiKeyProvider,
328
+ getDefaultProfileIdForApiKeyProvider,
329
+ getModelConfig,
330
+ setModelConfig,
94
331
  getCodexProfile,
95
332
  hasCodexOauthProfile,
96
333
  upsertCodexProfile,
97
334
  removeCodexProfiles,
335
+ loadAuthStore,
98
336
  };
99
337
  };
100
338
 
@@ -153,26 +153,38 @@ const kKnownVars = [
153
153
  {
154
154
  key: "ANTHROPIC_API_KEY",
155
155
  label: "Anthropic API Key",
156
- group: "models",
156
+ group: "ai",
157
157
  hint: "From console.anthropic.com",
158
+ features: ["Models"],
158
159
  },
159
160
  {
160
161
  key: "ANTHROPIC_TOKEN",
161
162
  label: "Anthropic Setup Token",
162
- group: "models",
163
+ group: "ai",
163
164
  hint: "From claude setup-token",
165
+ features: ["Models"],
166
+ visibleInEnvars: false,
164
167
  },
165
168
  {
166
169
  key: "OPENAI_API_KEY",
167
170
  label: "OpenAI API Key",
168
- group: "models",
171
+ group: "ai",
169
172
  hint: "From platform.openai.com",
173
+ features: ["Models", "Embeddings", "TTS", "STT"],
170
174
  },
171
175
  {
172
176
  key: "GEMINI_API_KEY",
173
177
  label: "Gemini API Key",
174
- group: "models",
178
+ group: "ai",
175
179
  hint: "From aistudio.google.com",
180
+ features: ["Models", "Embeddings", "Image", "STT"],
181
+ },
182
+ {
183
+ key: "ELEVENLABS_API_KEY",
184
+ label: "ElevenLabs API Key",
185
+ group: "ai",
186
+ hint: "From elevenlabs.io (XI_API_KEY also works)",
187
+ features: ["TTS"],
176
188
  },
177
189
  {
178
190
  key: "GITHUB_TOKEN",
@@ -201,26 +213,30 @@ const kKnownVars = [
201
213
  {
202
214
  key: "MISTRAL_API_KEY",
203
215
  label: "Mistral API Key",
204
- group: "models",
216
+ group: "ai",
205
217
  hint: "From console.mistral.ai",
218
+ features: ["Models", "Embeddings", "STT"],
206
219
  },
207
220
  {
208
221
  key: "VOYAGE_API_KEY",
209
222
  label: "Voyage API Key",
210
- group: "models",
223
+ group: "ai",
211
224
  hint: "From dash.voyageai.com",
225
+ features: ["Embeddings"],
212
226
  },
213
227
  {
214
228
  key: "GROQ_API_KEY",
215
229
  label: "Groq API Key",
216
- group: "models",
230
+ group: "ai",
217
231
  hint: "From console.groq.com",
232
+ features: ["Models", "STT"],
218
233
  },
219
234
  {
220
235
  key: "DEEPGRAM_API_KEY",
221
236
  label: "Deepgram API Key",
222
- group: "models",
237
+ group: "ai",
223
238
  hint: "From console.deepgram.com",
239
+ features: ["STT"],
224
240
  },
225
241
  {
226
242
  key: "BRAVE_API_KEY",
@@ -230,9 +230,6 @@ const createDoctorService = ({
230
230
  }
231
231
  const stdoutText = String(result.stdout || "");
232
232
  const stderrText = String(result.stderr || "");
233
- console.log(
234
- `[doctor] run ${runId} command result ok=${result.ok} code=${result.code ?? 0} stdout_chars=${stdoutText.length} stderr_chars=${stderrText.length}`,
235
- );
236
233
  let normalizedResult = null;
237
234
  try {
238
235
  normalizedResult = normalizeDoctorResult(stdoutText);
@@ -1,7 +1,13 @@
1
1
  const { spawn, execSync } = require("child_process");
2
2
  const fs = require("fs");
3
3
  const net = require("net");
4
- const { OPENCLAW_DIR, GATEWAY_HOST, GATEWAY_PORT, kChannelDefs, kRootDir } = require("./constants");
4
+ const {
5
+ OPENCLAW_DIR,
6
+ GATEWAY_HOST,
7
+ GATEWAY_PORT,
8
+ kChannelDefs,
9
+ kRootDir,
10
+ } = require("./constants");
5
11
 
6
12
  let gatewayChild = null;
7
13
  let gatewayExitHandler = null;
@@ -11,7 +17,9 @@ let gatewayStderrTail = [];
11
17
  const expectedExitPids = new Set();
12
18
 
13
19
  const appendStderrTail = (chunk) => {
14
- const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk ?? "");
20
+ const text = Buffer.isBuffer(chunk)
21
+ ? chunk.toString("utf8")
22
+ : String(chunk ?? "");
15
23
  for (const line of text.split("\n")) {
16
24
  const trimmed = line.trimEnd();
17
25
  if (!trimmed) continue;
@@ -37,7 +45,19 @@ const gatewayEnv = () => ({
37
45
  XDG_CONFIG_HOME: OPENCLAW_DIR,
38
46
  });
39
47
 
40
- const isOnboarded = () => fs.existsSync(`${OPENCLAW_DIR}/openclaw.json`);
48
+ const isOnboarded = () => {
49
+ const configPath = `${OPENCLAW_DIR}/openclaw.json`;
50
+ if (!fs.existsSync(configPath)) return false;
51
+ try {
52
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
53
+ const primaryModel = String(
54
+ config?.agents?.defaults?.model?.primary || "",
55
+ ).trim();
56
+ return primaryModel.includes("/");
57
+ } catch {
58
+ return false;
59
+ }
60
+ };
41
61
 
42
62
  const isGatewayRunning = () =>
43
63
  new Promise((resolve) => {
@@ -76,7 +96,9 @@ const runGatewayCmd = (cmd) => {
76
96
 
77
97
  const launchGatewayProcess = () => {
78
98
  if (gatewayChild && gatewayChild.exitCode === null && !gatewayChild.killed) {
79
- console.log("[alphaclaw] Managed gateway process already running — skipping launch");
99
+ console.log(
100
+ "[alphaclaw] Managed gateway process already running — skipping launch",
101
+ );
80
102
  return gatewayChild;
81
103
  }
82
104
  gatewayStderrTail = [];
@@ -123,6 +145,19 @@ const launchGatewayProcess = () => {
123
145
  return child;
124
146
  };
125
147
 
148
+ const markManagedGatewayExitExpected = () => {
149
+ if (
150
+ !gatewayChild ||
151
+ gatewayChild.exitCode !== null ||
152
+ gatewayChild.killed ||
153
+ !gatewayChild.pid
154
+ ) {
155
+ return false;
156
+ }
157
+ expectedExitPids.add(gatewayChild.pid);
158
+ return true;
159
+ };
160
+
126
161
  const startGateway = async () => {
127
162
  if (!isOnboarded()) {
128
163
  console.log("[alphaclaw] Not onboarded yet — skipping gateway start");
@@ -138,30 +173,8 @@ const startGateway = async () => {
138
173
 
139
174
  const restartGateway = (reloadEnv) => {
140
175
  reloadEnv();
141
- if (gatewayChild && gatewayChild.exitCode === null && !gatewayChild.killed) {
142
- console.log("[alphaclaw] Stopping managed gateway process...");
143
- try {
144
- expectedExitPids.add(gatewayChild.pid);
145
- gatewayChild.kill("SIGTERM");
146
- gatewayChild = null;
147
- } catch (e) {
148
- console.log(`[alphaclaw] Failed to stop managed gateway process: ${e.message}`);
149
- runGatewayCmd("stop");
150
- }
151
- } else {
152
- runGatewayCmd("stop");
153
- }
154
- runGatewayCmd("install --force");
155
- const launchWhenReady = async () => {
156
- const waitUntil = Date.now() + 8000;
157
- while (Date.now() < waitUntil) {
158
- if (!(await isGatewayRunning())) break;
159
- await new Promise((resolve) => setTimeout(resolve, 250));
160
- }
161
- console.log("[alphaclaw] Starting openclaw gateway with refreshed environment...");
162
- launchGatewayProcess();
163
- };
164
- void launchWhenReady();
176
+ markManagedGatewayExitExpected();
177
+ runGatewayCmd("--force");
165
178
  };
166
179
 
167
180
  const attachGatewaySignalHandlers = () => {
@@ -275,7 +288,9 @@ const syncChannelConfig = (savedVars, mode = "all") => {
275
288
 
276
289
  const getChannelStatus = () => {
277
290
  try {
278
- const config = JSON.parse(fs.readFileSync(`${OPENCLAW_DIR}/openclaw.json`, "utf8"));
291
+ const config = JSON.parse(
292
+ fs.readFileSync(`${OPENCLAW_DIR}/openclaw.json`, "utf8"),
293
+ );
279
294
  const credDir = `${OPENCLAW_DIR}/credentials`;
280
295
  const channels = {};
281
296
 
@@ -287,9 +302,13 @@ const getChannelStatus = () => {
287
302
  try {
288
303
  const files = fs
289
304
  .readdirSync(credDir)
290
- .filter((f) => f.startsWith(`${ch}-`) && f.endsWith("-allowFrom.json"));
305
+ .filter(
306
+ (f) => f.startsWith(`${ch}-`) && f.endsWith("-allowFrom.json"),
307
+ );
291
308
  for (const file of files) {
292
- const data = JSON.parse(fs.readFileSync(`${credDir}/${file}`, "utf8"));
309
+ const data = JSON.parse(
310
+ fs.readFileSync(`${credDir}/${file}`, "utf8"),
311
+ );
293
312
  paired += (data.allowFrom || []).length;
294
313
  }
295
314
  } catch {}
@@ -29,6 +29,7 @@ const createOnboardingService = ({
29
29
  resolveGithubRepoUrl,
30
30
  resolveModelProvider,
31
31
  hasCodexOauthProfile,
32
+ authProfiles,
32
33
  ensureGatewayProxyConfig,
33
34
  getBaseUrl,
34
35
  startGateway,
@@ -148,6 +149,7 @@ const createOnboardingService = ({
148
149
  } catch {}
149
150
 
150
151
  writeSanitizedOpenclawConfig({ fs, openclawDir: OPENCLAW_DIR, varMap });
152
+ authProfiles?.syncConfigAuthReferencesForAgent?.();
151
153
  ensureGatewayProxyConfig(getBaseUrl(req));
152
154
 
153
155
  installControlUiSkill({
@@ -46,7 +46,7 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
46
46
  const hasAiByProvider = {
47
47
  anthropic: !!(varMap.ANTHROPIC_API_KEY || varMap.ANTHROPIC_TOKEN),
48
48
  openai: !!varMap.OPENAI_API_KEY,
49
- "openai-codex": !!(hasCodexOauth || varMap.OPENAI_API_KEY),
49
+ "openai-codex": !!hasCodexOauth,
50
50
  google: !!varMap.GEMINI_API_KEY,
51
51
  };
52
52
  const hasAnyAi = !!(
@@ -68,7 +68,7 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
68
68
  return {
69
69
  ok: false,
70
70
  status: 400,
71
- error: "Connect OpenAI Codex OAuth or provide OPENAI_API_KEY before continuing",
71
+ error: "Connect OpenAI Codex OAuth before continuing",
72
72
  };
73
73
  }
74
74
  return {