Add Hub Console with agent management and message routing

Implement Hub that auto-connects to Gateway, manages agents via REST API,
and routes WebSocket messages to agents by payload.agentId with echo responses
sent back to the original sender.

- Add Hub with GatewayClient auto-connect, agent CRUD, and message routing
- Add Console (NestJS) with REST API and static pages (management + demo client)
- Switch Gateway registration from explicit event to query-based on connect
- Remove deprecated types (RegisterPayload, metadata, SendMessagePayload)
- Add @nestjs/serve-static for serving console UI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yushen 2026-01-29 16:14:29 +08:00
parent 4b3592b5e4
commit dcca9333ab
18 changed files with 773 additions and 79 deletions

View file

@ -17,7 +17,6 @@ interface ResolvedOptions {
path: string;
deviceId: string;
deviceType: DeviceType;
metadata: Record<string, unknown> | undefined;
autoReconnect: boolean;
reconnectDelay: number;
}
@ -38,7 +37,6 @@ export class GatewayClient {
path: options.path ?? "/ws",
deviceId: options.deviceId,
deviceType: options.deviceType,
metadata: options.metadata,
autoReconnect: options.autoReconnect ?? true,
reconnectDelay: options.reconnectDelay ?? 1000,
};
@ -74,7 +72,7 @@ export class GatewayClient {
return this._state === "registered";
}
/** 连接到服务器 */
/** 连接到服务器deviceId 和 deviceType 通过 query 传递 */
connect(): this {
if (this.socket) {
return this;
@ -82,8 +80,14 @@ export class GatewayClient {
this.setState("connecting");
const query: Record<string, string> = {
deviceId: this.options.deviceId,
deviceType: this.options.deviceType,
};
this.socket = io(this.options.url, {
path: this.options.path,
query,
reconnection: this.options.autoReconnect,
reconnectionDelay: this.options.reconnectDelay,
});
@ -204,24 +208,13 @@ export class GatewayClient {
return uuidv7();
}
private register(): void {
if (!this.socket) return;
this.socket.emit(GatewayEvents.REGISTER, {
deviceId: this.options.deviceId,
deviceType: this.options.deviceType,
metadata: this.options.metadata,
});
}
private setupListeners(): void {
if (!this.socket) return;
this.socket.on("connect", () => {
this.setState("connected");
this.callbacks.onConnect?.(this.socket!.id!);
// 连接后自动注册
this.register();
// 服务端在连接时从 query 自动注册,等待 registered 事件即可
});
this.socket.on("disconnect", (reason: string) => {

View file

@ -3,7 +3,6 @@ export {
GatewayEvents,
type DeviceType,
type DeviceInfo,
type RegisterPayload,
type RegisteredResponse,
type RoutedMessage,
type SendErrorResponse,

View file

@ -3,7 +3,6 @@ export const GatewayEvents = {
// 系统事件
PING: "ping",
PONG: "pong",
REGISTER: "register",
REGISTERED: "registered",
// 消息路由
@ -21,14 +20,6 @@ export type DeviceType = "client" | "agent";
export interface DeviceInfo {
deviceId: string;
deviceType: DeviceType;
metadata?: Record<string, unknown> | undefined;
}
/** 注册请求 */
export interface RegisterPayload {
deviceId: string;
deviceType: DeviceType;
metadata?: Record<string, unknown>;
}
/** 注册响应 */
@ -88,8 +79,6 @@ export interface GatewayClientOptions {
deviceId: string;
/** 设备类型 */
deviceType: DeviceType;
/** 设备元数据 */
metadata?: Record<string, unknown> | undefined;
/** 自动重连,默认 true */
autoReconnect?: boolean | undefined;
/** 重连延迟(毫秒),默认 1000 */
@ -115,16 +104,3 @@ export interface GatewayClientCallbacks {
onStateChange?: (state: ConnectionState) => void;
}
// ============ 兼容旧API可删除 ============
/** @deprecated 使用 RoutedMessage */
export interface SendMessagePayload {
text: string;
}
/** @deprecated 使用 RoutedMessage */
export interface BroadcastMessage {
from: string;
text: string;
timestamp: string;
}