

















11// Coordinates gateway restart requests across supported supervisors.
22import { spawnSync } from "node:child_process";
3-import fs from "node:fs";
43import os from "node:os";
54import path from "node:path";
65import { getRuntimeConfig } from "../config/config.js";
7-import { resolveStateDir } from "../config/paths.js";
86import {
97resolveGatewayLaunchAgentLabel,
108resolveGatewaySystemdServiceName,
119} from "../daemon/constants.js";
1210import { createSubsystemLogger } from "../logging/subsystem.js";
1311import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";
14-import { replaceFileAtomicSync } from "./replace-file.js";
12+import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
13+import {
14+openOpenClawStateDatabase,
15+runOpenClawStateWriteTransaction,
16+} from "../state/openclaw-state-db.js";
17+import {
18+executeSqliteQuerySync,
19+executeSqliteQueryTakeFirstSync,
20+getNodeSqliteKysely,
21+} from "./kysely-sync.js";
1522import { cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } from "./restart-stale-pids.js";
1623import type { RestartAttempt } from "./restart.types.js";
1724import { relaunchGatewayScheduledTask } from "./windows-task-restart.js";
@@ -25,11 +32,11 @@ const DEFAULT_DEFERRAL_STILL_PENDING_WARN_MS = 30_000;
2532export const DEFAULT_RESTART_DEFERRAL_TIMEOUT_MS = 300_000;
2633const RESTART_COOLDOWN_MS = 30_000;
2734const LAUNCHCTL_ALREADY_LOADED_EXIT_CODE = 37;
28-const GATEWAY_RESTART_INTENT_FILENAME = "gateway-restart-intent.json";
35+const GATEWAY_RESTART_INTENT_KEY = "gateway-restart";
2936const GATEWAY_RESTART_INTENT_TTL_MS = 60_000;
30-const GATEWAY_RESTART_INTENT_MAX_BYTES = 1024;
31373238const restartLog = createSubsystemLogger("restart");
39+type GatewayRestartIntentDatabase = Pick<OpenClawStateKyselyDatabase, "gateway_restart_intent">;
33403441export { findGatewayPidsOnPortSync };
3542@@ -137,23 +144,6 @@ export type GatewayRestartIntent = {
137144waitMs?: number;
138145};
139146140-function resolveGatewayRestartIntentPath(env: NodeJS.ProcessEnv = process.env): string {
141-return path.join(resolveStateDir(env), GATEWAY_RESTART_INTENT_FILENAME);
142-}
143-144-function unlinkGatewayRestartIntentFileSync(intentPath: string): boolean {
145-try {
146-const stat = fs.lstatSync(intentPath);
147-if (!stat.isFile() || stat.nlink > 1) {
148-return false;
149-}
150-fs.unlinkSync(intentPath);
151-return true;
152-} catch {
153-return false;
154-}
155-}
156-157147function normalizeRestartIntentPid(pid: number | undefined): number | null {
158148return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : null;
159149}
@@ -170,26 +160,46 @@ export function writeGatewayRestartIntentSync(opts: {
170160}
171161const env = opts.env ?? process.env;
172162try {
173-const intentPath = resolveGatewayRestartIntentPath(env);
174163const reason = normalizeRestartIntentReason(opts.reason ?? opts.intent?.reason);
175-const payload: GatewayRestartIntentPayload = {
176-kind: "gateway-restart",
177-pid: targetPid,
178-createdAt: Date.now(),
179- ...(reason ? { reason } : {}),
180- ...(opts.intent?.force ? { force: true } : {}),
181- ...(typeof opts.intent?.waitMs === "number" &&
164+const waitMs =
165+typeof opts.intent?.waitMs === "number" &&
182166Number.isFinite(opts.intent.waitMs) &&
183167opts.intent.waitMs >= 0
184- ? { waitMs: Math.floor(opts.intent.waitMs) }
185- : {}),
186-};
187-replaceFileAtomicSync({
188-filePath: intentPath,
189-content: `${JSON.stringify(payload)}\n`,
190-mode: 0o600,
191-tempPrefix: ".gateway-restart-intent",
192-});
168+ ? Math.floor(opts.intent.waitMs)
169+ : null;
170+const createdAt = Date.now();
171+runOpenClawStateWriteTransaction(
172+({ db }) => {
173+const stateDb = getNodeSqliteKysely<GatewayRestartIntentDatabase>(db);
174+executeSqliteQuerySync(
175+db,
176+stateDb
177+.insertInto("gateway_restart_intent")
178+.values({
179+intent_key: GATEWAY_RESTART_INTENT_KEY,
180+kind: "gateway-restart",
181+pid: targetPid,
182+created_at: createdAt,
183+reason: reason ?? null,
184+force: opts.intent?.force ? 1 : null,
185+wait_ms: waitMs,
186+updated_at_ms: createdAt,
187+})
188+.onConflict((conflict) =>
189+conflict.column("intent_key").doUpdateSet({
190+kind: (eb) => eb.ref("excluded.kind"),
191+pid: (eb) => eb.ref("excluded.pid"),
192+created_at: (eb) => eb.ref("excluded.created_at"),
193+reason: (eb) => eb.ref("excluded.reason"),
194+force: (eb) => eb.ref("excluded.force"),
195+wait_ms: (eb) => eb.ref("excluded.wait_ms"),
196+updated_at_ms: (eb) => eb.ref("excluded.updated_at_ms"),
197+}),
198+),
199+);
200+},
201+{ env },
202+);
193203return true;
194204} catch (err) {
195205restartLog.warn(`failed to write gateway restart intent: ${String(err)}`);
@@ -198,31 +208,57 @@ export function writeGatewayRestartIntentSync(opts: {
198208}
199209200210export function clearGatewayRestartIntentSync(env: NodeJS.ProcessEnv = process.env): void {
201-unlinkGatewayRestartIntentFileSync(resolveGatewayRestartIntentPath(env));
211+try {
212+runOpenClawStateWriteTransaction(
213+({ db }) => {
214+const stateDb = getNodeSqliteKysely<GatewayRestartIntentDatabase>(db);
215+executeSqliteQuerySync(
216+db,
217+stateDb
218+.deleteFrom("gateway_restart_intent")
219+.where("intent_key", "=", GATEWAY_RESTART_INTENT_KEY),
220+);
221+},
222+{ env },
223+);
224+} catch {}
202225}
203226204-function parseGatewayRestartIntent(raw: string): GatewayRestartIntentPayload | null {
227+function readGatewayRestartIntentPayloadSync(
228+env: NodeJS.ProcessEnv,
229+): GatewayRestartIntentPayload | null {
205230try {
206-const parsed = JSON.parse(raw) as Partial<GatewayRestartIntentPayload>;
231+const { db } = openOpenClawStateDatabase({ env });
232+const stateDb = getNodeSqliteKysely<GatewayRestartIntentDatabase>(db);
233+const parsed = executeSqliteQueryTakeFirstSync(
234+db,
235+stateDb
236+.selectFrom("gateway_restart_intent")
237+.select(["kind", "pid", "created_at", "reason", "force", "wait_ms"])
238+.where("intent_key", "=", GATEWAY_RESTART_INTENT_KEY),
239+);
207240if (
208-parsed.kind === "gateway-restart" &&
241+parsed?.kind === "gateway-restart" &&
209242typeof parsed.pid === "number" &&
210243Number.isFinite(parsed.pid) &&
211-typeof parsed.createdAt === "number" &&
212-Number.isFinite(parsed.createdAt) &&
213-(parsed.reason === undefined || typeof parsed.reason === "string") &&
214-(parsed.force === undefined || typeof parsed.force === "boolean") &&
215-(parsed.waitMs === undefined ||
216-(typeof parsed.waitMs === "number" && Number.isFinite(parsed.waitMs) && parsed.waitMs >= 0))
244+typeof parsed.created_at === "number" &&
245+Number.isFinite(parsed.created_at) &&
246+(parsed.reason === null || typeof parsed.reason === "string") &&
247+(parsed.force === null ||
248+(typeof parsed.force === "number" && Number.isFinite(parsed.force))) &&
249+(parsed.wait_ms === null ||
250+(typeof parsed.wait_ms === "number" &&
251+Number.isFinite(parsed.wait_ms) &&
252+parsed.wait_ms >= 0))
217253) {
218-const reason = normalizeRestartIntentReason(parsed.reason);
254+const reason = normalizeRestartIntentReason(parsed.reason ?? undefined);
219255return {
220256kind: "gateway-restart",
221257pid: parsed.pid,
222-createdAt: parsed.createdAt,
258+createdAt: parsed.created_at,
223259 ...(reason ? { reason } : {}),
224260 ...(parsed.force ? { force: true } : {}),
225- ...(typeof parsed.waitMs === "number" ? { waitMs: Math.floor(parsed.waitMs) } : {}),
261+ ...(typeof parsed.wait_ms === "number" ? { waitMs: Math.floor(parsed.wait_ms) } : {}),
226262};
227263}
228264} catch {
@@ -240,20 +276,8 @@ export function consumeGatewayRestartIntentPayloadSync(
240276env: NodeJS.ProcessEnv = process.env,
241277now = Date.now(),
242278): GatewayRestartIntent | null {
243-const intentPath = resolveGatewayRestartIntentPath(env);
244-let raw: string;
245-try {
246-const stat = fs.lstatSync(intentPath);
247-if (!stat.isFile() || stat.size > GATEWAY_RESTART_INTENT_MAX_BYTES) {
248-return null;
249-}
250-raw = fs.readFileSync(intentPath, "utf8");
251-} catch {
252-return null;
253-} finally {
254-clearGatewayRestartIntentSync(env);
255-}
256-const payload = parseGatewayRestartIntent(raw);
279+const payload = readGatewayRestartIntentPayloadSync(env);
280+clearGatewayRestartIntentSync(env);
257281if (!payload) {
258282return null;
259283}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。