fix(device-pair): keep notifier poll off Gateway startup (#119936)

Punchcard-Session: frost-harbor-willow-ev
This commit is contained in:
Vincent Koc
2026-08-06 20:51:20 +08:00
committed by GitHub
parent 52b8f9be80
commit 95fd4d3dc9
2 changed files with 18 additions and 18 deletions
+15 -14
View File
@@ -96,12 +96,11 @@ describe("device-pair notify persistence", () => {
});
}
it("keeps one notify poll in flight across service recreation", async () => {
it("defers the first notify poll and keeps one in flight across service recreation", async () => {
vi.useFakeTimers();
const firstPoll = createDeferred<Awaited<ReturnType<typeof listDevicePairingMock>>>();
const failedPoll = createDeferred<Awaited<ReturnType<typeof listDevicePairingMock>>>();
listDevicePairingMock
.mockResolvedValueOnce({ pending: [], paired: [] })
.mockImplementationOnce(() => firstPoll.promise)
.mockImplementationOnce(() => failedPoll.promise)
.mockResolvedValue({ pending: [], paired: [] });
@@ -109,29 +108,30 @@ describe("device-pair notify persistence", () => {
let service = createPairingNotifierService(api);
await service.start({} as never);
expect(listDevicePairingMock).toHaveBeenCalledTimes(1);
expect(listDevicePairingMock).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(10_000);
expect(listDevicePairingMock).toHaveBeenCalledTimes(2);
expect(listDevicePairingMock).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(20_000);
expect(listDevicePairingMock).toHaveBeenCalledTimes(2);
expect(listDevicePairingMock).toHaveBeenCalledTimes(1);
await service.stop?.({} as never);
service = createPairingNotifierService(createApi());
await service.start({} as never);
expect(listDevicePairingMock).toHaveBeenCalledTimes(2);
await vi.advanceTimersByTimeAsync(10_000);
expect(listDevicePairingMock).toHaveBeenCalledTimes(1);
firstPoll.resolve({ pending: [], paired: [] });
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(10_000);
expect(listDevicePairingMock).toHaveBeenCalledTimes(3);
expect(listDevicePairingMock).toHaveBeenCalledTimes(2);
await vi.advanceTimersByTimeAsync(20_000);
expect(listDevicePairingMock).toHaveBeenCalledTimes(3);
expect(listDevicePairingMock).toHaveBeenCalledTimes(2);
failedPoll.reject(new Error("poll failed"));
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(10_000);
expect(listDevicePairingMock).toHaveBeenCalledTimes(4);
expect(listDevicePairingMock).toHaveBeenCalledTimes(3);
await service.stop?.({} as never);
});
@@ -166,9 +166,7 @@ describe("device-pair notify persistence", () => {
},
action: "on",
});
listDevicePairingMock
.mockResolvedValueOnce({ pending: [], paired: [] })
.mockResolvedValue({ pending: [firstRequest], paired: [] });
listDevicePairingMock.mockResolvedValue({ pending: [firstRequest], paired: [] });
let service = createPairingNotifierService(api);
await service.start({} as never);
@@ -211,7 +209,7 @@ describe("device-pair notify persistence", () => {
ctx: { channel: "telegram", senderId: "old-chat" },
action: "on",
});
listDevicePairingMock.mockResolvedValueOnce({ pending: [], paired: [] }).mockResolvedValue({
listDevicePairingMock.mockResolvedValue({
pending: [
{
requestId: "request-1",
@@ -261,7 +259,7 @@ describe("device-pair notify persistence", () => {
ctx: { channel: "telegram", senderId: "chat-123" },
action: "once",
});
listDevicePairingMock.mockResolvedValueOnce({ pending: [], paired: [] }).mockResolvedValue({
listDevicePairingMock.mockResolvedValue({
pending: [
{
requestId: "request-1",
@@ -325,6 +323,7 @@ describe("device-pair notify persistence", () => {
const service = createPairingNotifierService(api);
await service.start({} as never);
await vi.advanceTimersByTimeAsync(10_000);
expect(sendText).not.toHaveBeenCalled();
await expect(
@@ -367,6 +366,7 @@ describe("device-pair notify persistence", () => {
});
const service = createPairingNotifierService(api);
await service.start({} as never);
await vi.advanceTimersByTimeAsync(10_000);
expect(sendText).toHaveBeenCalledWith(
expect.objectContaining({ text: expect.stringContaining("ID: request-same-ms") }),
@@ -404,6 +404,7 @@ describe("device-pair notify persistence", () => {
const service = createPairingNotifierService(api);
await service.start({} as never);
await vi.advanceTimersByTimeAsync(10_000);
expect(sendText).toHaveBeenCalledTimes(1);
expect(sendText).toHaveBeenCalledWith(
+3 -4
View File
@@ -454,14 +454,13 @@ export function createPairingNotifierService(api: OpenClawPluginApi): OpenClawPl
return {
id: "device-pair-notifier",
start: async () => {
start: () => {
const tick = async () => {
await runNotifyPoll(api);
};
await tick().catch((err: unknown) => {
api.logger.warn(`device-pair: initial notify poll failed: ${formatErrorMessage(err)}`);
});
// Pairing notifications are eventual background work. Starting on the
// existing interval keeps SQLite pairing scans out of Gateway readiness.
notifyInterval = setInterval(() => {
tick().catch((err: unknown) => {
api.logger.warn(`device-pair: notify poll failed: ${formatErrorMessage(err)}`);