shellx-ai 1.0.11 → 1.0.12

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/dist/index.js CHANGED
@@ -71,7 +71,6 @@ function getWebSocket() {
71
71
  }
72
72
  // Node.js环境下动态导入ws模块
73
73
  try {
74
- // @ts-ignore - Dynamic import may not have types
75
74
  const wsModule = yield Promise.resolve().then(() => __importStar(require('ws')));
76
75
  return wsModule.default || wsModule;
77
76
  }
@@ -108,7 +107,11 @@ class ConnectionTaskClient {
108
107
  var _a, _b;
109
108
  try {
110
109
  this.wsUrl = yield this.authenticateDevice(this.deviceId);
111
- console.log('Initializing WebSocket client...' + this.wsUrl);
110
+ if (!this.wsUrl) {
111
+ this.config.onError(new Event("connection"));
112
+ return;
113
+ }
114
+ console.log('Initializing ShellX client wsUrl:' + this.wsUrl + " deviceId: " + this.deviceId);
112
115
  // 获取适合当前环境的WebSocket构造函数
113
116
  const WebSocketConstructor = yield getWebSocket();
114
117
  this.ws = new WebSocketConstructor(this.wsUrl);
@@ -153,6 +156,7 @@ class ConnectionTaskClient {
153
156
  }
154
157
  authenticateDevice(deviceId) {
155
158
  return __awaiter(this, void 0, void 0, function* () {
159
+ let fallbackUrl = undefined;
156
160
  // 1. 优先检测本地服务
157
161
  try {
158
162
  // fetch超时实现
@@ -164,9 +168,9 @@ class ConnectionTaskClient {
164
168
  if (localResp.ok) {
165
169
  const info = yield localResp.json();
166
170
  if (info && (info.status === 'ok' || info.status === 1) && info.uuid) {
167
- if (deviceId == info.uuid) {
171
+ if (deviceId == undefined || deviceId == info.uuid) {
168
172
  const localUuid = info.uuid;
169
- const fallbackUrl = `ws://127.0.0.1:9091/api/s/${localUuid}`;
173
+ fallbackUrl = `ws://127.0.0.1:9091/api/s/${localUuid}`;
170
174
  console.log('✅ [Auth] 本地ShellX服务可用,使用本地 /info 返回的 uuid:', localUuid, ',服务地址:', fallbackUrl);
171
175
  return fallbackUrl;
172
176
  }
@@ -176,11 +180,15 @@ class ConnectionTaskClient {
176
180
  catch (e) {
177
181
  // 本地不可用,继续走远程
178
182
  }
179
- const fallbackUrl = `ws://127.0.0.1:9091/api/s/${deviceId}`;
183
+ if (deviceId == undefined) {
184
+ console.warn('❌ [Auth] 设备ID未设置,本地未连接USB,请设置环境变量 SHELLX_DEVICE_ID 或者 USB连接设备');
185
+ return undefined;
186
+ }
180
187
  // 2. 远程认证逻辑
181
188
  authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
182
189
  if (!authKey) {
183
- throw new Error('SHELLX_AUTH_KEY environment variable is required');
190
+ authKey = (0, uuid_1.v4)();
191
+ console.log('✅ [Auth] SHELLX_AUTH_KEY 环境变量未设置, 生成新的authKey: ' + authKey);
184
192
  }
185
193
  try {
186
194
  console.log('🔑 [Auth] 正在认证设备...');
@@ -192,6 +200,12 @@ class ConnectionTaskClient {
192
200
  },
193
201
  });
194
202
  console.log('ShellX.ai设备认证响应:', jsonData);
203
+ // 验证认证响应是否有效
204
+ // 如果接口返回 null 或者缺少必要字段,说明设备未注册
205
+ if (!jsonData || jsonData === null || !jsonData.machine || !jsonData.authenticate) {
206
+ console.error('❌ [Auth] ShellX.ai设备未注册或认证信息无效');
207
+ return `wss://shellx.ai/api/s/${deviceId}`;
208
+ }
195
209
  // const jsonData = JSON.parse(data);
196
210
  console.log('✅ [Auth] ShellX.ai设备认证成功');
197
211
  console.log(`📡 [Auth] 设备ID: ${jsonData.authenticate}`);
@@ -252,19 +266,16 @@ class ConnectionTaskClient {
252
266
  try {
253
267
  authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
254
268
  if (!authKey) {
255
- console.error('❌ [Auth] SHELLX_AUTH_KEY 环境变量未设置');
256
- return;
269
+ authKey = (0, uuid_1.v4)();
270
+ console.log('✅ [Auth] SHELLX_AUTH_KEY 环境变量未设置, 生成新的authKey: ' + authKey);
257
271
  }
258
- console.log('🔑 [Auth] 发送认证消息...');
259
- // 从URL中提取认证密钥(如果URL包含认证信息)
260
- // 发送认证消息
261
272
  const authMessage = { authenticate: authKey };
262
273
  console.log('📤 [Auth] 发送认证消息:', { authenticate: authKey });
263
274
  if (this.ws && this.wsConnected) {
264
275
  this.ws.send((0, cbor_1.encode)(authMessage));
265
276
  }
266
277
  else {
267
- console.error('❌ [Auth] WebSocket未连接,无法发送认证消息');
278
+ console.error('❌ [Auth] ShellX 未连接,无法发送认证消息');
268
279
  }
269
280
  }
270
281
  catch (error) {
@@ -391,12 +402,8 @@ class ConnectionTaskClient {
391
402
  if (shouldResolve) {
392
403
  clearTimeout(task.timer);
393
404
  this.pendingTasks.delete(taskId);
394
- if ((responseData === null || responseData === void 0 ? void 0 : responseData.success) === false) {
395
- task.reject(new Error(responseData.errorMessage || 'Task failed'));
396
- }
397
- else {
398
- task.resolve(responseData);
399
- }
405
+ // 始终 resolve,保持原始响应数据结构不变
406
+ task.resolve(responseData);
400
407
  break; // Only resolve the first matching task
401
408
  }
402
409
  }
@@ -408,7 +415,9 @@ class ConnectionTaskClient {
408
415
  if (taskType) {
409
416
  const timer = setTimeout(() => {
410
417
  this.pendingTasks.delete(taskId);
411
- reject(new Error(`Task ${taskType} timeout (${timeout ? timeout : this.config.timeout}ms)`));
418
+ // 超时时返回 undefined 而不是 reject
419
+ console.log(`⏰ [ShellX] 任务超时: ${taskType}, 返回 undefined`);
420
+ resolve(undefined);
412
421
  }, timeout ? timeout : this.config.timeout);
413
422
  this.pendingTasks.set(taskId, {
414
423
  resolve,
@@ -429,7 +438,9 @@ class ConnectionTaskClient {
429
438
  if (taskType) {
430
439
  this.pendingTasks.delete(taskId);
431
440
  }
432
- reject(error);
441
+ // 发送失败时返回 undefined 而不是 reject
442
+ console.log(`❌ [ShellX] 发送消息失败,返回 undefined:`, error);
443
+ resolve(undefined);
433
444
  }
434
445
  }
435
446
  else {
@@ -673,7 +684,7 @@ class ConnectionTaskClient {
673
684
  });
674
685
  }
675
686
  flushQueue() {
676
- while (this.messageQueue.length > 0 && this.ws) {
687
+ while (this.messageQueue.length > 0 && this.ws && this.shellxConnected) {
677
688
  const message = this.messageQueue.shift();
678
689
  if (message) {
679
690
  try {
@@ -682,6 +693,9 @@ class ConnectionTaskClient {
682
693
  }
683
694
  catch (error) {
684
695
  console.error('Failed to send queued message:', error);
696
+ // Re-queue the message if sending fails
697
+ this.messageQueue.unshift(message);
698
+ break;
685
699
  }
686
700
  }
687
701
  }
package/dist/shellx.d.ts CHANGED
@@ -49,7 +49,7 @@ export declare class ShellX {
49
49
  */
50
50
  private combineSessionOutputs;
51
51
  /**
52
- * 通用重试机制
52
+ * 通用重试机制 - 失败时返回 undefined 而不是抛出异常
53
53
  */
54
54
  private withRetry;
55
55
  /**
@@ -88,6 +88,8 @@ export declare class ShellX {
88
88
  * 执行命令 - 兼容现有的 shell 命令执行
89
89
  */
90
90
  executeCommand(commandData: Command): Promise<CommandResult>;
91
+ executeCodeEval(context: any, code: string, timeout?: number): Promise<any>;
92
+ executeCode(agentCode: string, context: any, timeout?: number): Promise<any>;
91
93
  /**
92
94
  * 获取应用信息
93
95
  */
package/dist/shellx.js CHANGED
@@ -16,7 +16,6 @@ exports.ShellX = void 0;
16
16
  exports.createShellX = createShellX;
17
17
  exports.createShellXWithShellMonitoring = createShellXWithShellMonitoring;
18
18
  const uuid_1 = require("uuid");
19
- // 导入 WebSocketTaskClient 类
20
19
  const index_1 = __importDefault(require("./index"));
21
20
  const COMMAND_PTY_SID = 999;
22
21
  /**
@@ -105,7 +104,7 @@ class ShellX {
105
104
  return combined;
106
105
  }
107
106
  /**
108
- * 通用重试机制
107
+ * 通用重试机制 - 失败时返回 undefined 而不是抛出异常
109
108
  */
110
109
  withRetry(operation_1) {
111
110
  return __awaiter(this, arguments, void 0, function* (operation, options = {}) {
@@ -116,7 +115,8 @@ class ShellX {
116
115
  }
117
116
  catch (error) {
118
117
  if (attempt === retry) {
119
- throw error;
118
+ console.log(`❌ [Retry] 重试 ${retry} 次后仍然失败,返回 undefined:`, error);
119
+ return undefined;
120
120
  }
121
121
  if (onRetry) {
122
122
  onRetry(attempt, error);
@@ -125,7 +125,7 @@ class ShellX {
125
125
  yield new Promise(resolve => setTimeout(resolve, delay));
126
126
  }
127
127
  }
128
- throw new Error('重试次数已用完');
128
+ return undefined;
129
129
  });
130
130
  }
131
131
  /**
@@ -165,7 +165,7 @@ class ShellX {
165
165
  click(clickData) {
166
166
  return __awaiter(this, void 0, void 0, function* () {
167
167
  const startTime = Date.now();
168
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
168
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
169
169
  try {
170
170
  let target;
171
171
  if (clickData.targetElementId) {
@@ -221,6 +221,15 @@ class ShellX {
221
221
  throw new Error(`点击操作失败: ${error instanceof Error ? error.message : String(error)}`);
222
222
  }
223
223
  }), { retry: clickData.retry, delay: 500 });
224
+ // 如果 withRetry 返回 undefined,返回失败的 ActionResult
225
+ if (!result) {
226
+ return {
227
+ success: false,
228
+ error: '点击操作失败',
229
+ duration: Date.now() - startTime
230
+ };
231
+ }
232
+ return result;
224
233
  });
225
234
  }
226
235
  /**
@@ -229,7 +238,7 @@ class ShellX {
229
238
  input(inputData) {
230
239
  return __awaiter(this, void 0, void 0, function* () {
231
240
  const startTime = Date.now();
232
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
241
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
233
242
  var _a, _b;
234
243
  try {
235
244
  let target;
@@ -279,6 +288,14 @@ class ShellX {
279
288
  throw new Error(`输入操作失败: ${error instanceof Error ? error.message : String(error)}`);
280
289
  }
281
290
  }), { retry: inputData.retry, delay: 500 });
291
+ if (!result) {
292
+ return {
293
+ success: false,
294
+ error: '输入操作失败',
295
+ duration: Date.now() - startTime
296
+ };
297
+ }
298
+ return result;
282
299
  });
283
300
  }
284
301
  /**
@@ -287,7 +304,7 @@ class ShellX {
287
304
  swipe(swipeData) {
288
305
  return __awaiter(this, void 0, void 0, function* () {
289
306
  const startTime = Date.now();
290
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
307
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
291
308
  try {
292
309
  const from = { x: swipeData.fromX, y: swipeData.fromY };
293
310
  const to = { x: swipeData.toX, y: swipeData.toY };
@@ -314,6 +331,14 @@ class ShellX {
314
331
  throw new Error(`滑动操作失败: ${error instanceof Error ? error.message : String(error)}`);
315
332
  }
316
333
  }), { retry: swipeData.retry, delay: 500 });
334
+ if (!result) {
335
+ return {
336
+ success: false,
337
+ error: '滑动操作失败',
338
+ duration: Date.now() - startTime
339
+ };
340
+ }
341
+ return result;
317
342
  });
318
343
  }
319
344
  /**
@@ -322,7 +347,7 @@ class ShellX {
322
347
  pressKey(keyData) {
323
348
  return __awaiter(this, void 0, void 0, function* () {
324
349
  const startTime = Date.now();
325
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
350
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
326
351
  try {
327
352
  const action = {
328
353
  title: `按键操作: ${keyData.key}${keyData.longPress ? ' (长按)' : ''}`,
@@ -348,6 +373,14 @@ class ShellX {
348
373
  throw new Error(`按键操作失败: ${error instanceof Error ? error.message : String(error)}`);
349
374
  }
350
375
  }), { retry: keyData.retry, delay: 500 });
376
+ if (!result) {
377
+ return {
378
+ success: false,
379
+ error: '按键操作失败',
380
+ duration: Date.now() - startTime
381
+ };
382
+ }
383
+ return result;
351
384
  });
352
385
  }
353
386
  /**
@@ -356,7 +389,7 @@ class ShellX {
356
389
  wait(waitData) {
357
390
  return __awaiter(this, void 0, void 0, function* () {
358
391
  const startTime = Date.now();
359
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
392
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
360
393
  try {
361
394
  const selector = this.convertSelector(waitData);
362
395
  const timeout = waitData.timeout || 10000;
@@ -370,7 +403,12 @@ class ShellX {
370
403
  visibleOnly: waitData.condition === 'visible',
371
404
  clickableOnly: waitData.condition === 'clickable'
372
405
  });
373
- if (result.elements.length > 0) {
406
+ // 如果服务器返回失败,跳过本次尝试
407
+ if (!result || result.success === false) {
408
+ yield new Promise(resolve => setTimeout(resolve, interval));
409
+ continue;
410
+ }
411
+ if (result.elements && result.elements.length > 0) {
374
412
  if (waitData.condition === 'gone') {
375
413
  // 等待元素消失,继续等待
376
414
  yield new Promise(resolve => setTimeout(resolve, interval));
@@ -405,6 +443,14 @@ class ShellX {
405
443
  throw new Error(`等待操作失败: ${error instanceof Error ? error.message : String(error)}`);
406
444
  }
407
445
  }), { retry: waitData.retry, delay: 500 });
446
+ if (!result) {
447
+ return {
448
+ success: false,
449
+ error: '等待操作失败',
450
+ duration: Date.now() - startTime
451
+ };
452
+ }
453
+ return result;
408
454
  });
409
455
  }
410
456
  /**
@@ -412,7 +458,7 @@ class ShellX {
412
458
  */
413
459
  find(findData) {
414
460
  return __awaiter(this, void 0, void 0, function* () {
415
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
461
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
416
462
  try {
417
463
  const selector = this.convertSelector(findData);
418
464
  const result = yield this.client.findElement(selector, {
@@ -423,6 +469,13 @@ class ShellX {
423
469
  clickableOnly: false,
424
470
  multiple: findData.multiple || false
425
471
  });
472
+ // 检查服务器是否返回了失败响应
473
+ if (!result || result.success === false) {
474
+ throw new Error((result === null || result === void 0 ? void 0 : result.errorMessage) || '查找元素失败');
475
+ }
476
+ if (!result.elements) {
477
+ throw new Error('未找到元素');
478
+ }
426
479
  const elements = result.elements.map(element => this.convertElement(element));
427
480
  return {
428
481
  elements,
@@ -435,6 +488,15 @@ class ShellX {
435
488
  throw new Error(`查找操作失败: ${error instanceof Error ? error.message : String(error)}`);
436
489
  }
437
490
  }), { retry: findData.retry, delay: 500 });
491
+ if (!result) {
492
+ return {
493
+ elements: [],
494
+ count: 0,
495
+ success: false,
496
+ found: false
497
+ };
498
+ }
499
+ return result;
438
500
  });
439
501
  }
440
502
  /**
@@ -442,7 +504,7 @@ class ShellX {
442
504
  */
443
505
  executeCommand(commandData) {
444
506
  return __awaiter(this, void 0, void 0, function* () {
445
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
507
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
446
508
  try {
447
509
  const result = yield this.executeShellCommand(commandData.cmd, {
448
510
  title: `执行命令: ${commandData.cmd}`,
@@ -455,6 +517,35 @@ class ShellX {
455
517
  throw new Error(`命令执行失败: ${error instanceof Error ? error.message : String(error)}`);
456
518
  }
457
519
  }), { retry: commandData.retry, delay: 1000 });
520
+ if (!result) {
521
+ return {
522
+ success: false,
523
+ output: '',
524
+ error: '命令执行失败',
525
+ duration: 0
526
+ };
527
+ }
528
+ return result;
529
+ });
530
+ }
531
+ executeCodeEval(context, code, timeout) {
532
+ const evalInstance = require('eval');
533
+ return evalInstance(code, context, true);
534
+ }
535
+ executeCode(agentCode, context, timeout) {
536
+ return __awaiter(this, void 0, void 0, function* () {
537
+ // console.log('executeCode', agentCode);
538
+ // Interpreter.global = context;
539
+ // const evalFunc = getEvalInstance(context);
540
+ // 如果没有指定 timeout,直接执行
541
+ if (!timeout) {
542
+ return yield this.executeCodeEval(context, agentCode);
543
+ }
544
+ // 使用 timeout 执行 - 修复:await 应该在 Promise.race 上,而不是在内部的 promise 上
545
+ return yield Promise.race([
546
+ this.executeCodeEval(context, agentCode),
547
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`代码执行超时: ${timeout}ms`)), timeout))
548
+ ]);
458
549
  });
459
550
  }
460
551
  /**
@@ -463,7 +554,7 @@ class ShellX {
463
554
  getAppInfo(appInfoData) {
464
555
  return __awaiter(this, void 0, void 0, function* () {
465
556
  const startTime = Date.now();
466
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
557
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
467
558
  try {
468
559
  const action = {
469
560
  title: `获取应用信息: ${appInfoData.package}`,
@@ -483,6 +574,14 @@ class ShellX {
483
574
  throw new Error(`获取应用信息失败: ${error instanceof Error ? error.message : String(error)}`);
484
575
  }
485
576
  }), { retry: appInfoData.retry, delay: 500 });
577
+ if (!result) {
578
+ return {
579
+ success: false,
580
+ error: '获取应用信息失败',
581
+ duration: Date.now() - startTime
582
+ };
583
+ }
584
+ return result;
486
585
  });
487
586
  }
488
587
  /**
@@ -491,7 +590,7 @@ class ShellX {
491
590
  takeScreenshot() {
492
591
  return __awaiter(this, arguments, void 0, function* (screenshotData = {}) {
493
592
  const startTime = Date.now();
494
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
593
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
495
594
  try {
496
595
  const options = {
497
596
  format: screenshotData.format || 'png',
@@ -508,6 +607,10 @@ class ShellX {
508
607
  };
509
608
  }
510
609
  const screenshot = yield this.client.screenShot(options);
610
+ // 检查服务器是否返回了失败响应
611
+ if (!screenshot || screenshot.success === false) {
612
+ throw new Error((screenshot === null || screenshot === void 0 ? void 0 : screenshot.errorMessage) || '截图失败');
613
+ }
511
614
  return {
512
615
  success: true,
513
616
  data: screenshot,
@@ -518,6 +621,14 @@ class ShellX {
518
621
  throw new Error(`截图操作失败: ${error instanceof Error ? error.message : String(error)}`);
519
622
  }
520
623
  }), { retry: screenshotData.retry, delay: 500 });
624
+ if (!result) {
625
+ return {
626
+ success: false,
627
+ error: '截图操作失败',
628
+ duration: Date.now() - startTime
629
+ };
630
+ }
631
+ return result;
521
632
  });
522
633
  }
523
634
  /**
@@ -568,11 +679,13 @@ class ShellX {
568
679
  result = yield this.takeScreenshot(action);
569
680
  }
570
681
  results.push(result);
571
- // 如果操作失败且不允许继续,则抛出错误
682
+ // 记录操作结果但不抛出错误,让调用者决定如何处理失败的操作
572
683
  if (!result.success) {
573
- throw new Error(`操作 ${index + 1} 失败: ${result.error}`);
684
+ console.warn(`⚠️ [Actions] ${index + 1} 个操作失败: ${result.error}`);
685
+ }
686
+ else {
687
+ console.log(`✅ [Actions] 第 ${index + 1} 个操作执行成功`);
574
688
  }
575
- console.log(`✅ [Actions] 第 ${index + 1} 个操作执行成功`);
576
689
  }
577
690
  catch (error) {
578
691
  const errorResult = {
@@ -582,7 +695,7 @@ class ShellX {
582
695
  };
583
696
  results.push(errorResult);
584
697
  console.error(`❌ [Actions] 第 ${index + 1} 个操作执行失败:`, error);
585
- throw error; // 停止执行后续操作
698
+ // 不再抛出错误,继续执行后续操作
586
699
  }
587
700
  }
588
701
  return results;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shellx-ai",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "shellx is a powerful WebSocket-based client for controlling shell commands and UI automation on remote devices.",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/10cl/shellx.git",
@@ -33,7 +33,9 @@
33
33
  "cbor": "^9.0.0",
34
34
  "dotenv": "^16.4.5",
35
35
  "ofetch": "^1.4.1",
36
- "uuid": "^11.1.0"
36
+ "uuid": "^11.1.0",
37
+ "promptflow-eval": "1.0.0",
38
+ "@babel/standalone": "^7.18.13"
37
39
  },
38
40
  "optionalDependencies": {
39
41
  "node-fetch": "^3.3.2",
@@ -47,6 +49,7 @@
47
49
  "langchain": "^0.1.34",
48
50
  "promptflow-eval": "1.0.0",
49
51
  "promptflow-template": "1.0.0",
52
+ "eval": "^0.1.8",
50
53
  "promptflowx": "^0.1.9",
51
54
  "ts-jest": "^29.4.0",
52
55
  "ts-node": "^10.9.2",