
























@@ -0,0 +1,136 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import type { RestartSentinelPayload } from "../../infra/restart-sentinel.js";
3+import type { HandleCommandsParams } from "./commands-types.js";
4+5+const mocks = vi.hoisted(() => ({
6+isRestartEnabled: vi.fn(() => true),
7+extractDeliveryInfo: vi.fn(() => ({
8+deliveryContext: {
9+channel: "telegram",
10+to: "telegram:123",
11+accountId: "default",
12+},
13+threadId: "thread-1",
14+})),
15+formatDoctorNonInteractiveHint: vi.fn(() => "Run: openclaw doctor --non-interactive"),
16+writeRestartSentinel: vi.fn(async (_payload: RestartSentinelPayload) => "/tmp/sentinel.json"),
17+scheduleGatewaySigusr1Restart: vi.fn(() => ({ scheduled: true })),
18+triggerOpenClawRestart: vi.fn(() => ({ ok: true, method: "launchctl" })),
19+}));
20+21+vi.mock("../../config/commands.flags.js", () => ({
22+isRestartEnabled: mocks.isRestartEnabled,
23+}));
24+25+vi.mock("../../config/sessions.js", () => ({
26+extractDeliveryInfo: mocks.extractDeliveryInfo,
27+}));
28+29+vi.mock("../../globals.js", () => ({
30+logVerbose: vi.fn(),
31+}));
32+33+vi.mock("../../channels/plugins/index.js", () => ({
34+getChannelPlugin: vi.fn(),
35+normalizeChannelId: (value?: string | null) => value?.trim().toLowerCase() ?? null,
36+}));
37+38+vi.mock("../../channels/plugins/conversation-bindings.js", () => ({
39+setChannelConversationBindingIdleTimeoutBySessionKey: vi.fn(),
40+setChannelConversationBindingMaxAgeBySessionKey: vi.fn(),
41+}));
42+43+vi.mock("../../infra/outbound/session-binding-service.js", () => ({
44+getSessionBindingService: vi.fn(),
45+}));
46+47+vi.mock("../../infra/restart-sentinel.js", async () => {
48+const actual = await vi.importActual<typeof import("../../infra/restart-sentinel.js")>(
49+"../../infra/restart-sentinel.js",
50+);
51+return {
52+ ...actual,
53+formatDoctorNonInteractiveHint: mocks.formatDoctorNonInteractiveHint,
54+writeRestartSentinel: mocks.writeRestartSentinel,
55+};
56+});
57+58+vi.mock("../../infra/restart.js", () => ({
59+scheduleGatewaySigusr1Restart: mocks.scheduleGatewaySigusr1Restart,
60+triggerOpenClawRestart: mocks.triggerOpenClawRestart,
61+}));
62+63+const { handleRestartCommand } = await import("./commands-session.js");
64+65+function restartCommandParams(overrides?: Partial<HandleCommandsParams>): HandleCommandsParams {
66+return {
67+ctx: {},
68+cfg: {},
69+command: {
70+surface: "telegram",
71+channel: "telegram",
72+ownerList: [],
73+senderIsOwner: true,
74+isAuthorizedSender: true,
75+senderId: "user-1",
76+rawBodyNormalized: "/restart",
77+commandBodyNormalized: "/restart",
78+from: "telegram:123",
79+to: "bot",
80+},
81+directives: {},
82+elevated: { enabled: true, allowed: true, failures: [] },
83+sessionKey: "agent:main:telegram:direct:123:thread:thread-1",
84+workspaceDir: "/tmp",
85+defaultGroupActivation: () => "mention",
86+resolvedVerboseLevel: "off",
87+resolvedReasoningLevel: "off",
88+resolveDefaultThinkingLevel: async () => undefined,
89+provider: "openai",
90+model: "gpt-5.4",
91+contextTokens: 0,
92+isGroup: false,
93+ ...overrides,
94+} as HandleCommandsParams;
95+}
96+97+describe("handleRestartCommand", () => {
98+beforeEach(() => {
99+mocks.isRestartEnabled.mockReset();
100+mocks.isRestartEnabled.mockReturnValue(true);
101+mocks.extractDeliveryInfo.mockClear();
102+mocks.formatDoctorNonInteractiveHint.mockClear();
103+mocks.writeRestartSentinel.mockClear();
104+mocks.scheduleGatewaySigusr1Restart.mockClear();
105+mocks.triggerOpenClawRestart.mockReset();
106+mocks.triggerOpenClawRestart.mockReturnValue({ ok: true, method: "launchctl" });
107+});
108+109+it("writes a routed restart sentinel before restarting from chat", async () => {
110+const { DEFAULT_RESTART_SUCCESS_CONTINUATION_MESSAGE } =
111+await import("../../infra/restart-sentinel.js");
112+113+const result = await handleRestartCommand(restartCommandParams(), true);
114+115+expect(result?.shouldContinue).toBe(false);
116+expect(mocks.writeRestartSentinel).toHaveBeenCalledWith(
117+expect.objectContaining({
118+kind: "restart",
119+status: "ok",
120+sessionKey: "agent:main:telegram:direct:123:thread:thread-1",
121+deliveryContext: {
122+channel: "telegram",
123+to: "telegram:123",
124+accountId: "default",
125+},
126+threadId: "thread-1",
127+message: "/restart",
128+continuation: {
129+kind: "agentTurn",
130+message: DEFAULT_RESTART_SUCCESS_CONTINUATION_MESSAGE,
131+},
132+}),
133+);
134+expect(mocks.triggerOpenClawRestart).toHaveBeenCalledTimes(1);
135+});
136+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。