@honor-claw/yoyo 1.2.1-beta.0 → 1.2.1-beta.2

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/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { type ZodType } from "zod";
2
3
  import { registerCommands } from "./src/commands/index.js";
3
4
  import { AGENT_PROMPT } from "./src/modules/prompt/index.js";
4
5
  import { setYoyoRuntime } from "./src/runtime.js";
@@ -10,7 +11,7 @@ const plugin = {
10
11
  id: "yoyo",
11
12
  name: "YOYOClaw",
12
13
  description: "OpenClaw Honor Yoyo connection plugin",
13
- configSchema: YoyoPluginConfigSchema,
14
+ configSchema: YoyoPluginConfigSchema as ZodType,
14
15
  register(api: OpenClawPluginApi) {
15
16
  setYoyoRuntime(api.runtime);
16
17
  setClawLogger(api.logger);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honor-claw/yoyo",
3
- "version": "1.2.1-beta.0",
3
+ "version": "1.2.1-beta.2",
4
4
  "description": "OpenClaw Honor Yoyo connection plugin",
5
5
  "keywords": [
6
6
  "ai",
@@ -31,7 +31,6 @@
31
31
  "test:unit:watch": "vitest"
32
32
  },
33
33
  "dependencies": {
34
- "@honor-claw/safe-exec": "0.0.1",
35
34
  "http-proxy-agent": "^8.0.0",
36
35
  "https-proxy-agent": "^8.0.0",
37
36
  "jsonwebtoken": "^9.0.3",
@@ -48,7 +47,7 @@
48
47
  "@types/ws": "^8.5.13",
49
48
  "@vitest/coverage-v8": "^2.1.8",
50
49
  "commander": "^14.0.3",
51
- "typescript": "^5.7.0",
50
+ "typescript": "^6.0.2",
52
51
  "vitest": "^2.1.8"
53
52
  },
54
53
  "openclaw": {
@@ -26,7 +26,7 @@ export class ClawCloudSocketClient {
26
26
  private retryTimer: NodeJS.Timeout | null = null;
27
27
  private isManualClose = false;
28
28
  private isRetryPaused = false; // 重试状态
29
- // ping/pong 配置
29
+ // ping 定时器(原生 ping + 业务 pingMessage)
30
30
  private pingTimer: NodeJS.Timeout | null = null;
31
31
  // 当前连接上下文
32
32
  private currentTraceId = "";
@@ -59,7 +59,6 @@ export class ClawCloudSocketClient {
59
59
  this.ws = new WebSocket(url, wsOptions);
60
60
  this.ws.on("open", this.handleOpen.bind(this, url, isRetry));
61
61
  this.ws.on("message", this.onMessage);
62
- this.ws.on("ping", this.handlePing);
63
62
  this.ws.on("pong", this.handlePong);
64
63
  this.ws.on("close", this.handleClose);
65
64
  this.ws.on("error", this.handleError);
@@ -90,10 +89,6 @@ export class ClawCloudSocketClient {
90
89
  this.options.onOpen?.();
91
90
  };
92
91
 
93
- private handlePing = (): void => {
94
- useClawLogger().debug?.("[claw-cloud-socket] received ping from server");
95
- };
96
-
97
92
  private handlePong = (): void => {
98
93
  useClawLogger().debug?.("[claw-cloud-socket] received pong from server");
99
94
  };
@@ -299,7 +294,7 @@ export class ClawCloudSocketClient {
299
294
  }
300
295
 
301
296
  /**
302
- * 启动 ping 定时器
297
+ * 启动 ping 定时器,同时发送原生 ping 和业务 pingMessage
303
298
  */
304
299
  private startPingTimer(): void {
305
300
  this.pingTimer = setInterval(() => {
@@ -308,9 +303,26 @@ export class ClawCloudSocketClient {
308
303
  return;
309
304
  }
310
305
 
311
- // 发送 ping
306
+ // 原生 ping
312
307
  this.ws.ping();
313
308
  useClawLogger().debug?.("[claw-cloud-socket] sent ping to server");
309
+
310
+ // 业务 pingMessage
311
+ const { deviceInfo } = this.options;
312
+ const pingMessage: YOYOClawServiceEvent = {
313
+ msgType: "pingMessage",
314
+ sourceRole: "yoyoclaw",
315
+ sourceDeviceId: deviceInfo.deviceId,
316
+ targetRole: "node",
317
+ port: deviceInfo.port,
318
+ };
319
+
320
+ try {
321
+ this.ws.send(JSON.stringify(pingMessage));
322
+ useClawLogger().debug?.("[claw-cloud-socket] sent pingMessage to server");
323
+ } catch {
324
+ useClawLogger().error("[claw-cloud-socket] failed to send pingMessage");
325
+ }
314
326
  }, PING_INTERVAL);
315
327
  }
316
328
 
@@ -18,8 +18,8 @@ export interface YOYOClawServiceEvent {
18
18
  sourceDeviceId: string;
19
19
  sourceDeviceInfo?: ClawDeviceInfo;
20
20
  targetRole?: DeviceRole;
21
- targetDeviceId: string;
22
- traceInfo: object;
21
+ targetDeviceId?: string;
22
+ traceInfo?: object;
23
23
  port: number | string;
24
24
  data?: string; // openclaw原生消息
25
25
  msgType:
@@ -28,7 +28,8 @@ export interface YOYOClawServiceEvent {
28
28
  | "deviceUnPairMessage"
29
29
  | "fetchContexts"
30
30
  | "updateContexts"
31
- | "deviceControl";
31
+ | "deviceControl"
32
+ | "pingMessage";
32
33
  /**
33
34
  * 会话轮次等追加信息,只有配对消息有
34
35
  */
@@ -1,7 +1,6 @@
1
1
  import { type Command } from "commander";
2
2
  import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
3
3
  import { performLogout } from "../../honor-auth/cloud.js";
4
- import { upgradeIdentityToV2 } from "../../modules/configs/identity-persist.js";
5
4
 
6
5
  export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
7
6
  const nextCommand = command
@@ -13,12 +12,6 @@ export function registerLogoutCommand(api: OpenClawPluginApi, command: Command)
13
12
  try {
14
13
  await performLogout();
15
14
 
16
- // 升级 Identity 配置到 v2 版本
17
- const upgraded = await upgradeIdentityToV2();
18
- if (upgraded) {
19
- api.logger.info("Identity upgraded from v1 to v2 during logout");
20
- }
21
-
22
15
  console.log(
23
16
  "✅ Logout successful, gateway will automatically restart to handle new configuration",
24
17
  );
@@ -0,0 +1,51 @@
1
+ export const AUTH_RESULT_HTML = `<!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>授权结果</title>
7
+ <style>
8
+ body {
9
+ font-family:
10
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
11
+ background-color: #f0f2f5;
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ height: 100vh;
16
+ margin: 0;
17
+ color: #333;
18
+ }
19
+ .container {
20
+ text-align: center;
21
+ background-color: #ffffff;
22
+ padding: 48px 40px;
23
+ border-radius: 16px;
24
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
25
+ max-width: 380px;
26
+ width: 90%;
27
+ }
28
+ .icon {
29
+ margin-bottom: 24px;
30
+ }
31
+ h1 {
32
+ font-size: 20px;
33
+ font-weight: 600;
34
+ margin: 0 0 12px;
35
+ color: #1a1a1a;
36
+ }
37
+ .hint {
38
+ font-size: 14px;
39
+ color: #999;
40
+ margin: 0;
41
+ }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <div class="container">
46
+ <div class="icon">{{ICON_SVG}}</div>
47
+ <h1>{{MESSAGE}}</h1>
48
+ <p class="hint">可安全关闭此页面</p>
49
+ </div>
50
+ </body>
51
+ </html>`;
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { createServer, type IncomingMessage, type ServerResponse } from "http";
6
6
  import { URL } from "url";
7
+ import { AUTH_RESULT_HTML } from "./auth-result-html.js";
7
8
 
8
9
  /**
9
10
  * 回调服务器选项
@@ -19,6 +20,15 @@ export interface CallbackServerOptions {
19
20
  onError?: (error: Error) => void;
20
21
  }
21
22
 
23
+ const SUCCESS_ICON_SVG = `<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M28 56C43.464 56 56 43.464 56 28C56 12.536 43.464 0 28 0C12.536 0 0 12.536 0 28C0 43.464 12.536 56 28 56Z" fill="#28a745"/><path d="M39.6667 20.3333L24.5 35.5L16.3333 27.3333" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
24
+ const FAILURE_ICON_SVG = `<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M28 56C43.464 56 56 43.464 56 28C56 12.536 43.464 0 28 0C12.536 0 0 12.536 0 28C0 43.464 12.536 56 28 56Z" fill="#dc3545"/><path d="M35 21L21 35M21 21L35 35" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
25
+
26
+
27
+ function getResultHtml(message: string, success: boolean): string {
28
+ const icon = success ? SUCCESS_ICON_SVG : FAILURE_ICON_SVG;
29
+ return AUTH_RESULT_HTML.replace("{{ICON_SVG}}", icon).replace("{{MESSAGE}}", message);
30
+ }
31
+
22
32
  /**
23
33
  * 启动本地回调服务器
24
34
  */
@@ -40,13 +50,15 @@ export function startCallbackServer(options: CallbackServerOptions): Promise<voi
40
50
 
41
51
  // 返回成功响应
42
52
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
43
- res.end("<h1>授权成功!可以关闭此窗口</h1>");
53
+ res.end(getResultHtml("授权成功", true));
44
54
 
45
55
  // 延迟关闭服务器,确保响应已发送
46
56
  setTimeout(() => {
47
57
  if (!serverClosed) {
48
58
  serverClosed = true;
49
- if (timeoutId) clearTimeout(timeoutId);
59
+ if (timeoutId) {
60
+ clearTimeout(timeoutId);
61
+ }
50
62
  server.close();
51
63
  }
52
64
  }, 100);
@@ -56,23 +68,25 @@ export function startCallbackServer(options: CallbackServerOptions): Promise<voi
56
68
  } else if (!hasReceivedCode) {
57
69
  // 返回错误响应
58
70
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
59
- res.end("<h1>授权失败:未获取到授权码</h1>");
71
+ res.end(getResultHtml("授权失败:未获取到授权码", false));
60
72
  } else {
61
73
  // 重复请求,返回成功响应但不处理
62
74
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
63
- res.end("<h1>授权成功!可以关闭此窗口</h1>");
75
+ res.end(getResultHtml("授权成功", true));
64
76
  }
65
77
  } catch (error) {
66
78
  console.error("authorize callback error:", error);
67
79
  res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
68
- res.end("<h1>服务器错误</h1>");
80
+ res.end(getResultHtml("授权失败:服务器内部错误", false));
69
81
  }
70
82
  });
71
83
 
72
84
  server.on("error", (error) => {
73
85
  if (!serverClosed) {
74
86
  serverClosed = true;
75
- if (timeoutId) clearTimeout(timeoutId);
87
+ if (timeoutId) {
88
+ clearTimeout(timeoutId);
89
+ }
76
90
  onError?.(error);
77
91
  if (!promiseResolved) {
78
92
  promiseResolved = true;
@@ -99,7 +113,9 @@ export function startCallbackServer(options: CallbackServerOptions): Promise<voi
99
113
 
100
114
  // 服务器关闭时解析Promise
101
115
  server.on("close", () => {
102
- if (timeoutId) clearTimeout(timeoutId);
116
+ if (timeoutId) {
117
+ clearTimeout(timeoutId);
118
+ }
103
119
  if (!promiseResolved) {
104
120
  promiseResolved = true;
105
121
  resolve();
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { createClawCloudClient } from "../apis/claw-cloud.js";
6
- import { upgradeIdentityToV2 } from "../modules/configs/identity-persist.js";
7
6
  import { getConfigManager } from "../modules/configs/index.js";
8
7
  import { getDeviceInfo } from "../modules/device/index.js";
9
8
  import type { HonorUserInfo } from "../types.js";
@@ -63,14 +62,7 @@ export async function saveToken(
63
62
  /**
64
63
  * 刷新并保存Token
65
64
  */
66
- async function refreshToken(
67
- userId: string,
68
- options?: { upgradeIdentity?: boolean },
69
- ): Promise<HonorUserInfo | null> {
70
- if (options?.upgradeIdentity) {
71
- await upgradeIdentityToV2();
72
- }
73
-
65
+ async function refreshToken(userId: string): Promise<HonorUserInfo | null> {
74
66
  const deviceInfo = await getDeviceInfo();
75
67
  const client = createClawCloudClient();
76
68
  const tokenInfo = await client.exchangeToken(deviceInfo, { userId });
@@ -108,7 +100,7 @@ export async function loadToken(): Promise<HonorUserInfo | null> {
108
100
 
109
101
  // 配置有 userId,优先使用 env userId 换取 token
110
102
  if (userConfig?.userId) {
111
- return await refreshToken(envUserId || userConfig.userId, { upgradeIdentity: !!envUserId });
103
+ return await refreshToken(envUserId || userConfig.userId);
112
104
  }
113
105
 
114
106
  // 有 token 且未过期,直接返回
@@ -122,7 +114,7 @@ export async function loadToken(): Promise<HonorUserInfo | null> {
122
114
  // 没有token或者已经过期,拿envUserId重新获取
123
115
  if (envUserId) {
124
116
  logger.debug?.("[yoyoclaw-auth] token expired, using env userId to exchange token");
125
- return await refreshToken(envUserId, { upgradeIdentity: true });
117
+ return await refreshToken(envUserId);
126
118
  }
127
119
 
128
120
  // 没有可用的鉴权信息且token过期了,清除token信息
@@ -17,9 +17,9 @@ export enum IdentityVersion {
17
17
  * 完整的设备 Identity 类型
18
18
  */
19
19
  export interface PersistedIdentity {
20
- /** 新版本 deviceId (基于公钥指纹) */
20
+ /** deviceId (基于公钥指纹) */
21
21
  deviceId?: string;
22
- /** 旧版本 deviceId (基于硬件信息) */
22
+ /** 旧版本 deviceId (v1 缓存兼容读取,不再生成) */
23
23
  legacyDeviceId?: string;
24
24
  publicKeyPem?: string;
25
25
  privateKeyPem?: string;
@@ -112,7 +112,7 @@ export async function updatePersistedIdentity(
112
112
  // 合并配置
113
113
  const updatedConfig: PersistedIdentity = {
114
114
  ...existingConfig,
115
- version: existingConfig?.version || IdentityVersion.LEGACY,
115
+ version: existingConfig?.version || IdentityVersion.NEW,
116
116
  };
117
117
 
118
118
  if (identity.legacyDeviceId) {
@@ -148,46 +148,6 @@ export async function updatePersistedIdentity(
148
148
  */
149
149
  function createDefaultConfig(): PersistedIdentity {
150
150
  return {
151
- version: IdentityVersion.LEGACY,
152
- };
153
- }
154
-
155
- /**
156
- * 升级 Identity 配置到 v2 版本
157
- * - 如果已经是 v2,不处理
158
- * - 如果是 v1,升级到 v2 并移除 legacyDeviceId
159
- * @returns 升级后的配置,如果已经是 v2 则返回 null
160
- */
161
- export async function upgradeIdentityToV2(): Promise<PersistedIdentity | null> {
162
- const homeDir = resolveEffectiveHomeDir();
163
- if (!homeDir) {
164
- useClawLogger().warn("[yoyo-identity] failed to find home dir");
165
- return null;
166
- }
167
-
168
- // 读取现有配置
169
- const existingConfig = await loadConfig(homeDir);
170
-
171
- // 如果已经有配置了,不做处理,这里老版本不删除这个问题就不走缓存咧
172
- if (existingConfig) {
173
- useClawLogger().warn("[yoyo-identity] identity exist, upgrade ignored");
174
- return null;
175
- }
176
-
177
- // 升级到 v2,移除 legacyDeviceId
178
- const upgradedConfig: PersistedIdentity = {
179
151
  version: IdentityVersion.NEW,
180
152
  };
181
-
182
- // 写入升级后的配置
183
- const configPath = getConfigPath();
184
- await safeWriteFile({
185
- rootDir: homeDir,
186
- relativePath: configPath,
187
- data: JSON.stringify(upgradedConfig, null, 2),
188
- encoding: "utf8",
189
- mkdir: true,
190
- });
191
-
192
- return upgradedConfig;
193
153
  }
@@ -19,19 +19,9 @@ export async function getDeviceInfo(): Promise<DeviceInfo> {
19
19
  // 确保 provider 初始化完成
20
20
  await provider.ensureInitialized();
21
21
 
22
- // 获取设备 ID:使用 identity 模块生成的 ID
23
- let deviceId: string;
24
- let deviceIdSource: "persisted" | "generated" = "generated";
25
-
26
- try {
27
- const identity = await loadOrCreateDeviceIdentity();
28
- deviceId = identity.legacyDeviceId ?? identity.deviceId;
29
- deviceIdSource = "persisted";
30
- } catch (error) {
31
- // 如果 identity 获取失败,降级使用 provider 生成的 ID
32
- useClawLogger().warn(`[yoyoclaw-device] failed to use identity: ${String(error)}`);
33
- deviceId = await provider.getDeviceIdAsync();
34
- }
22
+ // 获取设备 ID:优先使用 legacyDeviceId(v1 缓存兼容),否则使用新的 identity deviceId
23
+ const identity = await loadOrCreateDeviceIdentity();
24
+ const deviceId = identity.legacyDeviceId ?? identity.deviceId;
35
25
 
36
26
  // 获取设备信息,优先级: env > config > provider
37
27
  const envVars = getDeviceEnvVars();
@@ -63,7 +53,7 @@ export async function getDeviceInfo(): Promise<DeviceInfo> {
63
53
  // 记录 deviceInfo 整体来源
64
54
  const source = envVars.brand || envVars.deviceType || envVars.manufacture ? "env" : "config";
65
55
  useClawLogger().info(
66
- `[yoyoclaw-device] device info: ${JSON.stringify(deviceInfo)} (deviceId source: ${deviceIdSource}, source: ${source})`,
56
+ `[yoyoclaw-device] device info: ${JSON.stringify(deviceInfo)} (source: ${source})`,
67
57
  );
68
58
 
69
59
  // env 与 config 的差异检测(key 映射: brand, manufacture, deviceType -> type)
@@ -2,10 +2,8 @@ import crypto from "node:crypto";
2
2
  import {
3
3
  getPersistedIdentity,
4
4
  updatePersistedIdentity,
5
- IdentityVersion,
6
5
  type PersistedIdentity,
7
6
  } from "../configs/identity-persist.js";
8
- import { getDeviceInfoProvider } from "./providers/index.js";
9
7
  import type { DeviceIdentity } from "./types.js";
10
8
 
11
9
  /**
@@ -55,33 +53,23 @@ function fingerprintPublicKey(publicKeyPem: string): string {
55
53
  /**
56
54
  * Generate a new device identity with ED25519 key pair
57
55
  */
58
- async function generateIdentity(identity: PersistedIdentity | null): Promise<DeviceIdentity> {
56
+ async function generateIdentity(): Promise<DeviceIdentity> {
59
57
  const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
60
58
  const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
61
59
  const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
62
60
  const deviceId = fingerprintPublicKey(publicKeyPem);
63
61
 
64
- const fullIdentity = {
62
+ return {
65
63
  deviceId,
66
64
  publicKeyPem,
67
65
  privateKeyPem,
68
66
  createdAtMs: Date.now(),
69
- } as DeviceIdentity;
70
-
71
- if (!identity || identity.version === IdentityVersion.LEGACY) {
72
- // 补充legacyDeviceId
73
- const provider = getDeviceInfoProvider();
74
- await provider.ensureInitialized();
75
- fullIdentity.legacyDeviceId = await provider.getDeviceIdAsync();
76
- }
77
-
78
- return fullIdentity;
67
+ };
79
68
  }
80
69
 
81
70
  /**
82
- * Load or create device identity (async version)
83
- * 优先使用 legacyDeviceId (version < 2 的情况),否则使用新的 identity deviceId
84
- * @returns Device identity with optional legacyDeviceId
71
+ * Load or create device identity
72
+ * @returns Device identity
85
73
  */
86
74
  export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
87
75
  // 获取缓存的 identity
@@ -120,7 +108,7 @@ export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
120
108
  }
121
109
 
122
110
  // No existing identity, generate new one
123
- const identity = await generateIdentity(persisted);
111
+ const identity = await generateIdentity();
124
112
  await updatePersistedIdentity(identity);
125
113
 
126
114
  return identity;
@@ -10,11 +10,6 @@ export interface DeviceInfoProvider {
10
10
  */
11
11
  ensureInitialized(): Promise<void>;
12
12
 
13
- /**
14
- * 获取设备唯一ID(异步)
15
- */
16
- getDeviceIdAsync(): Promise<string>;
17
-
18
13
  /**
19
14
  * 获取设备名称
20
15
  */
@@ -1,4 +1,3 @@
1
- import { createHash } from "crypto";
2
1
  import fs from "fs";
3
2
  import os from "os";
4
3
  import type { DeviceType } from "../../../types.js";
@@ -26,53 +25,6 @@ export class LinuxDeviceInfoProvider implements DeviceInfoProvider {
26
25
  // Linux provider 同步执行,无需初始化
27
26
  }
28
27
 
29
- /**
30
- * 获取 Linux 设备唯一ID
31
- */
32
- private getLinuxDeviceId(): string {
33
- // 尝试获取机器ID(systemd-based systems)
34
- const machineId = readFile("/etc/machine-id");
35
- if (machineId) {
36
- return machineId;
37
- }
38
-
39
- // 尝试获取 D-Bus 机器ID
40
- const dbusMachineId = readFile("/var/lib/dbus/machine-id");
41
- if (dbusMachineId) {
42
- return dbusMachineId;
43
- }
44
-
45
- // 尝试读取主板序列号
46
- const boardSerial = readFile("/sys/class/dmi/id/board_serial");
47
- if (boardSerial && boardSerial !== "Not Specified") {
48
- return boardSerial;
49
- }
50
-
51
- // 尝试读取产品序列号
52
- const productSerial = readFile("/sys/class/dmi/id/product_serial");
53
- if (productSerial && productSerial !== "Not Specified") {
54
- return productSerial;
55
- }
56
-
57
- // 尝试从 /proc/cpuinfo 提取序列号
58
- const cpuinfo = readFile("/proc/cpuinfo");
59
- if (cpuinfo) {
60
- const match = cpuinfo.match(/serial\s*:\s*(.+)/i);
61
- if (match && match[1]) {
62
- const cpuSerial = match[1].trim();
63
- if (cpuSerial && cpuSerial !== "0") {
64
- return cpuSerial;
65
- }
66
- }
67
- }
68
-
69
- // 最后备用方案:使用主机名和架构生成哈希
70
- const hostname = os.hostname();
71
- const arch = os.arch();
72
- const raw = `${hostname}_${arch}_${os.platform()}`;
73
- return createHash("sha256").update(raw, "utf-8").digest("hex");
74
- }
75
-
76
28
  /**
77
29
  * 获取 Linux 设备型号
78
30
  */
@@ -101,13 +53,6 @@ export class LinuxDeviceInfoProvider implements DeviceInfoProvider {
101
53
  return `${hostname} (${model})`;
102
54
  }
103
55
 
104
- /**
105
- * 获取设备唯一ID(异步)
106
- */
107
- async getDeviceIdAsync(): Promise<string> {
108
- return this.getLinuxDeviceId();
109
- }
110
-
111
56
  /**
112
57
  * 获取设备名称
113
58
  */
@@ -1,25 +1,7 @@
1
- import { createHash } from "crypto";
2
1
  import os from "os";
3
- import { execCommand } from "@honor-claw/safe-exec";
4
2
  import type { DeviceType } from "../../../types.js";
5
3
  import type { DeviceInfoProvider } from "./base.js";
6
4
 
7
- /**
8
- * 从 system_profiler 输出中提取指定字段值
9
- */
10
- function extractField(output: string, fieldName: string): string {
11
- const lines = output.split("\n");
12
- for (const line of lines) {
13
- if (line.includes(fieldName)) {
14
- const parts = line.split(":");
15
- if (parts.length >= 2) {
16
- return parts.slice(1).join(":").trim();
17
- }
18
- }
19
- }
20
- return "";
21
- }
22
-
23
5
  export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
24
6
  /**
25
7
  * 确保 provider 初始化完成(macOS 无需异步初始化)
@@ -28,47 +10,6 @@ export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
28
10
  // macOS provider 同步执行,无需初始化
29
11
  }
30
12
 
31
- /**
32
- * 获取 macOS 设备唯一ID
33
- */
34
- private async getMacOSDeviceId(): Promise<string> {
35
- // 获取硬件信息
36
- const output = await execCommand("system_profiler", ["SPHardwareDataType"]);
37
- if (!output) {
38
- return this.generateFallbackId();
39
- }
40
-
41
- // 提取硬件 UUID
42
- const hardwareUuid = extractField(output, "Hardware UUID");
43
- if (hardwareUuid) {
44
- return hardwareUuid.replace(/[{}]/g, "");
45
- }
46
-
47
- // 提取序列号
48
- const serialNumber = extractField(output, "Serial Number");
49
- if (serialNumber) {
50
- return serialNumber;
51
- }
52
-
53
- // 提取 Board ID
54
- const boardId = extractField(output, "Board ID");
55
- if (boardId) {
56
- return boardId;
57
- }
58
-
59
- return this.generateFallbackId();
60
- }
61
-
62
- /**
63
- * 生成备用 ID
64
- */
65
- private generateFallbackId(): string {
66
- const hostname = os.hostname();
67
- const arch = os.arch();
68
- const raw = `${hostname}_${arch}_macOS`;
69
- return createHash("sha256").update(raw, "utf-8").digest("hex");
70
- }
71
-
72
13
  /**
73
14
  * 获取 macOS 设备型号
74
15
  */
@@ -87,13 +28,6 @@ export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
87
28
  return `${hostname} (${model})`;
88
29
  }
89
30
 
90
- /**
91
- * 获取设备唯一ID(异步)
92
- */
93
- async getDeviceIdAsync(): Promise<string> {
94
- return this.getMacOSDeviceId();
95
- }
96
-
97
31
  /**
98
32
  * 获取设备名称
99
33
  */
@@ -1,5 +1,4 @@
1
1
  import { execFile } from "child_process";
2
- import { createHash } from "crypto";
3
2
  import fs from "fs";
4
3
  import os from "os";
5
4
  import { promisify } from "util";
@@ -12,11 +11,9 @@ const execFileAsync = promisify(execFile);
12
11
  * 设备信息缓存
13
12
  */
14
13
  interface DeviceInfoCache {
15
- androidId: string;
16
14
  brand: string;
17
15
  model: string;
18
16
  device: string;
19
- deviceId: string;
20
17
  }
21
18
 
22
19
  /**
@@ -24,11 +21,9 @@ interface DeviceInfoCache {
24
21
  */
25
22
  export class PadDeviceInfoProvider implements DeviceInfoProvider {
26
23
  private cache: DeviceInfoCache = {
27
- androidId: "",
28
24
  brand: "",
29
25
  model: "",
30
26
  device: "",
31
- deviceId: "",
32
27
  };
33
28
 
34
29
  private initPromise: Promise<void>;
@@ -65,21 +60,15 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
65
60
  private async _initializeCache(): Promise<void> {
66
61
  try {
67
62
  // 并行获取所有属性
68
- const [androidId, brand, model, device] = await Promise.all([
69
- this.execAndroidCmd("/system/bin/settings get secure android_id"),
63
+ const [brand, model, device] = await Promise.all([
70
64
  this.execAndroidCmd("/system/bin/getprop ro.product.brand"),
71
65
  this.execAndroidCmd("/system/bin/getprop ro.product.model"),
72
66
  this.execAndroidCmd("/system/bin/getprop ro.product.device"),
73
67
  ]);
74
68
 
75
- this.cache.androidId = androidId && androidId !== "null" ? androidId : "";
76
69
  this.cache.brand = brand || "";
77
70
  this.cache.model = model || "";
78
71
  this.cache.device = device || "";
79
-
80
- // 计算 deviceId
81
- const raw = `${this.cache.androidId}_${this.cache.brand}_${this.cache.model}_${this.cache.device}`;
82
- this.cache.deviceId = createHash("sha256").update(raw, "utf-8").digest("hex");
83
72
  } catch {
84
73
  // 初始化失败,保持默认值
85
74
  }
@@ -116,14 +105,6 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
116
105
  return this.getAndroidDeviceModel();
117
106
  }
118
107
 
119
- /**
120
- * 获取设备唯一ID(异步)
121
- */
122
- async getDeviceIdAsync(): Promise<string> {
123
- await this.initPromise;
124
- return this.cache.deviceId || "invalid";
125
- }
126
-
127
108
  /**
128
109
  * 获取设备名称
129
110
  */
@@ -1,14 +1,12 @@
1
1
  import os from "os";
2
2
  import * as Registry from "winreg";
3
3
  import type { DeviceType } from "../../../types.js";
4
- import { uuid } from "../../../utils/id.js";
5
4
  import type { DeviceInfoProvider } from "./base.js";
6
5
 
7
6
  /**
8
7
  * 设备信息缓存
9
8
  */
10
9
  interface DeviceInfoCache {
11
- deviceId: string;
12
10
  deviceBrand: string;
13
11
  }
14
12
 
@@ -67,7 +65,6 @@ function registryKeyExistsAsync(hive: number, keyPath: string): Promise<boolean>
67
65
 
68
66
  export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
69
67
  private cache: DeviceInfoCache = {
70
- deviceId: "",
71
68
  deviceBrand: "",
72
69
  };
73
70
 
@@ -90,43 +87,30 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
90
87
  private async _initializeCache(): Promise<void> {
91
88
  try {
92
89
  // 并行获取所有注册表值
93
- const [machineGuid, systemManufacturer, biosVendor, biosVendor2, systemManufacturer2] =
94
- await Promise.all([
95
- getRegistryStringValueAsync(
96
- Registry.HKLM,
97
- "\\SOFTWARE\\Microsoft\\Cryptography",
98
- "MachineGuid",
99
- ),
100
- // 原有路径
101
- getRegistryStringValueAsync(
102
- Registry.HKLM,
103
- "\\HARDWARE\\DESCRIPTION\\System",
104
- "SystemManufacturer",
105
- ),
106
- getRegistryStringValueAsync(
107
- Registry.HKLM,
108
- "\\HARDWARE\\DESCRIPTION\\System",
109
- "SystemBiosVendor",
110
- ),
111
- // 新增路径(BIOS 子键)
112
- getRegistryStringValueAsync(
113
- Registry.HKLM,
114
- "\\HARDWARE\\DESCRIPTION\\System\\BIOS",
115
- "Vendor",
116
- ),
117
- getRegistryStringValueAsync(
118
- Registry.HKLM,
119
- "\\HARDWARE\\DESCRIPTION\\System\\BIOS",
120
- "SystemManufacturer",
121
- ),
122
- ]);
123
-
124
- // 缓存 deviceId
125
- if (machineGuid) {
126
- this.cache.deviceId = machineGuid;
127
- } else {
128
- this.cache.deviceId = uuid();
129
- }
90
+ const [systemManufacturer, biosVendor, biosVendor2, systemManufacturer2] = await Promise.all([
91
+ // 原有路径
92
+ getRegistryStringValueAsync(
93
+ Registry.HKLM,
94
+ "\\HARDWARE\\DESCRIPTION\\System",
95
+ "SystemManufacturer",
96
+ ),
97
+ getRegistryStringValueAsync(
98
+ Registry.HKLM,
99
+ "\\HARDWARE\\DESCRIPTION\\System",
100
+ "SystemBiosVendor",
101
+ ),
102
+ // 新增路径(BIOS 子键)
103
+ getRegistryStringValueAsync(
104
+ Registry.HKLM,
105
+ "\\HARDWARE\\DESCRIPTION\\System\\BIOS",
106
+ "Vendor",
107
+ ),
108
+ getRegistryStringValueAsync(
109
+ Registry.HKLM,
110
+ "\\HARDWARE\\DESCRIPTION\\System\\BIOS",
111
+ "SystemManufacturer",
112
+ ),
113
+ ]);
130
114
 
131
115
  // 缓存 deviceBrand(检查多个来源)
132
116
  const manufacturerSources = [
@@ -141,7 +125,10 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
141
125
  this.cache.deviceBrand = "HONOR";
142
126
  } else {
143
127
  // 最后兜底:检查 HKLM:\SOFTWARE\HONOR 注册表键是否存在
144
- const honorKeyExists = await registryKeyExistsAsync(Registry.HKLM, "\\SOFTWARE\\HONOR");
128
+ const honorKeyExists = await registryKeyExistsAsync(
129
+ Registry.HKLM,
130
+ "\\SOFTWARE\\HONOR\\PCManager",
131
+ );
145
132
  if (honorKeyExists) {
146
133
  this.cache.deviceBrand = "HONOR";
147
134
  } else {
@@ -150,7 +137,6 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
150
137
  }
151
138
  } catch {
152
139
  // 初始化失败,使用默认值
153
- this.cache.deviceId = uuid();
154
140
  this.cache.deviceBrand = "";
155
141
  }
156
142
  }
@@ -172,14 +158,6 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
172
158
  return `${hostname} (${model})`;
173
159
  }
174
160
 
175
- /**
176
- * 获取设备唯一 ID(异步)
177
- */
178
- async getDeviceIdAsync(): Promise<string> {
179
- await this.initPromise;
180
- return this.cache.deviceId;
181
- }
182
-
183
161
  /**
184
162
  * 获取设备名称
185
163
  */
@@ -2,10 +2,9 @@
2
2
  * 登录命令行的入口,提供登录流程的服务
3
3
  */
4
4
  import { performOAuth2AuthWithBrowser } from "../../honor-auth/index.js";
5
- import { loadToken, saveToken } from "../../honor-auth/token-manager.js";
5
+ import { saveToken } from "../../honor-auth/token-manager.js";
6
6
  import type { HonorUserInfo } from "../../types.js";
7
7
  import { useClawLogger } from "../../utils/logger.js";
8
- import { upgradeIdentityToV2 } from "../configs/identity-persist.js";
9
8
  import { getDeviceInfo, registerDevice } from "../device/index.js";
10
9
 
11
10
  /**
@@ -33,18 +32,6 @@ export async function performLogin(options: LoginOptions = {}) {
33
32
  try {
34
33
  logger.debug?.("Starting login process...");
35
34
 
36
- // 检查是否是新的登录触发(没有 token)
37
- const existingToken = await loadToken();
38
- const isNewLogin = !existingToken?.token;
39
-
40
- if (isNewLogin) {
41
- logger.debug?.("New login detected, upgrading identity to v2...");
42
- const upgraded = await upgradeIdentityToV2();
43
- if (upgraded) {
44
- logger.info("Identity upgraded from v1 to v2 during login");
45
- }
46
- }
47
-
48
35
  // 步骤 1: 获取设备信息
49
36
  const deviceInfo = await getDeviceInfo();
50
37