refactor: 统一项目名称从OpenFang到ZCLAW
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括:
- 配置文件中的项目名称
- 代码注释和文档引用
- 环境变量和路径
- 类型定义和接口名称
- 测试用例和模拟数据

同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
This commit is contained in:
iven
2026-03-27 07:36:03 +08:00
parent 4b08804aa9
commit 0d4fa96b82
226 changed files with 7288 additions and 5788 deletions

View File

@@ -1,7 +1,7 @@
/**
/**
* ZCLAW Gateway Client (Browser/Tauri side)
*
* Core WebSocket client for OpenFang Kernel protocol.
* Core WebSocket client for ZCLAW Kernel protocol.
* Handles connection management, WebSocket framing, heartbeat,
* event dispatch, and chat/stream operations.
*
@@ -22,7 +22,7 @@ export type {
GatewayPong,
GatewayFrame,
AgentStreamDelta,
OpenFangStreamEvent,
ZclawStreamEvent,
ConnectionState,
EventCallback,
} from './gateway-types';
@@ -51,7 +51,7 @@ import type {
GatewayFrame,
GatewayResponse,
GatewayEvent,
OpenFangStreamEvent,
ZclawStreamEvent,
ConnectionState,
EventCallback,
AgentStreamDelta,
@@ -158,7 +158,7 @@ function createIdempotencyKey(): string {
export class GatewayClient {
private ws: WebSocket | null = null;
private openfangWs: WebSocket | null = null; // OpenFang stream WebSocket
private zclawWs: WebSocket | null = null; // ZCLAW stream WebSocket
private state: ConnectionState = 'disconnected';
private requestId = 0;
private pendingRequests = new Map<string, {
@@ -243,20 +243,20 @@ export class GatewayClient {
// === Connection ===
/** Connect using REST API only (for OpenFang mode) */
/** Connect using REST API only (for ZCLAW mode) */
async connectRest(): Promise<void> {
if (this.state === 'connected') {
return;
}
this.setState('connecting');
try {
// Check if OpenFang API is healthy
// Check if ZCLAW API is healthy
const health = await this.restGet<{ status: string; version?: string }>('/api/health');
if (health.status === 'ok') {
this.reconnectAttempts = 0;
this.setState('connected');
this.startHeartbeat(); // Start heartbeat after successful connection
this.log('info', `Connected to OpenFang via REST API${health.version ? ` (v${health.version})` : ''}`);
this.log('info', `Connected to ZCLAW via REST API${health.version ? ` (v${health.version})` : ''}`);
this.emitEvent('connected', { version: health.version });
} else {
throw new Error('Health check failed');
@@ -264,7 +264,7 @@ export class GatewayClient {
} catch (err: unknown) {
this.setState('disconnected');
const errorMessage = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to connect to OpenFang: ${errorMessage}`);
throw new Error(`Failed to connect to ZCLAW: ${errorMessage}`);
}
}
@@ -273,7 +273,7 @@ export class GatewayClient {
return Promise.resolve();
}
// Check if URL is for OpenFang (port 4200 or 50051) - use REST mode
// Check if URL is for ZCLAW (port 4200 or 50051) - use REST mode
if (this.url.includes(':4200') || this.url.includes(':50051')) {
return this.connectRest();
}
@@ -389,10 +389,10 @@ export class GatewayClient {
// === High-level API ===
// Default agent ID for OpenFang (will be set dynamically from /api/agents)
// Default agent ID for ZCLAW (will be set dynamically from /api/agents)
private defaultAgentId: string = '';
/** Try to fetch default agent ID from OpenFang /api/agents endpoint */
/** Try to fetch default agent ID from ZCLAW /api/agents endpoint */
async fetchDefaultAgentId(): Promise<string | null> {
try {
// Use /api/agents endpoint which returns array of agents
@@ -422,7 +422,7 @@ export class GatewayClient {
return this.defaultAgentId;
}
/** Send message to agent (OpenFang chat API) */
/** Send message to agent (ZCLAW chat API) */
async chat(message: string, opts?: {
sessionKey?: string;
agentId?: string;
@@ -432,24 +432,24 @@ export class GatewayClient {
temperature?: number;
maxTokens?: number;
}): Promise<{ runId: string; sessionId?: string; response?: string }> {
// OpenFang uses /api/agents/{agentId}/message endpoint
// ZCLAW uses /api/agents/{agentId}/message endpoint
let agentId = opts?.agentId || this.defaultAgentId;
// If no agent ID, try to fetch from OpenFang status
// If no agent ID, try to fetch from ZCLAW status
if (!agentId) {
await this.fetchDefaultAgentId();
agentId = this.defaultAgentId;
}
if (!agentId) {
throw new Error('No agent available. Please ensure OpenFang has at least one agent.');
throw new Error('No agent available. Please ensure ZCLAW has at least one agent.');
}
const result = await this.restPost<{ response?: string; input_tokens?: number; output_tokens?: number }>(`/api/agents/${agentId}/message`, {
message,
session_id: opts?.sessionKey,
});
// OpenFang returns { response, input_tokens, output_tokens }
// ZCLAW returns { response, input_tokens, output_tokens }
return {
runId: createIdempotencyKey(),
sessionId: opts?.sessionKey,
@@ -457,7 +457,7 @@ export class GatewayClient {
};
}
/** Send message with streaming response (OpenFang WebSocket) */
/** Send message with streaming response (ZCLAW WebSocket) */
async chatStream(
message: string,
callbacks: {
@@ -472,20 +472,20 @@ export class GatewayClient {
agentId?: string;
}
): Promise<{ runId: string }> {
let agentId = opts?.agentId || this.defaultAgentId;
const agentId = opts?.agentId || this.defaultAgentId;
const runId = createIdempotencyKey();
const sessionId = opts?.sessionKey || `session_${Date.now()}`;
// If no agent ID, try to fetch from OpenFang status (async, but we'll handle it in connectOpenFangStream)
// If no agent ID, try to fetch from ZCLAW status (async, but we'll handle it in connectZclawStream)
if (!agentId) {
// Try to get default agent asynchronously
this.fetchDefaultAgentId().then(() => {
const resolvedAgentId = this.defaultAgentId;
if (resolvedAgentId) {
this.streamCallbacks.set(runId, callbacks);
this.connectOpenFangStream(resolvedAgentId, runId, sessionId, message);
this.connectZclawStream(resolvedAgentId, runId, sessionId, message);
} else {
callbacks.onError('No agent available. Please ensure OpenFang has at least one agent.');
callbacks.onError('No agent available. Please ensure ZCLAW has at least one agent.');
callbacks.onComplete();
}
}).catch((err) => {
@@ -498,22 +498,22 @@ export class GatewayClient {
// Store callbacks for this run
this.streamCallbacks.set(runId, callbacks);
// Connect to OpenFang WebSocket if not connected
this.connectOpenFangStream(agentId, runId, sessionId, message);
// Connect to ZCLAW WebSocket if not connected
this.connectZclawStream(agentId, runId, sessionId, message);
return { runId };
}
/** Connect to OpenFang streaming WebSocket */
private connectOpenFangStream(
/** Connect to ZCLAW streaming WebSocket */
private connectZclawStream(
agentId: string,
runId: string,
sessionId: string,
message: string
): void {
// Close existing connection if any
if (this.openfangWs && this.openfangWs.readyState !== WebSocket.CLOSED) {
this.openfangWs.close();
if (this.zclawWs && this.zclawWs.readyState !== WebSocket.CLOSED) {
this.zclawWs.close();
}
// Build WebSocket URL
@@ -528,34 +528,34 @@ export class GatewayClient {
wsUrl = httpUrl.replace(/^http/, 'ws') + `/api/agents/${agentId}/ws`;
}
this.log('info', `Connecting to OpenFang stream: ${wsUrl}`);
this.log('info', `Connecting to ZCLAW stream: ${wsUrl}`);
try {
this.openfangWs = new WebSocket(wsUrl);
this.zclawWs = new WebSocket(wsUrl);
this.openfangWs.onopen = () => {
this.log('info', 'OpenFang WebSocket connected');
// Send chat message using OpenFang actual protocol
this.zclawWs.onopen = () => {
this.log('info', 'ZCLAW WebSocket connected');
// Send chat message using ZCLAW actual protocol
const chatRequest = {
type: 'message',
content: message,
session_id: sessionId,
};
this.openfangWs?.send(JSON.stringify(chatRequest));
this.zclawWs?.send(JSON.stringify(chatRequest));
};
this.openfangWs.onmessage = (event) => {
this.zclawWs.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleOpenFangStreamEvent(runId, data, sessionId);
this.handleZclawStreamEvent(runId, data, sessionId);
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
this.log('error', `Failed to parse stream event: ${errorMessage}`);
}
};
this.openfangWs.onerror = (_event) => {
this.log('error', 'OpenFang WebSocket error');
this.zclawWs.onerror = (_event) => {
this.log('error', 'ZCLAW WebSocket error');
const callbacks = this.streamCallbacks.get(runId);
if (callbacks) {
callbacks.onError('WebSocket connection failed');
@@ -563,14 +563,14 @@ export class GatewayClient {
}
};
this.openfangWs.onclose = (event) => {
this.log('info', `OpenFang WebSocket closed: ${event.code} ${event.reason}`);
this.zclawWs.onclose = (event) => {
this.log('info', `ZCLAW WebSocket closed: ${event.code} ${event.reason}`);
const callbacks = this.streamCallbacks.get(runId);
if (callbacks && event.code !== 1000) {
callbacks.onError(`Connection closed: ${event.reason || 'unknown'}`);
}
this.streamCallbacks.delete(runId);
this.openfangWs = null;
this.zclawWs = null;
};
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
@@ -583,13 +583,13 @@ export class GatewayClient {
}
}
/** Handle OpenFang stream events */
private handleOpenFangStreamEvent(runId: string, data: OpenFangStreamEvent, sessionId: string): void {
/** Handle ZCLAW stream events */
private handleZclawStreamEvent(runId: string, data: ZclawStreamEvent, sessionId: string): void {
const callbacks = this.streamCallbacks.get(runId);
if (!callbacks) return;
switch (data.type) {
// OpenFang actual event types
// ZCLAW actual event types
case 'text_delta':
// Stream delta content
if (data.content) {
@@ -602,8 +602,8 @@ export class GatewayClient {
if (data.phase === 'done') {
callbacks.onComplete();
this.streamCallbacks.delete(runId);
if (this.openfangWs) {
this.openfangWs.close(1000, 'Stream complete');
if (this.zclawWs) {
this.zclawWs.close(1000, 'Stream complete');
}
}
break;
@@ -617,8 +617,8 @@ export class GatewayClient {
// Mark complete if phase done wasn't sent
callbacks.onComplete();
this.streamCallbacks.delete(runId);
if (this.openfangWs) {
this.openfangWs.close(1000, 'Stream complete');
if (this.zclawWs) {
this.zclawWs.close(1000, 'Stream complete');
}
break;
@@ -649,14 +649,14 @@ export class GatewayClient {
case 'error':
callbacks.onError(data.message || data.code || data.content || 'Unknown error');
this.streamCallbacks.delete(runId);
if (this.openfangWs) {
this.openfangWs.close(1011, 'Error');
if (this.zclawWs) {
this.zclawWs.close(1011, 'Error');
}
break;
case 'connected':
// Connection established
this.log('info', `OpenFang agent connected: ${data.agent_id}`);
this.log('info', `ZCLAW agent connected: ${data.agent_id}`);
break;
case 'agents_updated':
@@ -687,12 +687,12 @@ export class GatewayClient {
callbacks.onError('Stream cancelled');
this.streamCallbacks.delete(runId);
}
if (this.openfangWs && this.openfangWs.readyState === WebSocket.OPEN) {
this.openfangWs.close(1000, 'User cancelled');
if (this.zclawWs && this.zclawWs.readyState === WebSocket.OPEN) {
this.zclawWs.close(1000, 'User cancelled');
}
}
// === REST API Helpers (OpenFang) ===
// === REST API Helpers (ZCLAW) ===
public getRestBaseUrl(): string {
// In browser dev mode, use Vite proxy (empty string = relative path)