
























@@ -3,6 +3,7 @@ import os from "node:os";
33import path from "node:path";
44import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
55import { createMockGatewayService } from "../../daemon/service.test-helpers.js";
6+import type { GatewayRestartHandoff } from "../../infra/restart-handoff.js";
67import { captureEnv } from "../../test-utils/env.js";
78import type { GatewayRestartSnapshot } from "./restart-health.js";
89import { gatherDaemonStatus } from "./status.gather.js";
@@ -27,6 +28,9 @@ const inspectPortUsage = vi.fn(async (port: number) => ({
2728hints: [],
2829}));
2930const readLastGatewayErrorLine = vi.fn(async (_env?: NodeJS.ProcessEnv) => null);
31+const readGatewayRestartHandoffSync = vi.fn<
32+(_env?: NodeJS.ProcessEnv) => GatewayRestartHandoff | null
33+>(() => null);
3034const auditGatewayServiceConfig = vi.fn(async (_opts?: unknown) => undefined);
3135const serviceIsLoaded = vi.fn(async (_opts?: unknown) => true);
3236const serviceReadRuntime = vi.fn(async (_env?: NodeJS.ProcessEnv) => ({ status: "running" }));
@@ -136,6 +140,10 @@ vi.mock("../../infra/ports.js", () => ({
136140formatPortDiagnostics: () => [],
137141}));
138142143+vi.mock("../../infra/restart-handoff.js", () => ({
144+readGatewayRestartHandoffSync: (env?: NodeJS.ProcessEnv) => readGatewayRestartHandoffSync(env),
145+}));
146+139147vi.mock("../../infra/tailnet.js", () => ({
140148pickPrimaryTailnetIPv4: () => pickPrimaryTailnetIPv4(),
141149}));
@@ -173,6 +181,7 @@ describe("gatherDaemonStatus", () => {
173181callGatewayStatusProbe.mockClear();
174182loadGatewayTlsRuntime.mockClear();
175183inspectGatewayRestart.mockClear();
184+readGatewayRestartHandoffSync.mockClear();
176185readConfigFileSnapshotCalls.mockClear();
177186loadConfigCalls.mockClear();
178187daemonLoadedConfig = {
@@ -369,6 +378,49 @@ describe("gatherDaemonStatus", () => {
369378});
370379});
371380381+it("surfaces recent service restart handoffs only during deep status", async () => {
382+readGatewayRestartHandoffSync.mockReturnValueOnce({
383+kind: "gateway-supervisor-restart-handoff",
384+version: 1,
385+intentId: "intent-1",
386+pid: 12_345,
387+createdAt: 10_000,
388+expiresAt: 70_000,
389+reason: "plugin source changed",
390+source: "plugin-change",
391+restartKind: "full-process",
392+supervisorMode: "launchd",
393+});
394+395+const status = await gatherDaemonStatus({
396+rpc: {},
397+probe: false,
398+deep: true,
399+});
400+401+expect(readGatewayRestartHandoffSync).toHaveBeenCalledWith(
402+expect.objectContaining({
403+OPENCLAW_STATE_DIR: "/tmp/openclaw-daemon",
404+OPENCLAW_CONFIG_PATH: "/tmp/openclaw-daemon/openclaw.json",
405+}),
406+);
407+expect(status.service.restartHandoff).toMatchObject({
408+reason: "plugin source changed",
409+restartKind: "full-process",
410+supervisorMode: "launchd",
411+});
412+});
413+414+it("does not read restart handoffs during normal status", async () => {
415+await gatherDaemonStatus({
416+rpc: {},
417+probe: false,
418+deep: false,
419+});
420+421+expect(readGatewayRestartHandoffSync).not.toHaveBeenCalled();
422+});
423+372424it("uses the fast config path for plain same-file status reads", async () => {
373425const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-status-config-"));
374426const configPath = path.join(tmp, "openclaw.json");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。