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:
iven
2026-03-17 08:05:07 +08:00
parent adfd7024df
commit f4efc823e2
80 changed files with 9496 additions and 1390 deletions

View File

@@ -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)}`);
}
}