fix(frontend): initializeStores dedup + retryAllMessages guard + as any cleanup
- index.ts: add _storesInitialized guard to prevent triple initialization - offlineStore.ts: add isRetrying mutex for retryAllMessages concurrency - PropertyPanel.tsx: replace 13x (data as any) with typed d accessor - chatStore.ts: replace window as any with Record<string, unknown> - kernel-*.ts: replace prototype as any with Record<string, unknown> - gateway-heartbeat.ts: delete dead code (9 as any, zero imports)
This commit is contained in:
@@ -39,7 +39,7 @@ const logger = createLogger('GatewayApi');
|
||||
// === Install all API methods onto GatewayClient prototype ===
|
||||
|
||||
export function installApiMethods(ClientClass: { prototype: GatewayClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
// ─── Health / Status ───
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* gateway-heartbeat.ts - Gateway Heartbeat Methods
|
||||
*
|
||||
* Extracted from gateway-client.ts for modularity.
|
||||
* Installs heartbeat methods onto GatewayClient.prototype via mixin pattern.
|
||||
*
|
||||
* Heartbeat constants are defined here as module-level values
|
||||
* to avoid static field coupling with the main class.
|
||||
*/
|
||||
|
||||
import type { GatewayClient } from './gateway-client';
|
||||
|
||||
// === Heartbeat Constants ===
|
||||
|
||||
/** Interval between heartbeat pings (30 seconds) */
|
||||
export const HEARTBEAT_INTERVAL = 30000;
|
||||
|
||||
/** Timeout for waiting for pong response (10 seconds) */
|
||||
export const HEARTBEAT_TIMEOUT = 10000;
|
||||
|
||||
/** Maximum missed heartbeats before reconnecting */
|
||||
export const MAX_MISSED_HEARTBEATS = 3;
|
||||
|
||||
// === Mixin Installer ===
|
||||
|
||||
/**
|
||||
* Install heartbeat methods onto GatewayClient.prototype.
|
||||
*
|
||||
* These methods access instance properties:
|
||||
* - this.ws: WebSocket | null
|
||||
* - this.heartbeatInterval: number | null
|
||||
* - this.heartbeatTimeout: number | null
|
||||
* - this.missedHeartbeats: number
|
||||
* - this.log(level, message): void
|
||||
* - this.stopHeartbeat(): void
|
||||
*/
|
||||
export function installHeartbeatMethods(ClientClass: { prototype: GatewayClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
|
||||
/**
|
||||
* Start heartbeat to keep connection alive.
|
||||
* Called after successful connection.
|
||||
*/
|
||||
proto.startHeartbeat = function (this: GatewayClient): void {
|
||||
(this as any).stopHeartbeat();
|
||||
(this as any).missedHeartbeats = 0;
|
||||
|
||||
(this as any).heartbeatInterval = window.setInterval(() => {
|
||||
(this as any).sendHeartbeat();
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
|
||||
(this as any).log('debug', 'Heartbeat started');
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop heartbeat.
|
||||
* Called on cleanup or disconnect.
|
||||
*/
|
||||
proto.stopHeartbeat = function (this: GatewayClient): void {
|
||||
const self = this as any;
|
||||
if (self.heartbeatInterval) {
|
||||
clearInterval(self.heartbeatInterval);
|
||||
self.heartbeatInterval = null;
|
||||
}
|
||||
if (self.heartbeatTimeout) {
|
||||
clearTimeout(self.heartbeatTimeout);
|
||||
self.heartbeatTimeout = null;
|
||||
}
|
||||
self.log('debug', 'Heartbeat stopped');
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a ping heartbeat to the server.
|
||||
*/
|
||||
proto.sendHeartbeat = function (this: GatewayClient): void {
|
||||
const self = this as any;
|
||||
if (self.ws?.readyState !== WebSocket.OPEN) {
|
||||
self.log('debug', 'Skipping heartbeat - WebSocket not open');
|
||||
return;
|
||||
}
|
||||
|
||||
self.missedHeartbeats++;
|
||||
if (self.missedHeartbeats > MAX_MISSED_HEARTBEATS) {
|
||||
self.log('warn', `Max missed heartbeats (${MAX_MISSED_HEARTBEATS}), reconnecting`);
|
||||
self.stopHeartbeat();
|
||||
self.ws.close(4000, 'Heartbeat timeout');
|
||||
return;
|
||||
}
|
||||
|
||||
// Send ping frame
|
||||
try {
|
||||
self.ws.send(JSON.stringify({ type: 'ping' }));
|
||||
self.log('debug', `Ping sent (missed: ${self.missedHeartbeats})`);
|
||||
|
||||
// Set timeout for pong
|
||||
self.heartbeatTimeout = window.setTimeout(() => {
|
||||
self.log('warn', 'Heartbeat pong timeout');
|
||||
// Don't reconnect immediately, let the next heartbeat check
|
||||
}, HEARTBEAT_TIMEOUT);
|
||||
} catch (error) {
|
||||
self.log('error', `Failed to send heartbeat: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle pong response from server.
|
||||
*/
|
||||
proto.handlePong = function (this: GatewayClient): void {
|
||||
const self = this as any;
|
||||
self.missedHeartbeats = 0;
|
||||
if (self.heartbeatTimeout) {
|
||||
clearTimeout(self.heartbeatTimeout);
|
||||
self.heartbeatTimeout = null;
|
||||
}
|
||||
self.log('debug', 'Pong received, heartbeat reset');
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import type { KernelClient } from './kernel-client';
|
||||
|
||||
export function installA2aMethods(ClientClass: { prototype: KernelClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
// ─── A2A (Agent-to-Agent) API ───
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { KernelClient } from './kernel-client';
|
||||
import type { AgentInfo, CreateAgentRequest, CreateAgentResponse } from './kernel-types';
|
||||
|
||||
export function installAgentMethods(ClientClass: { prototype: KernelClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
// ─── Agent Management ───
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { ChatResponse, StreamCallbacks, StreamChunkPayload } from './kernel
|
||||
const log = createLogger('KernelClient');
|
||||
|
||||
export function installChatMethods(ClientClass: { prototype: KernelClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Send a message and get a response
|
||||
@@ -227,13 +227,13 @@ export function installChatMethods(ClientClass: { prototype: KernelClient }): vo
|
||||
* Set default agent ID
|
||||
*/
|
||||
proto.setDefaultAgentId = function (this: KernelClient, agentId: string): void {
|
||||
(this as any).defaultAgentId = agentId;
|
||||
this.defaultAgentId = agentId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get default agent ID
|
||||
*/
|
||||
proto.getDefaultAgentId = function (this: KernelClient): string {
|
||||
return (this as any).defaultAgentId || '';
|
||||
return this.defaultAgentId || '';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface HandExecutionCompletePayload {
|
||||
}
|
||||
|
||||
export function installHandMethods(ClientClass: { prototype: KernelClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
// ─── Hands API ───
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ type SkillItem = {
|
||||
type SkillListResult = { skills: SkillItem[] };
|
||||
|
||||
export function installSkillMethods(ClientClass: { prototype: KernelClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
// ─── Skills API ───
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ type TriggerTypeSpec = {
|
||||
};
|
||||
|
||||
export function installTriggerMethods(ClientClass: { prototype: KernelClient }): void {
|
||||
const proto = ClientClass.prototype as any;
|
||||
const proto = ClientClass.prototype as unknown as Record<string, unknown>;
|
||||
|
||||
// ─── Triggers API ───
|
||||
|
||||
|
||||
Reference in New Issue
Block a user