Optimize gateway comms reload behavior and strengthen regression coverage (#496)
This commit is contained in:
committed by
GitHub
Unverified
parent
08960d700f
commit
1dbe4a8466
@@ -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(() => {});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user