@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.
- package/lib/public/js/app.js +158 -1073
- package/lib/public/js/components/envars.js +146 -29
- package/lib/public/js/components/features.js +1 -1
- package/lib/public/js/components/general/index.js +155 -0
- package/lib/public/js/components/icons.js +52 -0
- package/lib/public/js/components/info-tooltip.js +4 -7
- package/lib/public/js/components/models-tab/index.js +286 -0
- package/lib/public/js/components/models-tab/provider-auth-card.js +369 -0
- package/lib/public/js/components/models-tab/use-models.js +262 -0
- package/lib/public/js/components/models.js +1 -1
- package/lib/public/js/components/providers.js +1 -1
- package/lib/public/js/components/routes/browse-route.js +35 -0
- package/lib/public/js/components/routes/doctor-route.js +21 -0
- package/lib/public/js/components/routes/envars-route.js +11 -0
- package/lib/public/js/components/routes/general-route.js +45 -0
- package/lib/public/js/components/routes/index.js +11 -0
- package/lib/public/js/components/routes/models-route.js +11 -0
- package/lib/public/js/components/routes/providers-route.js +11 -0
- package/lib/public/js/components/routes/route-redirect.js +10 -0
- package/lib/public/js/components/routes/telegram-route.js +11 -0
- package/lib/public/js/components/routes/usage-route.js +15 -0
- package/lib/public/js/components/routes/watchdog-route.js +32 -0
- package/lib/public/js/components/routes/webhooks-route.js +43 -0
- package/lib/public/js/components/sidebar.js +2 -3
- package/lib/public/js/components/tooltip.js +106 -0
- package/lib/public/js/components/usage-tab/constants.js +1 -1
- package/lib/public/js/components/usage-tab/overview-section.js +124 -50
- package/lib/public/js/components/usage-tab/use-usage-tab.js +42 -11
- package/lib/public/js/components/welcome.js +1 -1
- package/lib/public/js/hooks/use-app-shell-controller.js +230 -0
- package/lib/public/js/hooks/use-app-shell-ui.js +112 -0
- package/lib/public/js/hooks/use-browse-navigation.js +193 -0
- package/lib/public/js/hooks/use-hash-location.js +32 -0
- package/lib/public/js/lib/api.js +35 -0
- package/lib/public/js/lib/app-navigation.js +39 -0
- package/lib/public/js/lib/browse-restart-policy.js +28 -0
- package/lib/public/js/lib/browse-route.js +57 -0
- package/lib/public/js/lib/format.js +12 -0
- package/lib/public/js/lib/model-config.js +1 -0
- package/lib/server/auth-profiles.js +291 -53
- package/lib/server/constants.js +24 -8
- package/lib/server/doctor/service.js +0 -3
- package/lib/server/gateway.js +50 -31
- package/lib/server/onboarding/index.js +2 -0
- package/lib/server/onboarding/validation.js +2 -2
- package/lib/server/routes/models.js +214 -2
- package/lib/server/routes/onboarding.js +2 -0
- package/lib/server/routes/system.js +42 -1
- package/lib/server/watchdog.js +14 -1
- package/lib/server.js +6 -0
- package/lib/setup/env.template +1 -0
- 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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
59
|
-
|
|
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
|
-
|
|
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 =
|
|
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)
|
|
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
|
|
package/lib/server/constants.js
CHANGED
|
@@ -153,26 +153,38 @@ const kKnownVars = [
|
|
|
153
153
|
{
|
|
154
154
|
key: "ANTHROPIC_API_KEY",
|
|
155
155
|
label: "Anthropic API Key",
|
|
156
|
-
group: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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);
|
package/lib/server/gateway.js
CHANGED
|
@@ -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 {
|
|
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)
|
|
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 = () =>
|
|
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(
|
|
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
|
-
|
|
142
|
-
|
|
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(
|
|
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(
|
|
305
|
+
.filter(
|
|
306
|
+
(f) => f.startsWith(`${ch}-`) && f.endsWith("-allowFrom.json"),
|
|
307
|
+
);
|
|
291
308
|
for (const file of files) {
|
|
292
|
-
const data = JSON.parse(
|
|
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": !!
|
|
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
|
|
71
|
+
error: "Connect OpenAI Codex OAuth before continuing",
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
return {
|