
























@@ -11,6 +11,7 @@ import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
1111import { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js";
1212import { approveNodePairing, requestNodePairing } from "../infra/node-pairing.js";
1313import { resolveRestartSentinelPath } from "../infra/restart-sentinel.js";
14+import { SUPERVISOR_HINT_ENV_VARS } from "../infra/supervisor-markers.js";
1415import { getActiveRuntimePluginRegistry } from "../plugins/active-runtime-registry.js";
1516import {
1617GATEWAY_CLIENT_MODES,
@@ -47,6 +48,26 @@ type PollWaitOptions = { timeout: number; interval: number };
4748let ws: WebSocket;
4849let port: number;
495051+async function withoutSupervisorHints<T>(fn: () => Promise<T>): Promise<T> {
52+const previousEnv = new Map<string, string | undefined>();
53+for (const key of SUPERVISOR_HINT_ENV_VARS) {
54+previousEnv.set(key, process.env[key]);
55+delete process.env[key];
56+}
57+58+try {
59+return await fn();
60+} finally {
61+for (const [key, value] of previousEnv) {
62+if (value === undefined) {
63+delete process.env[key];
64+} else {
65+process.env[key] = value;
66+}
67+}
68+}
69+}
70+5071function countConnectedNodes(nodes: readonly { connected?: boolean }[] | undefined): number {
5172let count = 0;
5273for (const node of nodes ?? []) {
@@ -371,72 +392,76 @@ describe("gateway role enforcement", () => {
371392372393describe("gateway update.run", () => {
373394test("writes sentinel and schedules restart", async () => {
374-const sigusr1 = vi.fn();
375-process.on("SIGUSR1", sigusr1);
376-377-try {
378-const id = "req-update";
379-ws.send(
380-JSON.stringify({
381-type: "req",
382- id,
383-method: "update.run",
384-params: {
385-sessionKey: "agent:main:whatsapp:dm:+15555550123",
386-restartDelayMs: 0,
387-},
388-}),
389-);
390-const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id);
391-expect(res.ok).toBe(true);
392-393-await vi.waitFor(() => {
394-expect(sigusr1.mock.calls.length).toBeGreaterThan(0);
395-}, FAST_WAIT_OPTS);
396-expect(sigusr1).toHaveBeenCalled();
397-398-const sentinelPath = resolveRestartSentinelPath();
399-const raw = await fs.readFile(sentinelPath, "utf-8");
400-const parsed = JSON.parse(raw) as {
401-payload?: { kind?: string; stats?: { mode?: string } };
402-};
403-expect(parsed.payload?.kind).toBe("update");
404-expect(parsed.payload?.stats?.mode).toBe("git");
405-} finally {
406-process.off("SIGUSR1", sigusr1);
407-}
395+await withoutSupervisorHints(async () => {
396+const sigusr1 = vi.fn();
397+process.on("SIGUSR1", sigusr1);
398+399+try {
400+const id = "req-update";
401+ws.send(
402+JSON.stringify({
403+type: "req",
404+ id,
405+method: "update.run",
406+params: {
407+sessionKey: "agent:main:whatsapp:dm:+15555550123",
408+restartDelayMs: 0,
409+},
410+}),
411+);
412+const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id);
413+expect(res.ok).toBe(true);
414+415+await vi.waitFor(() => {
416+expect(sigusr1.mock.calls.length).toBeGreaterThan(0);
417+}, FAST_WAIT_OPTS);
418+expect(sigusr1).toHaveBeenCalled();
419+420+const sentinelPath = resolveRestartSentinelPath();
421+const raw = await fs.readFile(sentinelPath, "utf-8");
422+const parsed = JSON.parse(raw) as {
423+payload?: { kind?: string; stats?: { mode?: string } };
424+};
425+expect(parsed.payload?.kind).toBe("update");
426+expect(parsed.payload?.stats?.mode).toBe("git");
427+} finally {
428+process.off("SIGUSR1", sigusr1);
429+}
430+});
408431});
409432410433test("uses configured update channel", async () => {
411-const sigusr1 = vi.fn();
412-process.on("SIGUSR1", sigusr1);
413-414-try {
415-const configPath = getGatewayTestConfigPath();
416-await fs.mkdir(path.dirname(configPath), { recursive: true });
417-await fs.writeFile(configPath, JSON.stringify({ update: { channel: "beta" } }, null, 2));
418-const updateMock = vi.mocked(runGatewayUpdate);
419-updateMock.mockClear();
420-421-const id = "req-update-channel";
422-ws.send(
423-JSON.stringify({
424-type: "req",
425- id,
426-method: "update.run",
427-params: {
428-restartDelayMs: 0,
429-},
430-}),
431-);
432-const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id);
433-expect(res.ok).toBe(true);
434-await vi.waitFor(() => {
435-expect(updateMock).toHaveBeenCalledOnce();
436-}, FAST_WAIT_OPTS);
437-} finally {
438-process.off("SIGUSR1", sigusr1);
439-}
434+await withoutSupervisorHints(async () => {
435+const sigusr1 = vi.fn();
436+process.on("SIGUSR1", sigusr1);
437+438+try {
439+const configPath = getGatewayTestConfigPath();
440+await fs.mkdir(path.dirname(configPath), { recursive: true });
441+await fs.writeFile(configPath, JSON.stringify({ update: { channel: "beta" } }, null, 2));
442+const updateMock = vi.mocked(runGatewayUpdate);
443+updateMock.mockClear();
444+445+const id = "req-update-channel";
446+ws.send(
447+JSON.stringify({
448+type: "req",
449+ id,
450+method: "update.run",
451+params: {
452+restartDelayMs: 0,
453+},
454+}),
455+);
456+const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id);
457+expect(res.ok).toBe(true);
458+await vi.waitFor(() => {
459+expect(updateMock).toHaveBeenCalledOnce();
460+}, FAST_WAIT_OPTS);
461+} finally {
462+process.off("SIGUSR1", sigusr1);
463+}
464+});
440465});
441466});
442467此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。