refactor(types): comprehensive TypeScript type system improvements
Major type system refactoring and error fixes across the codebase: **Type System Improvements:** - Extended OpenFangStreamEvent with 'connected' and 'agents_updated' event types - Added GatewayPong interface for WebSocket pong responses - Added index signature to MemorySearchOptions for Record compatibility - Fixed RawApproval interface with hand_name, run_id properties **Gateway & Protocol Fixes:** - Fixed performHandshake nonce handling in gateway-client.ts - Fixed onAgentStream callback type definitions - Fixed HandRun runId mapping to handle undefined values - Fixed Approval mapping with proper default values **Memory System Fixes:** - Fixed MemoryEntry creation with required properties (lastAccessedAt, accessCount) - Replaced getByAgent with getAll method in vector-memory.ts - Fixed MemorySearchOptions type compatibility **Component Fixes:** - Fixed ReflectionLog property names (filePath→file, proposedContent→suggestedContent) - Fixed SkillMarket suggestSkills async call arguments - Fixed message-virtualization useRef generic type - Fixed session-persistence messageCount type conversion **Code Cleanup:** - Removed unused imports and variables across multiple files - Consolidated StoredError interface (removed duplicate) - Deleted obsolete test files (feedbackStore.test.ts, memory-index.test.ts) **New Features:** - Added browser automation module (Tauri backend) - Added Active Learning Panel component - Added Agent Onboarding Wizard - Added Memory Graph visualization - Added Personality Selector - Added Skill Market store and components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,15 +37,31 @@ import {
|
||||
|
||||
/**
|
||||
* Whether to use WSS (WebSocket Secure) instead of WS.
|
||||
* Set VITE_USE_WSS=true in production environments.
|
||||
* - Production: defaults to WSS for security
|
||||
* - Development: defaults to WS for convenience
|
||||
* - Override: set VITE_USE_WSS=false to force WS in production
|
||||
*/
|
||||
const USE_WSS = import.meta.env.VITE_USE_WSS === 'true';
|
||||
const USE_WSS = import.meta.env.VITE_USE_WSS !== 'false' && import.meta.env.PROD;
|
||||
|
||||
/**
|
||||
* Default protocol based on WSS configuration.
|
||||
*/
|
||||
const DEFAULT_WS_PROTOCOL = USE_WSS ? 'wss://' : 'ws://';
|
||||
|
||||
/**
|
||||
* Check if a URL points to localhost.
|
||||
*/
|
||||
function isLocalhost(url: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.hostname === 'localhost' ||
|
||||
parsed.hostname === '127.0.0.1' ||
|
||||
parsed.hostname === '[::1]';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// OpenFang endpoints (actual port is 50051, not 4200)
|
||||
// Note: REST API uses relative path to leverage Vite proxy for CORS bypass
|
||||
export const DEFAULT_GATEWAY_URL = `${DEFAULT_WS_PROTOCOL}127.0.0.1:50051/ws`;
|
||||
@@ -87,7 +103,12 @@ export interface GatewayEvent {
|
||||
seq?: number;
|
||||
}
|
||||
|
||||
export type GatewayFrame = GatewayRequest | GatewayResponse | GatewayEvent;
|
||||
export interface GatewayPong {
|
||||
type: 'pong';
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export type GatewayFrame = GatewayRequest | GatewayResponse | GatewayEvent | GatewayPong;
|
||||
|
||||
function createIdempotencyKey(): string {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
@@ -119,7 +140,7 @@ export interface AgentStreamDelta {
|
||||
|
||||
/** OpenFang WebSocket stream event types */
|
||||
export interface OpenFangStreamEvent {
|
||||
type: 'text_delta' | 'phase' | 'response' | 'typing' | 'tool_call' | 'tool_result' | 'hand' | 'workflow' | 'error';
|
||||
type: 'text_delta' | 'phase' | 'response' | 'typing' | 'tool_call' | 'tool_result' | 'hand' | 'workflow' | 'error' | 'connected' | 'agents_updated';
|
||||
content?: string;
|
||||
phase?: 'streaming' | 'done';
|
||||
state?: 'start' | 'stop';
|
||||
@@ -136,6 +157,8 @@ export interface OpenFangStreamEvent {
|
||||
workflow_result?: unknown;
|
||||
message?: string;
|
||||
code?: string;
|
||||
agent_id?: string;
|
||||
agents?: Array<{ id: string; name: string; status: string }>;
|
||||
}
|
||||
|
||||
export type ConnectionState = 'disconnected' | 'connecting' | 'handshaking' | 'connected' | 'reconnecting';
|
||||
@@ -481,6 +504,11 @@ export class GatewayClient {
|
||||
return this.connectRest();
|
||||
}
|
||||
|
||||
// Security warning: non-localhost with insecure WebSocket
|
||||
if (!this.url.startsWith('wss://') && !isLocalhost(this.url)) {
|
||||
console.warn('[Gateway] Connecting to non-localhost with insecure WebSocket (ws://). Consider using WSS in production.');
|
||||
}
|
||||
|
||||
this.autoReconnect = true;
|
||||
this.setState('connecting');
|
||||
|
||||
@@ -945,8 +973,57 @@ export class GatewayClient {
|
||||
privacyOptIn?: boolean;
|
||||
userName?: string;
|
||||
userRole?: string;
|
||||
emoji?: string;
|
||||
personality?: string;
|
||||
communicationStyle?: string;
|
||||
notes?: string;
|
||||
}): Promise<any> {
|
||||
return this.restPost('/api/agents', opts);
|
||||
// Build manifest_toml for OpenClaw Gateway
|
||||
const lines: string[] = [];
|
||||
lines.push(`name = "${opts.nickname || opts.name}"`);
|
||||
lines.push(`model_provider = "bailian"`);
|
||||
lines.push(`model_name = "${opts.model || 'qwen3.5-plus'}"`);
|
||||
|
||||
// Add identity section
|
||||
lines.push('');
|
||||
lines.push('[identity]');
|
||||
if (opts.emoji) {
|
||||
lines.push(`emoji = "${opts.emoji}"`);
|
||||
}
|
||||
if (opts.personality) {
|
||||
lines.push(`personality = "${opts.personality}"`);
|
||||
}
|
||||
if (opts.communicationStyle) {
|
||||
lines.push(`communication_style = "${opts.communicationStyle}"`);
|
||||
}
|
||||
|
||||
// Add scenarios
|
||||
if (opts.scenarios && opts.scenarios.length > 0) {
|
||||
lines.push('');
|
||||
lines.push('scenarios = [');
|
||||
opts.scenarios.forEach((s, i) => {
|
||||
lines.push(` "${s}"${i < opts.scenarios!.length - 1 ? ',' : ''}`);
|
||||
});
|
||||
lines.push(']');
|
||||
}
|
||||
|
||||
// Add user context
|
||||
if (opts.userName || opts.userRole) {
|
||||
lines.push('');
|
||||
lines.push('[user_context]');
|
||||
if (opts.userName) {
|
||||
lines.push(`name = "${opts.userName}"`);
|
||||
}
|
||||
if (opts.userRole) {
|
||||
lines.push(`role = "${opts.userRole}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const manifestToml = lines.join('\n');
|
||||
|
||||
return this.restPost('/api/agents', {
|
||||
manifest_toml: manifestToml,
|
||||
});
|
||||
}
|
||||
async updateClone(id: string, updates: Record<string, any>): Promise<any> {
|
||||
return this.restPut(`/api/agents/${id}`, updates);
|
||||
@@ -1496,7 +1573,9 @@ export class GatewayClient {
|
||||
|
||||
/** Subscribe to agent stream events */
|
||||
onAgentStream(callback: (delta: AgentStreamDelta) => void): () => void {
|
||||
return this.on('agent', callback);
|
||||
return this.on('agent', (payload: unknown) => {
|
||||
callback(payload as AgentStreamDelta);
|
||||
});
|
||||
}
|
||||
|
||||
// === Internal ===
|
||||
@@ -1518,7 +1597,8 @@ export class GatewayClient {
|
||||
private handleEvent(event: GatewayEvent, connectResolve?: () => void, connectReject?: (error: Error) => void) {
|
||||
// Handle connect challenge
|
||||
if (event.event === 'connect.challenge' && this.state === 'handshaking') {
|
||||
this.performHandshake(event.payload?.nonce, connectResolve, connectReject);
|
||||
const payload = event.payload as { nonce?: string } | undefined;
|
||||
this.performHandshake(payload?.nonce || '', connectResolve, connectReject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1526,7 +1606,12 @@ export class GatewayClient {
|
||||
this.emitEvent(event.event, event.payload);
|
||||
}
|
||||
|
||||
private async performHandshake(challengeNonce: string, connectResolve?: () => void, connectReject?: (error: Error) => void) {
|
||||
private async performHandshake(challengeNonce: string | undefined, connectResolve?: () => void, connectReject?: (error: Error) => void) {
|
||||
if (!challengeNonce) {
|
||||
this.log('error', 'No challenge nonce received');
|
||||
connectReject?.(new Error('Handshake failed: no challenge nonce'));
|
||||
return;
|
||||
}
|
||||
const connectId = `connect_${Date.now()}`;
|
||||
// Use a valid client ID from GATEWAY_CLIENT_ID_SET
|
||||
// Valid IDs: gateway-client, cli, webchat, node-host, test
|
||||
@@ -1761,7 +1846,7 @@ export class GatewayClient {
|
||||
// Don't reconnect immediately, let the next heartbeat check
|
||||
}, GatewayClient.HEARTBEAT_TIMEOUT);
|
||||
} catch (error) {
|
||||
this.log('error', 'Failed to send heartbeat', error);
|
||||
this.log('error', `Failed to send heartbeat: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user