























@@ -194,6 +194,36 @@ describe("restart sentinel", () => {
194194});
195195});
196196197+it("keeps old config restart sentinels readable without restart-required stats", async () => {
198+await withRestartSentinelStateDir(async () => {
199+const filePath = path.join(process.env.OPENCLAW_STATE_DIR ?? "", "restart-sentinel.json");
200+const payload = {
201+kind: "config-patch" as const,
202+status: "ok" as const,
203+ts: Date.now(),
204+message: "Config updated successfully",
205+stats: { mode: "config.patch" },
206+};
207+await fs.mkdir(path.dirname(filePath), { recursive: true });
208+await fs.writeFile(filePath, JSON.stringify({ version: 1, payload }, null, 2), "utf-8");
209+210+const read = await readRestartSentinel();
211+212+expect(read?.payload).toEqual(payload);
213+if (!read) {
214+throw new Error("Expected old restart sentinel to be readable");
215+}
216+expect(summarizeRestartSentinel(read.payload)).toBe(
217+"Gateway restart config-patch ok (config.patch)",
218+);
219+expect(formatRestartSentinelMessage(read.payload)).toBe(
220+["Gateway restart config-patch ok (config.patch)", "Config updated successfully"].join(
221+"\n",
222+),
223+);
224+});
225+});
226+197227it("formatRestartSentinelMessage uses custom message when present", () => {
198228const payload = {
199229kind: "config-apply" as const,
@@ -242,6 +272,48 @@ describe("restart sentinel", () => {
242272expect(result).toContain("Gateway restart");
243273});
244274275+it("formats config write success notices as restart required when marked", () => {
276+const payload = {
277+kind: "config-patch" as const,
278+status: "ok" as const,
279+ts: Date.now(),
280+message: "Run restart-gateway.ps1 to apply config changes.",
281+doctorHint: "Run openclaw doctor --non-interactive",
282+stats: { mode: "config.patch", requiresRestart: true },
283+};
284+285+expect(formatRestartSentinelMessage(payload)).toBe(
286+[
287+"Gateway restart required (config.patch)",
288+"Run restart-gateway.ps1 to apply config changes.",
289+"Run openclaw doctor --non-interactive",
290+].join("\n"),
291+);
292+expect(summarizeRestartSentinel(payload)).toBe("Gateway restart required (config.patch)");
293+294+expect(
295+summarizeRestartSentinel({
296+kind: "config-apply",
297+status: "ok",
298+ts: Date.now(),
299+stats: { mode: "config.apply", requiresRestart: true },
300+}),
301+).toBe("Gateway restart required (config.apply)");
302+});
303+304+it("does not mark hot-reloaded config patch notices as restart required", () => {
305+const payload = {
306+kind: "config-patch" as const,
307+status: "ok" as const,
308+ts: Date.now(),
309+stats: { mode: "config.patch", requiresRestart: false },
310+};
311+312+expect(summarizeRestartSentinel(payload)).toBe(
313+"Gateway restart config-patch ok (config.patch)",
314+);
315+});
316+245317it("formats summary, distinct reason, and doctor hint together", () => {
246318const payload = {
247319kind: "config-patch" as const,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。