@honor-claw/yoyo 0.0.1-beta.6 → 0.0.1-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "env": {
27
27
  "type": "string",
28
- "enum": ["test", "production"]
28
+ "enum": ["dev", "test", "production"]
29
29
  }
30
30
  }
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honor-claw/yoyo",
3
- "version": "0.0.1-beta.6",
3
+ "version": "0.0.1-beta.7",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Honor Yoyo connection plugin",
6
6
  "scripts": {
@@ -9,6 +9,7 @@ import type { TokenResponse, HonorAuthConfig } from '../honor-auth/types.js';
9
9
  import { uuid } from '../utils/id.js';
10
10
  import { isOKResponse } from './helpers.js';
11
11
  import type { HttpApiWrapper } from './types.js';
12
+ import { takeApiHost } from '../modules/claw-configs/hosts.js';
12
13
 
13
14
  /**
14
15
  * PKCE参数
@@ -101,7 +102,8 @@ export class HonorAuthClient {
101
102
  * 使用授权码换取Token
102
103
  */
103
104
  async exchangeToken(code: string) {
104
- const tokenUrl = 'https://api-agd-test-drcn.hiboard.hihonorcloud.com/honorboard/auth/v1/oauth/jwtToken';
105
+ const hosts = takeApiHost();
106
+ const tokenUrl = `https://${hosts.ics}/honorboard/auth/v1/oauth/jwtToken`;
105
107
 
106
108
  const data = {
107
109
  clientId: this.config.clientId,
@@ -1,10 +1,12 @@
1
- import type { ClawChannelConfig, YoyoClawMessage, ClawSocketSourceInfo } from './types.js';
2
- import { ClawCloudSocketClient } from './client.js';
3
- import { GatewayClient } from '../gateway-client/client.js';
4
- import { useClawLogger } from '../utils/logger.js';
5
-
6
- const DEFAULT_WS_SERVER_URL =
7
- 'ws://omni-pre-drcn.hiboard.hihonorcloud.com/aicloud/yoyo-claw-service/v1/yoyoclaw/fullduplex';
1
+ import type {
2
+ ClawChannelConfig,
3
+ YoyoClawMessage,
4
+ DeviceSessionInfo,
5
+ } from "./types.js";
6
+ import { ClawCloudSocketClient } from "./client.js";
7
+ import { GatewayClient } from "../gateway-client/client.js";
8
+ import { useClawLogger } from "../utils/logger.js";
9
+ import { takeApiHost } from "../modules/claw-configs/hosts.js";
8
10
 
9
11
  /**
10
12
  * 新的Channel实现
@@ -15,14 +17,17 @@ const DEFAULT_WS_SERVER_URL =
15
17
  export class ClawChannel {
16
18
  private cloudClient: ClawCloudSocketClient;
17
19
  private gatewayClientsMap = new Map<string, GatewayClient>();
18
- private deviceIdToSourceInfoMap = new Map<string, ClawSocketSourceInfo>();
20
+ private deviceSessionsMap = new Map<string, DeviceSessionInfo[]>();
21
+ private sessionIdToHardwareDeviceMap = new Map<string, string>();
19
22
  private config: ClawChannelConfig;
20
23
 
21
24
  constructor(config: ClawChannelConfig) {
22
25
  this.config = config;
23
26
 
27
+ const hosts = takeApiHost();
28
+
24
29
  this.cloudClient = new ClawCloudSocketClient({
25
- serverUrl: DEFAULT_WS_SERVER_URL,
30
+ serverUrl: `wss://${hosts.clawCloud}/aicloud/yoyo-claw-service/v1/yoyoclaw/fullduplex`,
26
31
  deviceInfo: config.deviceInfo,
27
32
  userInfo: config.userInfo,
28
33
  onMessage: this.handleCloudMessage,
@@ -37,7 +42,7 @@ export class ClawChannel {
37
42
  * 启动连接
38
43
  */
39
44
  start(): void {
40
- useClawLogger().info('[yoyoclaw-channel] starting connection');
45
+ useClawLogger().info("[yoyoclaw-channel] starting connection");
41
46
  this.cloudClient.connect();
42
47
  }
43
48
 
@@ -45,7 +50,7 @@ export class ClawChannel {
45
50
  * 关闭连接
46
51
  */
47
52
  destroy(): void {
48
- useClawLogger().info('[yoyoclaw-channel] closing connection');
53
+ useClawLogger().info("[yoyoclaw-channel] closing connection");
49
54
  this.closeAllGatewayClients();
50
55
  this.cloudClient.close();
51
56
  }
@@ -54,7 +59,7 @@ export class ClawChannel {
54
59
  * 处理云侧连接打开
55
60
  */
56
61
  private handleCloudOpen = () => {
57
- useClawLogger().info('[yoyoclaw-channel] cloud connection established');
62
+ useClawLogger().info("[yoyoclaw-channel] cloud connection established");
58
63
  this.config.onOpen?.();
59
64
  };
60
65
 
@@ -62,7 +67,7 @@ export class ClawChannel {
62
67
  * 处理云侧连接关闭
63
68
  */
64
69
  private handleCloudClose = () => {
65
- useClawLogger().info('[yoyoclaw-channel] cloud connection closed');
70
+ useClawLogger().info("[yoyoclaw-channel] cloud connection closed");
66
71
  this.config.onClose?.();
67
72
  };
68
73
 
@@ -70,23 +75,16 @@ export class ClawChannel {
70
75
  * 处理对向设备离线
71
76
  */
72
77
  private handleRemoteDeviceOffline = (sourceDeviceId: string) => {
73
- // 关闭对应的 GatewayClient 连接
74
- const gatewayClient = this.gatewayClientsMap.get(sourceDeviceId);
75
- if (gatewayClient) {
76
- gatewayClient.stop();
77
- this.gatewayClientsMap.delete(sourceDeviceId);
78
- this.deviceIdToSourceInfoMap.delete(sourceDeviceId);
79
- useClawLogger().info(`[yoyoclaw-channel] closed gateway client for offline device: ${sourceDeviceId}`);
80
- } else {
81
- useClawLogger().warn(`[yoyoclaw-channel] no gateway client found for offline device: ${sourceDeviceId}`);
82
- }
78
+ this.closeGatewayClient(sourceDeviceId);
83
79
  };
84
80
 
85
81
  /**
86
82
  * 处理当前设备未注册
87
83
  */
88
84
  private handleDeviceNotRegistered = () => {
89
- useClawLogger().info('[yoyoclaw-channel] device not registered, notifying connection layer');
85
+ useClawLogger().info(
86
+ "[yoyoclaw-channel] device not registered, notifying connection layer"
87
+ );
90
88
  this.config.onDeviceNotRegistered?.();
91
89
  };
92
90
 
@@ -95,13 +93,13 @@ export class ClawChannel {
95
93
  */
96
94
  private handleCloudMessage = (message: YoyoClawMessage) => {
97
95
  // 处理配对消息
98
- if (message.msgType === 'devicePairMessage') {
96
+ if (message.msgType === "devicePairMessage") {
99
97
  this.handlePairMessage(message);
100
98
  return;
101
99
  }
102
100
 
103
101
  // 处理用户消息,转发到对应的GatewayClient
104
- if (message.msgType === 'userMessage') {
102
+ if (message.msgType === "userMessage") {
105
103
  this.handleUserMessage(message);
106
104
  }
107
105
  };
@@ -110,35 +108,73 @@ export class ClawChannel {
110
108
  * 处理配对消息,创建GatewayClient连接
111
109
  */
112
110
  private handlePairMessage(message: YoyoClawMessage): void {
113
- if (message.msgType !== 'devicePairMessage') {
111
+ if (message.msgType !== "devicePairMessage") {
114
112
  return;
115
113
  }
116
114
 
117
115
  try {
118
- const { sourceDeviceId, sourceDeviceInfo, targetDeviceId, port } = message;
116
+ const {
117
+ sourceDeviceId,
118
+ sourceDeviceInfo,
119
+ targetDeviceId,
120
+ port,
121
+ sessionInfo,
122
+ } = message;
123
+
124
+ if (
125
+ sourceDeviceId &&
126
+ targetDeviceId &&
127
+ port &&
128
+ sourceDeviceInfo?.deviceId
129
+ ) {
130
+ const hardwareDeviceId = sourceDeviceInfo.deviceId;
131
+ const newTimestamp = sessionInfo?.nodeConnectTimestamp;
132
+
133
+ if (!newTimestamp) {
134
+ // 兜底旧版本,异常场景,暂时不移除旧连接
135
+ useClawLogger().warn(
136
+ `[yoyoclaw-channel] pair message missing timestamp for device: ${sourceDeviceId}`
137
+ );
138
+ } else {
139
+ // 关闭该硬件设备ID对应的所有前序 GatewayClient(timestamp 小于新 timestamp)
140
+ this.closePreviousGatewayClients(hardwareDeviceId, newTimestamp);
141
+ }
119
142
 
120
- if (sourceDeviceId && targetDeviceId && port) {
121
143
  useClawLogger().info(
122
- `[yoyoclaw-channel] received pair message, creating gateway client for device: ${sourceDeviceId}`
144
+ `[yoyoclaw-channel] received pair message, creating gateway client for device: ${sourceDeviceId}, hardware device: ${hardwareDeviceId}, timestamp: ${newTimestamp}`
123
145
  );
124
146
 
125
- // 保存设备ID对应的源信息
126
- this.deviceIdToSourceInfoMap.set(sourceDeviceId, {
127
- sourceRole: sourceDeviceInfo?.role ?? 'node',
128
- sourceDeviceId: sourceDeviceId,
129
- sourceDeviceInfo: sourceDeviceInfo,
130
- });
147
+ // 创建会话信息
148
+ const sessionInfoData: DeviceSessionInfo = {
149
+ sessionId: sourceDeviceId,
150
+ timestamp: newTimestamp,
151
+ sourceInfo: {
152
+ sourceRole: sourceDeviceInfo?.role ?? "node",
153
+ sourceDeviceId: sourceDeviceId,
154
+ sourceDeviceInfo: sourceDeviceInfo,
155
+ },
156
+ };
157
+
158
+ // 保存会话信息
159
+ const sessions = this.deviceSessionsMap.get(hardwareDeviceId) || [];
160
+ sessions.push(sessionInfoData);
161
+ this.deviceSessionsMap.set(hardwareDeviceId, sessions);
162
+ this.sessionIdToHardwareDeviceMap.set(sourceDeviceId, hardwareDeviceId);
131
163
 
132
164
  // 创建GatewayClient
133
165
  const gatewayClient = new GatewayClient({
134
166
  onOpen: () => {
135
- useClawLogger().info(`[yoyoclaw-channel] gateway client connected for device: ${sourceDeviceId}`);
167
+ useClawLogger().info(
168
+ `[yoyoclaw-channel] gateway client connected for device: ${sourceDeviceId}`
169
+ );
136
170
  },
137
- onMessage: data => {
171
+ onMessage: (data) => {
138
172
  this.handleGatewayMessage(sourceDeviceId, data);
139
173
  },
140
- onClose: () => {
141
- useClawLogger().info(`[yoyoclaw-channel] gateway client closed for device: ${sourceDeviceId}`);
174
+ onClose: (reason) => {
175
+ useClawLogger().info(
176
+ `[yoyoclaw-channel] gateway client closed for device: ${sourceDeviceId}, with ${reason}`
177
+ );
142
178
  },
143
179
  });
144
180
 
@@ -146,7 +182,9 @@ export class ClawChannel {
146
182
  gatewayClient.start();
147
183
  }
148
184
  } catch (error) {
149
- useClawLogger().error(`[yoyoclaw-channel] failed to handle pair message: ${String(error)}`);
185
+ useClawLogger().error(
186
+ `[yoyoclaw-channel] failed to handle pair message: ${String(error)}`
187
+ );
150
188
  }
151
189
  }
152
190
 
@@ -155,24 +193,38 @@ export class ClawChannel {
155
193
  */
156
194
  private handleUserMessage(message: YoyoClawMessage): void {
157
195
  if (!message.sourceDeviceId) {
158
- useClawLogger().warn('[yoyoclaw-channel] user message missing sourceDeviceId');
196
+ useClawLogger().warn(
197
+ "[yoyoclaw-channel] user message missing sourceDeviceId"
198
+ );
159
199
  return;
160
200
  }
161
201
 
202
+ const hardwareDeviceId = this.sessionIdToHardwareDeviceMap.get(
203
+ message.sourceDeviceId
204
+ );
205
+
162
206
  try {
163
207
  const gatewayClient = this.gatewayClientsMap.get(message.sourceDeviceId);
164
208
  if (!gatewayClient) {
165
- useClawLogger().warn(`[yoyoclaw-channel] no gateway client found for source device: ${message.sourceDeviceId}`);
209
+ useClawLogger().warn(
210
+ `[yoyoclaw-channel] no gateway client found for source device: ${hardwareDeviceId}, session: ${message.sourceDeviceId}`
211
+ );
166
212
  return;
167
213
  }
168
214
 
169
215
  // 只发送data部分
170
216
  if (message.data) {
171
- useClawLogger().info(`[yoyoclaw-channel] forwarding user message to gateway from ${message.sourceDeviceId}`);
217
+ useClawLogger().info(
218
+ `[yoyoclaw-channel] forwarding user message to gateway from ${hardwareDeviceId}, session: ${message.sourceDeviceId}`
219
+ );
172
220
  gatewayClient.send(message.data);
173
221
  }
174
222
  } catch (error) {
175
- useClawLogger().error(`[yoyoclaw-channel] failed to handle user message: ${String(error)}`);
223
+ useClawLogger().error(
224
+ `[yoyoclaw-channel] failed to send gateway message to ${hardwareDeviceId}: ${String(
225
+ error
226
+ )}, session: ${message.sourceDeviceId}`
227
+ );
176
228
  }
177
229
  }
178
230
 
@@ -180,39 +232,62 @@ export class ClawChannel {
180
232
  * 处理来自Gateway的消息,转发到云侧
181
233
  */
182
234
  private handleGatewayMessage(sourceDeviceId: string, data: string): void {
183
- const sourceInfo = this.deviceIdToSourceInfoMap.get(sourceDeviceId);
235
+ const hardwareDeviceId =
236
+ this.sessionIdToHardwareDeviceMap.get(sourceDeviceId);
237
+ if (!hardwareDeviceId) {
238
+ useClawLogger().warn(
239
+ `[yoyoclaw-channel] gateway source node offline, session: ${sourceDeviceId}`
240
+ );
241
+ return;
242
+ }
243
+
244
+ const sessions = this.deviceSessionsMap.get(hardwareDeviceId);
245
+ const sessionInfo = sessions?.find((s) => s.sessionId === sourceDeviceId);
184
246
 
185
- if (!sourceInfo) {
186
- useClawLogger().warn('[yoyoclaw-channel] gateway source node offline');
247
+ if (!sessionInfo) {
248
+ useClawLogger().warn(
249
+ `[yoyoclaw-channel] gateway source node offline, session: ${sourceDeviceId}`
250
+ );
187
251
  return;
188
252
  }
189
253
 
254
+ useClawLogger().debug?.(
255
+ `[yoyoclaw-channel] received gateway message from ${
256
+ sessionInfo.sourceInfo.sourceDeviceInfo?.deviceId
257
+ } to cloud:, ${data.slice(0, 1000)}`
258
+ );
259
+
190
260
  try {
191
261
  // 将Gateway的消息封装成YoyoClawMessage发送到云侧
192
262
  // 使用当前接收到的socket对应deviceId的来源信息
193
263
  const cloudMessage: YoyoClawMessage = {
194
- msgType: 'userMessage',
195
- sourceRole: 'yoyoclaw',
264
+ msgType: "userMessage",
265
+ sourceRole: "yoyoclaw",
196
266
  sourceDeviceId: this.config.deviceInfo.deviceId,
197
- targetRole: 'node',
198
- targetDeviceId: sourceInfo.sourceDeviceId,
267
+ targetRole: "node",
268
+ targetDeviceId: sessionInfo.sourceInfo.sourceDeviceId,
199
269
  port: this.config.deviceInfo.port,
200
270
  data,
201
271
  };
202
272
 
203
- const result = this.cloudClient.send(cloudMessage);
273
+ const result = this.cloudClient.send(
274
+ cloudMessage,
275
+ sessionInfo.sourceInfo.sourceDeviceInfo?.deviceId
276
+ );
204
277
 
205
278
  if (!result) {
206
279
  // 云socket挂了,这个gateway连接就需要取消掉
207
280
  // 最好不要一次性把所有的gateway连接都干掉,让惰性关闭
208
281
  useClawLogger().error(
209
- `[yoyoclaw-channel] failed to send message, cloud socket closed, from: ${sourceInfo.sourceDeviceId}`
282
+ `[yoyoclaw-channel] failed to send message, cloud socket closed, from: ${sessionInfo.sourceInfo.sourceDeviceId}`
210
283
  );
211
284
 
212
- this.handleRemoteDeviceOffline(sourceInfo.sourceDeviceId);
285
+ this.handleRemoteDeviceOffline(sessionInfo.sourceInfo.sourceDeviceId);
213
286
  }
214
287
  } catch (error) {
215
- useClawLogger().error(`[yoyoclaw-channel] failed to handle gateway message: ${String(error)}`);
288
+ useClawLogger().error(
289
+ `[yoyoclaw-channel] failed to handle gateway message: ${String(error)}`
290
+ );
216
291
  }
217
292
  }
218
293
 
@@ -220,11 +295,73 @@ export class ClawChannel {
220
295
  * 关闭所有GatewayClient连接
221
296
  */
222
297
  private closeAllGatewayClients(): void {
223
- for (const [deviceId, client] of this.gatewayClientsMap.entries()) {
298
+ for (const [sessionId, client] of this.gatewayClientsMap.entries()) {
224
299
  client.stop();
225
- useClawLogger().info(`[yoyoclaw-channel] closed gateway client for device: ${deviceId}`);
300
+ useClawLogger().info(
301
+ `[yoyoclaw-channel] closed gateway client for session: ${sessionId}`
302
+ );
226
303
  }
227
304
  this.gatewayClientsMap.clear();
228
- this.deviceIdToSourceInfoMap.clear();
305
+ this.deviceSessionsMap.clear();
306
+ this.sessionIdToHardwareDeviceMap.clear();
307
+ }
308
+
309
+ /**
310
+ * 关闭硬件设备的所有前序 GatewayClient(timestamp 小于指定值)
311
+ */
312
+ private closePreviousGatewayClients(
313
+ hardwareDeviceId: string,
314
+ newTimestamp: number
315
+ ): void {
316
+ const sessions = this.deviceSessionsMap.get(hardwareDeviceId);
317
+ if (!sessions || sessions.length === 0) {
318
+ return;
319
+ }
320
+
321
+ // 找出所有 timestamp 小于新 timestamp 的会话
322
+ const previousSessions = sessions.filter((s) => s.timestamp < newTimestamp);
323
+
324
+ if (previousSessions.length > 0) {
325
+ useClawLogger().info(
326
+ `[yoyoclaw-channel] closing ${previousSessions.length} previous gateway client(s) for hardware device: ${hardwareDeviceId}, new timestamp: ${newTimestamp}`
327
+ );
328
+
329
+ for (const session of previousSessions) {
330
+ this.closeGatewayClient(session.sessionId);
331
+ }
332
+ }
333
+ }
334
+
335
+ /**
336
+ * 关闭单个 GatewayClient
337
+ */
338
+ private closeGatewayClient(sessionId: string): void {
339
+ const gatewayClient = this.gatewayClientsMap.get(sessionId);
340
+ if (gatewayClient) {
341
+ gatewayClient.stop();
342
+ this.gatewayClientsMap.delete(sessionId);
343
+
344
+ // 从会话映射中移除
345
+ const hardwareDeviceId = this.sessionIdToHardwareDeviceMap.get(sessionId);
346
+ if (hardwareDeviceId) {
347
+ const sessions = this.deviceSessionsMap.get(hardwareDeviceId);
348
+ if (sessions) {
349
+ const index = sessions.findIndex((s) => s.sessionId === sessionId);
350
+ if (index !== -1) {
351
+ sessions.splice(index, 1);
352
+ }
353
+
354
+ // 如果没有会话了,清理该硬件设备的记录
355
+ if (sessions.length === 0) {
356
+ this.deviceSessionsMap.delete(hardwareDeviceId);
357
+ }
358
+ }
359
+ this.sessionIdToHardwareDeviceMap.delete(sessionId);
360
+ }
361
+
362
+ useClawLogger().info(
363
+ `[yoyoclaw-channel] closed gateway client for session: ${sessionId}`
364
+ );
365
+ }
229
366
  }
230
367
  }
@@ -80,7 +80,9 @@ export class ClawCloudSocketClient {
80
80
 
81
81
  this.ws.on("open", () => {
82
82
  const connectionType = isRetry ? "reconnected" : "connected";
83
- useClawLogger().info(`[claw-cloud-socket] ${connectionType} to ${url}`);
83
+ useClawLogger().info(
84
+ `[claw-cloud-socket] ${connectionType} to ${url.slice(0, 15)}`
85
+ );
84
86
 
85
87
  // 清除重试定时器
86
88
  this.clearRetryTimer();
@@ -129,7 +131,7 @@ export class ClawCloudSocketClient {
129
131
  /**
130
132
  * 发送消息
131
133
  */
132
- send(message: YoyoClawMessage): boolean {
134
+ send(message: YoyoClawMessage, toDeviceId?: string): boolean {
133
135
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
134
136
  useClawLogger().error(
135
137
  "[claw-cloud-socket] cannot send message: connection not open"
@@ -141,14 +143,18 @@ export class ClawCloudSocketClient {
141
143
  const msgText = JSON.stringify(message);
142
144
 
143
145
  useClawLogger().debug?.(
144
- `[yoyoclaw-cloud] send message to cloud:, ${msgText.slice(0, 500)}`
146
+ `[yoyoclaw-cloud] send message to cloud session ${
147
+ message.targetDeviceId
148
+ }, device: ${toDeviceId}:, ${msgText.slice(0, 1000)}`
145
149
  );
146
150
 
147
151
  this.ws.send(msgText);
148
152
  return true;
149
153
  } catch (error) {
150
154
  useClawLogger().error(
151
- `[claw-cloud-socket] failed to send message: ${
155
+ `[claw-cloud-socket] failed to send message to cloud session ${
156
+ message.targetDeviceId
157
+ }, device: ${toDeviceId}: ${
152
158
  error instanceof Error ? error.message : String(error)
153
159
  }`
154
160
  );
@@ -188,11 +194,19 @@ export class ClawCloudSocketClient {
188
194
  const message: YoyoClawSocketMessage = JSON.parse(dataText);
189
195
 
190
196
  useClawLogger().debug?.(
191
- `[yoyoclaw-channel] received cloud message: ${dataText.slice(0, 500)}`
197
+ `[yoyoclaw-channel] received cloud message from session ${
198
+ message.wsOutputEvent?.sourceDeviceId
199
+ }, deviceId ${
200
+ message.wsOutputEvent?.sourceDeviceInfo?.deviceId
201
+ }: ${dataText.slice(0, 1000)}`
192
202
  );
193
203
 
194
204
  if (message.code === "YOYO_CLAW_100000") {
195
205
  if (message.wsOutputEvent) {
206
+ // 将 sessionInfo 从 wrapper 复制到 message 中
207
+ if (message.sessionInfo) {
208
+ message.wsOutputEvent.sessionInfo = message.sessionInfo;
209
+ }
196
210
  this.options.onMessage?.(message.wsOutputEvent);
197
211
  return;
198
212
  }
@@ -200,7 +214,7 @@ export class ClawCloudSocketClient {
200
214
  // 对向设备离线,当前设备对应的gateway连接都可以销毁掉
201
215
  if (message.extData?.offlineSocketId) {
202
216
  useClawLogger().info(
203
- `[claw-cloud-socket] remote device offline: ${message.extData.offlineSocketId}`
217
+ `[claw-cloud-socket] remote device offline, session: ${message.extData.offlineSocketId}`
204
218
  );
205
219
  this.options.onRemoteDeviceOffline?.(message.extData.offlineSocketId);
206
220
 
@@ -17,6 +17,12 @@ export interface YoyoClawMessage {
17
17
  port: number | string;
18
18
  data?: string;
19
19
  msgType: "userMessage" | "devicePairMessage";
20
+ /**
21
+ * 会话轮次等追加信息,只有配对消息有
22
+ */
23
+ sessionInfo?: {
24
+ nodeConnectTimestamp: number;
25
+ };
20
26
  }
21
27
 
22
28
  /**
@@ -43,6 +49,12 @@ export interface YoyoClawSocketWrapper<T> {
43
49
  extData?: {
44
50
  offlineSocketId?: string;
45
51
  };
52
+ /**
53
+ * 会话轮次等追加信息,只有配对消息有
54
+ */
55
+ sessionInfo?: {
56
+ nodeConnectTimestamp: number;
57
+ }
46
58
  }
47
59
 
48
60
  /**
@@ -79,3 +91,12 @@ export interface ClawSocketClientOptions {
79
91
  // 当前设备未注册回调
80
92
  onDeviceNotRegistered?: () => void;
81
93
  }
94
+
95
+ /**
96
+ * 设备会话信息
97
+ */
98
+ export interface DeviceSessionInfo {
99
+ sessionId: string;
100
+ timestamp: number;
101
+ sourceInfo: ClawSocketSourceInfo;
102
+ }
@@ -5,8 +5,8 @@ import type { Command } from "commander";
5
5
  export function registerEnvCommand(_: unknown, command: Command) {
6
6
  const nextCommand = command
7
7
  .command("env")
8
- .description("Manage runtime environment (test/production)")
9
- .option("--set <env>", "Set environment: test or production")
8
+ .description("Manage runtime environment (dev/test/production)")
9
+ .option("--set <env>", "Set environment: dev, test or production")
10
10
  .action(async (options) => {
11
11
  const { set: setEnv } = options;
12
12
  const logger = useClawLogger();
@@ -16,7 +16,7 @@ export function registerEnvCommand(_: unknown, command: Command) {
16
16
 
17
17
  if (setEnv) {
18
18
  // 设置环境
19
- if (setEnv !== "test" && setEnv !== "production") {
19
+ if (setEnv !== "test" && setEnv !== "dev" && setEnv !== "production") {
20
20
  logger.error("❌ Invalid environment. Use 'test' or 'production'.");
21
21
  return;
22
22
  }
@@ -1,6 +1,7 @@
1
1
  import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { registerLoginCommand } from "./login/index.js";
3
3
  import { registerStatusCommand } from "./status/index.js";
4
+ import { registerLogoutCommand } from "./logout/index.js";
4
5
  import { registerEnvCommand } from "./env/index.js";
5
6
  import { isBetaVersion } from "../utils/version.js";
6
7
 
@@ -15,7 +16,8 @@ export function registerCommands(api: OpenClawPluginApi) {
15
16
  registerLoginCommand(api, rootCommand);
16
17
  // @ts-ignore
17
18
  registerStatusCommand(api, rootCommand);
18
- // registerLogoutCommand(api, rootCommand);
19
+ // @ts-ignore
20
+ registerLogoutCommand(api, rootCommand);
19
21
 
20
22
  // 只在 beta 版本时注册 env 命令
21
23
  if (isBetaVersion()) {
@@ -0,0 +1,26 @@
1
+ import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { type Command } from "commander";
3
+ import { getConfigManager } from "../../modules/claw-configs/config-manager.js";
4
+
5
+ export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
6
+ const nextCommand = command
7
+ .command("logout")
8
+ .description("退出登录并清除用户配置")
9
+ .action(async () => {
10
+ api.logger.info("logout CLI command called");
11
+
12
+ try {
13
+ // 清除用户配置
14
+ const configManager = getConfigManager();
15
+ await configManager.clearUserConfig();
16
+ console.log("✅ 用户配置已清除");
17
+ console.log("✅ 去登录成功,网关将自动重启处理新配置");
18
+ } catch (error) {
19
+ const errorMessage = error instanceof Error ? error.message : String(error);
20
+ console.error("❌ 去登录失败:", errorMessage);
21
+ throw error;
22
+ }
23
+ });
24
+
25
+ return nextCommand;
26
+ }
@@ -1,53 +1 @@
1
- import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { getConnectionManager } from "../../services/connection-manager.js";
3
- import { type Command } from "commander";
4
-
5
- export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
6
- const nextCommand = command
7
- .command("logout")
8
- .description("Logout and disconnect from YOYOClaw")
9
- .option("--force", "Force logout without confirmation")
10
- .action(async (options) => {
11
- api.logger.info("honor logout CLI command called");
12
-
13
- const { force } = options;
14
- const connectionManager = getConnectionManager();
15
- const isConnected = connectionManager.isConnected();
16
- const connectedDevices = connectionManager.getConnectedDevices();
17
-
18
- if (!isConnected) {
19
- console.log("⚠️ 当前未连接到 YOYOClaw");
20
- return;
21
- }
22
-
23
- // 如果有活跃的设备连接且不是强制模式,提示确认
24
- if (connectedDevices.length > 0 && !force) {
25
- console.log(`⚠️ 当前有 ${connectedDevices.length} 个活跃的设备连接:`);
26
- for (const device of connectedDevices) {
27
- console.log(` - ${device}`);
28
- }
29
- console.log("\n⚠️ 断开连接将停止所有设备间的通信");
30
- console.log('💡 使用 --force 选项可以跳过此确认');
31
-
32
- // 注意:在命令行工具中,我们无法直接等待用户输入
33
- // 这里我们给出提示,然后继续执行
34
- console.log("\n🔌 正在断开连接...");
35
- } else {
36
- console.log("🔌 正在断开连接...");
37
- }
38
-
39
- try {
40
- await connectionManager.cleanup();
41
- console.log("✅ WebSocket 连接已关闭");
42
- console.log("✅ 所有设备连接已清理");
43
- console.log("✅ 资源已释放");
44
- console.log("👋 已登出");
45
- } catch (error) {
46
- const errorMessage = error instanceof Error ? error.message : String(error);
47
- console.error("❌ 登出失败:", errorMessage);
48
- throw error;
49
- }
50
- });
51
-
52
- return nextCommand;
53
- }
1
+ export * from "./impl.js";
@@ -1,9 +1,9 @@
1
- import { rawDataToString } from "openclaw/plugin-sdk";
2
- import { WebSocket } from "ws";
3
- import { GatewayClientOptions } from "./types.js";
4
- import { useClawLogger } from "../utils/logger.js";
1
+ import { rawDataToString } from 'openclaw/plugin-sdk';
2
+ import { WebSocket } from 'ws';
3
+ import { GatewayClientOptions } from './types.js';
4
+ import { getConfigManager } from '../modules/claw-configs/config-manager.js';
5
5
 
6
- const GATEWAY_SERVER_URL = "ws://127.0.0.1:18789";
6
+ const GATEWAY_SERVER_URL = 'ws://127.0.0.1:18789';
7
7
 
8
8
  /**
9
9
  * 纯透传 Gateway Client
@@ -26,29 +26,28 @@ export class GatewayClient {
26
26
  if (this.closed) {
27
27
  return;
28
28
  }
29
- const url = GATEWAY_SERVER_URL;
29
+
30
+ const configManager = getConfigManager();
31
+ const url = `ws://127.0.0.1:${configManager.getGatewayPort()}`;
30
32
  this.ws = new WebSocket(url, { maxPayload: 25 * 1024 * 1024 });
31
33
 
32
- this.ws.on("open", () => {
34
+ this.ws.on('open', () => {
33
35
  this.opts.onOpen?.();
34
36
  });
35
37
 
36
- this.ws.on("message", (data) => {
38
+ this.ws.on('message', data => {
37
39
  const dataText = rawDataToString(data);
38
- useClawLogger().debug?.(`[yoyoclaw-gateway] received message:, ${dataText.slice(0, 500)}`);
39
40
  this.opts.onMessage?.(dataText);
40
41
  });
41
42
 
42
- this.ws.on("close", (code, reason) => {
43
+ this.ws.on('close', (code, reason) => {
43
44
  const reasonText = rawDataToString(reason);
44
45
  this.ws = null;
45
- useClawLogger().info(`[yoyoclaw-gateway] closed (${code}): ${reasonText}`);
46
- this.opts.onClose?.();
46
+ this.opts.onClose?.(`code: ${code}, reason: ${reasonText ?? ''}`);
47
47
  });
48
48
 
49
- this.ws.on("error", (err) => {
50
- useClawLogger().error(`[yoyoclaw-gateway] error: ${String(err)}`);
51
- this.opts.onClose?.();
49
+ this.ws.on('error', err => {
50
+ this.opts.onClose?.(`socket error: ${(err as Error).message}`);
52
51
  });
53
52
  }
54
53
 
@@ -66,11 +65,9 @@ export class GatewayClient {
66
65
  */
67
66
  send(data: Buffer | string): void {
68
67
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
69
- useClawLogger().error(
70
- "[yoyoclaw-gateway] send failed: gateway not connected"
71
- );
72
- return;
68
+ throw new Error('gateway not connected');
73
69
  }
70
+
74
71
  this.ws.send(data);
75
72
  }
76
73
  }
@@ -4,5 +4,5 @@
4
4
  export interface GatewayClientOptions {
5
5
  onMessage?: (data: string) => void; // 原始消息透传
6
6
  onOpen?: () => void;
7
- onClose?: () => void;
8
- }
7
+ onClose?: (reason: string) => void;
8
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type { OpenClawConfig } from "openclaw/plugin-sdk";
6
6
  import { getYoyoRuntime } from "../../runtime.js";
7
+ import { isBetaVersion } from "../../utils/version.js";
7
8
  import type { GatewayAuthConfig, UserConfig } from "./types.js";
8
9
 
9
10
  const PLUGIN_YOYO_ID = "yoyo";
@@ -21,7 +22,8 @@ export class ConfigManager {
21
22
  return runtime.config.loadConfig();
22
23
  } catch (error) {
23
24
  throw new Error(
24
- `Failed to load config: ${error instanceof Error ? error.message : String(error)
25
+ `Failed to load config: ${
26
+ error instanceof Error ? error.message : String(error)
25
27
  }`
26
28
  );
27
29
  }
@@ -36,7 +38,8 @@ export class ConfigManager {
36
38
  await runtime.config.writeConfigFile(config);
37
39
  } catch (error) {
38
40
  throw new Error(
39
- `Failed to save config: ${error instanceof Error ? error.message : String(error)
41
+ `Failed to save config: ${
42
+ error instanceof Error ? error.message : String(error)
40
43
  }`
41
44
  );
42
45
  }
@@ -112,22 +115,24 @@ export class ConfigManager {
112
115
  /**
113
116
  * 获取运行环境配置
114
117
  */
115
- getEnv(): 'test' | 'production' {
118
+ getEnv(): "test" | "production" {
116
119
  try {
117
120
  const config = this.loadConfig();
118
- const env = config.plugins?.entries?.[PLUGIN_YOYO_ID]?.config
119
- ?.env as 'test' | 'production' | undefined;
120
- return env || 'test';
121
+ const env = config.plugins?.entries?.[PLUGIN_YOYO_ID]?.config?.env as
122
+ | "test"
123
+ | "production"
124
+ | undefined;
125
+ return env || "test";
121
126
  } catch (error) {
122
127
  console.error(`[claw-configs] Failed to read env config: ${error}`);
123
- return 'test';
128
+ return "test";
124
129
  }
125
130
  }
126
131
 
127
132
  /**
128
133
  * 更新运行环境配置
129
134
  */
130
- async updateEnv(env: 'test' | 'production'): Promise<void> {
135
+ async updateEnv(env: "test" | "production"): Promise<void> {
131
136
  try {
132
137
  const currentConfig = this.loadConfig();
133
138
  const currentUserConfig = this.getUserConfig() || {};
@@ -187,7 +192,8 @@ export class ConfigManager {
187
192
  await this.saveConfig(updatedConfig);
188
193
  } catch (error) {
189
194
  throw new Error(
190
- `Failed to update user config: ${error instanceof Error ? error.message : String(error)
195
+ `Failed to update user config: ${
196
+ error instanceof Error ? error.message : String(error)
191
197
  }`
192
198
  );
193
199
  }
@@ -219,7 +225,8 @@ export class ConfigManager {
219
225
  await this.saveConfig(updatedConfig);
220
226
  } catch (error) {
221
227
  throw new Error(
222
- `Failed to clear user config: ${error instanceof Error ? error.message : String(error)
228
+ `Failed to clear user config: ${
229
+ error instanceof Error ? error.message : String(error)
223
230
  }`
224
231
  );
225
232
  }
@@ -266,7 +273,7 @@ export class ConfigManager {
266
273
  "sound.vibration",
267
274
  "ringing.mode",
268
275
  "vibration.mode",
269
- "close.app"
276
+ "close.app",
270
277
  ];
271
278
 
272
279
  const currentAllowCommands =
@@ -275,12 +282,33 @@ export class ConfigManager {
275
282
  new Set([...currentAllowCommands, ...commandsToAdd])
276
283
  );
277
284
 
285
+ // 处理 env 默认值(仅在未设置时设置)
286
+ const currentEnv =
287
+ currentConfig.plugins?.entries?.[pluginId]?.config?.env;
288
+ const shouldSetEnv = !currentEnv;
289
+ const defaultEnv = isBetaVersion() ? "test" : "production";
290
+
291
+ // 构建插件配置
292
+ const pluginConfig =
293
+ currentConfig.plugins?.entries?.[pluginId]?.config || {};
294
+ const updatedPluginConfig = shouldSetEnv
295
+ ? { ...pluginConfig, env: defaultEnv }
296
+ : pluginConfig;
297
+
278
298
  // 一次性更新所有配置
279
299
  const updatedConfig: OpenClawConfig = {
280
300
  ...currentConfig,
281
301
  plugins: {
282
302
  ...currentConfig.plugins,
283
303
  allow: updatedAllow,
304
+ entries: {
305
+ ...currentConfig.plugins?.entries,
306
+ [pluginId]: {
307
+ ...currentConfig.plugins?.entries?.[pluginId],
308
+ enabled: true,
309
+ config: updatedPluginConfig,
310
+ },
311
+ },
284
312
  },
285
313
  gateway: {
286
314
  ...currentConfig.gateway,
@@ -294,7 +322,8 @@ export class ConfigManager {
294
322
  await this.saveConfig(updatedConfig);
295
323
  } catch (error) {
296
324
  throw new Error(
297
- `failed to initialize plugin config: ${error instanceof Error ? error.message : String(error)
325
+ `failed to initialize plugin config: ${
326
+ error instanceof Error ? error.message : String(error)
298
327
  }`
299
328
  );
300
329
  }
@@ -0,0 +1,30 @@
1
+ import { getYoyoEnv } from '../../runtime.js';
2
+
3
+ /**
4
+ * 获取当前环境的api host信息
5
+ */
6
+ export function takeApiHost() {
7
+ const env = getYoyoEnv();
8
+
9
+ switch (env) {
10
+ case 'dev': {
11
+ return {
12
+ clawCloud: 'omni-dev-drcn.hiboard.hihonorcloud.com',
13
+ ics: 'api-agd-test-drcn.hiboard.hihonorcloud.com',
14
+ };
15
+ }
16
+ case 'test': {
17
+ return {
18
+ clawCloud: 'omni-pre-drcn.hiboard.hihonorcloud.com',
19
+ ics: 'api-agd-test-drcn.hiboard.hihonorcloud.com',
20
+ };
21
+ }
22
+ case 'production':
23
+ default: {
24
+ return {
25
+ clawCloud: 'yoyoclaw-drcn.hiboard.hihonorcloud.com',
26
+ ics: 'api-prd-drcn.hiboard.hihonorcloud.com',
27
+ };
28
+ }
29
+ }
30
+ }
@@ -5,3 +5,4 @@
5
5
 
6
6
  export * from './types.js';
7
7
  export * from './config-manager.js';
8
+ export * from './hosts.js';
@@ -27,6 +27,6 @@ export interface YoyoClawPluginConfig {
27
27
  config?: {
28
28
  user?: UserConfig;
29
29
  /** 运行环境:test 或 production */
30
- env?: 'test' | 'production';
30
+ env?: 'dev' | 'test' | 'production';
31
31
  };
32
32
  }
@@ -4,6 +4,8 @@
4
4
  import type { DeviceInfo } from '../../types.js';
5
5
  import { WindowsDeviceInfoProvider } from './providers/windows.js';
6
6
  import { PadDevice } from './providers/pad.js';
7
+ import { LinuxDeviceInfoProvider } from './providers/linux.js';
8
+ import { MacOSDeviceInfoProvider } from './providers/macos.js';
7
9
  import type { DeviceInfoProvider } from './providers/base.js';
8
10
  import { useClawLogger } from '../../utils/logger.js';
9
11
  import { getConfigManager } from '../claw-configs/config-manager.js';
@@ -11,7 +13,7 @@ import { getConfigManager } from '../claw-configs/config-manager.js';
11
13
  /**
12
14
  * 检测当前设备类型
13
15
  */
14
- function detectDeviceType(): 'windows' | 'android' {
16
+ function detectDeviceType(): 'windows' | 'pad' | 'linux' | 'macos' {
15
17
  const platform = process.platform;
16
18
 
17
19
  if (platform === 'win32') {
@@ -19,20 +21,24 @@ function detectDeviceType(): 'windows' | 'android' {
19
21
  }
20
22
 
21
23
  if (platform === 'linux') {
22
- // 检查是否为 Android 环境
24
+ // 检查是否为 pad 环境
23
25
  try {
24
26
  const fs = require('fs');
25
27
  if (fs.existsSync('/system/bin/getprop')) {
26
- return 'android';
28
+ return 'pad';
27
29
  }
28
30
  } catch {
29
- // 忽略错误,默认为 android
31
+ // 忽略错误,继续检测为 Linux
30
32
  }
31
- return 'android';
33
+ return 'linux';
32
34
  }
33
35
 
34
- // 默认返回 windows(兼容性)
35
- return 'windows';
36
+ if (platform === 'darwin') {
37
+ return 'macos';
38
+ }
39
+
40
+ // 默认返回 linux(兼容性)
41
+ return 'linux';
36
42
  }
37
43
 
38
44
  /**
@@ -42,11 +48,16 @@ function getDeviceInfoProvider(): DeviceInfoProvider {
42
48
  const deviceType = detectDeviceType();
43
49
 
44
50
  switch (deviceType) {
45
- case 'android':
51
+ case 'pad':
46
52
  return new PadDevice();
47
53
  case 'windows':
48
- default:
49
54
  return new WindowsDeviceInfoProvider();
55
+ case 'linux':
56
+ return new LinuxDeviceInfoProvider();
57
+ case 'macos':
58
+ return new MacOSDeviceInfoProvider();
59
+ default:
60
+ return new LinuxDeviceInfoProvider();
50
61
  }
51
62
  }
52
63
 
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Linux 设备信息提供者
3
+ */
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import os from 'os';
6
+ import { execSync } from 'child_process';
7
+ import { createHash } from 'crypto';
8
+ import type { DeviceInfoProvider } from './base.js';
9
+ import type { DeviceType } from '../../../types.js';
10
+
11
+ export class LinuxDeviceInfoProvider implements DeviceInfoProvider {
12
+ /**
13
+ * 执行 shell 命令并获取输出
14
+ */
15
+ private execCommand(command: string): string {
16
+ try {
17
+ return execSync(command, { encoding: 'utf-8' }).trim();
18
+ } catch {
19
+ return '';
20
+ }
21
+ }
22
+
23
+ /**
24
+ * 获取 Linux 设备唯一ID
25
+ */
26
+ private getLinuxDeviceId(): string {
27
+ try {
28
+ // 尝试获取机器ID(systemd-based systems)
29
+ const machineId = this.execCommand('cat /etc/machine-id 2>/dev/null');
30
+ if (machineId) {
31
+ return machineId;
32
+ }
33
+
34
+ // 尝试获取 D-Bus 机器ID
35
+ const dbusMachineId = this.execCommand('cat /var/lib/dbus/machine-id 2>/dev/null');
36
+ if (dbusMachineId) {
37
+ return dbusMachineId;
38
+ }
39
+
40
+ // 尝试获取主板序列号
41
+ const serial = this.execCommand('sudo dmidecode -s system-serial-number 2>/dev/null');
42
+ if (serial && serial !== 'Not Specified') {
43
+ return serial;
44
+ }
45
+
46
+ // 获取 CPU 信息作为备选
47
+ const cpuInfo = this.execCommand('cat /proc/cpuinfo | grep "serial" | head -1 | cut -d":" -f2 | tr -d " "');
48
+ if (cpuInfo) {
49
+ return cpuInfo;
50
+ }
51
+
52
+ // 最后备用方案:使用主机名和架构生成哈希
53
+ const hostname = os.hostname();
54
+ const arch = os.arch();
55
+ const raw = `${hostname}_${arch}_${os.platform()}`;
56
+ return createHash('sha256').update(raw, 'utf-8').digest('hex');
57
+ } catch {
58
+ return uuidv4();
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 获取 Linux 设备型号
64
+ */
65
+ private getLinuxDeviceModel(): string {
66
+ try {
67
+ // 尝试获取产品名称
68
+ const productName = this.execCommand('sudo dmidecode -s system-product-name 2>/dev/null');
69
+ if (productName && productName !== 'Not Specified') {
70
+ return productName;
71
+ }
72
+
73
+ // 尝试获取主板信息
74
+ const boardName = this.execCommand('sudo dmidecode -s baseboard-product-name 2>/dev/null');
75
+ if (boardName && boardName !== 'Not Specified') {
76
+ return boardName;
77
+ }
78
+
79
+ // 尝试从 /sys/class/dmi/id 获取信息
80
+ const sysProductName = this.execCommand('cat /sys/class/dmi/id/product_name 2>/dev/null');
81
+ if (sysProductName) {
82
+ return sysProductName;
83
+ }
84
+
85
+ // 默认返回 Linux PC
86
+ return 'Linux PC';
87
+ } catch {
88
+ return 'Linux PC';
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 获取 Linux 设备名称
94
+ */
95
+ private getLinuxDeviceName(): string {
96
+ const hostname = os.hostname();
97
+ const model = this.getLinuxDeviceModel();
98
+ return `${hostname} (${model})`;
99
+ }
100
+
101
+ /**
102
+ * 获取设备唯一ID
103
+ */
104
+ getDeviceId(): string {
105
+ return this.getLinuxDeviceId();
106
+ }
107
+
108
+ /**
109
+ * 获取设备名称
110
+ */
111
+ getDeviceName(): string {
112
+ return this.getLinuxDeviceName();
113
+ }
114
+
115
+ /**
116
+ * 获取设备型号
117
+ */
118
+ getDeviceModel(): string {
119
+ return this.getLinuxDeviceModel();
120
+ }
121
+
122
+ /**
123
+ * 获取设备类型
124
+ */
125
+ getDeviceType(): DeviceType {
126
+ return 'pc';
127
+ }
128
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * macOS 设备信息提供者
3
+ */
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import os from 'os';
6
+ import { execSync } from 'child_process';
7
+ import { createHash } from 'crypto';
8
+ import type { DeviceInfoProvider } from './base.js';
9
+ import type { DeviceType } from '../../../types.js';
10
+
11
+ export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
12
+ /**
13
+ * 执行 shell 命令并获取输出
14
+ */
15
+ private execCommand(command: string): string {
16
+ try {
17
+ return execSync(command, { encoding: 'utf-8' }).trim();
18
+ } catch {
19
+ return '';
20
+ }
21
+ }
22
+
23
+ /**
24
+ * 获取 macOS 设备唯一ID
25
+ */
26
+ private getMacOSDeviceId(): string {
27
+ try {
28
+ // 获取硬件UUID(最可靠的macOS设备标识)
29
+ const hardwareUuid = this.execCommand('system_profiler SPHardwareDataType | grep "Hardware UUID" | cut -d ":" -f2 | tr -d " "');
30
+ if (hardwareUuid) {
31
+ return hardwareUuid;
32
+ }
33
+
34
+ // 获取序列号
35
+ const serialNumber = this.execCommand('system_profiler SPHardwareDataType | grep "Serial Number" | cut -d ":" -f2 | tr -d " "');
36
+ if (serialNumber) {
37
+ return serialNumber;
38
+ }
39
+
40
+ // 获取主板序列号
41
+ const boardSerial = this.execCommand('system_profiler SPHardwareDataType | grep "Board ID" | cut -d ":" -f2 | tr -d " "');
42
+ if (boardSerial) {
43
+ return boardSerial;
44
+ }
45
+
46
+ // 最后备用方案:使用主机名和架构生成哈希
47
+ const hostname = os.hostname();
48
+ const arch = os.arch();
49
+ const raw = `${hostname}_${arch}_macOS`;
50
+ return createHash('sha256').update(raw, 'utf-8').digest('hex');
51
+ } catch {
52
+ return uuidv4();
53
+ }
54
+ }
55
+
56
+ /**
57
+ * 获取 macOS 设备型号
58
+ */
59
+ private getMacOSDeviceModel(): string {
60
+ try {
61
+ // 获取硬件型号
62
+ const modelName = this.execCommand('system_profiler SPHardwareDataType | grep "Model Name" | cut -d ":" -f2 | sed "s/^ //"');
63
+ if (modelName) {
64
+ return modelName;
65
+ }
66
+
67
+ // 获取机型标识符
68
+ const modelIdentifier = this.execCommand('system_profiler SPHardwareDataType | grep "Model Identifier" | cut -d ":" -f2 | tr -d " "');
69
+ if (modelIdentifier) {
70
+ return modelIdentifier;
71
+ }
72
+
73
+ // 默认返回 Mac
74
+ return 'Mac';
75
+ } catch {
76
+ return 'Mac';
77
+ }
78
+ }
79
+
80
+ /**
81
+ * 获取 macOS 设备名称
82
+ */
83
+ private getMacOSDeviceName(): string {
84
+ const hostname = os.hostname();
85
+ const model = this.getMacOSDeviceModel();
86
+ return `${hostname} (${model})`;
87
+ }
88
+
89
+ /**
90
+ * 获取设备唯一ID
91
+ */
92
+ getDeviceId(): string {
93
+ return this.getMacOSDeviceId();
94
+ }
95
+
96
+ /**
97
+ * 获取设备名称
98
+ */
99
+ getDeviceName(): string {
100
+ return this.getMacOSDeviceName();
101
+ }
102
+
103
+ /**
104
+ * 获取设备型号
105
+ */
106
+ getDeviceModel(): string {
107
+ return this.getMacOSDeviceModel();
108
+ }
109
+
110
+ /**
111
+ * 获取设备类型
112
+ */
113
+ getDeviceType(): DeviceType {
114
+ return 'pc';
115
+ }
116
+ }
@@ -67,22 +67,6 @@ export class PadDevice implements DeviceInfoProvider {
67
67
  return this.getAndroidDeviceModel();
68
68
  }
69
69
 
70
- /**
71
- * 获取 Android 品牌
72
- */
73
- private getAndroidBrand(): string {
74
- const brand = this.getAndroidProp('ro.product.brand');
75
- return brand || 'Android';
76
- }
77
-
78
- /**
79
- * 获取 Android 系统版本
80
- */
81
- private getAndroidSysVersion(): string {
82
- const version = this.getAndroidProp('ro.honor.build.display.id');
83
- return version || 'Android';
84
- }
85
-
86
70
  /**
87
71
  * 获取设备唯一ID
88
72
  */
@@ -1,7 +1,7 @@
1
1
  import { createClawCloudClient } from '../../apis/claw-cloud.js';
2
2
  import { isOKResponse } from '../../apis/index.js';
3
3
  import type { DeviceInfo, HonorUserInfo } from '../../types.js';
4
- import { getConfigManager } from '../claw-configs/index.js';
4
+ import { getConfigManager, takeApiHost } from '../claw-configs/index.js';
5
5
 
6
6
  /**
7
7
  * 注册设备到 Claw Cloud
@@ -17,7 +17,8 @@ export async function registerDevice(deviceInfo: DeviceInfo, userInfo: HonorUser
17
17
  const gatewayAuthConfig = configManager.getGatewayAuthConfig();
18
18
 
19
19
  // 创建 Claw Cloud 客户端
20
- const baseUrl = 'http://omni-pre-drcn.hiboard.hihonorcloud.com/aicloud/yoyo-claw-service';
20
+ const hosts = takeApiHost();
21
+ const baseUrl = `https://${hosts.clawCloud}/aicloud/yoyo-claw-service`;
21
22
  const client = createClawCloudClient(baseUrl);
22
23
 
23
24
  // 调用注册接口,传入认证信息
package/src/runtime.ts CHANGED
@@ -17,7 +17,7 @@ export function getYoyoRuntime(): PluginRuntime {
17
17
  /**
18
18
  * Yoyo 环境类型
19
19
  */
20
- export type YoyoEnv = "test" | "production";
20
+ export type YoyoEnv = "test" | "production" | "dev";
21
21
 
22
22
  /**
23
23
  * 获取当前运行环境
@@ -29,6 +29,6 @@ export function getYoyoEnv(): YoyoEnv {
29
29
  return configManager.getEnv();
30
30
  } catch (error) {
31
31
  console.error(`[runtime] Failed to get env from config: ${error}`);
32
- return "test"; // 默认返回测试环境
32
+ return "production"; // 默认返回生产环境
33
33
  }
34
34
  }
package/src/schemas.ts CHANGED
@@ -17,5 +17,5 @@ const UserInfoSchema = z.object({
17
17
  export const YoyoPluginConfigSchema = z.object({
18
18
  user: UserInfoSchema.optional(),
19
19
  /** 运行环境:test 或 production */
20
- env: z.enum(['test', 'production']).optional(),
20
+ env: z.enum(['dev', 'test', 'production']).optional(),
21
21
  });