@honor-claw/yoyo 0.0.1-alpha.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.
Files changed (71) hide show
  1. package/index.ts +25 -0
  2. package/openclaw.plugin.json +28 -0
  3. package/package.json +59 -0
  4. package/skills/yoyo-control/SKILL.md +346 -0
  5. package/skills/yoyo-control/references/capture-screenshot.md +66 -0
  6. package/skills/yoyo-control/references/local-search.md +27 -0
  7. package/skills/yoyo-control/references/open-app.md +54 -0
  8. package/skills/yoyo-control/references/phone-call.md +217 -0
  9. package/skills/yoyo-control/references/schedule.md +107 -0
  10. package/skills/yoyo-control/references/screen-recorder.md +67 -0
  11. package/skills/yoyo-control/references/search-contact.md +37 -0
  12. package/skills/yoyo-control/references/send-message.md +155 -0
  13. package/skills/yoyo-control/references/volume.md +536 -0
  14. package/skills/yoyo-control/scripts/README.md +103 -0
  15. package/skills/yoyo-control/scripts/invoke.js +119 -0
  16. package/skills/yoyo-control/scripts/volume-up.json +7 -0
  17. package/src/apis/claw-cloud.ts +74 -0
  18. package/src/apis/helpers.ts +10 -0
  19. package/src/apis/honor-auth.ts +148 -0
  20. package/src/apis/http-client.ts +239 -0
  21. package/src/apis/index.ts +8 -0
  22. package/src/apis/types.ts +47 -0
  23. package/src/cloud-channel/channel.ts +230 -0
  24. package/src/cloud-channel/client.ts +312 -0
  25. package/src/cloud-channel/index.ts +4 -0
  26. package/src/cloud-channel/types.ts +81 -0
  27. package/src/commands/index.ts +19 -0
  28. package/src/commands/login/impl.ts +21 -0
  29. package/src/commands/login/index.ts +1 -0
  30. package/src/commands/logout/index.ts +53 -0
  31. package/src/commands/status/index.ts +64 -0
  32. package/src/gateway-client/client.deprecated.ts +376 -0
  33. package/src/gateway-client/client.ts +76 -0
  34. package/src/gateway-client/device/auth.ts +57 -0
  35. package/src/gateway-client/device/builder.ts +105 -0
  36. package/src/gateway-client/device/helpers.ts +40 -0
  37. package/src/gateway-client/device/identity.ts +251 -0
  38. package/src/gateway-client/device/index.ts +40 -0
  39. package/src/gateway-client/device/types.ts +57 -0
  40. package/src/gateway-client/index.ts +2 -0
  41. package/src/gateway-client/types.deprecated.ts +217 -0
  42. package/src/gateway-client/types.ts +8 -0
  43. package/src/honor-auth/browser.ts +82 -0
  44. package/src/honor-auth/callback-server.ts +112 -0
  45. package/src/honor-auth/cloud.ts +70 -0
  46. package/src/honor-auth/config.ts +35 -0
  47. package/src/honor-auth/index.ts +2 -0
  48. package/src/honor-auth/token-manager.ts +80 -0
  49. package/src/honor-auth/types.ts +43 -0
  50. package/src/index.ts +10 -0
  51. package/src/modules/claw-configs/config-manager.ts +172 -0
  52. package/src/modules/claw-configs/index.ts +7 -0
  53. package/src/modules/claw-configs/types.ts +30 -0
  54. package/src/modules/device/device-info.ts +70 -0
  55. package/src/modules/device/index.ts +3 -0
  56. package/src/modules/device/providers/base.ts +27 -0
  57. package/src/modules/device/providers/pad.ts +114 -0
  58. package/src/modules/device/providers/windows.ts +67 -0
  59. package/src/modules/device/registry.ts +34 -0
  60. package/src/modules/login/impl.ts +70 -0
  61. package/src/modules/login/index.ts +6 -0
  62. package/src/runtime.ts +14 -0
  63. package/src/schemas.ts +20 -0
  64. package/src/services/connection/impl.ts +259 -0
  65. package/src/services/connection/index.ts +1 -0
  66. package/src/services/connection/types.ts +20 -0
  67. package/src/types.ts +64 -0
  68. package/src/utils/id.ts +8 -0
  69. package/src/utils/jwt.ts +36 -0
  70. package/src/utils/logger.ts +20 -0
  71. package/src/utils/proxy.ts +58 -0
@@ -0,0 +1,105 @@
1
+ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../types.js";
2
+ import { buildDeviceAuthPayloadV3 } from "./auth.js";
3
+ import {
4
+ publicKeyRawBase64UrlFromPem,
5
+ signDevicePayload,
6
+ } from "./identity.js";
7
+ import {
8
+ type DeviceBuilderOptions,
9
+ type DeviceInfo,
10
+ } from "./types.js";
11
+
12
+ /**
13
+ * Build device information for gateway connection
14
+ * This is the main function to generate device authentication data
15
+ *
16
+ * @param options - Device builder options
17
+ * @returns Device information object or undefined if no device identity provided
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const deviceInfo = buildDeviceInfo({
22
+ * deviceIdentity: loadOrCreateDeviceIdentity(),
23
+ * clientName: "my-client",
24
+ * role: "node",
25
+ * scopes: ["node.invoke"],
26
+ * nonce: "challenge-nonce",
27
+ * platform: "ios",
28
+ * deviceFamily: "iPhone"
29
+ * });
30
+ * ```
31
+ */
32
+ export function buildDeviceInfo(options: DeviceBuilderOptions): DeviceInfo | undefined {
33
+ const {
34
+ deviceIdentity,
35
+ clientName = GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
36
+ clientMode = GATEWAY_CLIENT_MODES.BACKEND,
37
+ role = "operator",
38
+ scopes = ["operator.admin"],
39
+ platform = process.platform,
40
+ deviceFamily,
41
+ authToken = null,
42
+ nonce,
43
+ signedAtMs = Date.now(),
44
+ } = options;
45
+
46
+ if (!deviceIdentity) {
47
+ return undefined;
48
+ }
49
+
50
+ // Build authentication payload
51
+ const payload = buildDeviceAuthPayloadV3({
52
+ deviceId: deviceIdentity.deviceId,
53
+ clientId: clientName,
54
+ clientMode,
55
+ role,
56
+ scopes,
57
+ signedAtMs,
58
+ token: authToken,
59
+ nonce,
60
+ platform,
61
+ deviceFamily,
62
+ });
63
+
64
+ // Sign the payload
65
+ const signature = signDevicePayload(deviceIdentity.privateKeyPem, payload);
66
+
67
+ // Return device information
68
+ return {
69
+ id: deviceIdentity.deviceId,
70
+ publicKey: publicKeyRawBase64UrlFromPem(deviceIdentity.publicKeyPem),
71
+ signature,
72
+ signedAt: signedAtMs,
73
+ nonce,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Build device information for node role
79
+ * Convenience function for node connections
80
+ *
81
+ * @param options - Device builder options
82
+ * @returns Device information object or undefined
83
+ */
84
+ export function buildNodeDeviceInfo(options: Omit<DeviceBuilderOptions, "role">): DeviceInfo | undefined {
85
+ return buildDeviceInfo({
86
+ ...options,
87
+ role: "node",
88
+ scopes: options.scopes ?? ["node.invoke"],
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Build device information for operator role
94
+ * Convenience function for operator connections
95
+ *
96
+ * @param options - Device builder options
97
+ * @returns Device information object or undefined
98
+ */
99
+ export function buildOperatorDeviceInfo(options: Omit<DeviceBuilderOptions, "role">): DeviceInfo | undefined {
100
+ return buildDeviceInfo({
101
+ ...options,
102
+ role: "operator",
103
+ scopes: options.scopes ?? ["operator.admin"],
104
+ });
105
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Normalize device metadata for authentication payloads
3
+ * Keeps cross-runtime normalization deterministic (TS/Swift/Kotlin)
4
+ * by only lowercasing ASCII metadata fields used in auth payloads.
5
+ */
6
+ function normalizeTrimmedMetadata(value?: string | null): string {
7
+ if (typeof value !== "string") {
8
+ return "";
9
+ }
10
+ const trimmed = value.trim();
11
+ return trimmed ? trimmed : "";
12
+ }
13
+
14
+ function toLowerAscii(input: string): string {
15
+ return input.replace(/[A-Z]/g, (char) => String.fromCharCode(char.charCodeAt(0) + 32));
16
+ }
17
+
18
+ export function normalizeDeviceMetadataForAuth(value?: string | null): string {
19
+ const trimmed = normalizeTrimmedMetadata(value);
20
+ if (!trimmed) {
21
+ return "";
22
+ }
23
+ // Keep cross-runtime normalization deterministic (TS/Swift/Kotlin) by only
24
+ // lowercasing ASCII metadata fields used in auth payloads.
25
+ return toLowerAscii(trimmed);
26
+ }
27
+
28
+ /**
29
+ * Normalize device metadata for policy classification
30
+ * Collapses Unicode confusables to stable ASCII-like tokens before matching platform/family rules.
31
+ */
32
+ export function normalizeDeviceMetadataForPolicy(value?: string | null): string {
33
+ const trimmed = normalizeTrimmedMetadata(value);
34
+ if (!trimmed) {
35
+ return "";
36
+ }
37
+ // Policy classification should collapse Unicode confusables to stable ASCII-ish
38
+ // tokens where possible before matching platform/family rules.
39
+ return trimmed.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
40
+ }
@@ -0,0 +1,251 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import type { DeviceIdentity } from "./types.js";
6
+
7
+ type StoredIdentity = {
8
+ version: 1;
9
+ deviceId: string;
10
+ publicKeyPem: string;
11
+ privateKeyPem: string;
12
+ createdAtMs: number;
13
+ };
14
+
15
+ /**
16
+ * Resolve default identity path
17
+ * Uses OS-specific state directory
18
+ */
19
+ function resolveDefaultIdentityPath(): string {
20
+ const platform = process.platform;
21
+ let stateDir: string;
22
+
23
+ if (platform === "darwin") {
24
+ stateDir = path.join(os.homedir(), "Library", "Application Support", "xh-gateway-client");
25
+ } else if (platform === "win32") {
26
+ stateDir = path.join(os.homedir(), "AppData", "Local", "xh-gateway-client");
27
+ } else {
28
+ // Linux and others
29
+ stateDir = process.env.XDG_STATE_HOME
30
+ ? path.join(process.env.XDG_STATE_HOME, "xh-gateway-client")
31
+ : path.join(os.homedir(), ".local", "state", "xh-gateway-client");
32
+ }
33
+
34
+ return path.join(stateDir, "identity", "device.json");
35
+ }
36
+
37
+ /**
38
+ * Ensure directory exists
39
+ */
40
+ function ensureDir(filePath: string): void {
41
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
42
+ }
43
+
44
+ /**
45
+ * ED25519 SPKI prefix for public key extraction
46
+ */
47
+ const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
48
+
49
+ /**
50
+ * Base64 URL encode a buffer
51
+ */
52
+ function base64UrlEncode(buf: Buffer): string {
53
+ return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
54
+ }
55
+
56
+ /**
57
+ * Base64 URL decode a string
58
+ */
59
+ function base64UrlDecode(input: string): Buffer {
60
+ const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
61
+ const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
62
+ return Buffer.from(padded, "base64");
63
+ }
64
+
65
+ /**
66
+ * Derive raw public key from PEM format
67
+ */
68
+ function derivePublicKeyRaw(publicKeyPem: string): Buffer {
69
+ const key = crypto.createPublicKey(publicKeyPem);
70
+ const spki = key.export({ type: "spki", format: "der" }) as Buffer;
71
+ if (
72
+ spki.length === ED25519_SPKI_PREFIX.length + 32 &&
73
+ spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)
74
+ ) {
75
+ return spki.subarray(ED25519_SPKI_PREFIX.length);
76
+ }
77
+ return spki;
78
+ }
79
+
80
+ /**
81
+ * Generate fingerprint from public key
82
+ */
83
+ function fingerprintPublicKey(publicKeyPem: string): string {
84
+ const raw = derivePublicKeyRaw(publicKeyPem);
85
+ return crypto.createHash("sha256").update(raw).digest("hex");
86
+ }
87
+
88
+ /**
89
+ * Generate a new device identity with ED25519 key pair
90
+ */
91
+ function generateIdentity(): DeviceIdentity {
92
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
93
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
94
+ const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
95
+ const deviceId = fingerprintPublicKey(publicKeyPem);
96
+ return { deviceId, publicKeyPem, privateKeyPem };
97
+ }
98
+
99
+ /**
100
+ * Load or create device identity from file
101
+ * @param filePath - Optional path to identity file (defaults to OS-specific location)
102
+ * @returns Device identity
103
+ */
104
+ export function loadOrCreateDeviceIdentity(
105
+ filePath: string = resolveDefaultIdentityPath(),
106
+ ): DeviceIdentity {
107
+ try {
108
+ if (fs.existsSync(filePath)) {
109
+ const raw = fs.readFileSync(filePath, "utf8");
110
+ const parsed = JSON.parse(raw) as StoredIdentity;
111
+ if (
112
+ parsed?.version === 1 &&
113
+ typeof parsed.deviceId === "string" &&
114
+ typeof parsed.publicKeyPem === "string" &&
115
+ typeof parsed.privateKeyPem === "string"
116
+ ) {
117
+ const derivedId = fingerprintPublicKey(parsed.publicKeyPem);
118
+ if (derivedId && derivedId !== parsed.deviceId) {
119
+ // Update stored deviceId if it doesn't match fingerprint
120
+ const updated: StoredIdentity = {
121
+ ...parsed,
122
+ deviceId: derivedId,
123
+ };
124
+ fs.writeFileSync(filePath, `${JSON.stringify(updated, null, 2)}\n`, { mode: 0o600 });
125
+ try {
126
+ fs.chmodSync(filePath, 0o600);
127
+ } catch {
128
+ // best-effort
129
+ }
130
+ return {
131
+ deviceId: derivedId,
132
+ publicKeyPem: parsed.publicKeyPem,
133
+ privateKeyPem: parsed.privateKeyPem,
134
+ };
135
+ }
136
+ return {
137
+ deviceId: parsed.deviceId,
138
+ publicKeyPem: parsed.publicKeyPem,
139
+ privateKeyPem: parsed.privateKeyPem,
140
+ };
141
+ }
142
+ }
143
+ } catch {
144
+ // fall through to regenerate on error
145
+ }
146
+
147
+ // Generate new identity
148
+ const identity = generateIdentity();
149
+ ensureDir(filePath);
150
+ const stored: StoredIdentity = {
151
+ version: 1,
152
+ deviceId: identity.deviceId,
153
+ publicKeyPem: identity.publicKeyPem,
154
+ privateKeyPem: identity.privateKeyPem,
155
+ createdAtMs: Date.now(),
156
+ };
157
+ fs.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}\n`, { mode: 0o600 });
158
+ try {
159
+ fs.chmodSync(filePath, 0o600);
160
+ } catch {
161
+ // best-effort
162
+ }
163
+ return identity;
164
+ }
165
+
166
+ /**
167
+ * Sign a payload using device private key
168
+ * @param privateKeyPem - Private key in PEM format
169
+ * @param payload - Payload string to sign
170
+ * @returns Base64 URL encoded signature
171
+ */
172
+ export function signDevicePayload(privateKeyPem: string, payload: string): string {
173
+ const key = crypto.createPrivateKey(privateKeyPem);
174
+ const sig = crypto.sign(null, Buffer.from(payload, "utf8"), key);
175
+ return base64UrlEncode(sig);
176
+ }
177
+
178
+ /**
179
+ * Normalize device public key to base64 URL format
180
+ * @param publicKey - Public key in PEM or base64 format
181
+ * @returns Base64 URL encoded public key or null on error
182
+ */
183
+ export function normalizeDevicePublicKeyBase64Url(publicKey: string): string | null {
184
+ try {
185
+ if (publicKey.includes("BEGIN")) {
186
+ return base64UrlEncode(derivePublicKeyRaw(publicKey));
187
+ }
188
+ const raw = base64UrlDecode(publicKey);
189
+ return base64UrlEncode(raw);
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Derive device ID from public key
197
+ * @param publicKey - Public key in PEM or base64 format
198
+ * @returns Device ID (SHA256 hash) or null on error
199
+ */
200
+ export function deriveDeviceIdFromPublicKey(publicKey: string): string | null {
201
+ try {
202
+ const raw = publicKey.includes("BEGIN")
203
+ ? derivePublicKeyRaw(publicKey)
204
+ : base64UrlDecode(publicKey);
205
+ return crypto.createHash("sha256").update(raw).digest("hex");
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Convert public key PEM to raw base64 URL format
213
+ * @param publicKeyPem - Public key in PEM format
214
+ * @returns Base64 URL encoded raw public key
215
+ */
216
+ export function publicKeyRawBase64UrlFromPem(publicKeyPem: string): string {
217
+ return base64UrlEncode(derivePublicKeyRaw(publicKeyPem));
218
+ }
219
+
220
+ /**
221
+ * Verify device signature
222
+ * @param publicKey - Public key in PEM or base64 format
223
+ * @param payload - Original payload string
224
+ * @param signatureBase64Url - Base64 URL encoded signature
225
+ * @returns True if signature is valid, false otherwise
226
+ */
227
+ export function verifyDeviceSignature(
228
+ publicKey: string,
229
+ payload: string,
230
+ signatureBase64Url: string,
231
+ ): boolean {
232
+ try {
233
+ const key = publicKey.includes("BEGIN")
234
+ ? crypto.createPublicKey(publicKey)
235
+ : crypto.createPublicKey({
236
+ key: Buffer.concat([ED25519_SPKI_PREFIX, base64UrlDecode(publicKey)]),
237
+ type: "spki",
238
+ format: "der",
239
+ });
240
+ const sig = (() => {
241
+ try {
242
+ return base64UrlDecode(signatureBase64Url);
243
+ } catch {
244
+ return Buffer.from(signatureBase64Url, "base64");
245
+ }
246
+ })();
247
+ return crypto.verify(null, Buffer.from(payload, "utf8"), key, sig);
248
+ } catch {
249
+ return false;
250
+ }
251
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * XH Gateway Client
3
+ *
4
+ * A library for device authentication and gateway connection utilities.
5
+ * Provides device identity management, authentication payload building,
6
+ * and device information generation for OpenClaw gateway connections.
7
+ */
8
+
9
+ // Types
10
+ export type {
11
+ DeviceIdentity,
12
+ DeviceAuthPayloadParams,
13
+ DeviceAuthPayloadV3Params,
14
+ DeviceInfo,
15
+ DeviceBuilderOptions,
16
+ } from "./types.js";
17
+
18
+ // Device identity management
19
+ export {
20
+ loadOrCreateDeviceIdentity,
21
+ signDevicePayload,
22
+ normalizeDevicePublicKeyBase64Url,
23
+ deriveDeviceIdFromPublicKey,
24
+ publicKeyRawBase64UrlFromPem,
25
+ verifyDeviceSignature,
26
+ } from "./identity.js";
27
+
28
+ // Device authentication
29
+ export {
30
+ buildDeviceAuthPayload,
31
+ buildDeviceAuthPayloadV3,
32
+ normalizeDeviceMetadataForAuth,
33
+ } from "./auth.js";
34
+
35
+ // Device builder
36
+ export {
37
+ buildDeviceInfo,
38
+ buildNodeDeviceInfo,
39
+ buildOperatorDeviceInfo,
40
+ } from "./builder.js";
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Device identity information
3
+ */
4
+ export type DeviceIdentity = {
5
+ deviceId: string;
6
+ publicKeyPem: string;
7
+ privateKeyPem: string;
8
+ };
9
+
10
+ /**
11
+ * Device authentication payload parameters
12
+ */
13
+ export type DeviceAuthPayloadParams = {
14
+ deviceId: string;
15
+ clientId: string;
16
+ clientMode: string;
17
+ role: string;
18
+ scopes: string[];
19
+ signedAtMs: number;
20
+ token?: string | null;
21
+ nonce: string;
22
+ };
23
+
24
+ /**
25
+ * Device authentication payload V3 parameters (with platform/device family)
26
+ */
27
+ export type DeviceAuthPayloadV3Params = DeviceAuthPayloadParams & {
28
+ platform?: string | null;
29
+ deviceFamily?: string | null;
30
+ };
31
+
32
+ /**
33
+ * Device information for gateway connection
34
+ */
35
+ export type DeviceInfo = {
36
+ id: string;
37
+ publicKey: string;
38
+ signature: string;
39
+ signedAt: number;
40
+ nonce: string;
41
+ };
42
+
43
+ /**
44
+ * Device builder options
45
+ */
46
+ export type DeviceBuilderOptions = {
47
+ deviceIdentity?: DeviceIdentity;
48
+ clientName?: string;
49
+ clientMode?: string;
50
+ role?: string;
51
+ scopes?: string[];
52
+ platform?: string;
53
+ deviceFamily?: string;
54
+ authToken?: string | null;
55
+ nonce: string;
56
+ signedAtMs?: number;
57
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./client.js";
2
+ export * from "./types.js"
@@ -0,0 +1,217 @@
1
+ // ==================== 类型定义 ====================
2
+
3
+ /**
4
+ * Gateway Client ID 枚举
5
+ */
6
+ export const GATEWAY_CLIENT_IDS = {
7
+ WEBCHAT_UI: "webchat-ui",
8
+ CONTROL_UI: "openclaw-control-ui",
9
+ WEBCHAT: "webchat",
10
+ CLI: "cli",
11
+ GATEWAY_CLIENT: "gateway-client",
12
+ MACOS_APP: "openclaw-macos",
13
+ IOS_APP: "openclaw-ios",
14
+ ANDROID_APP: "openclaw-android",
15
+ NODE_HOST: "node-host",
16
+ TEST: "test",
17
+ FINGERPRINT: "fingerprint",
18
+ PROBE: "openclaw-probe",
19
+ } as const;
20
+
21
+ /**
22
+ * Gateway Client Mode 枚举
23
+ */
24
+ export const GATEWAY_CLIENT_MODES = {
25
+ WEBCHAT: "webchat",
26
+ CLI: "cli",
27
+ UI: "ui",
28
+ BACKEND: "backend",
29
+ NODE: "node",
30
+ PROBE: "probe",
31
+ TEST: "test",
32
+ } as const;
33
+
34
+ /**
35
+ * Gateway Client ID 类型
36
+ */
37
+ export type GatewayClientId =
38
+ (typeof GATEWAY_CLIENT_IDS)[keyof typeof GATEWAY_CLIENT_IDS];
39
+
40
+ /**
41
+ * Gateway Client Mode 类型
42
+ */
43
+ export type GatewayClientMode =
44
+ (typeof GATEWAY_CLIENT_MODES)[keyof typeof GATEWAY_CLIENT_MODES];
45
+
46
+ /**
47
+ * Back-compat naming (internal): these values are IDs, not display names.
48
+ */
49
+ export const GATEWAY_CLIENT_NAMES = GATEWAY_CLIENT_IDS;
50
+ export type GatewayClientName = GatewayClientId;
51
+
52
+ /**
53
+ * Gateway Client Info 类型
54
+ */
55
+ export type GatewayClientInfo = {
56
+ id: GatewayClientId;
57
+ displayName?: string;
58
+ version: string;
59
+ platform: string;
60
+ deviceFamily?: string;
61
+ modelIdentifier?: string;
62
+ mode: GatewayClientMode;
63
+ instanceId?: string;
64
+ };
65
+
66
+ /**
67
+ * Gateway Client Cap 枚举
68
+ */
69
+ export const GATEWAY_CLIENT_CAPS = {
70
+ TOOL_EVENTS: "tool-events",
71
+ } as const;
72
+
73
+ export type GatewayClientCap =
74
+ (typeof GATEWAY_CLIENT_CAPS)[keyof typeof GATEWAY_CLIENT_CAPS];
75
+
76
+ const GATEWAY_CLIENT_ID_SET = new Set<GatewayClientId>(
77
+ Object.values(GATEWAY_CLIENT_IDS)
78
+ );
79
+ const GATEWAY_CLIENT_MODE_SET = new Set<GatewayClientMode>(
80
+ Object.values(GATEWAY_CLIENT_MODES)
81
+ );
82
+
83
+ export function normalizeGatewayClientId(
84
+ raw?: string | null
85
+ ): GatewayClientId | undefined {
86
+ const normalized = raw?.trim().toLowerCase();
87
+ if (!normalized) {
88
+ return undefined;
89
+ }
90
+ return GATEWAY_CLIENT_ID_SET.has(normalized as GatewayClientId)
91
+ ? (normalized as GatewayClientId)
92
+ : undefined;
93
+ }
94
+
95
+ export function normalizeGatewayClientName(
96
+ raw?: string | null
97
+ ): GatewayClientName | undefined {
98
+ return normalizeGatewayClientId(raw);
99
+ }
100
+
101
+ export function normalizeGatewayClientMode(
102
+ raw?: string | null
103
+ ): GatewayClientMode | undefined {
104
+ const normalized = raw?.trim().toLowerCase();
105
+ if (!normalized) {
106
+ return undefined;
107
+ }
108
+ return GATEWAY_CLIENT_MODE_SET.has(normalized as GatewayClientMode)
109
+ ? (normalized as GatewayClientMode)
110
+ : undefined;
111
+ }
112
+
113
+ export function hasGatewayClientCap(
114
+ caps: string[] | null | undefined,
115
+ cap: GatewayClientCap
116
+ ): boolean {
117
+ if (!Array.isArray(caps)) {
118
+ return false;
119
+ }
120
+ return caps.includes(cap);
121
+ }
122
+
123
+ /**
124
+ * 协议帧类型 - 请求帧
125
+ */
126
+ export interface RequestFrame {
127
+ type: "req";
128
+ id: string;
129
+ method: string;
130
+ params?: unknown;
131
+ }
132
+
133
+ /**
134
+ * 协议帧类型 - 响应帧
135
+ */
136
+ export interface ResponseFrame {
137
+ type: "res";
138
+ id: string;
139
+ ok: boolean;
140
+ payload?: unknown;
141
+ error?: {
142
+ code: number;
143
+ message: string;
144
+ details?: unknown;
145
+ retryable?: boolean;
146
+ retryAfterMs?: boolean;
147
+ };
148
+ }
149
+
150
+ /**
151
+ * 协议帧类型 - 事件帧
152
+ */
153
+ export interface EventFrame {
154
+ type: "evt" | "event";
155
+ event: string;
156
+ seq?: number;
157
+ payload?: unknown;
158
+ }
159
+
160
+ /**
161
+ * 连接成功响应
162
+ */
163
+ export interface HelloOk {
164
+ type: "hello-ok";
165
+ protocol: number;
166
+ gateway: {
167
+ id: string;
168
+ version: string;
169
+ };
170
+ auth?: {
171
+ deviceToken?: string;
172
+ role?: string;
173
+ scopes?: string[];
174
+ };
175
+ policy?: {
176
+ tickIntervalMs?: number;
177
+ };
178
+ }
179
+
180
+ /**
181
+ * 连接参数(简化版)
182
+ */
183
+ export interface ConnectParams {
184
+ minProtocol?: number;
185
+ maxProtocol?: number;
186
+ client: {
187
+ id: GatewayClientId;
188
+ displayName?: string;
189
+ version?: string;
190
+ platform?: string;
191
+ deviceFamily?: string;
192
+ modelIdentifier?: string;
193
+ mode: GatewayClientMode;
194
+ instanceId?: string;
195
+ };
196
+ caps?: string[];
197
+ commands?: string[];
198
+ permissions?: Record<string, boolean>;
199
+ pathEnv?: string;
200
+ role?: string;
201
+ scopes?: string[];
202
+ device?: {
203
+ id: string;
204
+ publicKey: string;
205
+ signature: string;
206
+ signedAt: number;
207
+ nonce: string;
208
+ };
209
+ auth?: {
210
+ token?: string;
211
+ deviceToken?: string;
212
+ password?: string;
213
+ };
214
+ modelIdentifier?: string;
215
+ locale?: string;
216
+ userAgent?: string;
217
+ }