























@@ -7,10 +7,10 @@ import {
77expectWaitStaysPendingUntilSigkillFallback,
88} from "./test-support.js";
9910-const { spawnWithFallbackMock, killProcessTreeMock, createWindowsOutputDecoderMock } = vi.hoisted(
10+const { spawnWithFallbackMock, signalProcessTreeMock, createWindowsOutputDecoderMock } = vi.hoisted(
1111() => ({
1212spawnWithFallbackMock: vi.fn(),
13-killProcessTreeMock: vi.fn(),
13+signalProcessTreeMock: vi.fn(),
1414createWindowsOutputDecoderMock: vi.fn(() => ({
1515decode: (chunk: Buffer | string) => (Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk),
1616flush: () => "",
@@ -23,7 +23,7 @@ vi.mock("../../spawn-utils.js", () => ({
2323}));
24242525vi.mock("../../kill-tree.js", () => ({
26-killProcessTree: killProcessTreeMock,
26+signalProcessTree: signalProcessTreeMock,
2727}));
28282929vi.mock("../../../infra/windows-encoding.js", () => ({
@@ -123,7 +123,7 @@ describe("createChildAdapter", () => {
123123124124beforeEach(() => {
125125spawnWithFallbackMock.mockClear();
126-killProcessTreeMock.mockClear();
126+signalProcessTreeMock.mockClear();
127127createWindowsOutputDecoderMock.mockClear();
128128createWindowsOutputDecoderMock.mockImplementation(() => ({
129129decode: (chunk: Buffer | string) => (Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk),
@@ -164,14 +164,16 @@ describe("createChildAdapter", () => {
164164165165adapter.kill();
166166167-// Detachment flag is now passed to killProcessTree so it knows whether
167+// Detachment flag is now passed to signalProcessTree so it knows whether
168168// it can safely group-kill via -pid. (#71662)
169169const expectedDetached = process.platform !== "win32" && !process.env.OPENCLAW_SERVICE_MARKER;
170-expect(killProcessTreeMock).toHaveBeenCalledWith(4321, { detached: expectedDetached });
170+expect(signalProcessTreeMock).toHaveBeenCalledWith(4321, "SIGKILL", {
171+detached: expectedDetached,
172+});
171173expect(killMock).toHaveBeenCalledWith("SIGKILL");
172174});
173175174-it("passes detached:false to killProcessTree when spawn fell back to no-detach (#71662 follow-up)", async () => {
176+it("passes detached:false to signalProcessTree when spawn fell back to no-detach (#71662 follow-up)", async () => {
175177// Simulate the fallback scenario: spawnWithFallback retried with
176178// detached:false because the initial detached spawn failed. The kill
177179// closure must NOT group-kill since the child shares the gateway's group.
@@ -188,7 +190,7 @@ describe("createChildAdapter", () => {
188190189191adapter.kill();
190192191-expect(killProcessTreeMock).toHaveBeenCalledWith(8888, { detached: false });
193+expect(signalProcessTreeMock).toHaveBeenCalledWith(8888, "SIGKILL", { detached: false });
192194expect(killMock).toHaveBeenCalledWith("SIGKILL");
193195});
194196@@ -197,20 +199,52 @@ describe("createChildAdapter", () => {
197199try {
198200const { adapter, killMock } = await createAdapterHarness({ pid: 9999 });
199201adapter.kill();
200-expect(killProcessTreeMock).toHaveBeenCalledWith(9999, { detached: false });
202+expect(signalProcessTreeMock).toHaveBeenCalledWith(9999, "SIGKILL", { detached: false });
201203expect(killMock).toHaveBeenCalledWith("SIGKILL");
202204} finally {
203205delete process.env.OPENCLAW_SERVICE_MARKER;
204206}
205207});
206208207-it("uses direct child.kill for non-SIGKILL signals", async () => {
209+it("uses process-tree kill for graceful SIGTERM cancellation", async () => {
208210const { adapter, killMock } = await createAdapterHarness({ pid: 7654 });
209211210212adapter.kill("SIGTERM");
211213212-expect(killProcessTreeMock).not.toHaveBeenCalled();
213-expect(killMock).toHaveBeenCalledWith("SIGTERM");
214+const expectedDetached = process.platform !== "win32" && !process.env.OPENCLAW_SERVICE_MARKER;
215+expect(signalProcessTreeMock).toHaveBeenCalledWith(7654, "SIGTERM", {
216+detached: expectedDetached,
217+});
218+expect(killMock).not.toHaveBeenCalled();
219+});
220+221+it("passes detached:false to process-tree SIGTERM when spawn fell back to no-detach", async () => {
222+const { child, killMock } = createStubChild(8765);
223+spawnWithFallbackMock.mockResolvedValue({
224+ child,
225+usedFallback: true,
226+fallbackLabel: "no-detach",
227+});
228+const adapter = await createChildAdapter({
229+argv: ["node", "-e", "setTimeout(() => {}, 1000)"],
230+stdinMode: "pipe-open",
231+});
232+233+adapter.kill("SIGTERM");
234+235+expect(signalProcessTreeMock).toHaveBeenCalledWith(8765, "SIGTERM", {
236+detached: false,
237+});
238+expect(killMock).not.toHaveBeenCalled();
239+});
240+241+it("uses direct child.kill for non-SIGTERM and non-SIGKILL signals", async () => {
242+const { adapter, killMock } = await createAdapterHarness({ pid: 7654 });
243+244+adapter.kill("SIGINT");
245+246+expect(signalProcessTreeMock).not.toHaveBeenCalled();
247+expect(killMock).toHaveBeenCalledWith("SIGINT");
214248});
215249216250it("preserves inherited stdin when no input pipe is requested", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。