

























@@ -1,6 +1,7 @@
11import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
22import type { ExtraGatewayService } from "../daemon/inspect.js";
33import * as launchd from "../daemon/launchd.js";
4+import type { GatewayRestartHandoff } from "../infra/restart-handoff.js";
45import { withEnvAsync } from "../test-utils/env.js";
56import { createDoctorPrompter } from "./doctor-prompter.js";
67import { EXTERNAL_SERVICE_REPAIR_NOTE } from "./doctor-service-repair-policy.js";
@@ -18,6 +19,9 @@ const sleep = vi.hoisted(() => vi.fn(async () => {}));
1819const healthCommand = vi.hoisted(() => vi.fn(async () => {}));
1920const inspectPortUsage = vi.hoisted(() => vi.fn());
2021const readLastGatewayErrorLine = vi.hoisted(() => vi.fn(async () => null));
22+const readGatewayRestartHandoffSync = vi.hoisted(() =>
23+vi.fn<() => GatewayRestartHandoff | null>(() => null),
24+);
2125const findSystemGatewayServices = vi.hoisted(() =>
2226vi.fn<() => Promise<ExtraGatewayService[]>>(async () => []),
2327);
@@ -82,6 +86,16 @@ vi.mock("../infra/ports.js", () => ({
8286formatPortDiagnostics: vi.fn(() => []),
8387}));
848889+vi.mock("../infra/restart-handoff.js", async () => {
90+const actual = await vi.importActual<typeof import("../infra/restart-handoff.js")>(
91+"../infra/restart-handoff.js",
92+);
93+return {
94+ ...actual,
95+ readGatewayRestartHandoffSync,
96+};
97+});
98+8599vi.mock("../infra/wsl.js", () => ({
86100isWSL: vi.fn(async () => false),
87101}));
@@ -133,7 +147,9 @@ describe("maybeRepairGatewayDaemon", () => {
133147vi.clearAllMocks();
134148service.isLoaded.mockResolvedValue(true);
135149service.readRuntime.mockResolvedValue({ status: "running" });
150+service.readCommand.mockResolvedValue(null);
136151service.restart.mockResolvedValue({ outcome: "completed" });
152+readGatewayRestartHandoffSync.mockReturnValue(null);
137153findSystemGatewayServices.mockResolvedValue([]);
138154inspectPortUsage.mockResolvedValue({
139155port: 18789,
@@ -245,6 +261,64 @@ describe("maybeRepairGatewayDaemon", () => {
245261await runScheduledGatewayRepair("Restart gateway service now?");
246262});
247263264+it("reports recent restart handoffs during deep doctor", async () => {
265+setPlatform("linux");
266+service.readCommand.mockResolvedValueOnce({
267+programArguments: ["/bin/node", "cli", "gateway"],
268+environment: {
269+OPENCLAW_STATE_DIR: "/tmp/openclaw-service",
270+OPENCLAW_CONFIG_PATH: "/tmp/openclaw-service/openclaw.json",
271+},
272+});
273+readGatewayRestartHandoffSync.mockReturnValueOnce({
274+kind: "gateway-supervisor-restart-handoff",
275+version: 1,
276+intentId: "intent-1",
277+pid: 12_345,
278+createdAt: 10_000,
279+expiresAt: 70_000,
280+reason: "plugin source changed",
281+source: "plugin-change",
282+restartKind: "full-process",
283+supervisorMode: "systemd",
284+});
285+286+await maybeRepairGatewayDaemon({
287+cfg: { gateway: {} },
288+runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
289+prompter: createDoctorPrompter({
290+runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
291+options: { deep: true, nonInteractive: true },
292+}),
293+options: { deep: true, nonInteractive: true },
294+gatewayDetailsMessage: "details",
295+healthOk: false,
296+});
297+298+expect(readGatewayRestartHandoffSync).toHaveBeenCalledWith(
299+expect.objectContaining({
300+OPENCLAW_STATE_DIR: "/tmp/openclaw-service",
301+OPENCLAW_CONFIG_PATH: "/tmp/openclaw-service/openclaw.json",
302+}),
303+);
304+expect(note).toHaveBeenCalledWith(
305+expect.stringContaining("Recent restart handoff: full-process via systemd"),
306+"Gateway",
307+);
308+expect(note).toHaveBeenCalledWith(
309+expect.stringContaining("reason=plugin source changed"),
310+"Gateway",
311+);
312+});
313+314+it("does not read restart handoffs during normal doctor", async () => {
315+setPlatform("linux");
316+317+await runNonInteractiveRepair();
318+319+expect(readGatewayRestartHandoffSync).not.toHaveBeenCalled();
320+});
321+248322it("skips start verification when a stopped service start is only scheduled", async () => {
249323service.readRuntime.mockResolvedValue({ status: "stopped" });
250324await runScheduledGatewayRepair("Start gateway service now?");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。