Optimize gateway comms reload behavior and strengthen regression coverage (#496)

This commit is contained in:
Lingxuan Zuo
2026-03-15 20:36:48 +08:00
committed by GitHub
Unverified
parent 08960d700f
commit 1dbe4a8466
36 changed files with 1511 additions and 197 deletions

View File

@@ -10,6 +10,12 @@ import type { GatewayStatus } from '../types/gateway';
let gatewayInitPromise: Promise<void> | null = null;
let gatewayEventUnsubscribers: Array<() => void> | null = null;
const gatewayEventDedupe = new Map<string, number>();
const GATEWAY_EVENT_DEDUPE_TTL_MS = 30_000;
const LOAD_SESSIONS_MIN_INTERVAL_MS = 1_200;
const LOAD_HISTORY_MIN_INTERVAL_MS = 800;
let lastLoadSessionsAt = 0;
let lastLoadHistoryAt = 0;
interface GatewayHealth {
ok: boolean;
@@ -32,6 +38,66 @@ interface GatewayState {
clearError: () => void;
}
function pruneGatewayEventDedupe(now: number): void {
for (const [key, ts] of gatewayEventDedupe) {
if (now - ts > GATEWAY_EVENT_DEDUPE_TTL_MS) {
gatewayEventDedupe.delete(key);
}
}
}
function buildGatewayEventDedupeKey(event: Record<string, unknown>): string | null {
const runId = event.runId != null ? String(event.runId) : '';
const sessionKey = event.sessionKey != null ? String(event.sessionKey) : '';
const seq = event.seq != null ? String(event.seq) : '';
const state = event.state != null ? String(event.state) : '';
if (runId || sessionKey || seq || state) {
return [runId, sessionKey, seq, state].join('|');
}
const message = event.message;
if (message && typeof message === 'object') {
const msg = message as Record<string, unknown>;
const messageId = msg.id != null ? String(msg.id) : '';
const stopReason = msg.stopReason ?? msg.stop_reason;
if (messageId || stopReason) {
return `msg|${messageId}|${String(stopReason ?? '')}`;
}
}
return null;
}
function shouldProcessGatewayEvent(event: Record<string, unknown>): boolean {
const key = buildGatewayEventDedupeKey(event);
if (!key) return true;
const now = Date.now();
pruneGatewayEventDedupe(now);
if (gatewayEventDedupe.has(key)) {
return false;
}
gatewayEventDedupe.set(key, now);
return true;
}
function maybeLoadSessions(
state: { loadSessions: () => Promise<void> },
force = false,
): void {
const now = Date.now();
if (!force && now - lastLoadSessionsAt < LOAD_SESSIONS_MIN_INTERVAL_MS) return;
lastLoadSessionsAt = now;
void state.loadSessions();
}
function maybeLoadHistory(
state: { loadHistory: (quiet?: boolean) => Promise<void> },
force = false,
): void {
const now = Date.now();
if (!force && now - lastLoadHistoryAt < LOAD_HISTORY_MIN_INTERVAL_MS) return;
lastLoadHistoryAt = now;
void state.loadHistory(true);
}
function handleGatewayNotification(notification: { method?: string; params?: Record<string, unknown> } | undefined): void {
const payload = notification;
if (!payload || payload.method !== 'agent' || !payload.params || typeof payload.params !== 'object') {
@@ -53,11 +119,13 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec
state: p.state ?? data.state,
message: p.message ?? data.message,
};
import('./chat')
.then(({ useChatStore }) => {
useChatStore.getState().handleChatEvent(normalizedEvent);
})
.catch(() => {});
if (shouldProcessGatewayEvent(normalizedEvent)) {
import('./chat')
.then(({ useChatStore }) => {
useChatStore.getState().handleChatEvent(normalizedEvent);
})
.catch(() => {});
}
}
const runId = p.runId ?? data.runId;
@@ -71,7 +139,7 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec
resolvedSessionKey !== state.currentSessionKey
|| !state.sessions.some((session) => session.key === resolvedSessionKey);
if (shouldRefreshSessions) {
void state.loadSessions();
maybeLoadSessions(state, true);
}
state.handleChatEvent({
@@ -93,14 +161,14 @@ function handleGatewayNotification(notification: { method?: string; params?: Rec
|| !state.sessions.some((session) => session.key === resolvedSessionKey)
);
if (shouldRefreshSessions) {
void state.loadSessions();
maybeLoadSessions(state);
}
const matchesCurrentSession = resolvedSessionKey == null || resolvedSessionKey === state.currentSessionKey;
const matchesActiveRun = runId != null && state.activeRunId != null && String(runId) === state.activeRunId;
if (matchesCurrentSession || matchesActiveRun) {
void state.loadHistory(true);
maybeLoadHistory(state);
}
if ((matchesCurrentSession || matchesActiveRun) && state.sending) {
useChatStore.setState({
@@ -123,15 +191,18 @@ function handleGatewayChatMessage(data: unknown): void {
: chatData;
if (payload.state) {
if (!shouldProcessGatewayEvent(payload)) return;
useChatStore.getState().handleChatEvent(payload);
return;
}
useChatStore.getState().handleChatEvent({
const normalized = {
state: 'final',
message: payload,
runId: chatData.runId ?? payload.runId,
});
};
if (!shouldProcessGatewayEvent(normalized)) return;
useChatStore.getState().handleChatEvent(normalized);
}).catch(() => {});
}