@honor-claw/yoyo 1.2.0-beta.9 → 1.2.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honor-claw/yoyo",
3
- "version": "1.2.0-beta.9",
3
+ "version": "1.2.0",
4
4
  "description": "OpenClaw Honor Yoyo connection plugin",
5
5
  "keywords": [
6
6
  "ai",
@@ -47,6 +47,7 @@ alarm.create
47
47
  │ - realRepeatType = 5(指定日期) 时,必须设置 specialTimeMillis │
48
48
  │ - realRepeatType = 9(大小周) 时,必须设置 bigWeek │
49
49
  │ - 校验时间格式是否为 THH:mm:ss │
50
+ | - 指代日期(如:明天、后天)的具体日期推理 → specialTimeMillis(时间戳)|
50
51
  └─────────────────────────────────────────────────────────────┘
51
52
 
52
53
  ┌─────────────────────────────────────────────────────────────┐
@@ -73,6 +74,7 @@ alarm.create
73
74
  | **创建指定日期闹钟** | 2026年3月20日早上八点的闹钟 | `time`, `realRepeatType: 5`, `specialTimeMillis` | - |
74
75
  | **创建节假日跳过闹钟** | 工作日闹钟,跳过节假日 | `time`, `realRepeatType: 1`, `skipHoliday: true` | - |
75
76
  | **创建大小周闹钟** | 大小周工作日上午九点的闹钟 | `time`, `realRepeatType: 9`, `bigWeek` | - |
77
+ | **创建指代日期闹钟** | 明天早上九点的闹钟 | `time`, `specialTimeMillis` | - |
76
78
 
77
79
  ## 参数定义
78
80
 
@@ -471,6 +473,41 @@ cmd /c 'openclaw nodes invoke --node <ID> --command alarm.create --params "{\"ti
471
473
  openclaw nodes invoke --node <ID> --command alarm.create --params '{"time":"T08:30:00","realRepeatType":10,"daysOfWeek":0,"specialTimeMillis":0,"skipHoliday":false}'
472
474
  ```
473
475
 
476
+ ---
477
+
478
+ ### 示例 12: 创建指代日期闹钟
479
+
480
+ **用户输入**: "创建明天早上八点的闹钟"
481
+ **处理逻辑**:
482
+ 1. 获取当前的日期,以“2026年4月15日12:09:57”为例,当前日期为 2026-04-15 12:09:57 对应时间戳: 1776226197000
483
+ 2. 根据用户输入推理闹钟时间为 2026-04-16 08:00:00,将时间转换为时间戳: 1776297600000
484
+ 3. `realRepeatType` 为 5,即为指定日期闹钟
485
+
486
+ **JSON 参数**:
487
+
488
+ ```json
489
+ {
490
+ "time": "T08:00:00",
491
+ "realRepeatType": 5,
492
+ "daysOfWeek": 0,
493
+ "specialTimeMillis": 1776297600000,
494
+ "skipHoliday": false
495
+ }
496
+ ```
497
+
498
+ **Windows (Cmd) 执行命令**:
499
+
500
+ ```bash
501
+ cmd /c 'openclaw nodes invoke --node <ID> --command alarm.create --params "{\"time\":\"T08:00:00\",\"realRepeatType\":5,\"daysOfWeek\":0,\"specialTimeMillis\":1776297600000,\"skipHoliday\":false}"'
502
+
503
+ ```
504
+
505
+ **Linux (Bash) 执行命令**:
506
+
507
+ ```bash
508
+ openclaw nodes invoke --node <ID> --command alarm.create --params '{"time":"T08:00:00","realRepeatType":5,"daysOfWeek":0,"specialTimeMillis":1776297600000,"skipHoliday":false}'
509
+ ```
510
+
474
511
  ## 错误处理
475
512
 
476
513
  如果工具执行错误(300),可能原因如下:
@@ -26,7 +26,6 @@ export class ClawCloudSocketClient {
26
26
  private retryTimer: NodeJS.Timeout | null = null;
27
27
  private isManualClose = false;
28
28
  private isRetryPaused = false; // 重试状态
29
- private hasRetried = false;
30
29
  // ping/pong 配置
31
30
  private pingTimer: NodeJS.Timeout | null = null;
32
31
  // 当前连接上下文
@@ -76,7 +75,6 @@ export class ClawCloudSocketClient {
76
75
  this.isManualClose = false;
77
76
  this.retryCount = 0;
78
77
  this.isRetryPaused = false;
79
- this.hasRetried = false;
80
78
 
81
79
  this.startPingTimer();
82
80
 
@@ -257,25 +255,7 @@ export class ClawCloudSocketClient {
257
255
 
258
256
  this.clearRetryTimer();
259
257
 
260
- // 首次重试立即执行,不等待
261
- if (!this.hasRetried) {
262
- this.hasRetried = true;
263
- useClawLogger().info(`[claw-cloud-socket] first retry, reconnecting immediately`);
264
-
265
- this.options.onStatusEvent?.({
266
- type: StatusEventType.CLOUD_SOCKET_RETRY,
267
- timestamp: Date.now(),
268
- data: {
269
- retryCount: this.retryCount,
270
- delay: 0,
271
- },
272
- });
273
-
274
- this.connect(true);
275
- return;
276
- }
277
-
278
- // 后续重试使用指数退避: 1s → 2s → 4s,封顶4s无限重试
258
+ // 使用指数退避重试: 1s → 2s → 4s,封顶4s无限重试
279
259
  const delay = this.calculateRetryDelay();
280
260
  this.retryCount = Math.min(this.retryCount + 1, RETRY_COUNT_CAP);
281
261
 
@@ -365,7 +345,6 @@ export class ClawCloudSocketClient {
365
345
 
366
346
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
367
347
  this.retryCount = 0;
368
- this.hasRetried = false;
369
348
  this.connect(true);
370
349
  }
371
350
  }
@@ -8,6 +8,7 @@ import {
8
8
  type DeviceSessionInfo,
9
9
  type YOYOClawServiceContext,
10
10
  type StatusEvent,
11
+ type RawGatewayMessage,
11
12
  StatusEventType,
12
13
  } from "./types.js";
13
14
 
@@ -358,7 +359,7 @@ export class MessageHandler {
358
359
  const targetDeviceId = sessionInfo.sourceInfo.sourceDeviceId;
359
360
 
360
361
  // 收到NOT_PAIRED错误时,自动获取待配对设备并审批
361
- let rawData: Record<string, any>;
362
+ let rawData: RawGatewayMessage;
362
363
  try {
363
364
  rawData = JSON.parse(data);
364
365
  } catch {
@@ -367,18 +368,16 @@ export class MessageHandler {
367
368
  }
368
369
 
369
370
  if (!rawData.ok && rawData.error?.code === "NOT_PAIRED") {
370
- const requestId = rawData.error?.details?.requestId;
371
+ const requestId = rawData.error?.details?.requestId as string | undefined;
371
372
  if (!requestId) {
372
373
  useClawLogger().warn(`${LOG_PREFIX} NOT_PAIRED without requestId, ignoring...`);
373
374
  return;
374
375
  }
375
- const success = await this.handleAutoPair(requestId);
376
- if (success) {
377
- const bizExtInfo = { id: rawData.id, type: "reconnect-required" };
378
- this.sendMessage("deviceControl", targetDeviceId, traceInfo, "", undefined, bizExtInfo);
379
- useClawLogger().info(`${LOG_PREFIX} auto pair is completed, requestId: ${requestId}`);
380
- return;
381
- }
376
+ await this.handleAutoPair(requestId);
377
+ const bizExtInfo = { id: rawData.id, type: "reconnect-required" };
378
+ this.sendMessage("deviceControl", targetDeviceId, traceInfo, "", undefined, bizExtInfo);
379
+ useClawLogger().info(`${LOG_PREFIX} auto pair is completed, requestId: ${requestId}`);
380
+ return;
382
381
  }
383
382
 
384
383
  useClawLogger().debug?.(
@@ -398,7 +397,7 @@ export class MessageHandler {
398
397
  }
399
398
  };
400
399
 
401
- private async handleAutoPair(requestId: string): Promise<boolean> {
400
+ private async handleAutoPair(requestId: string) {
402
401
  const adminClient = this.adminClientManager.getClient();
403
402
  if (!adminClient) {
404
403
  useClawLogger().warn(`${LOG_PREFIX} admin client not available for auto pair`);
@@ -408,12 +407,10 @@ export class MessageHandler {
408
407
  try {
409
408
  useClawLogger().info(`${LOG_PREFIX} auto pair approve, requestId: ${requestId}`);
410
409
  await adminClient.devicePairApprove(requestId);
411
- return true;
412
410
  } catch (error) {
413
411
  useClawLogger().error(
414
- `${LOG_PREFIX} auto pair approve failed, requestId: ${requestId}, error: ${String(error)}`,
412
+ `${LOG_PREFIX} auto pair approve failed, requestId: ${requestId}, error: ${String(error)}, ignore...`,
415
413
  );
416
- return false;
417
414
  }
418
415
  }
419
416
 
@@ -42,7 +42,7 @@ export interface YOYOClawServiceEvent {
42
42
  /**
43
43
  * 业务扩展信息
44
44
  */
45
- bizExtInfo?: Record<string, any>;
45
+ bizExtInfo?: Record<string, unknown>;
46
46
  }
47
47
 
48
48
  /**
@@ -126,3 +126,18 @@ export interface DeviceSessionInfo {
126
126
  timestamp: number;
127
127
  sourceInfo: ClawSocketSourceInfo;
128
128
  }
129
+
130
+ /**
131
+ * 通用网关原始消息结构
132
+ * 用于 JSON.parse 后的数据结构,允许任意扩展字段
133
+ */
134
+ export interface RawGatewayMessage {
135
+ ok?: boolean;
136
+ id?: string;
137
+ error?: {
138
+ code?: string;
139
+ message?: string;
140
+ details?: Record<string, unknown>;
141
+ };
142
+ [key: string]: unknown;
143
+ }
@@ -40,6 +40,31 @@ function getRegistryStringValueAsync(
40
40
  });
41
41
  }
42
42
 
43
+ /**
44
+ * 异步检查注册表键是否存在
45
+ * 使用 winreg 的 values() 方法,键不存在时会报错
46
+ */
47
+ function registryKeyExistsAsync(hive: number, keyPath: string): Promise<boolean> {
48
+ return new Promise((resolve) => {
49
+ try {
50
+ const regKey = new Registry({
51
+ hive,
52
+ key: keyPath,
53
+ });
54
+
55
+ regKey.values((err: Error | null) => {
56
+ if (err) {
57
+ resolve(false);
58
+ } else {
59
+ resolve(true);
60
+ }
61
+ });
62
+ } catch {
63
+ resolve(false);
64
+ }
65
+ });
66
+ }
67
+
43
68
  export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
44
69
  private cache: DeviceInfoCache = {
45
70
  deviceId: "",
@@ -111,11 +136,17 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
111
136
  systemManufacturer2,
112
137
  ];
113
138
 
114
- const manufacturer = manufacturerSources.find((m) => m && m.trim()) || "";
115
- if (manufacturer.toLowerCase().includes("honor")) {
139
+ const manufacturer = manufacturerSources.find((m) => m && m.toLowerCase().includes("honor"));
140
+ if (manufacturer) {
116
141
  this.cache.deviceBrand = "HONOR";
117
142
  } else {
118
- this.cache.deviceBrand = "";
143
+ // 最后兜底:检查 HKLM:\SOFTWARE\HONOR 注册表键是否存在
144
+ const honorKeyExists = await registryKeyExistsAsync(Registry.HKLM, "\\SOFTWARE\\HONOR");
145
+ if (honorKeyExists) {
146
+ this.cache.deviceBrand = "HONOR";
147
+ } else {
148
+ this.cache.deviceBrand = "";
149
+ }
119
150
  }
120
151
  } catch {
121
152
  // 初始化失败,使用默认值