@chrysb/alphaclaw 0.9.15 → 0.9.17

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 (37) hide show
  1. package/lib/public/css/tailwind.generated.css +1 -1
  2. package/lib/public/dist/app.bundle.js +2168 -2103
  3. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +59 -7
  4. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +124 -0
  5. package/lib/public/js/components/envars.js +1 -1
  6. package/lib/public/js/components/file-tree.js +82 -6
  7. package/lib/public/js/components/row-accessory-select.js +52 -0
  8. package/lib/public/js/lib/api.js +11 -1
  9. package/lib/public/js/lib/model-catalog.js +6 -0
  10. package/lib/public/js/lib/model-config.js +12 -7
  11. package/lib/public/js/lib/thinking-levels.js +37 -0
  12. package/lib/server/agents/agents.js +33 -7
  13. package/lib/server/agents/channels.js +4 -2
  14. package/lib/server/chat-ws.js +4 -1
  15. package/lib/server/constants.js +25 -0
  16. package/lib/server/cost-utils.js +2 -0
  17. package/lib/server/db/auth/index.js +147 -0
  18. package/lib/server/db/auth/schema.js +17 -0
  19. package/lib/server/gateway.js +158 -19
  20. package/lib/server/helpers.js +1 -3
  21. package/lib/server/init/register-server-routes.js +37 -18
  22. package/lib/server/init/runtime-init.js +4 -0
  23. package/lib/server/init/server-lifecycle.js +1 -24
  24. package/lib/server/login-throttle.js +242 -60
  25. package/lib/server/model-catalog-bootstrap.json +5 -0
  26. package/lib/server/onboarding/index.js +2 -2
  27. package/lib/server/openclaw-thinking.js +103 -0
  28. package/lib/server/openclaw-version.js +1 -1
  29. package/lib/server/routes/agents.js +10 -3
  30. package/lib/server/routes/browse/constants.js +3 -1
  31. package/lib/server/routes/browse/index.js +39 -11
  32. package/lib/server/routes/models.js +35 -1
  33. package/lib/server/routes/onboarding.js +2 -2
  34. package/lib/server/routes/system.js +2 -2
  35. package/lib/server/usage-tracker-config.js +52 -1
  36. package/lib/server.js +26 -22
  37. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  const path = require("path");
2
+ const { normalizeThinkingDefaultValue } = require("../openclaw-thinking");
2
3
 
3
4
  const {
4
5
  kDefaultAgentId,
@@ -42,11 +43,25 @@ const toReadableAgent = (agent = {}) => ({
42
43
  });
43
44
 
44
45
  const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
45
- const listAgents = () => {
46
- const cfg = withNormalizedAgentsConfig({
46
+ const readAgentsConfig = () =>
47
+ withNormalizedAgentsConfig({
47
48
  OPENCLAW_DIR,
48
49
  cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
49
50
  });
51
+
52
+ const getAgentDefaults = () => {
53
+ const cfg = readAgentsConfig();
54
+ const thinkingDefault = cfg.agents?.defaults?.thinkingDefault;
55
+ return {
56
+ thinkingDefault:
57
+ typeof thinkingDefault === "string" && thinkingDefault.trim()
58
+ ? thinkingDefault.trim()
59
+ : null,
60
+ };
61
+ };
62
+
63
+ const listAgents = () => {
64
+ const cfg = readAgentsConfig();
50
65
  return (cfg.agents?.list || []).map((entry) => toReadableAgent(entry));
51
66
  };
52
67
 
@@ -131,12 +146,9 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
131
146
  return toReadableAgent(nextAgent);
132
147
  };
133
148
 
134
- const updateAgent = (agentId, patch = {}) => {
149
+ const updateAgent = async (agentId, patch = {}) => {
135
150
  const normalized = String(agentId || "").trim();
136
- const cfg = withNormalizedAgentsConfig({
137
- OPENCLAW_DIR,
138
- cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
139
- });
151
+ const cfg = readAgentsConfig();
140
152
  const index = cfg.agents.list.findIndex((entry) => entry.id === normalized);
141
153
  if (index < 0) throw new Error(`Agent "${normalized}" not found`);
142
154
  const current = cfg.agents.list[index];
@@ -188,6 +200,19 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
188
200
  delete next.tools;
189
201
  }
190
202
  }
203
+ if (patch.thinkingDefault !== undefined) {
204
+ if (patch.thinkingDefault === null) {
205
+ delete next.thinkingDefault;
206
+ } else {
207
+ const normalizedThinking = await normalizeThinkingDefaultValue(
208
+ patch.thinkingDefault,
209
+ );
210
+ if (!normalizedThinking) {
211
+ throw new Error("Invalid thinkingDefault value");
212
+ }
213
+ next.thinkingDefault = normalizedThinking;
214
+ }
215
+ }
191
216
  cfg.agents.list[index] = next;
192
217
  saveConfig({ fsImpl, OPENCLAW_DIR, config: cfg });
193
218
  return toReadableAgent(next);
@@ -253,6 +278,7 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
253
278
  return {
254
279
  listAgents,
255
280
  getAgent,
281
+ getAgentDefaults,
256
282
  getAgentWorkspaceSize,
257
283
  createAgent,
258
284
  updateAgent,
@@ -270,7 +270,7 @@ const createChannelsDomain = ({
270
270
 
271
271
  const previousConfig = cloneJson(cfg);
272
272
  try {
273
- onProgress({ phase: "restarting", label: "Rebooting..." });
273
+ onProgress({ phase: "configuring", label: "Configuring..." });
274
274
  writeEnvFile(nextEnvVars);
275
275
  reloadEnv();
276
276
  assertActiveChannelTokenEnvVars({
@@ -280,7 +280,6 @@ const createChannelsDomain = ({
280
280
  }),
281
281
  envVars: nextEnvVars,
282
282
  });
283
- await restartGateway();
284
283
  const pluginEnabledCfg = withNormalizedAgentsConfig({
285
284
  OPENCLAW_DIR,
286
285
  cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
@@ -374,6 +373,8 @@ const createChannelsDomain = ({
374
373
  "Could not bind channel account",
375
374
  );
376
375
  }
376
+ onProgress({ phase: "restarting", label: "Rebooting..." });
377
+ await restartGateway();
377
378
  } catch (error) {
378
379
  try {
379
380
  await clawCmd(
@@ -760,6 +761,7 @@ const createChannelsDomain = ({
760
761
  }
761
762
 
762
763
  cleanupChannelAccountPairingFiles({ provider, accountId });
764
+ await restartGateway();
763
765
  return { ok: true };
764
766
  }
765
767
 
@@ -6,7 +6,7 @@ const kEnvRefPattern = /^\$\{([A-Z0-9_]+)\}$/i;
6
6
  const kConnectTimeoutMs = 8000;
7
7
  const kHistoryTimeoutMs = 12000;
8
8
  const kGatewayReqTimeoutMs = 15000;
9
- const kGatewayProtocolVersion = 3;
9
+ const kGatewayProtocolVersion = 4;
10
10
  // Gateway method auth (see OpenClaw method-scopes): chat.history needs operator.read;
11
11
  // chat.send / chat.abort need operator.write. Align with CLI_DEFAULT_OPERATOR_SCOPES plus admin.
12
12
  const kGatewayChatBridgeScopes = [
@@ -283,6 +283,9 @@ const sanitizeError = (error) => {
283
283
  ) {
284
284
  return "Gateway authentication failed. Verify OPENCLAW_GATEWAY_TOKEN matches the gateway.";
285
285
  }
286
+ if (lower.includes("protocol mismatch")) {
287
+ return "Chat cannot connect to the gateway (protocol version mismatch). Update AlphaClaw to match your OpenClaw version.";
288
+ }
286
289
  if (lower.includes("method not found") || lower.includes("unknown method")) {
287
290
  return "This gateway build does not support chat APIs. Update OpenClaw.";
288
291
  }
@@ -54,6 +54,22 @@ const kLoginMaxLockMs = parsePositiveInt(
54
54
  process.env.LOGIN_RATE_MAX_LOCK_MS,
55
55
  15 * 60 * 1000,
56
56
  );
57
+ const kLoginGlobalWindowMs = parsePositiveInt(
58
+ process.env.LOGIN_RATE_GLOBAL_WINDOW_MS,
59
+ kLoginWindowMs,
60
+ );
61
+ const kLoginGlobalMaxAttempts = parsePositiveInt(
62
+ process.env.LOGIN_RATE_GLOBAL_MAX_ATTEMPTS,
63
+ Math.max(kLoginMaxAttempts * 5, 25),
64
+ );
65
+ const kLoginGlobalBaseLockMs = parsePositiveInt(
66
+ process.env.LOGIN_RATE_GLOBAL_BASE_LOCK_MS,
67
+ kLoginBaseLockMs,
68
+ );
69
+ const kLoginGlobalMaxLockMs = parsePositiveInt(
70
+ process.env.LOGIN_RATE_GLOBAL_MAX_LOCK_MS,
71
+ kLoginMaxLockMs,
72
+ );
57
73
  const kLoginCleanupIntervalMs = parsePositiveInt(
58
74
  process.env.LOGIN_RATE_CLEANUP_INTERVAL_MS,
59
75
  60 * 1000,
@@ -92,6 +108,11 @@ const kOnboardingModelProviders = new Set([
92
108
  "vllm",
93
109
  ]);
94
110
  const kMinimalFallbackOnboardingModels = [
111
+ {
112
+ key: "anthropic/claude-opus-4-8",
113
+ provider: "anthropic",
114
+ label: "Claude Opus 4.8",
115
+ },
95
116
  {
96
117
  key: "anthropic/claude-opus-4-7",
97
118
  provider: "anthropic",
@@ -445,6 +466,10 @@ module.exports = {
445
466
  kLoginMaxAttempts,
446
467
  kLoginBaseLockMs,
447
468
  kLoginMaxLockMs,
469
+ kLoginGlobalWindowMs,
470
+ kLoginGlobalMaxAttempts,
471
+ kLoginGlobalBaseLockMs,
472
+ kLoginGlobalMaxLockMs,
448
473
  kLoginCleanupIntervalMs,
449
474
  kLoginStateTtlMs,
450
475
  kOnboardingModelProviders,
@@ -13,6 +13,8 @@ const kClaudeOpus47Pricing = {
13
13
  };
14
14
 
15
15
  const kGlobalModelPricing = {
16
+ "claude-opus-4-8": kClaudeOpus47Pricing,
17
+ "claude-opus-4.8": kClaudeOpus47Pricing,
16
18
  "claude-opus-4-7": kClaudeOpus47Pricing,
17
19
  "claude-opus-4.7": kClaudeOpus47Pricing,
18
20
  "claude-opus-4-6": {
@@ -0,0 +1,147 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { DatabaseSync } = require("node:sqlite");
4
+ const { createSchema } = require("./schema");
5
+
6
+ let db = null;
7
+
8
+ const ensureDb = () => {
9
+ if (!db) throw new Error("Auth DB not initialized");
10
+ return db;
11
+ };
12
+
13
+ const closeAuthDb = () => {
14
+ if (!db) return;
15
+ const database = db;
16
+ db = null;
17
+ database.close();
18
+ };
19
+
20
+ const initAuthDb = ({ rootDir }) => {
21
+ closeAuthDb();
22
+ const dbDir = path.join(rootDir, "db");
23
+ fs.mkdirSync(dbDir, { recursive: true });
24
+ const dbPath = path.join(dbDir, "auth.db");
25
+ db = new DatabaseSync(dbPath);
26
+ db.exec("PRAGMA busy_timeout = 5000");
27
+ createSchema(db);
28
+ return { path: dbPath };
29
+ };
30
+
31
+ const toStateModel = (row) => {
32
+ if (!row) return null;
33
+ return {
34
+ attempts: Number(row.attempts || 0),
35
+ windowStart: Number(row.window_start || 0),
36
+ lockUntil: Number(row.lock_until || 0),
37
+ failStreak: Number(row.fail_streak || 0),
38
+ lastSeenAt: Number(row.last_seen_at || 0),
39
+ };
40
+ };
41
+
42
+ const createLoginThrottleStore = () => ({
43
+ get: (stateKey) => {
44
+ const row = ensureDb()
45
+ .prepare(
46
+ `
47
+ SELECT
48
+ attempts,
49
+ window_start,
50
+ lock_until,
51
+ fail_streak,
52
+ last_seen_at
53
+ FROM login_throttle_states
54
+ WHERE state_key = $state_key
55
+ LIMIT 1
56
+ `,
57
+ )
58
+ .get({ $state_key: String(stateKey || "") });
59
+ return toStateModel(row);
60
+ },
61
+
62
+ set: (stateKey, state) => {
63
+ ensureDb()
64
+ .prepare(
65
+ `
66
+ INSERT INTO login_throttle_states (
67
+ state_key,
68
+ attempts,
69
+ window_start,
70
+ lock_until,
71
+ fail_streak,
72
+ last_seen_at
73
+ ) VALUES (
74
+ $state_key,
75
+ $attempts,
76
+ $window_start,
77
+ $lock_until,
78
+ $fail_streak,
79
+ $last_seen_at
80
+ )
81
+ ON CONFLICT(state_key) DO UPDATE SET
82
+ attempts = excluded.attempts,
83
+ window_start = excluded.window_start,
84
+ lock_until = excluded.lock_until,
85
+ fail_streak = excluded.fail_streak,
86
+ last_seen_at = excluded.last_seen_at
87
+ `,
88
+ )
89
+ .run({
90
+ $state_key: String(stateKey || ""),
91
+ $attempts: Number(state?.attempts || 0),
92
+ $window_start: Number(state?.windowStart || 0),
93
+ $lock_until: Number(state?.lockUntil || 0),
94
+ $fail_streak: Number(state?.failStreak || 0),
95
+ $last_seen_at: Number(state?.lastSeenAt || 0),
96
+ });
97
+ },
98
+
99
+ delete: (stateKey) => {
100
+ ensureDb()
101
+ .prepare(
102
+ `
103
+ DELETE FROM login_throttle_states
104
+ WHERE state_key = $state_key
105
+ `,
106
+ )
107
+ .run({ $state_key: String(stateKey || "") });
108
+ },
109
+
110
+ entries: () =>
111
+ ensureDb()
112
+ .prepare(
113
+ `
114
+ SELECT
115
+ state_key,
116
+ attempts,
117
+ window_start,
118
+ lock_until,
119
+ fail_streak,
120
+ last_seen_at
121
+ FROM login_throttle_states
122
+ `,
123
+ )
124
+ .all()
125
+ .map((row) => [String(row.state_key || ""), toStateModel(row)]),
126
+
127
+ runExclusive: (callback) => {
128
+ const database = ensureDb();
129
+ database.exec("BEGIN IMMEDIATE");
130
+ try {
131
+ const result = callback();
132
+ database.exec("COMMIT");
133
+ return result;
134
+ } catch (err) {
135
+ try {
136
+ database.exec("ROLLBACK");
137
+ } catch {}
138
+ throw err;
139
+ }
140
+ },
141
+ });
142
+
143
+ module.exports = {
144
+ initAuthDb,
145
+ closeAuthDb,
146
+ createLoginThrottleStore,
147
+ };
@@ -0,0 +1,17 @@
1
+ const createSchema = (db) => {
2
+ db.exec(`
3
+ CREATE TABLE IF NOT EXISTS login_throttle_states (
4
+ state_key TEXT PRIMARY KEY,
5
+ attempts INTEGER NOT NULL DEFAULT 0,
6
+ window_start INTEGER NOT NULL,
7
+ lock_until INTEGER NOT NULL DEFAULT 0,
8
+ fail_streak INTEGER NOT NULL DEFAULT 0,
9
+ last_seen_at INTEGER NOT NULL
10
+ );
11
+
12
+ CREATE INDEX IF NOT EXISTS idx_login_throttle_states_last_seen
13
+ ON login_throttle_states(last_seen_at);
14
+ `);
15
+ };
16
+
17
+ module.exports = { createSchema };
@@ -18,6 +18,10 @@ let gatewayExitHandler = null;
18
18
  let gatewayLaunchHandler = null;
19
19
  const kGatewayStderrTailLines = 50;
20
20
  const kPluginRuntimeDepsPreflightTimeoutMs = 120 * 1000;
21
+ const kGatewayShortCmdTimeoutMs = 15 * 1000;
22
+ const kGatewayLifecycleCmdTimeoutMs = 90 * 1000;
23
+ const kGatewayRestartReadyTimeoutMs = 120 * 1000;
24
+ const kGatewayRestartReadyPollMs = 500;
21
25
  let gatewayStderrTail = [];
22
26
  const expectedExitPids = new Set();
23
27
 
@@ -225,27 +229,122 @@ const isGatewayRunning = () =>
225
229
  });
226
230
  });
227
231
 
228
- const runGatewayCmd = (cmd) => {
229
- console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
232
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
233
+
234
+ const waitForGatewayReady = async ({
235
+ timeoutMs = kGatewayRestartReadyTimeoutMs,
236
+ } = {}) => {
237
+ const startedAt = Date.now();
238
+ while (Date.now() - startedAt < timeoutMs) {
239
+ if (await isGatewayRunning()) return true;
240
+ await sleep(kGatewayRestartReadyPollMs);
241
+ }
242
+ return false;
243
+ };
244
+
245
+ const logGatewayCmdOutput = (cmd, e) => {
246
+ if (e?.stdout?.trim()) {
247
+ console.log(`[alphaclaw] gateway ${cmd} stdout: ${e.stdout.trim()}`);
248
+ }
249
+ if (e?.stderr?.trim()) {
250
+ console.log(`[alphaclaw] gateway ${cmd} stderr: ${e.stderr.trim()}`);
251
+ }
252
+ if (!e?.stdout?.trim() && !e?.stderr?.trim()) {
253
+ console.log(`[alphaclaw] gateway ${cmd} error: ${e.message}`);
254
+ }
255
+ if (e?.status !== undefined && e?.status !== null) {
256
+ console.log(`[alphaclaw] gateway ${cmd} exit code: ${e.status}`);
257
+ }
258
+ };
259
+
260
+ const runGatewayShortCmd = (cmd) => {
230
261
  try {
231
- if (cmd === "--force" || cmd === "restart") {
232
- prepareOpenclawChannelPlugins();
233
- }
234
262
  const out = execSync(`openclaw gateway ${cmd}`, {
235
263
  env: gatewayEnv(),
236
- timeout: 15000,
264
+ timeout: kGatewayShortCmdTimeoutMs,
237
265
  encoding: "utf8",
238
266
  });
239
267
  if (out.trim()) console.log(`[alphaclaw] ${out.trim()}`);
240
268
  } catch (e) {
241
- if (e.stdout?.trim())
242
- console.log(`[alphaclaw] gateway ${cmd} stdout: ${e.stdout.trim()}`);
243
- if (e.stderr?.trim())
244
- console.log(`[alphaclaw] gateway ${cmd} stderr: ${e.stderr.trim()}`);
245
- if (!e.stdout?.trim() && !e.stderr?.trim())
246
- console.log(`[alphaclaw] gateway ${cmd} error: ${e.message}`);
247
- console.log(`[alphaclaw] gateway ${cmd} exit code: ${e.status}`);
269
+ logGatewayCmdOutput(cmd, e);
270
+ }
271
+ };
272
+
273
+ const runGatewayLifecycleRestart = () => {
274
+ console.log("[alphaclaw] Running: openclaw gateway restart");
275
+ try {
276
+ const out = execSync("openclaw gateway restart", {
277
+ env: gatewayEnv(),
278
+ timeout: kGatewayLifecycleCmdTimeoutMs,
279
+ encoding: "utf8",
280
+ });
281
+ if (out.trim()) console.log(`[alphaclaw] ${out.trim()}`);
282
+ return true;
283
+ } catch (e) {
284
+ logGatewayCmdOutput("restart", e);
285
+ return false;
286
+ }
287
+ };
288
+
289
+ const hasActiveManagedGatewayChild = () =>
290
+ !!(
291
+ gatewayChild &&
292
+ gatewayChild.exitCode === null &&
293
+ !gatewayChild.killed
294
+ );
295
+
296
+ const runGatewayRestartCmd = async (cmd) => {
297
+ prepareOpenclawChannelPlugins();
298
+ const startedAt = Date.now();
299
+ const child = spawn("openclaw", ["gateway", cmd], {
300
+ env: gatewayEnv(),
301
+ stdio: ["ignore", "pipe", "pipe"],
302
+ });
303
+ child.stdout.on("data", (d) => process.stdout.write(`[gateway] ${d}`));
304
+ child.stderr.on("data", (d) => {
305
+ appendStderrTail(d);
306
+ process.stderr.write(`[gateway] ${d}`);
307
+ });
308
+ child.on("exit", (code, signal) => {
309
+ console.log(
310
+ `[alphaclaw] gateway ${cmd} supervisor exited: code=${code ?? "null"}${signal ? ` signal=${signal}` : ""}`,
311
+ );
312
+ });
313
+
314
+ const ready = await waitForGatewayReady();
315
+ if (ready) {
316
+ console.log(
317
+ `[alphaclaw] Gateway ${cmd} ready (${Date.now() - startedAt}ms); leaving supervisor running`,
318
+ );
319
+ gatewayChild = null;
320
+ await notifyGatewayLaunch();
321
+ return;
322
+ }
323
+
324
+ console.warn(
325
+ `[alphaclaw] Gateway ${cmd} did not become ready within ${kGatewayRestartReadyTimeoutMs}ms; stopping`,
326
+ );
327
+ try {
328
+ child.kill("SIGTERM");
329
+ } catch {
330
+ // ignore
331
+ }
332
+ runGatewayShortCmd("stop");
333
+ };
334
+
335
+ const runGatewayColdStart = async () => {
336
+ stopManagedGatewayChild();
337
+ runGatewayShortCmd("stop");
338
+ await runGatewayRestartCmd("--force");
339
+ };
340
+
341
+ const runGatewayCmd = async (cmd) => {
342
+ console.log(`[alphaclaw] Running: openclaw gateway ${cmd}`);
343
+ if (cmd === "--force") {
344
+ await runGatewayRestartCmd("--force");
345
+ return;
248
346
  }
347
+ runGatewayShortCmd(cmd);
249
348
  };
250
349
 
251
350
  const launchGatewayProcess = () => {
@@ -322,6 +421,23 @@ const markManagedGatewayExitExpected = () => {
322
421
  return true;
323
422
  };
324
423
 
424
+ const notifyGatewayLaunch = async () => {
425
+ if (!gatewayLaunchHandler) return;
426
+ if (!(await isGatewayRunning())) return;
427
+ const pid =
428
+ gatewayChild &&
429
+ gatewayChild.exitCode === null &&
430
+ !gatewayChild.killed &&
431
+ gatewayChild.pid
432
+ ? gatewayChild.pid
433
+ : null;
434
+ try {
435
+ gatewayLaunchHandler({ startedAt: Date.now(), pid });
436
+ } catch (err) {
437
+ console.error(`[alphaclaw] Gateway launch handler error: ${err.message}`);
438
+ }
439
+ };
440
+
325
441
  const startGateway = async () => {
326
442
  if (!isOnboarded()) {
327
443
  console.log("[alphaclaw] Not onboarded yet — skipping gateway start");
@@ -329,22 +445,45 @@ const startGateway = async () => {
329
445
  }
330
446
  if (await isGatewayRunning()) {
331
447
  console.log("[alphaclaw] Gateway already running — skipping start");
448
+ await notifyGatewayLaunch();
332
449
  return;
333
450
  }
334
451
  console.log("[alphaclaw] Starting openclaw gateway...");
335
452
  launchGatewayProcess();
336
453
  };
337
454
 
338
- const restartGateway = (reloadEnv) => {
339
- reloadEnv();
455
+ const stopManagedGatewayChild = () => {
340
456
  markManagedGatewayExitExpected();
341
- runGatewayCmd("--force");
457
+ if (!gatewayChild || gatewayChild.exitCode !== null || gatewayChild.killed) {
458
+ return;
459
+ }
460
+ try {
461
+ gatewayChild.kill("SIGTERM");
462
+ } catch {
463
+ // ignore
464
+ }
465
+ gatewayChild = null;
342
466
  };
343
467
 
344
- const restartGatewayLight = (reloadEnv) => {
468
+ const restartGateway = async (reloadEnv) => {
345
469
  reloadEnv();
346
- markManagedGatewayExitExpected();
347
- runGatewayCmd("restart");
470
+ await runGatewayColdStart();
471
+ };
472
+
473
+ const restartGatewayLight = async (reloadEnv) => {
474
+ reloadEnv();
475
+ if (await isGatewayRunning()) {
476
+ if (runGatewayLifecycleRestart()) {
477
+ console.log("[alphaclaw] Gateway light restart complete");
478
+ return;
479
+ }
480
+ console.warn("[alphaclaw] Gateway light restart failed");
481
+ return;
482
+ }
483
+ if (!hasActiveManagedGatewayChild()) {
484
+ console.log("[alphaclaw] Gateway not running — starting managed process");
485
+ launchGatewayProcess();
486
+ }
348
487
  };
349
488
 
350
489
  const attachGatewaySignalHandlers = () => {
@@ -60,9 +60,7 @@ const isDebugEnabled = () =>
60
60
  isTruthyEnvFlag(process.env.DEBUG);
61
61
 
62
62
  const getClientKey = (req) =>
63
- normalizeIp(
64
- req.ip || req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "",
65
- ) || "unknown";
63
+ normalizeIp(req.ip || req.socket?.remoteAddress || "") || "unknown";
66
64
 
67
65
  const resolveGithubRepoUrl = (repoInput) => {
68
66
  const cleaned = String(repoInput || "")
@@ -1,3 +1,4 @@
1
+ const { runOnboardedBootSequence } = require("../startup");
1
2
  const { registerAuthRoutes } = require("../routes/auth");
2
3
  const { registerPageRoutes } = require("../routes/pages");
3
4
  const { registerModelRoutes } = require("../routes/models");
@@ -43,6 +44,9 @@ const registerServerRoutes = ({
43
44
  ensureGatewayProxyConfig,
44
45
  getBaseUrl,
45
46
  startGateway,
47
+ ensureManagedExecDefaults,
48
+ ensureUsageTrackerPluginConfig,
49
+ resolveSetupUrl,
46
50
  syncChannelConfig,
47
51
  getChannelStatus,
48
52
  openclawVersionService,
@@ -105,24 +109,6 @@ const registerServerRoutes = ({
105
109
  writeEnvFile,
106
110
  reloadEnv,
107
111
  });
108
- registerOnboardingRoutes({
109
- app,
110
- fs,
111
- constants,
112
- shellCmd,
113
- gatewayEnv,
114
- readEnvFile,
115
- writeEnvFile,
116
- reloadEnv,
117
- isOnboarded,
118
- resolveGithubRepoUrl,
119
- resolveModelProvider,
120
- hasCodexOauthProfile: authProfiles.hasCodexOauthProfile,
121
- authProfiles,
122
- ensureGatewayProxyConfig,
123
- getBaseUrl,
124
- startGateway,
125
- });
126
112
  registerSystemRoutes({
127
113
  app,
128
114
  fs,
@@ -183,6 +169,38 @@ const registerServerRoutes = ({
183
169
  reloadEnv,
184
170
  restartRequiredState,
185
171
  });
172
+ const runOnboardedBoot = () =>
173
+ runOnboardedBootSequence({
174
+ ensureManagedExecDefaults,
175
+ ensureUsageTrackerPluginConfig,
176
+ doSyncPromptFiles,
177
+ reloadEnv,
178
+ syncChannelConfig,
179
+ readEnvFile,
180
+ ensureGatewayProxyConfig,
181
+ resolveSetupUrl,
182
+ startGateway,
183
+ watchdog,
184
+ gmailWatchService,
185
+ });
186
+ registerOnboardingRoutes({
187
+ app,
188
+ fs,
189
+ constants,
190
+ shellCmd,
191
+ gatewayEnv,
192
+ readEnvFile,
193
+ writeEnvFile,
194
+ reloadEnv,
195
+ isOnboarded,
196
+ resolveGithubRepoUrl,
197
+ resolveModelProvider,
198
+ hasCodexOauthProfile: authProfiles.hasCodexOauthProfile,
199
+ authProfiles,
200
+ ensureGatewayProxyConfig,
201
+ getBaseUrl,
202
+ runOnboardedBootSequence: runOnboardedBoot,
203
+ });
186
204
  registerTelegramRoutes({
187
205
  app,
188
206
  telegramApi,
@@ -266,6 +284,7 @@ const registerServerRoutes = ({
266
284
  requireAuth,
267
285
  isAuthorizedRequest,
268
286
  gmailWatchService,
287
+ runOnboardedBootSequence: runOnboardedBoot,
269
288
  };
270
289
  };
271
290
 
@@ -19,11 +19,15 @@ const initializeServerRuntime = ({
19
19
 
20
20
  const initializeServerDatabases = ({
21
21
  constants,
22
+ initAuthDb,
22
23
  initWebhooksDb,
23
24
  initWatchdogDb,
24
25
  initUsageDb,
25
26
  initDoctorDb,
26
27
  }) => {
28
+ initAuthDb({
29
+ rootDir: constants.kRootDir,
30
+ });
27
31
  initWebhooksDb({
28
32
  rootDir: constants.kRootDir,
29
33
  pruneDays: constants.kWebhookPruneDays,