@chrysb/alphaclaw 0.4.6-beta.5 → 0.4.6-beta.7
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/components/agent-send-modal.js +142 -0
- package/lib/public/js/components/doctor/fix-card-modal.js +15 -89
- package/lib/public/js/components/envars.js +146 -29
- package/lib/public/js/components/features.js +1 -1
- package/lib/public/js/components/gateway.js +1 -0
- package/lib/public/js/components/google/gmail-setup-wizard.js +28 -4
- 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/provider-auth-card.js +2 -2
- package/lib/public/js/components/models.js +1 -1
- package/lib/public/js/components/providers.js +1 -1
- package/lib/public/js/components/tooltip.js +106 -0
- package/lib/public/js/components/webhooks.js +103 -2
- package/lib/public/js/components/welcome.js +1 -1
- package/lib/public/js/lib/model-config.js +1 -0
- package/lib/server/auth-profiles.js +67 -0
- package/lib/server/constants.js +30 -8
- package/lib/server/doctor/service.js +0 -3
- package/lib/server/gateway.js +68 -29
- package/lib/server/gmail-watch.js +6 -3
- package/lib/server/onboarding/index.js +16 -1
- package/lib/server/onboarding/validation.js +2 -2
- package/lib/server/routes/models.js +44 -0
- package/lib/server/routes/onboarding.js +2 -0
- package/lib/server/routes/system.js +66 -11
- package/lib/server/watchdog.js +2 -2
- package/lib/server.js +5 -1
- package/lib/setup/env.template +1 -0
- package/package.json +1 -1
package/lib/server/gateway.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const path = require("path");
|
|
1
2
|
const { spawn, execSync } = require("child_process");
|
|
2
3
|
const fs = require("fs");
|
|
3
4
|
const net = require("net");
|
|
@@ -5,7 +6,9 @@ const {
|
|
|
5
6
|
OPENCLAW_DIR,
|
|
6
7
|
GATEWAY_HOST,
|
|
7
8
|
GATEWAY_PORT,
|
|
9
|
+
kControlUiSkillPath,
|
|
8
10
|
kChannelDefs,
|
|
11
|
+
kOnboardingMarkerPath,
|
|
9
12
|
kRootDir,
|
|
10
13
|
} = require("./constants");
|
|
11
14
|
|
|
@@ -45,7 +48,56 @@ const gatewayEnv = () => ({
|
|
|
45
48
|
XDG_CONFIG_HOME: OPENCLAW_DIR,
|
|
46
49
|
});
|
|
47
50
|
|
|
48
|
-
const
|
|
51
|
+
const hasOnboardingModelConfig = () => {
|
|
52
|
+
const configPath = `${OPENCLAW_DIR}/openclaw.json`;
|
|
53
|
+
if (!fs.existsSync(configPath)) return false;
|
|
54
|
+
try {
|
|
55
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
56
|
+
const primaryModel = String(
|
|
57
|
+
config?.agents?.defaults?.model?.primary || "",
|
|
58
|
+
).trim();
|
|
59
|
+
return primaryModel.includes("/");
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const hasLegacyOnboardingArtifacts = () => fs.existsSync(kControlUiSkillPath);
|
|
66
|
+
|
|
67
|
+
const writeOnboardingMarker = (reason) => {
|
|
68
|
+
try {
|
|
69
|
+
fs.mkdirSync(path.dirname(kOnboardingMarkerPath), { recursive: true });
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
kOnboardingMarkerPath,
|
|
72
|
+
JSON.stringify(
|
|
73
|
+
{
|
|
74
|
+
onboarded: true,
|
|
75
|
+
reason: String(reason || "unknown"),
|
|
76
|
+
markedAt: new Date().toISOString(),
|
|
77
|
+
},
|
|
78
|
+
null,
|
|
79
|
+
2,
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
return true;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(`[alphaclaw] Failed to write onboarding marker: ${err.message}`);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const isOnboarded = () => {
|
|
90
|
+
if (fs.existsSync(kOnboardingMarkerPath)) return true;
|
|
91
|
+
if (hasOnboardingModelConfig()) {
|
|
92
|
+
writeOnboardingMarker("config_primary_model");
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (hasLegacyOnboardingArtifacts()) {
|
|
96
|
+
writeOnboardingMarker("legacy_artifact_backfill");
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
49
101
|
|
|
50
102
|
const isGatewayRunning = () =>
|
|
51
103
|
new Promise((resolve) => {
|
|
@@ -133,6 +185,19 @@ const launchGatewayProcess = () => {
|
|
|
133
185
|
return child;
|
|
134
186
|
};
|
|
135
187
|
|
|
188
|
+
const markManagedGatewayExitExpected = () => {
|
|
189
|
+
if (
|
|
190
|
+
!gatewayChild ||
|
|
191
|
+
gatewayChild.exitCode !== null ||
|
|
192
|
+
gatewayChild.killed ||
|
|
193
|
+
!gatewayChild.pid
|
|
194
|
+
) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
expectedExitPids.add(gatewayChild.pid);
|
|
198
|
+
return true;
|
|
199
|
+
};
|
|
200
|
+
|
|
136
201
|
const startGateway = async () => {
|
|
137
202
|
if (!isOnboarded()) {
|
|
138
203
|
console.log("[alphaclaw] Not onboarded yet — skipping gateway start");
|
|
@@ -148,34 +213,8 @@ const startGateway = async () => {
|
|
|
148
213
|
|
|
149
214
|
const restartGateway = (reloadEnv) => {
|
|
150
215
|
reloadEnv();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
expectedExitPids.add(gatewayChild.pid);
|
|
155
|
-
gatewayChild.kill("SIGTERM");
|
|
156
|
-
gatewayChild = null;
|
|
157
|
-
} catch (e) {
|
|
158
|
-
console.log(
|
|
159
|
-
`[alphaclaw] Failed to stop managed gateway process: ${e.message}`,
|
|
160
|
-
);
|
|
161
|
-
runGatewayCmd("stop");
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
runGatewayCmd("stop");
|
|
165
|
-
}
|
|
166
|
-
runGatewayCmd("start");
|
|
167
|
-
const launchWhenReady = async () => {
|
|
168
|
-
const waitUntil = Date.now() + 8000;
|
|
169
|
-
while (Date.now() < waitUntil) {
|
|
170
|
-
if (!(await isGatewayRunning())) break;
|
|
171
|
-
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
172
|
-
}
|
|
173
|
-
console.log(
|
|
174
|
-
"[alphaclaw] Starting openclaw gateway with refreshed environment...",
|
|
175
|
-
);
|
|
176
|
-
launchGatewayProcess();
|
|
177
|
-
};
|
|
178
|
-
void launchWhenReady();
|
|
216
|
+
markManagedGatewayExitExpected();
|
|
217
|
+
runGatewayCmd("--force");
|
|
179
218
|
};
|
|
180
219
|
|
|
181
220
|
const attachGatewaySignalHandlers = () => {
|
|
@@ -70,19 +70,22 @@ const ensureTopicPathForClient = ({
|
|
|
70
70
|
const normalizedClient = String(client || "default").trim() || "default";
|
|
71
71
|
const push = getGmailPushConfig(state);
|
|
72
72
|
const existingTopic = String(push.topics?.[normalizedClient] || "").trim();
|
|
73
|
-
|
|
73
|
+
const requestedProjectId = String(projectIdOverride || "").trim();
|
|
74
|
+
const existingProjectId = parseProjectIdFromTopicPath(existingTopic);
|
|
75
|
+
if (existingTopic && (!requestedProjectId || requestedProjectId === existingProjectId)) {
|
|
74
76
|
return { state, topicPath: existingTopic };
|
|
75
77
|
}
|
|
76
78
|
const credentials = readGoogleCredentials(normalizedClient);
|
|
77
79
|
const projectId =
|
|
78
|
-
|
|
80
|
+
requestedProjectId ||
|
|
79
81
|
String(credentials?.projectId || "").trim();
|
|
80
82
|
if (!projectId) {
|
|
81
83
|
throw new Error(
|
|
82
84
|
`Could not detect GCP project_id for client "${normalizedClient}". Save Google credentials first.`,
|
|
83
85
|
);
|
|
84
86
|
}
|
|
85
|
-
const topicName =
|
|
87
|
+
const topicName =
|
|
88
|
+
parseTopicName(existingTopic) || createTopicNameForClient(normalizedClient);
|
|
86
89
|
const topicPath = `projects/${projectId}/topics/${topicName}`;
|
|
87
90
|
const updated = setGmailPushConfig({
|
|
88
91
|
state,
|
|
@@ -29,11 +29,12 @@ const createOnboardingService = ({
|
|
|
29
29
|
resolveGithubRepoUrl,
|
|
30
30
|
resolveModelProvider,
|
|
31
31
|
hasCodexOauthProfile,
|
|
32
|
+
authProfiles,
|
|
32
33
|
ensureGatewayProxyConfig,
|
|
33
34
|
getBaseUrl,
|
|
34
35
|
startGateway,
|
|
35
36
|
}) => {
|
|
36
|
-
const { OPENCLAW_DIR, WORKSPACE_DIR } = constants;
|
|
37
|
+
const { OPENCLAW_DIR, WORKSPACE_DIR, kOnboardingMarkerPath } = constants;
|
|
37
38
|
|
|
38
39
|
const verifyGithubSetup = async ({
|
|
39
40
|
githubRepoInput,
|
|
@@ -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({
|
|
@@ -158,6 +160,19 @@ const createOnboardingService = ({
|
|
|
158
160
|
|
|
159
161
|
installHourlyGitSyncScript({ fs, openclawDir: OPENCLAW_DIR });
|
|
160
162
|
await installHourlyGitSyncCron({ fs, openclawDir: OPENCLAW_DIR });
|
|
163
|
+
fs.mkdirSync(path.dirname(kOnboardingMarkerPath), { recursive: true });
|
|
164
|
+
fs.writeFileSync(
|
|
165
|
+
kOnboardingMarkerPath,
|
|
166
|
+
JSON.stringify(
|
|
167
|
+
{
|
|
168
|
+
onboarded: true,
|
|
169
|
+
reason: "onboarding_complete",
|
|
170
|
+
markedAt: new Date().toISOString(),
|
|
171
|
+
},
|
|
172
|
+
null,
|
|
173
|
+
2,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
161
176
|
|
|
162
177
|
try {
|
|
163
178
|
await shellCmd(`alphaclaw git-sync -m "initial setup"`, {
|
|
@@ -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 {
|
|
@@ -19,7 +19,49 @@ const registerModelRoutes = ({
|
|
|
19
19
|
parseJsonFromNoisyOutput,
|
|
20
20
|
normalizeOnboardingModels,
|
|
21
21
|
authProfiles,
|
|
22
|
+
readEnvFile,
|
|
23
|
+
writeEnvFile,
|
|
24
|
+
reloadEnv,
|
|
22
25
|
}) => {
|
|
26
|
+
const upsertEnvVar = (items, key, value) => {
|
|
27
|
+
const next = Array.isArray(items) ? [...items] : [];
|
|
28
|
+
const existing = next.find((entry) => entry.key === key);
|
|
29
|
+
if (existing) {
|
|
30
|
+
existing.value = value;
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
next.push({ key, value });
|
|
34
|
+
return next;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const syncEnvVarsForProfiles = (profiles) => {
|
|
38
|
+
if (
|
|
39
|
+
!Array.isArray(profiles) ||
|
|
40
|
+
typeof readEnvFile !== "function" ||
|
|
41
|
+
typeof writeEnvFile !== "function" ||
|
|
42
|
+
typeof reloadEnv !== "function"
|
|
43
|
+
) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let nextEnvVars = readEnvFile();
|
|
47
|
+
let changed = false;
|
|
48
|
+
for (const profile of profiles) {
|
|
49
|
+
if (profile?.type !== "api_key") continue;
|
|
50
|
+
const envKey = authProfiles.getEnvVarForApiKeyProvider?.(profile.provider);
|
|
51
|
+
const envValue = String(profile?.key || "").trim();
|
|
52
|
+
if (!envKey || !envValue) continue;
|
|
53
|
+
const prevValue = String(
|
|
54
|
+
nextEnvVars.find((entry) => entry.key === envKey)?.value || "",
|
|
55
|
+
);
|
|
56
|
+
if (prevValue === envValue) continue;
|
|
57
|
+
nextEnvVars = upsertEnvVar(nextEnvVars, envKey, envValue);
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
if (!changed) return;
|
|
61
|
+
writeEnvFile(nextEnvVars);
|
|
62
|
+
reloadEnv();
|
|
63
|
+
};
|
|
64
|
+
|
|
23
65
|
// ── Existing CLI-backed catalog/status routes ──
|
|
24
66
|
|
|
25
67
|
app.get("/api/models", async (req, res) => {
|
|
@@ -134,6 +176,7 @@ const registerModelRoutes = ({
|
|
|
134
176
|
authProfiles.upsertProfile(profileId, credential, agentId);
|
|
135
177
|
}
|
|
136
178
|
}
|
|
179
|
+
syncEnvVarsForProfiles(profiles);
|
|
137
180
|
}
|
|
138
181
|
|
|
139
182
|
if (authOrder && typeof authOrder === "object") {
|
|
@@ -200,6 +243,7 @@ const registerModelRoutes = ({
|
|
|
200
243
|
try {
|
|
201
244
|
const agentId = req.query.agentId || undefined;
|
|
202
245
|
authProfiles.upsertProfile(profileId, credential, agentId);
|
|
246
|
+
syncEnvVarsForProfiles([{ id: profileId, ...credential }]);
|
|
203
247
|
res.json({ ok: true });
|
|
204
248
|
} catch (err) {
|
|
205
249
|
res
|
|
@@ -71,6 +71,7 @@ const registerOnboardingRoutes = ({
|
|
|
71
71
|
resolveGithubRepoUrl,
|
|
72
72
|
resolveModelProvider,
|
|
73
73
|
hasCodexOauthProfile,
|
|
74
|
+
authProfiles,
|
|
74
75
|
ensureGatewayProxyConfig,
|
|
75
76
|
getBaseUrl,
|
|
76
77
|
startGateway,
|
|
@@ -85,6 +86,7 @@ const registerOnboardingRoutes = ({
|
|
|
85
86
|
resolveGithubRepoUrl,
|
|
86
87
|
resolveModelProvider,
|
|
87
88
|
hasCodexOauthProfile,
|
|
89
|
+
authProfiles,
|
|
88
90
|
ensureGatewayProxyConfig,
|
|
89
91
|
getBaseUrl,
|
|
90
92
|
startGateway,
|
|
@@ -17,10 +17,10 @@ const registerSystemRoutes = ({
|
|
|
17
17
|
alphaclawVersionService,
|
|
18
18
|
clawCmd,
|
|
19
19
|
restartGateway,
|
|
20
|
-
onExpectedGatewayRestart,
|
|
21
20
|
OPENCLAW_DIR,
|
|
22
21
|
restartRequiredState,
|
|
23
22
|
topicRegistry,
|
|
23
|
+
authProfiles,
|
|
24
24
|
}) => {
|
|
25
25
|
let envRestartPending = false;
|
|
26
26
|
const kEnvVarsReservedForUserInput = new Set([
|
|
@@ -58,7 +58,9 @@ const registerSystemRoutes = ({
|
|
|
58
58
|
const parseJsonFromStdout = (stdout) => {
|
|
59
59
|
const raw = String(stdout || "").trim();
|
|
60
60
|
if (!raw) return null;
|
|
61
|
-
const candidateStarts = [raw.indexOf("{"), raw.indexOf("[")].filter(
|
|
61
|
+
const candidateStarts = [raw.indexOf("{"), raw.indexOf("[")].filter(
|
|
62
|
+
(idx) => idx >= 0,
|
|
63
|
+
);
|
|
62
64
|
for (const start of candidateStarts) {
|
|
63
65
|
const candidate = raw.slice(start);
|
|
64
66
|
try {
|
|
@@ -74,7 +76,9 @@ const registerSystemRoutes = ({
|
|
|
74
76
|
if (telegramMatch) {
|
|
75
77
|
return `Telegram ${telegramMatch[1]}`;
|
|
76
78
|
}
|
|
77
|
-
const telegramTopicMatch = key.match(
|
|
79
|
+
const telegramTopicMatch = key.match(
|
|
80
|
+
/:telegram:group:([^:]+):topic:([^:]+)$/,
|
|
81
|
+
);
|
|
78
82
|
if (telegramTopicMatch) {
|
|
79
83
|
const [, groupId, topicId] = telegramTopicMatch;
|
|
80
84
|
let groupEntry = null;
|
|
@@ -82,7 +86,9 @@ const registerSystemRoutes = ({
|
|
|
82
86
|
groupEntry = topicRegistry?.getGroup?.(groupId) || null;
|
|
83
87
|
} catch {}
|
|
84
88
|
const groupName = String(groupEntry?.name || "").trim();
|
|
85
|
-
const topicName = String(
|
|
89
|
+
const topicName = String(
|
|
90
|
+
groupEntry?.topics?.[topicId]?.name || "",
|
|
91
|
+
).trim();
|
|
86
92
|
if (groupName && topicName) return `Telegram ${groupName} · ${topicName}`;
|
|
87
93
|
if (topicName) return `Telegram Topic ${topicName}`;
|
|
88
94
|
return `Telegram Topic ${topicId}`;
|
|
@@ -93,6 +99,34 @@ const registerSystemRoutes = ({
|
|
|
93
99
|
}
|
|
94
100
|
return key || "Session";
|
|
95
101
|
};
|
|
102
|
+
const syncApiKeyAuthProfilesFromEnvVars = (nextEnvVars) => {
|
|
103
|
+
if (!authProfiles) return;
|
|
104
|
+
const envMap = new Map(
|
|
105
|
+
(nextEnvVars || []).map((entry) => [
|
|
106
|
+
String(entry?.key || "").trim(),
|
|
107
|
+
String(entry?.value || ""),
|
|
108
|
+
]),
|
|
109
|
+
);
|
|
110
|
+
const providers = [
|
|
111
|
+
"anthropic",
|
|
112
|
+
"openai",
|
|
113
|
+
"google",
|
|
114
|
+
"mistral",
|
|
115
|
+
"voyage",
|
|
116
|
+
"groq",
|
|
117
|
+
"deepgram",
|
|
118
|
+
];
|
|
119
|
+
for (const provider of providers) {
|
|
120
|
+
const envKey = authProfiles.getEnvVarForApiKeyProvider?.(provider);
|
|
121
|
+
if (!envKey) continue;
|
|
122
|
+
const value = envMap.get(envKey) || "";
|
|
123
|
+
if (!value.trim()) {
|
|
124
|
+
authProfiles.removeApiKeyProfileForEnvVar?.(provider);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
authProfiles.upsertApiKeyProfileForEnvVar(provider, value);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
96
130
|
const listSendableAgentSessions = async () => {
|
|
97
131
|
const result = await clawCmd("sessions --json", { quiet: true });
|
|
98
132
|
if (!result.ok) {
|
|
@@ -168,6 +202,7 @@ const registerSystemRoutes = ({
|
|
|
168
202
|
}
|
|
169
203
|
return getSystemCronStatus();
|
|
170
204
|
};
|
|
205
|
+
const isVisibleInEnvars = (def) => def?.visibleInEnvars !== false;
|
|
171
206
|
|
|
172
207
|
app.get("/api/env", (req, res) => {
|
|
173
208
|
const fileVars = readEnvFile();
|
|
@@ -175,6 +210,7 @@ const registerSystemRoutes = ({
|
|
|
175
210
|
|
|
176
211
|
for (const def of kKnownVars) {
|
|
177
212
|
if (isReservedUserEnvVar(def.key)) continue;
|
|
213
|
+
if (!isVisibleInEnvars(def)) continue;
|
|
178
214
|
const fileEntry = fileVars.find((v) => v.key === def.key);
|
|
179
215
|
const value = fileEntry?.value || "";
|
|
180
216
|
merged.push({
|
|
@@ -183,6 +219,7 @@ const registerSystemRoutes = ({
|
|
|
183
219
|
label: def.label,
|
|
184
220
|
group: def.group,
|
|
185
221
|
hint: def.hint,
|
|
222
|
+
features: def.features,
|
|
186
223
|
source: fileEntry?.value ? "env_file" : "unset",
|
|
187
224
|
editable: true,
|
|
188
225
|
});
|
|
@@ -232,10 +269,25 @@ const registerSystemRoutes = ({
|
|
|
232
269
|
const existingLockedVars = readEnvFile().filter((v) =>
|
|
233
270
|
isReservedUserEnvVar(v.key),
|
|
234
271
|
);
|
|
235
|
-
const
|
|
272
|
+
const hiddenKnownVarKeys = new Set(
|
|
273
|
+
kKnownVars
|
|
274
|
+
.filter(
|
|
275
|
+
(def) => !isReservedUserEnvVar(def.key) && !isVisibleInEnvars(def),
|
|
276
|
+
)
|
|
277
|
+
.map((def) => def.key),
|
|
278
|
+
);
|
|
279
|
+
const existingHiddenKnownVars = readEnvFile().filter((v) =>
|
|
280
|
+
hiddenKnownVarKeys.has(v.key),
|
|
281
|
+
);
|
|
282
|
+
const nextEnvVars = [
|
|
283
|
+
...filtered,
|
|
284
|
+
...existingHiddenKnownVars,
|
|
285
|
+
...existingLockedVars,
|
|
286
|
+
];
|
|
236
287
|
syncChannelConfig(nextEnvVars, "remove");
|
|
237
288
|
writeEnvFile(nextEnvVars);
|
|
238
289
|
const changed = reloadEnv();
|
|
290
|
+
syncApiKeyAuthProfilesFromEnvVars(nextEnvVars);
|
|
239
291
|
if (changed && isOnboarded()) {
|
|
240
292
|
envRestartPending = true;
|
|
241
293
|
}
|
|
@@ -361,12 +413,15 @@ const registerSystemRoutes = ({
|
|
|
361
413
|
let selectedSession = null;
|
|
362
414
|
try {
|
|
363
415
|
const sessions = await listSendableAgentSessions();
|
|
364
|
-
selectedSession =
|
|
416
|
+
selectedSession =
|
|
417
|
+
sessions.find((sessionRow) => sessionRow.key === sessionKey) || null;
|
|
365
418
|
} catch (err) {
|
|
366
419
|
return res.status(502).json({ ok: false, error: err.message });
|
|
367
420
|
}
|
|
368
421
|
if (!selectedSession) {
|
|
369
|
-
return res
|
|
422
|
+
return res
|
|
423
|
+
.status(400)
|
|
424
|
+
.json({ ok: false, error: "Selected session was not found" });
|
|
370
425
|
}
|
|
371
426
|
if (selectedSession.replyChannel && selectedSession.replyTo) {
|
|
372
427
|
command +=
|
|
@@ -380,7 +435,10 @@ const registerSystemRoutes = ({
|
|
|
380
435
|
if (!result.ok) {
|
|
381
436
|
return res
|
|
382
437
|
.status(502)
|
|
383
|
-
.json({
|
|
438
|
+
.json({
|
|
439
|
+
ok: false,
|
|
440
|
+
error: result.stderr || "Could not send message to agent",
|
|
441
|
+
});
|
|
384
442
|
}
|
|
385
443
|
return res.json({ ok: true, stdout: result.stdout || "" });
|
|
386
444
|
});
|
|
@@ -417,9 +475,6 @@ const registerSystemRoutes = ({
|
|
|
417
475
|
}
|
|
418
476
|
restartRequiredState.markRestartInProgress();
|
|
419
477
|
try {
|
|
420
|
-
if (typeof onExpectedGatewayRestart === "function") {
|
|
421
|
-
onExpectedGatewayRestart();
|
|
422
|
-
}
|
|
423
478
|
restartGateway();
|
|
424
479
|
envRestartPending = false;
|
|
425
480
|
restartRequiredState.clearRequired();
|
package/lib/server/watchdog.js
CHANGED
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
|
|
10
10
|
const kHealthStartupGraceMs = 30 * 1000;
|
|
11
11
|
const kBootstrapHealthCheckMs = 5 * 1000;
|
|
12
|
-
const kExpectedRestartWindowMs =
|
|
12
|
+
const kExpectedRestartWindowMs = 15 * 1000;
|
|
13
13
|
|
|
14
14
|
const isTruthy = (value) =>
|
|
15
15
|
["1", "true", "yes", "on"].includes(String(value || "").trim().toLowerCase());
|
|
@@ -506,7 +506,7 @@ const createWatchdog = ({
|
|
|
506
506
|
const onGatewayExit = ({ code, signal, expectedExit = false, stderrTail = [] } = {}) => {
|
|
507
507
|
const correlationId = createCorrelationId();
|
|
508
508
|
clearDegradedHealthCheckTimer();
|
|
509
|
-
if (expectedExit) {
|
|
509
|
+
if (expectedExit && (code == null || code === 0)) {
|
|
510
510
|
state.lifecycle = "restarting";
|
|
511
511
|
state.health = "unknown";
|
|
512
512
|
state.crashRecoveryActive = false;
|
package/lib/server.js
CHANGED
|
@@ -204,6 +204,9 @@ registerModelRoutes({
|
|
|
204
204
|
parseJsonFromNoisyOutput,
|
|
205
205
|
normalizeOnboardingModels,
|
|
206
206
|
authProfiles,
|
|
207
|
+
readEnvFile,
|
|
208
|
+
writeEnvFile,
|
|
209
|
+
reloadEnv,
|
|
207
210
|
});
|
|
208
211
|
registerOnboardingRoutes({
|
|
209
212
|
app,
|
|
@@ -217,6 +220,7 @@ registerOnboardingRoutes({
|
|
|
217
220
|
resolveGithubRepoUrl,
|
|
218
221
|
resolveModelProvider,
|
|
219
222
|
hasCodexOauthProfile: authProfiles.hasCodexOauthProfile,
|
|
223
|
+
authProfiles,
|
|
220
224
|
ensureGatewayProxyConfig,
|
|
221
225
|
getBaseUrl,
|
|
222
226
|
startGateway,
|
|
@@ -238,10 +242,10 @@ registerSystemRoutes({
|
|
|
238
242
|
alphaclawVersionService,
|
|
239
243
|
clawCmd,
|
|
240
244
|
restartGateway,
|
|
241
|
-
onExpectedGatewayRestart: () => watchdog.onExpectedRestart(),
|
|
242
245
|
OPENCLAW_DIR: constants.OPENCLAW_DIR,
|
|
243
246
|
restartRequiredState,
|
|
244
247
|
topicRegistry,
|
|
248
|
+
authProfiles,
|
|
245
249
|
});
|
|
246
250
|
registerBrowseRoutes({
|
|
247
251
|
app,
|
package/lib/setup/env.template
CHANGED