mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 00:13:28 +00:00
fix: keep channel typing active during long inference (#25886, thanks @stakeswky)
Co-authored-by: stakeswky <stakeswky@users.noreply.github.com>
This commit is contained in:
@@ -190,4 +190,48 @@ describe("createTypingCallbacks", () => {
|
||||
expect(stop).toHaveBeenCalledTimes(1);
|
||||
expect(onStopError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("sends typing keepalive pings until idle cleanup", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const start = vi.fn().mockResolvedValue(undefined);
|
||||
const stop = vi.fn().mockResolvedValue(undefined);
|
||||
const onStartError = vi.fn();
|
||||
const callbacks = createTypingCallbacks({ start, stop, onStartError });
|
||||
|
||||
await callbacks.onReplyStart();
|
||||
expect(start).toHaveBeenCalledTimes(1);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(2_999);
|
||||
expect(start).toHaveBeenCalledTimes(1);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(start).toHaveBeenCalledTimes(2);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(3_000);
|
||||
expect(start).toHaveBeenCalledTimes(3);
|
||||
|
||||
callbacks.onIdle?.();
|
||||
await flushMicrotasks();
|
||||
expect(stop).toHaveBeenCalledTimes(1);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(9_000);
|
||||
expect(start).toHaveBeenCalledTimes(3);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("deduplicates stop across idle and cleanup", async () => {
|
||||
const start = vi.fn().mockResolvedValue(undefined);
|
||||
const stop = vi.fn().mockResolvedValue(undefined);
|
||||
const onStartError = vi.fn();
|
||||
const callbacks = createTypingCallbacks({ start, stop, onStartError });
|
||||
|
||||
callbacks.onIdle?.();
|
||||
callbacks.onCleanup?.();
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,9 +10,15 @@ export function createTypingCallbacks(params: {
|
||||
stop?: () => Promise<void>;
|
||||
onStartError: (err: unknown) => void;
|
||||
onStopError?: (err: unknown) => void;
|
||||
keepaliveIntervalMs?: number;
|
||||
}): TypingCallbacks {
|
||||
const stop = params.stop;
|
||||
const onReplyStart = async () => {
|
||||
const keepaliveIntervalMs = params.keepaliveIntervalMs ?? 3_000;
|
||||
let keepaliveTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let keepaliveStartInFlight = false;
|
||||
let stopSent = false;
|
||||
|
||||
const fireStart = async () => {
|
||||
try {
|
||||
await params.start();
|
||||
} catch (err) {
|
||||
@@ -20,11 +26,41 @@ export function createTypingCallbacks(params: {
|
||||
}
|
||||
};
|
||||
|
||||
const fireStop = stop
|
||||
? () => {
|
||||
void stop().catch((err) => (params.onStopError ?? params.onStartError)(err));
|
||||
const clearKeepalive = () => {
|
||||
if (!keepaliveTimer) {
|
||||
return;
|
||||
}
|
||||
clearInterval(keepaliveTimer);
|
||||
keepaliveTimer = undefined;
|
||||
keepaliveStartInFlight = false;
|
||||
};
|
||||
|
||||
const onReplyStart = async () => {
|
||||
stopSent = false;
|
||||
clearKeepalive();
|
||||
await fireStart();
|
||||
if (keepaliveIntervalMs <= 0) {
|
||||
return;
|
||||
}
|
||||
keepaliveTimer = setInterval(() => {
|
||||
if (keepaliveStartInFlight) {
|
||||
return;
|
||||
}
|
||||
: undefined;
|
||||
keepaliveStartInFlight = true;
|
||||
void fireStart().finally(() => {
|
||||
keepaliveStartInFlight = false;
|
||||
});
|
||||
}, keepaliveIntervalMs);
|
||||
};
|
||||
|
||||
const fireStop = () => {
|
||||
clearKeepalive();
|
||||
if (!stop || stopSent) {
|
||||
return;
|
||||
}
|
||||
stopSent = true;
|
||||
void stop().catch((err) => (params.onStopError ?? params.onStartError)(err));
|
||||
};
|
||||
|
||||
return { onReplyStart, onIdle: fireStop, onCleanup: fireStop };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user