@honor-claw/yoyo 0.0.1-beta.2 → 0.0.1-beta.21

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 (79) hide show
  1. package/index.ts +2 -2
  2. package/openclaw.plugin.json +7 -0
  3. package/package.json +20 -20
  4. package/skills/search/SKILL.md +182 -0
  5. package/skills/search/scripts/search.sh +69 -0
  6. package/skills/yoyo-control/SKILL.md +105 -120
  7. package/skills/yoyo-control/references/alarm-create.md +473 -0
  8. package/skills/yoyo-control/references/app-close.md +183 -0
  9. package/skills/yoyo-control/references/app-open.md +178 -0
  10. package/skills/yoyo-control/references/call-phone.md +250 -0
  11. package/skills/yoyo-control/references/capture-screenshot.md +205 -54
  12. package/skills/yoyo-control/references/contact-search.md +235 -0
  13. package/skills/yoyo-control/references/hotspot.md +208 -0
  14. package/skills/yoyo-control/references/local-search.md +224 -15
  15. package/skills/yoyo-control/references/message-send.md +246 -0
  16. package/skills/yoyo-control/references/mobile-data.md +248 -0
  17. package/skills/yoyo-control/references/no-disturb.md +239 -0
  18. package/skills/yoyo-control/references/quiet-mode.md +228 -0
  19. package/skills/yoyo-control/references/ringing-mode.md +223 -0
  20. package/skills/yoyo-control/references/screen-record.md +220 -0
  21. package/skills/yoyo-control/references/vibration-mode.md +235 -0
  22. package/skills/yoyo-control/references/volume-operate.md +274 -0
  23. package/skills/yoyo-control/scripts/invoke.js +33 -111
  24. package/src/agent/copy-templates.ts +56 -0
  25. package/src/agent/index.ts +3 -0
  26. package/src/agent/templates/AGENTS.md +223 -0
  27. package/src/apis/claw-cloud.ts +70 -23
  28. package/src/apis/honor-auth.ts +20 -10
  29. package/src/apis/types.ts +24 -1
  30. package/src/cloud-channel/channel.ts +245 -58
  31. package/src/cloud-channel/client.ts +87 -12
  32. package/src/cloud-channel/types.ts +30 -0
  33. package/src/commands/env/impl.ts +58 -0
  34. package/src/commands/env/index.ts +1 -0
  35. package/src/commands/index.ts +11 -1
  36. package/src/commands/login/impl.ts +17 -8
  37. package/src/commands/logout/impl.ts +23 -0
  38. package/src/commands/logout/index.ts +1 -53
  39. package/src/commands/status/index.ts +172 -42
  40. package/src/gateway-client/client.deprecated.ts +1 -1
  41. package/src/gateway-client/client.ts +15 -20
  42. package/src/gateway-client/types.ts +2 -2
  43. package/src/honor-auth/browser.ts +12 -15
  44. package/src/honor-auth/callback-server.ts +3 -6
  45. package/src/honor-auth/cloud.ts +65 -12
  46. package/src/honor-auth/config.ts +25 -17
  47. package/src/honor-auth/index.ts +1 -0
  48. package/src/honor-auth/token-manager.ts +24 -14
  49. package/src/modules/claw-configs/config-manager.ts +211 -11
  50. package/src/modules/claw-configs/hosts.ts +48 -0
  51. package/src/modules/claw-configs/index.ts +1 -0
  52. package/src/modules/claw-configs/types.ts +4 -0
  53. package/src/modules/device/device-info.ts +20 -9
  54. package/src/modules/device/providers/linux.ts +128 -0
  55. package/src/modules/device/providers/macos.ts +123 -0
  56. package/src/modules/device/providers/pad.ts +0 -16
  57. package/src/modules/device/registry.ts +12 -3
  58. package/src/modules/login/impl.ts +38 -16
  59. package/src/runtime.ts +44 -0
  60. package/src/schemas.ts +4 -1
  61. package/src/services/connection/impl.ts +89 -9
  62. package/src/services/connection/status-tracker/events.ts +127 -0
  63. package/src/services/connection/status-tracker/index.ts +31 -0
  64. package/src/services/connection/status-tracker/storage.ts +133 -0
  65. package/src/services/connection/status-tracker/tracker.ts +370 -0
  66. package/src/services/connection/status-tracker/types.ts +131 -0
  67. package/src/types.ts +0 -4
  68. package/src/utils/fs-safe.ts +544 -0
  69. package/src/utils/version.ts +29 -0
  70. package/src/utils/ws.ts +21 -0
  71. package/skills/yoyo-control/references/open-app.md +0 -54
  72. package/skills/yoyo-control/references/phone-call.md +0 -217
  73. package/skills/yoyo-control/references/schedule.md +0 -107
  74. package/skills/yoyo-control/references/screen-recorder.md +0 -67
  75. package/skills/yoyo-control/references/search-contact.md +0 -37
  76. package/skills/yoyo-control/references/send-message.md +0 -155
  77. package/skills/yoyo-control/references/volume.md +0 -536
  78. package/skills/yoyo-control/scripts/README.md +0 -103
  79. package/skills/yoyo-control/scripts/volume-up.json +0 -7
@@ -0,0 +1,544 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { Stats } from "node:fs";
3
+ import { constants as fsConstants } from "node:fs";
4
+ import type { FileHandle } from "node:fs/promises";
5
+ import fs from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+
9
+ export type SafeFsErrorCode =
10
+ | "invalid-path"
11
+ | "not-found"
12
+ | "outside-root"
13
+ | "symlink"
14
+ | "not-file"
15
+ | "path-mismatch"
16
+ | "too-large"
17
+ | "write-failed";
18
+
19
+ export class SafeFsError extends Error {
20
+ code: SafeFsErrorCode;
21
+
22
+ constructor(code: SafeFsErrorCode, message: string, options?: ErrorOptions) {
23
+ super(message, options);
24
+ this.code = code;
25
+ this.name = "SafeFsError";
26
+ }
27
+ }
28
+
29
+ export type WriteMode = "overwrite" | "append";
30
+
31
+ export type SafeWriteOptions = {
32
+ rootDir: string;
33
+ relativePath: string;
34
+ data: string | Buffer;
35
+ encoding?: BufferEncoding;
36
+ mode?: WriteMode;
37
+ mkdir?: boolean;
38
+ maxBytes?: number;
39
+ };
40
+
41
+ export type SafeReadResult = {
42
+ buffer: Buffer;
43
+ realPath: string;
44
+ stat: Stats;
45
+ };
46
+
47
+ export type SafeWriteResult = {
48
+ realPath: string;
49
+ bytesWritten: number;
50
+ created: boolean;
51
+ };
52
+
53
+ const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
54
+ const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
55
+
56
+ function isNodeError(value: unknown): value is NodeJS.ErrnoException {
57
+ return Boolean(
58
+ value && typeof value === "object" && "code" in (value as Record<string, unknown>)
59
+ );
60
+ }
61
+
62
+ function isNotFoundPathError(value: unknown): boolean {
63
+ return isNodeError(value) && typeof value.code === "string" && NOT_FOUND_CODES.has(value.code);
64
+ }
65
+
66
+ function isSymlinkOpenError(value: unknown): boolean {
67
+ return isNodeError(value) && typeof value.code === "string" && SYMLINK_OPEN_CODES.has(value.code);
68
+ }
69
+
70
+ function normalizeWindowsPathForComparison(input: string): string {
71
+ let normalized = path.win32.normalize(input);
72
+ if (normalized.startsWith("\\\\?\\")) {
73
+ normalized = normalized.slice(4);
74
+ if (normalized.toUpperCase().startsWith("UNC\\")) {
75
+ normalized = `\\\\${normalized.slice(4)}`;
76
+ }
77
+ }
78
+ return normalized.replaceAll("/", "\\").toLowerCase();
79
+ }
80
+
81
+ function isPathInside(root: string, target: string): boolean {
82
+ const resolvedRoot = path.resolve(root);
83
+ const resolvedTarget = path.resolve(target);
84
+
85
+ if (process.platform === "win32") {
86
+ const rootForCompare = normalizeWindowsPathForComparison(resolvedRoot);
87
+ const targetForCompare = normalizeWindowsPathForComparison(resolvedTarget);
88
+ const relative = path.win32.relative(rootForCompare, targetForCompare);
89
+ return relative === "" || (!relative.startsWith("..") && !path.win32.isAbsolute(relative));
90
+ }
91
+
92
+ const relative = path.relative(resolvedRoot, resolvedTarget);
93
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
94
+ }
95
+
96
+ type FileIdentityStat = {
97
+ dev: number | bigint;
98
+ ino: number | bigint;
99
+ };
100
+
101
+ function isZero(value: number | bigint): boolean {
102
+ return value === 0 || value === 0n;
103
+ }
104
+
105
+ function sameFileIdentity(
106
+ left: FileIdentityStat,
107
+ right: FileIdentityStat,
108
+ platform: NodeJS.Platform = process.platform
109
+ ): boolean {
110
+ if (left.ino !== right.ino) {
111
+ return false;
112
+ }
113
+ if (left.dev === right.dev) {
114
+ return true;
115
+ }
116
+ return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
117
+ }
118
+
119
+ const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
120
+ const OPEN_READ_FLAGS = fsConstants.O_RDONLY | (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
121
+ const OPEN_WRITE_CREATE_FLAGS =
122
+ fsConstants.O_WRONLY |
123
+ fsConstants.O_CREAT |
124
+ fsConstants.O_EXCL |
125
+ (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
126
+ const OPEN_APPEND_FLAGS =
127
+ fsConstants.O_WRONLY | fsConstants.O_APPEND | (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
128
+
129
+ const ensureTrailingSep = (value: string) => (value.endsWith(path.sep) ? value : value + path.sep);
130
+
131
+ function expandHomePrefix(input: string, home?: string): string {
132
+ if (!input.startsWith("~")) {
133
+ return input;
134
+ }
135
+ const resolvedHome = home ?? process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
136
+ if (!resolvedHome) {
137
+ return input;
138
+ }
139
+ return input.replace(/^~(?=$|[\\/])/, resolvedHome);
140
+ }
141
+
142
+ async function resolvePathWithinRoot(params: {
143
+ rootDir: string;
144
+ relativePath: string;
145
+ }): Promise<{ rootReal: string; rootWithSep: string; resolved: string }> {
146
+ let rootReal: string;
147
+ try {
148
+ rootReal = await fs.realpath(params.rootDir);
149
+ } catch (err) {
150
+ if (isNotFoundPathError(err)) {
151
+ throw new SafeFsError("not-found", "root dir not found");
152
+ }
153
+ throw err;
154
+ }
155
+ const rootWithSep = ensureTrailingSep(rootReal);
156
+ const expanded = expandHomePrefix(params.relativePath);
157
+ const resolved = path.resolve(rootWithSep, expanded);
158
+ if (!isPathInside(rootWithSep, resolved)) {
159
+ throw new SafeFsError("outside-root", "file is outside workspace root");
160
+ }
161
+ return { rootReal, rootWithSep, resolved };
162
+ }
163
+
164
+ async function openVerifiedLocalFile(
165
+ filePath: string,
166
+ options?: { rejectHardlinks?: boolean }
167
+ ): Promise<{ handle: FileHandle; realPath: string; stat: Stats }> {
168
+ try {
169
+ const preStat = await fs.lstat(filePath);
170
+ if (preStat.isDirectory()) {
171
+ throw new SafeFsError("not-file", "not a file");
172
+ }
173
+ } catch (err) {
174
+ if (err instanceof SafeFsError) {
175
+ throw err;
176
+ }
177
+ }
178
+
179
+ let handle: FileHandle;
180
+ try {
181
+ handle = await fs.open(filePath, OPEN_READ_FLAGS);
182
+ } catch (err) {
183
+ if (isNotFoundPathError(err)) {
184
+ throw new SafeFsError("not-found", "file not found");
185
+ }
186
+ if (isSymlinkOpenError(err)) {
187
+ throw new SafeFsError("symlink", "symlink open blocked", { cause: err });
188
+ }
189
+ if (isNodeError(err) && err.code === "EISDIR") {
190
+ throw new SafeFsError("not-file", "not a file");
191
+ }
192
+ throw err;
193
+ }
194
+
195
+ try {
196
+ const [stat, lstat] = await Promise.all([handle.stat(), fs.lstat(filePath)]);
197
+ if (lstat.isSymbolicLink()) {
198
+ throw new SafeFsError("symlink", "symlink not allowed");
199
+ }
200
+ if (!stat.isFile()) {
201
+ throw new SafeFsError("not-file", "not a file");
202
+ }
203
+ if (options?.rejectHardlinks && stat.nlink > 1) {
204
+ throw new SafeFsError("invalid-path", "hardlinked path not allowed");
205
+ }
206
+ if (!sameFileIdentity(stat, lstat)) {
207
+ throw new SafeFsError("path-mismatch", "path changed during read");
208
+ }
209
+
210
+ const realPath = await fs.realpath(filePath);
211
+ const realStat = await fs.stat(realPath);
212
+ if (options?.rejectHardlinks && realStat.nlink > 1) {
213
+ throw new SafeFsError("invalid-path", "hardlinked path not allowed");
214
+ }
215
+ if (!sameFileIdentity(stat, realStat)) {
216
+ throw new SafeFsError("path-mismatch", "path mismatch");
217
+ }
218
+
219
+ return { handle, realPath, stat };
220
+ } catch (err) {
221
+ await handle.close().catch(() => { });
222
+ if (err instanceof SafeFsError) {
223
+ throw err;
224
+ }
225
+ if (isNotFoundPathError(err)) {
226
+ throw new SafeFsError("not-found", "file not found");
227
+ }
228
+ throw err;
229
+ }
230
+ }
231
+
232
+ export async function safeReadFile(params: {
233
+ rootDir: string;
234
+ relativePath: string;
235
+ maxBytes?: number;
236
+ }): Promise<SafeReadResult> {
237
+ const { rootWithSep, resolved } = await resolvePathWithinRoot(params);
238
+
239
+ const opened = await openVerifiedLocalFile(resolved);
240
+
241
+ if (!isPathInside(rootWithSep, opened.realPath)) {
242
+ await opened.handle.close().catch(() => { });
243
+ throw new SafeFsError("outside-root", "file is outside workspace root");
244
+ }
245
+
246
+ try {
247
+ if (params.maxBytes !== undefined && opened.stat.size > params.maxBytes) {
248
+ throw new SafeFsError(
249
+ "too-large",
250
+ `file exceeds limit of ${params.maxBytes} bytes (got ${opened.stat.size})`
251
+ );
252
+ }
253
+ const buffer = await opened.handle.readFile();
254
+ return {
255
+ buffer,
256
+ realPath: opened.realPath,
257
+ stat: opened.stat,
258
+ };
259
+ } finally {
260
+ await opened.handle.close().catch(() => { });
261
+ }
262
+ }
263
+
264
+ function buildAtomicWriteTempPath(targetPath: string): string {
265
+ const dir = path.dirname(targetPath);
266
+ const base = path.basename(targetPath);
267
+ return path.join(dir, `.${base}.${process.pid}.${randomUUID()}.tmp`);
268
+ }
269
+
270
+ async function writeTempFileForAtomicReplace(params: {
271
+ tempPath: string;
272
+ data: string | Buffer;
273
+ encoding?: BufferEncoding;
274
+ mode: number;
275
+ }): Promise<Stats> {
276
+ const tempHandle = await fs.open(params.tempPath, OPEN_WRITE_CREATE_FLAGS, params.mode);
277
+ try {
278
+ if (typeof params.data === "string") {
279
+ await tempHandle.writeFile(params.data, params.encoding ?? "utf8");
280
+ } else {
281
+ await tempHandle.writeFile(params.data);
282
+ }
283
+ return await tempHandle.stat();
284
+ } finally {
285
+ await tempHandle.close().catch(() => { });
286
+ }
287
+ }
288
+
289
+ async function verifyAtomicWriteResult(params: {
290
+ rootDir: string;
291
+ targetPath: string;
292
+ expectedStat: Stats;
293
+ }): Promise<void> {
294
+ const rootReal = await fs.realpath(params.rootDir);
295
+ const rootWithSep = ensureTrailingSep(rootReal);
296
+ const opened = await openVerifiedLocalFile(params.targetPath, { rejectHardlinks: true });
297
+ try {
298
+ if (!sameFileIdentity(opened.stat, params.expectedStat)) {
299
+ throw new SafeFsError("path-mismatch", "path changed during write");
300
+ }
301
+ if (!isPathInside(rootWithSep, opened.realPath)) {
302
+ throw new SafeFsError("outside-root", "file is outside workspace root");
303
+ }
304
+ } finally {
305
+ await opened.handle.close().catch(() => { });
306
+ }
307
+ }
308
+
309
+ export async function safeWriteFile(params: SafeWriteOptions): Promise<SafeWriteResult> {
310
+ const { rootWithSep, resolved } = await resolvePathWithinRoot(params);
311
+
312
+ if (params.mkdir !== false) {
313
+ await fs.mkdir(path.dirname(resolved), { recursive: true });
314
+ }
315
+
316
+ let ioPath = resolved;
317
+ let existingFile = false;
318
+
319
+ try {
320
+ const resolvedRealPath = await fs.realpath(resolved);
321
+ if (!isPathInside(rootWithSep, resolvedRealPath)) {
322
+ throw new SafeFsError("outside-root", "file is outside workspace root");
323
+ }
324
+ ioPath = resolvedRealPath;
325
+ existingFile = true;
326
+ } catch (err) {
327
+ if (err instanceof SafeFsError) {
328
+ throw err;
329
+ }
330
+ if (!isNotFoundPathError(err)) {
331
+ throw err;
332
+ }
333
+ }
334
+
335
+ const fileMode = 0o600;
336
+ const isAppend = params.mode === "append";
337
+
338
+ if (isAppend && !existingFile) {
339
+ throw new SafeFsError("not-found", "cannot append to non-existent file");
340
+ }
341
+
342
+ if (isAppend) {
343
+ return await appendToFile({
344
+ ioPath,
345
+ rootWithSep,
346
+ data: params.data,
347
+ encoding: params.encoding,
348
+ maxBytes: params.maxBytes,
349
+ });
350
+ }
351
+
352
+ return await overwriteFile({
353
+ ioPath,
354
+ rootWithSep,
355
+ rootDir: params.rootDir,
356
+ data: params.data,
357
+ encoding: params.encoding,
358
+ existingFile,
359
+ fileMode,
360
+ });
361
+ }
362
+
363
+ async function appendToFile(params: {
364
+ ioPath: string;
365
+ rootWithSep: string;
366
+ data: string | Buffer;
367
+ encoding?: BufferEncoding;
368
+ maxBytes?: number;
369
+ }): Promise<SafeWriteResult> {
370
+ let handle: FileHandle;
371
+ try {
372
+ handle = await fs.open(params.ioPath, OPEN_APPEND_FLAGS);
373
+ } catch (err) {
374
+ if (isNotFoundPathError(err)) {
375
+ throw new SafeFsError("not-found", "file not found");
376
+ }
377
+ if (isSymlinkOpenError(err)) {
378
+ throw new SafeFsError("symlink", "symlink open blocked", { cause: err });
379
+ }
380
+ throw err;
381
+ }
382
+
383
+ try {
384
+ const stat = await handle.stat();
385
+ if (!stat.isFile()) {
386
+ throw new SafeFsError("not-file", "path is not a regular file");
387
+ }
388
+
389
+ const lstat = await fs.lstat(params.ioPath);
390
+ if (lstat.isSymbolicLink()) {
391
+ throw new SafeFsError("symlink", "path is a symlink");
392
+ }
393
+ if (!sameFileIdentity(stat, lstat)) {
394
+ throw new SafeFsError("path-mismatch", "path changed during write");
395
+ }
396
+
397
+ const realPath = await fs.realpath(params.ioPath);
398
+ if (!isPathInside(params.rootWithSep, realPath)) {
399
+ throw new SafeFsError("outside-root", "file is outside workspace root");
400
+ }
401
+
402
+ const dataSize = typeof params.data === "string"
403
+ ? Buffer.byteLength(params.data, params.encoding ?? "utf8")
404
+ : params.data.length;
405
+
406
+ if (params.maxBytes !== undefined && stat.size + dataSize > params.maxBytes) {
407
+ throw new SafeFsError(
408
+ "too-large",
409
+ `file would exceed limit of ${params.maxBytes} bytes`
410
+ );
411
+ }
412
+
413
+ if (typeof params.data === "string") {
414
+ await handle.writeFile(params.data, params.encoding ?? "utf8");
415
+ } else {
416
+ await handle.writeFile(params.data);
417
+ }
418
+
419
+ return {
420
+ realPath,
421
+ bytesWritten: dataSize,
422
+ created: false,
423
+ };
424
+ } finally {
425
+ await handle.close().catch(() => { });
426
+ }
427
+ }
428
+
429
+ async function overwriteFile(params: {
430
+ ioPath: string;
431
+ rootWithSep: string;
432
+ rootDir: string;
433
+ data: string | Buffer;
434
+ encoding?: BufferEncoding;
435
+ existingFile: boolean;
436
+ fileMode: number;
437
+ }): Promise<SafeWriteResult> {
438
+ let tempPath: string | null = null;
439
+ const destinationPath = params.ioPath;
440
+
441
+ try {
442
+ tempPath = buildAtomicWriteTempPath(destinationPath);
443
+ const writtenStat = await writeTempFileForAtomicReplace({
444
+ tempPath,
445
+ data: params.data,
446
+ encoding: params.encoding,
447
+ mode: params.fileMode,
448
+ });
449
+
450
+ await fs.rename(tempPath, destinationPath);
451
+ tempPath = null;
452
+
453
+ try {
454
+ await verifyAtomicWriteResult({
455
+ rootDir: params.rootDir,
456
+ targetPath: destinationPath,
457
+ expectedStat: writtenStat,
458
+ });
459
+ } catch (err) {
460
+ // 修复:重新抛出错误
461
+ throw new SafeFsError("write-failed", `${JSON.stringify(err)}`);
462
+ }
463
+
464
+ const dataSize = typeof params.data === "string"
465
+ ? Buffer.byteLength(params.data, params.encoding ?? "utf8")
466
+ : params.data.length;
467
+
468
+ return {
469
+ realPath: destinationPath,
470
+ bytesWritten: dataSize,
471
+ created: !params.existingFile,
472
+ };
473
+ } finally {
474
+ if (tempPath) {
475
+ await fs.rm(tempPath, { force: true }).catch(() => { });
476
+ }
477
+ }
478
+ }
479
+
480
+ export async function safeCopyFile(params: {
481
+ sourcePath: string;
482
+ rootDir: string;
483
+ relativePath: string;
484
+ maxBytes?: number;
485
+ mkdir?: boolean;
486
+ }): Promise<void> {
487
+ const source = await openVerifiedLocalFile(params.sourcePath, { rejectHardlinks: true });
488
+
489
+ if (params.maxBytes !== undefined && source.stat.size > params.maxBytes) {
490
+ await source.handle.close().catch(() => { });
491
+ throw new SafeFsError(
492
+ "too-large",
493
+ `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`
494
+ );
495
+ }
496
+
497
+ const { resolved } = await resolvePathWithinRoot({
498
+ rootDir: params.rootDir,
499
+ relativePath: params.relativePath,
500
+ });
501
+
502
+ if (params.mkdir !== false) {
503
+ await fs.mkdir(path.dirname(resolved), { recursive: true });
504
+ }
505
+
506
+ let tempPath: string | null = null;
507
+ let tempHandle: FileHandle | null = null;
508
+
509
+ try {
510
+ tempPath = buildAtomicWriteTempPath(resolved);
511
+ tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
512
+
513
+ const sourceStream = source.handle.createReadStream();
514
+ const targetStream = tempHandle.createWriteStream();
515
+
516
+ await new Promise<void>((resolve, reject) => {
517
+ sourceStream.pipe(targetStream);
518
+ sourceStream.on("end", resolve);
519
+ sourceStream.on("error", reject);
520
+ targetStream.on("error", reject);
521
+ });
522
+
523
+ await tempHandle.close().catch(() => { });
524
+ tempHandle = null;
525
+
526
+ const writtenStat = await fs.stat(tempPath);
527
+ await fs.rename(tempPath, resolved);
528
+ tempPath = null;
529
+
530
+ await verifyAtomicWriteResult({
531
+ rootDir: params.rootDir,
532
+ targetPath: resolved,
533
+ expectedStat: writtenStat,
534
+ });
535
+ } finally {
536
+ if (tempPath) {
537
+ await fs.rm(tempPath, { force: true }).catch(() => { });
538
+ }
539
+ if (tempHandle) {
540
+ await tempHandle.close().catch(() => { });
541
+ }
542
+ await source.handle.close().catch(() => { });
543
+ }
544
+ }
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from "fs";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname, join } from "path";
4
+
5
+ let g_isBetaVersion: boolean | null = null;
6
+
7
+ /**
8
+ * 检查当前版本是否为 beta 版本
9
+ */
10
+ export function isBetaVersion(): boolean {
11
+ if (g_isBetaVersion !== null) {
12
+ return !!g_isBetaVersion;
13
+ }
14
+
15
+ try {
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const packagePath = join(__dirname, "../../package.json");
19
+ const pkgContent = readFileSync(packagePath, "utf-8");
20
+ const pkg = JSON.parse(pkgContent);
21
+ const version = pkg.version || "";
22
+
23
+ g_isBetaVersion = version.includes("beta");
24
+ } catch (error) {
25
+ console.error(`Failed to check version: ${error}`);
26
+ }
27
+
28
+ return !!g_isBetaVersion;
29
+ }
@@ -0,0 +1,21 @@
1
+ import { Buffer } from "node:buffer";
2
+ import type WebSocket from "ws";
3
+
4
+ export function rawDataToString(
5
+ data: WebSocket.RawData,
6
+ encoding: BufferEncoding = "utf8",
7
+ ): string {
8
+ if (typeof data === "string") {
9
+ return data;
10
+ }
11
+ if (Buffer.isBuffer(data)) {
12
+ return data.toString(encoding);
13
+ }
14
+ if (Array.isArray(data)) {
15
+ return Buffer.concat(data).toString(encoding);
16
+ }
17
+ if (data instanceof ArrayBuffer) {
18
+ return Buffer.from(data).toString(encoding);
19
+ }
20
+ return Buffer.from(String(data)).toString(encoding);
21
+ }
@@ -1,54 +0,0 @@
1
- # open_app 打开应用工具使用说明
2
-
3
- ## Tool Command
4
-
5
- ```bash
6
- app.open
7
- ```
8
-
9
- ## Tool Parameters and Examples
10
-
11
- ```json
12
- {
13
- "name": "open_app",
14
- "description": "该工具用于帮助用户打开指定的 APP 应用,且不涉及任何具体 App 内部操作。",
15
- "parameters": {
16
- "app": {
17
- "description": "需要打开的应用名称,若用户指定具体应用,则填写应用名称,若用户明确表示“所有应用”,则参数值为 all",
18
- "type": "string"
19
- },
20
- "pkgName": {
21
- "description": "应用包名",
22
- "type": "string"
23
- },
24
- "app_description": {
25
- "description": "类型描述。对 app 的补充描述,无论是否为空,都可以有这个参数,当且仅当 app 为空时才调用 description。示例:办公应用、聊天软件、音乐类应用。",
26
- "type": "string"
27
- }
28
- },
29
- "required": [],
30
- "examples": [
31
- {
32
- "query": "打开小红书",
33
- "arguments": {
34
- "app": "网易云音乐",
35
- "pkgName": "com.xingin.xhs"
36
- }
37
- },
38
- {
39
- "query": "打开淘宝",
40
- "arguments": {
41
- "app": "微信",
42
- "pkgName": "com.taobao.taobao"
43
- }
44
- },
45
- {
46
- "query": "打开生活类软件",
47
- "arguments": {
48
- "app": "支付宝",
49
- "app_description": "生活"
50
- }
51
- }
52
- ]
53
- }
54
- ```