shellx-ai 1.0.10 → 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.d.ts CHANGED
@@ -25,7 +25,6 @@ export interface PendingTask {
25
25
  reject: (err: Error) => void;
26
26
  timer: number;
27
27
  type: string;
28
- commandType?: string;
29
28
  }
30
29
  /**
31
30
  * Enhanced WebSocket Task Client with protocol-aware task methods
@@ -59,7 +58,7 @@ export declare class ConnectionTaskClient {
59
58
  private handleMessage;
60
59
  private processServerMessage;
61
60
  private handleJsonDataResponse;
62
- sendMessage(message: WsClient, taskType?: string, commandType?: string): Promise<any>;
61
+ sendMessage(message: WsClient, taskType?: string, timeout?: number): Promise<any>;
63
62
  /**
64
63
  * Find UI elements on the screen
65
64
  */
@@ -90,7 +89,7 @@ export declare class ConnectionTaskClient {
90
89
  /**
91
90
  * Execute an action sequence
92
91
  */
93
- executeAction(actionSequence: ActionSequence, taskId?: string): Promise<any>;
92
+ executeAction(actionSequence: ActionSequence, taskId?: string, timeeout?: number): Promise<any>;
94
93
  /**
95
94
  * Execute promptflow actions
96
95
  */
@@ -130,7 +129,7 @@ export declare class ConnectionTaskClient {
130
129
  * @param taskType 任务类型
131
130
  * @returns Promise<{taskId: string, promise: Promise<any>}>
132
131
  */
133
- sendMessageWithTaskId(message: WsClient, taskType?: string, definedTaskId?: string): Promise<{
132
+ sendMessageWithTaskId(message: WsClient, taskType: string, definedTaskId?: string, timeout?: number): Promise<{
134
133
  taskId: string;
135
134
  promise: Promise<any>;
136
135
  }>;
@@ -168,7 +167,6 @@ export declare class ConnectionTaskClient {
168
167
  */
169
168
  getTaskInfo(taskId: string): {
170
169
  type: string;
171
- commandType?: string;
172
170
  } | null;
173
171
  /**
174
172
  * 批量完成指定类型的任务
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
  }
@@ -81,6 +80,9 @@ function getWebSocket() {
81
80
  }
82
81
  });
83
82
  }
83
+ function isPendingTask(taskType) {
84
+ return taskType && taskType !== "command";
85
+ }
84
86
  /**
85
87
  * Enhanced WebSocket Task Client with protocol-aware task methods
86
88
  */
@@ -105,7 +107,11 @@ class ConnectionTaskClient {
105
107
  var _a, _b;
106
108
  try {
107
109
  this.wsUrl = yield this.authenticateDevice(this.deviceId);
108
- 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);
109
115
  // 获取适合当前环境的WebSocket构造函数
110
116
  const WebSocketConstructor = yield getWebSocket();
111
117
  this.ws = new WebSocketConstructor(this.wsUrl);
@@ -150,6 +156,7 @@ class ConnectionTaskClient {
150
156
  }
151
157
  authenticateDevice(deviceId) {
152
158
  return __awaiter(this, void 0, void 0, function* () {
159
+ let fallbackUrl = undefined;
153
160
  // 1. 优先检测本地服务
154
161
  try {
155
162
  // fetch超时实现
@@ -161,9 +168,9 @@ class ConnectionTaskClient {
161
168
  if (localResp.ok) {
162
169
  const info = yield localResp.json();
163
170
  if (info && (info.status === 'ok' || info.status === 1) && info.uuid) {
164
- if (deviceId == info.uuid) {
171
+ if (deviceId == undefined || deviceId == info.uuid) {
165
172
  const localUuid = info.uuid;
166
- const fallbackUrl = `ws://127.0.0.1:9091/api/s/${localUuid}`;
173
+ fallbackUrl = `ws://127.0.0.1:9091/api/s/${localUuid}`;
167
174
  console.log('✅ [Auth] 本地ShellX服务可用,使用本地 /info 返回的 uuid:', localUuid, ',服务地址:', fallbackUrl);
168
175
  return fallbackUrl;
169
176
  }
@@ -173,11 +180,15 @@ class ConnectionTaskClient {
173
180
  catch (e) {
174
181
  // 本地不可用,继续走远程
175
182
  }
176
- 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
+ }
177
187
  // 2. 远程认证逻辑
178
188
  authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
179
189
  if (!authKey) {
180
- 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);
181
192
  }
182
193
  try {
183
194
  console.log('🔑 [Auth] 正在认证设备...');
@@ -189,6 +200,12 @@ class ConnectionTaskClient {
189
200
  },
190
201
  });
191
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
+ }
192
209
  // const jsonData = JSON.parse(data);
193
210
  console.log('✅ [Auth] ShellX.ai设备认证成功');
194
211
  console.log(`📡 [Auth] 设备ID: ${jsonData.authenticate}`);
@@ -249,19 +266,16 @@ class ConnectionTaskClient {
249
266
  try {
250
267
  authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
251
268
  if (!authKey) {
252
- console.error('❌ [Auth] SHELLX_AUTH_KEY 环境变量未设置');
253
- return;
269
+ authKey = (0, uuid_1.v4)();
270
+ console.log('✅ [Auth] SHELLX_AUTH_KEY 环境变量未设置, 生成新的authKey: ' + authKey);
254
271
  }
255
- console.log('🔑 [Auth] 发送认证消息...');
256
- // 从URL中提取认证密钥(如果URL包含认证信息)
257
- // 发送认证消息
258
272
  const authMessage = { authenticate: authKey };
259
273
  console.log('📤 [Auth] 发送认证消息:', { authenticate: authKey });
260
274
  if (this.ws && this.wsConnected) {
261
275
  this.ws.send((0, cbor_1.encode)(authMessage));
262
276
  }
263
277
  else {
264
- console.error('❌ [Auth] WebSocket未连接,无法发送认证消息');
278
+ console.error('❌ [Auth] ShellX 未连接,无法发送认证消息');
265
279
  }
266
280
  }
267
281
  catch (error) {
@@ -373,42 +387,43 @@ class ConnectionTaskClient {
373
387
  }
374
388
  break;
375
389
  case 'action':
376
- /*
377
390
  if (jsonData.action_event) {
378
- responseData = jsonData.action_event;
379
- shouldResolve = true;
380
- }*/
381
- // 命令,按键,滑动,点击,等待,查找,等待,屏幕截图,屏幕信息,屏幕变化,应用列表, 需要自己处理
391
+ responseData = jsonData.action_event;
392
+ shouldResolve = true;
393
+ }
394
+ break;
395
+ case 'actions':
396
+ if (jsonData.actions) {
397
+ responseData = jsonData.actions;
398
+ shouldResolve = true;
399
+ }
382
400
  break;
383
401
  }
384
402
  if (shouldResolve) {
385
403
  clearTimeout(task.timer);
386
404
  this.pendingTasks.delete(taskId);
387
- if ((responseData === null || responseData === void 0 ? void 0 : responseData.success) === false) {
388
- task.reject(new Error(responseData.errorMessage || 'Task failed'));
389
- }
390
- else {
391
- task.resolve(responseData);
392
- }
405
+ // 始终 resolve,保持原始响应数据结构不变
406
+ task.resolve(responseData);
393
407
  break; // Only resolve the first matching task
394
408
  }
395
409
  }
396
410
  }
397
- sendMessage(message, taskType, commandType) {
411
+ sendMessage(message, taskType, timeout) {
398
412
  return __awaiter(this, void 0, void 0, function* () {
399
413
  return new Promise((resolve, reject) => {
400
414
  const taskId = (0, uuid_1.v4)();
401
415
  if (taskType) {
402
416
  const timer = setTimeout(() => {
403
417
  this.pendingTasks.delete(taskId);
404
- reject(new Error(`Task ${taskType} timeout (${this.config.timeout}ms)`));
405
- }, this.config.timeout);
418
+ // 超时时返回 undefined 而不是 reject
419
+ console.log(`⏰ [ShellX] 任务超时: ${taskType}, 返回 undefined`);
420
+ resolve(undefined);
421
+ }, timeout ? timeout : this.config.timeout);
406
422
  this.pendingTasks.set(taskId, {
407
423
  resolve,
408
424
  reject,
409
425
  timer,
410
- type: taskType,
411
- commandType,
426
+ type: taskType
412
427
  });
413
428
  console.log(`📋 [ShellX] 创建任务: ${taskId}, 类型: ${taskType}}`);
414
429
  }
@@ -423,7 +438,9 @@ class ConnectionTaskClient {
423
438
  if (taskType) {
424
439
  this.pendingTasks.delete(taskId);
425
440
  }
426
- reject(error);
441
+ // 发送失败时返回 undefined 而不是 reject
442
+ console.log(`❌ [ShellX] 发送消息失败,返回 undefined:`, error);
443
+ resolve(undefined);
427
444
  }
428
445
  }
429
446
  else {
@@ -510,9 +527,9 @@ class ConnectionTaskClient {
510
527
  /**
511
528
  * Execute an action sequence
512
529
  */
513
- executeAction(actionSequence, taskId) {
530
+ executeAction(actionSequence, taskId, timeeout) {
514
531
  return __awaiter(this, void 0, void 0, function* () {
515
- return this.sendMessageWithTaskId({ actions: actionSequence }, 'action', taskId);
532
+ return this.sendMessageWithTaskId({ actions: actionSequence }, 'action', taskId, timeeout);
516
533
  });
517
534
  }
518
535
  /**
@@ -579,19 +596,19 @@ class ConnectionTaskClient {
579
596
  * @param taskType 任务类型
580
597
  * @returns Promise<{taskId: string, promise: Promise<any>}>
581
598
  */
582
- sendMessageWithTaskId(message, taskType, definedTaskId) {
599
+ sendMessageWithTaskId(message, taskType, definedTaskId, timeout) {
583
600
  return new Promise((resolve) => {
584
601
  let taskId = definedTaskId;
585
602
  if (taskId == null) {
586
603
  taskId = (0, uuid_1.v4)();
587
604
  }
588
- if (taskType) {
605
+ if (isPendingTask(taskType)) {
589
606
  const timer = setTimeout(() => {
590
607
  if (taskId != null) {
591
608
  this.pendingTasks.delete(taskId);
592
609
  }
593
610
  console.log(`⏰ [ShellX] 任务超时: ${taskId}, 类型: ${taskType}`);
594
- }, this.config.timeout);
611
+ }, timeout ? timeout : this.config.timeout);
595
612
  this.pendingTasks.set(taskId, {
596
613
  resolve: () => { },
597
614
  reject: () => { },
@@ -605,7 +622,7 @@ class ConnectionTaskClient {
605
622
  console.log(Date.now() + ' 📤 [ShellX] 发送消息:', JSON.stringify(message));
606
623
  this.ws.send((0, cbor_1.encode)(message));
607
624
  const promise = new Promise((promiseResolve, promiseReject) => {
608
- if (taskType) {
625
+ if (isPendingTask(taskType)) {
609
626
  if (taskId != null) {
610
627
  const task = this.pendingTasks.get(taskId);
611
628
  if (task) {
@@ -621,7 +638,7 @@ class ConnectionTaskClient {
621
638
  resolve({ taskId, promise });
622
639
  }
623
640
  catch (error) {
624
- if (taskType) {
641
+ if (isPendingTask(taskType)) {
625
642
  this.pendingTasks.delete(taskId);
626
643
  }
627
644
  resolve({ taskId, promise: Promise.reject(error) });
@@ -667,7 +684,7 @@ class ConnectionTaskClient {
667
684
  });
668
685
  }
669
686
  flushQueue() {
670
- while (this.messageQueue.length > 0 && this.ws) {
687
+ while (this.messageQueue.length > 0 && this.ws && this.shellxConnected) {
671
688
  const message = this.messageQueue.shift();
672
689
  if (message) {
673
690
  try {
@@ -676,6 +693,9 @@ class ConnectionTaskClient {
676
693
  }
677
694
  catch (error) {
678
695
  console.error('Failed to send queued message:', error);
696
+ // Re-queue the message if sending fails
697
+ this.messageQueue.unshift(message);
698
+ break;
679
699
  }
680
700
  }
681
701
  }
@@ -760,8 +780,7 @@ class ConnectionTaskClient {
760
780
  return null;
761
781
  }
762
782
  return {
763
- type: task.type,
764
- commandType: task.commandType,
783
+ type: task.type
765
784
  };
766
785
  }
767
786
  /**
package/dist/shellx.d.ts CHANGED
@@ -40,10 +40,6 @@ export declare class ShellX {
40
40
  * 处理 shell 输出数据
41
41
  */
42
42
  handleShellOutput(chunks: [any, number, Uint8Array[]]): void;
43
- /**
44
- * 判断输出是否属于特定命令
45
- */
46
- private isOutputForCommand;
47
43
  /**
48
44
  * 清理命令输出,去除输入的命令内容
49
45
  */
@@ -53,31 +49,7 @@ export declare class ShellX {
53
49
  */
54
50
  private combineSessionOutputs;
55
51
  /**
56
- * 转义正则表达式特殊字符
57
- */
58
- private escapeRegExp;
59
- /**
60
- * 检查命令是否完成
61
- */
62
- private checkCommandCompletion;
63
- /**
64
- * 检查输出是否包含错误指示器
65
- */
66
- private hasErrorIndicators;
67
- /**
68
- * 判断命令是否完成(基于时间)
69
- */
70
- private isCommandComplete;
71
- /**
72
- * 解析命令 Promise
73
- */
74
- private resolveCommand;
75
- /**
76
- * 生成命令唯一标识
77
- */
78
- private generateCommandKey;
79
- /**
80
- * 通用重试机制
52
+ * 通用重试机制 - 失败时返回 undefined 而不是抛出异常
81
53
  */
82
54
  private withRetry;
83
55
  /**
@@ -116,6 +88,8 @@ export declare class ShellX {
116
88
  * 执行命令 - 兼容现有的 shell 命令执行
117
89
  */
118
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>;
119
93
  /**
120
94
  * 获取应用信息
121
95
  */
package/dist/shellx.js CHANGED
@@ -16,11 +16,7 @@ exports.ShellX = void 0;
16
16
  exports.createShellX = createShellX;
17
17
  exports.createShellXWithShellMonitoring = createShellXWithShellMonitoring;
18
18
  const uuid_1 = require("uuid");
19
- const utils_1 = require("./utils");
20
- // 导入 WebSocketTaskClient 类
21
19
  const index_1 = __importDefault(require("./index"));
22
- // 安全地获取环境变量,兼容浏览器和Node.js环境
23
- let authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
24
20
  const COMMAND_PTY_SID = 999;
25
21
  /**
26
22
  * ShellX automation utilities for common patterns
@@ -63,27 +59,24 @@ class ShellX {
63
59
  for (const data of dataArrays) {
64
60
  output += new TextDecoder().decode(data);
65
61
  }
66
- console.log(`📟 [Shell] 收到输出 (Session ${sessionId}): ${output.trim()}`);
62
+ /*console.log(
63
+ `📟 [Shell] 收到输出 (Session ${sessionId}): ${output.trim()}`,
64
+ );*/
67
65
  // 为每个等待的命令累积输出
68
- for (const [commandKey, commandPromise] of this.shellCommandPromises.entries()) {
69
- // 检查是否该命令相关的输出
70
- if (this.isOutputForCommand(output, commandPromise.command, sessionId)) {
71
- // 存储该 session 的输出
72
- if (!commandPromise.sessionOutputs.has(sessionId)) {
73
- commandPromise.sessionOutputs.set(sessionId, '');
74
- }
75
- const currentSessionOutput = commandPromise.sessionOutputs.get(sessionId) || '';
76
- commandPromise.sessionOutputs.set(sessionId, currentSessionOutput + output);
77
- // 更新总输出
78
- commandPromise.output = this.combineSessionOutputs(commandPromise.sessionOutputs);
79
- console.log(`📊 [Shell] 命令 ${commandKey} 累积输出长度: ${commandPromise.output.length}`);
80
- // 调用输出回调(传递清理后的输出)
81
- if (commandPromise.options.onOutput) {
82
- const cleanOutput = this.cleanCommandOutput(output, commandPromise.command);
83
- commandPromise.options.onOutput(cleanOutput);
84
- }
85
- // 检查是否满足完成条件
86
- this.checkCommandCompletion(commandKey, commandPromise, output);
66
+ for (const [commandKey, commandPromise,] of this.shellCommandPromises.entries()) {
67
+ if (!commandPromise.sessionOutputs.has(sessionId)) {
68
+ commandPromise.sessionOutputs.set(sessionId, '');
69
+ }
70
+ const currentSessionOutput = commandPromise.sessionOutputs.get(sessionId) || '';
71
+ commandPromise.sessionOutputs.set(sessionId, currentSessionOutput + output);
72
+ commandPromise.output = this.combineSessionOutputs(commandPromise.sessionOutputs);
73
+ /*console.log(
74
+ `📊 [Shell] 命令 ${commandKey} 累积输出长度: ${commandPromise.output.length}`,
75
+ );*/
76
+ // 调用输出回调(传递清理后的输出)
77
+ if (commandPromise.options.onOutput) {
78
+ const cleanOutput = this.cleanCommandOutput(output, commandPromise.command);
79
+ commandPromise.options.onOutput(cleanOutput);
87
80
  }
88
81
  }
89
82
  }
@@ -92,56 +85,11 @@ class ShellX {
92
85
  }
93
86
  }
94
87
  }
95
- /**
96
- * 判断输出是否属于特定命令
97
- */
98
- isOutputForCommand(output, command, sessionId) {
99
- // 如果输出包含命令本身,说明是命令回显
100
- if (output.includes(command)) {
101
- return true;
102
- }
103
- // 如果有多个命令在等待,按时间顺序分配
104
- const waitingCommands = Array.from(this.shellCommandPromises.keys());
105
- if (waitingCommands.length === 1) {
106
- return true; // 只有一个命令在等待,所有输出都属于它
107
- }
108
- // 多个命令时,根据 sessionId 和命令创建时间进行启发式匹配
109
- return true; // 暂时返回 true,让所有命令都接收输出
110
- }
111
88
  /**
112
89
  * 清理命令输出,去除输入的命令内容
113
90
  */
114
91
  cleanCommandOutput(output, command) {
115
- if (!output || !command) {
116
- return output;
117
- }
118
- let cleanOutput = output;
119
- // 去除命令回显(命令本身)
120
- cleanOutput = cleanOutput.replace(new RegExp(this.escapeRegExp(command), 'g'), '');
121
- // 去除可能的命令提示符前缀和后缀
122
- const promptPatterns = [
123
- /^\s*[^$#>\s]*[#$>]\s*/, // 匹配提示符前缀
124
- /^\s*[^$#>\s]*@[^$#>\s]*[#$>]\s*/, // 匹配 user@host 格式
125
- /^\s*[^$#>\s]*:\s*[^$#>\s]*[#$>]\s*/, // 匹配 path: 格式
126
- /\s*[^$#>\s]*[#$>]\s*$/, // 匹配提示符后缀
127
- /\s*[^$#>\s]*@[^$#>\s]*[#$>]\s*$/, // 匹配 user@host 后缀
128
- /\s*[^$#>\s]*:\s*[^$#>\s]*[#$>]\s*$/, // 匹配 path: 后缀
129
- /\s*\d+\|[^$#>\s]*[#$>]\s*$/, // 匹配 Android 格式后缀
130
- ];
131
- for (const pattern of promptPatterns) {
132
- cleanOutput = cleanOutput.replace(pattern, '');
133
- }
134
- // 去除多余的空行和空格
135
- cleanOutput = cleanOutput.replace(/^\s+|\s+$/g, ''); // 去除首尾空白
136
- cleanOutput = cleanOutput.replace(/\n\s*\n/g, '\n'); // 去除多余空行
137
- // 如果清理后为空,返回空字符串
138
- if (cleanOutput.trim().length === 0) {
139
- return '';
140
- }
141
- console.log(`🧹 [Shell] 清理命令输出:`);
142
- console.log(` 原始: "${output.trim()}"`);
143
- console.log(` 清理后: "${cleanOutput.trim()}"`);
144
- return cleanOutput;
92
+ return output.replace(command, '');
145
93
  }
146
94
  /**
147
95
  * 合并多个 session 的输出
@@ -156,75 +104,7 @@ class ShellX {
156
104
  return combined;
157
105
  }
158
106
  /**
159
- * 转义正则表达式特殊字符
160
- */
161
- escapeRegExp(string) {
162
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
163
- }
164
- /**
165
- * 检查命令是否完成
166
- */
167
- checkCommandCompletion(commandKey, commandPromise, newOutput) {
168
- const { resolve, startTime, options, output, command } = commandPromise;
169
- console.log(`🔍 [Shell] 检查命令 "${command}" 完成条件,当前输出长度: ${output.length}`);
170
- if (this.isCommandComplete(output, command)) {
171
- console.log(`✅ [Shell] 命令 "${command}" 执行完成 (检测到提示符)`);
172
- this.resolveCommand(commandKey, {
173
- success: true,
174
- output: output.trim(),
175
- duration: Date.now() - startTime
176
- });
177
- return;
178
- }
179
- console.log(`⏳ [Shell] 命令 "${command}" 继续等待完成...`);
180
- }
181
- /**
182
- * 检查输出是否包含错误指示器
183
- */
184
- hasErrorIndicators(output) {
185
- const errorPatterns = [
186
- /error/i,
187
- /failed/i,
188
- /not found/i,
189
- /permission denied/i,
190
- /command not found/i,
191
- /no such file/i,
192
- /syntax error/i
193
- ];
194
- return errorPatterns.some(pattern => pattern.test(output));
195
- }
196
- /**
197
- * 判断命令是否完成(基于时间)
198
- */
199
- isCommandComplete(output, command) {
200
- // 简化逻辑:只基于时间判断,不依赖提示符检测
201
- // 命令完成由超时机制控制
202
- return false; // 让超时机制处理命令完成
203
- }
204
- /**
205
- * 解析命令 Promise
206
- */
207
- resolveCommand(commandKey, result) {
208
- const commandPromise = this.shellCommandPromises.get(commandKey);
209
- if (commandPromise) {
210
- console.log(`✅ [Shell] 解析命令 Promise: ${commandKey}`);
211
- // 清理最终输出结果
212
- const cleanResult = Object.assign(Object.assign({}, result), { output: this.cleanCommandOutput(result.output, commandPromise.command) });
213
- commandPromise.resolve(cleanResult);
214
- this.shellCommandPromises.delete(commandKey);
215
- }
216
- else {
217
- console.log(`⚠️ [Shell] 未找到命令 Promise: ${commandKey}`);
218
- }
219
- }
220
- /**
221
- * 生成命令唯一标识
222
- */
223
- generateCommandKey(command) {
224
- return `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
225
- }
226
- /**
227
- * 通用重试机制
107
+ * 通用重试机制 - 失败时返回 undefined 而不是抛出异常
228
108
  */
229
109
  withRetry(operation_1) {
230
110
  return __awaiter(this, arguments, void 0, function* (operation, options = {}) {
@@ -235,7 +115,8 @@ class ShellX {
235
115
  }
236
116
  catch (error) {
237
117
  if (attempt === retry) {
238
- throw error;
118
+ console.log(`❌ [Retry] 重试 ${retry} 次后仍然失败,返回 undefined:`, error);
119
+ return undefined;
239
120
  }
240
121
  if (onRetry) {
241
122
  onRetry(attempt, error);
@@ -244,7 +125,7 @@ class ShellX {
244
125
  yield new Promise(resolve => setTimeout(resolve, delay));
245
126
  }
246
127
  }
247
- throw new Error('重试次数已用完');
128
+ return undefined;
248
129
  });
249
130
  }
250
131
  /**
@@ -284,7 +165,7 @@ class ShellX {
284
165
  click(clickData) {
285
166
  return __awaiter(this, void 0, void 0, function* () {
286
167
  const startTime = Date.now();
287
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
168
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
288
169
  try {
289
170
  let target;
290
171
  if (clickData.targetElementId) {
@@ -340,6 +221,15 @@ class ShellX {
340
221
  throw new Error(`点击操作失败: ${error instanceof Error ? error.message : String(error)}`);
341
222
  }
342
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;
343
233
  });
344
234
  }
345
235
  /**
@@ -348,7 +238,7 @@ class ShellX {
348
238
  input(inputData) {
349
239
  return __awaiter(this, void 0, void 0, function* () {
350
240
  const startTime = Date.now();
351
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
241
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
352
242
  var _a, _b;
353
243
  try {
354
244
  let target;
@@ -398,6 +288,14 @@ class ShellX {
398
288
  throw new Error(`输入操作失败: ${error instanceof Error ? error.message : String(error)}`);
399
289
  }
400
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;
401
299
  });
402
300
  }
403
301
  /**
@@ -406,7 +304,7 @@ class ShellX {
406
304
  swipe(swipeData) {
407
305
  return __awaiter(this, void 0, void 0, function* () {
408
306
  const startTime = Date.now();
409
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
307
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
410
308
  try {
411
309
  const from = { x: swipeData.fromX, y: swipeData.fromY };
412
310
  const to = { x: swipeData.toX, y: swipeData.toY };
@@ -433,6 +331,14 @@ class ShellX {
433
331
  throw new Error(`滑动操作失败: ${error instanceof Error ? error.message : String(error)}`);
434
332
  }
435
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;
436
342
  });
437
343
  }
438
344
  /**
@@ -441,7 +347,7 @@ class ShellX {
441
347
  pressKey(keyData) {
442
348
  return __awaiter(this, void 0, void 0, function* () {
443
349
  const startTime = Date.now();
444
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
350
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
445
351
  try {
446
352
  const action = {
447
353
  title: `按键操作: ${keyData.key}${keyData.longPress ? ' (长按)' : ''}`,
@@ -467,6 +373,14 @@ class ShellX {
467
373
  throw new Error(`按键操作失败: ${error instanceof Error ? error.message : String(error)}`);
468
374
  }
469
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;
470
384
  });
471
385
  }
472
386
  /**
@@ -475,7 +389,7 @@ class ShellX {
475
389
  wait(waitData) {
476
390
  return __awaiter(this, void 0, void 0, function* () {
477
391
  const startTime = Date.now();
478
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
392
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
479
393
  try {
480
394
  const selector = this.convertSelector(waitData);
481
395
  const timeout = waitData.timeout || 10000;
@@ -489,7 +403,12 @@ class ShellX {
489
403
  visibleOnly: waitData.condition === 'visible',
490
404
  clickableOnly: waitData.condition === 'clickable'
491
405
  });
492
- 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) {
493
412
  if (waitData.condition === 'gone') {
494
413
  // 等待元素消失,继续等待
495
414
  yield new Promise(resolve => setTimeout(resolve, interval));
@@ -524,6 +443,14 @@ class ShellX {
524
443
  throw new Error(`等待操作失败: ${error instanceof Error ? error.message : String(error)}`);
525
444
  }
526
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;
527
454
  });
528
455
  }
529
456
  /**
@@ -531,7 +458,7 @@ class ShellX {
531
458
  */
532
459
  find(findData) {
533
460
  return __awaiter(this, void 0, void 0, function* () {
534
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
461
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
535
462
  try {
536
463
  const selector = this.convertSelector(findData);
537
464
  const result = yield this.client.findElement(selector, {
@@ -542,6 +469,13 @@ class ShellX {
542
469
  clickableOnly: false,
543
470
  multiple: findData.multiple || false
544
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
+ }
545
479
  const elements = result.elements.map(element => this.convertElement(element));
546
480
  return {
547
481
  elements,
@@ -554,6 +488,15 @@ class ShellX {
554
488
  throw new Error(`查找操作失败: ${error instanceof Error ? error.message : String(error)}`);
555
489
  }
556
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;
557
500
  });
558
501
  }
559
502
  /**
@@ -561,7 +504,7 @@ class ShellX {
561
504
  */
562
505
  executeCommand(commandData) {
563
506
  return __awaiter(this, void 0, void 0, function* () {
564
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
507
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
565
508
  try {
566
509
  const result = yield this.executeShellCommand(commandData.cmd, {
567
510
  title: `执行命令: ${commandData.cmd}`,
@@ -574,6 +517,35 @@ class ShellX {
574
517
  throw new Error(`命令执行失败: ${error instanceof Error ? error.message : String(error)}`);
575
518
  }
576
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
+ ]);
577
549
  });
578
550
  }
579
551
  /**
@@ -582,7 +554,7 @@ class ShellX {
582
554
  getAppInfo(appInfoData) {
583
555
  return __awaiter(this, void 0, void 0, function* () {
584
556
  const startTime = Date.now();
585
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
557
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
586
558
  try {
587
559
  const action = {
588
560
  title: `获取应用信息: ${appInfoData.package}`,
@@ -602,6 +574,14 @@ class ShellX {
602
574
  throw new Error(`获取应用信息失败: ${error instanceof Error ? error.message : String(error)}`);
603
575
  }
604
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;
605
585
  });
606
586
  }
607
587
  /**
@@ -610,7 +590,7 @@ class ShellX {
610
590
  takeScreenshot() {
611
591
  return __awaiter(this, arguments, void 0, function* (screenshotData = {}) {
612
592
  const startTime = Date.now();
613
- return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
593
+ const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
614
594
  try {
615
595
  const options = {
616
596
  format: screenshotData.format || 'png',
@@ -627,6 +607,10 @@ class ShellX {
627
607
  };
628
608
  }
629
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
+ }
630
614
  return {
631
615
  success: true,
632
616
  data: screenshot,
@@ -637,6 +621,14 @@ class ShellX {
637
621
  throw new Error(`截图操作失败: ${error instanceof Error ? error.message : String(error)}`);
638
622
  }
639
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;
640
632
  });
641
633
  }
642
634
  /**
@@ -687,11 +679,13 @@ class ShellX {
687
679
  result = yield this.takeScreenshot(action);
688
680
  }
689
681
  results.push(result);
690
- // 如果操作失败且不允许继续,则抛出错误
682
+ // 记录操作结果但不抛出错误,让调用者决定如何处理失败的操作
691
683
  if (!result.success) {
692
- throw new Error(`操作 ${index + 1} 失败: ${result.error}`);
684
+ console.warn(`⚠️ [Actions] ${index + 1} 个操作失败: ${result.error}`);
685
+ }
686
+ else {
687
+ console.log(`✅ [Actions] 第 ${index + 1} 个操作执行成功`);
693
688
  }
694
- console.log(`✅ [Actions] 第 ${index + 1} 个操作执行成功`);
695
689
  }
696
690
  catch (error) {
697
691
  const errorResult = {
@@ -701,7 +695,7 @@ class ShellX {
701
695
  };
702
696
  results.push(errorResult);
703
697
  console.error(`❌ [Actions] 第 ${index + 1} 个操作执行失败:`, error);
704
- throw error; // 停止执行后续操作
698
+ // 不再抛出错误,继续执行后续操作
705
699
  }
706
700
  }
707
701
  return results;
@@ -1005,27 +999,17 @@ class ShellX {
1005
999
  // 设置超时
1006
1000
  const timeoutId = setTimeout(() => {
1007
1001
  if (this.shellCommandPromises.has(commandKey)) {
1002
+ console.log(`✅ [Shell] 命令 ${command} 执行完成`);
1008
1003
  const commandPromise = this.shellCommandPromises.get(commandKey);
1009
1004
  this.shellCommandPromises.delete(commandKey);
1010
- if (commandPromise && commandPromise.output.trim().length > 0) {
1011
- resolve({
1012
- success: true,
1013
- output: commandPromise.output.trim(),
1014
- duration: Date.now() - startTime
1015
- });
1016
- }
1017
- else {
1018
- resolve({
1019
- success: true,
1020
- output: "",
1021
- error: `Command timeout after ${timeout}ms, but got partial output`,
1022
- duration: Date.now() - startTime
1023
- });
1024
- }
1005
+ resolve({
1006
+ success: true,
1007
+ output: commandPromise ? commandPromise.output.trim() : "",
1008
+ duration: Date.now() - startTime
1009
+ });
1025
1010
  }
1026
1011
  }, timeout);
1027
1012
  try {
1028
- // 构建 shell 命令操作
1029
1013
  const shellAction = {
1030
1014
  title,
1031
1015
  actions: [{
@@ -1038,7 +1022,7 @@ class ShellX {
1038
1022
  }
1039
1023
  };
1040
1024
  // 发送命令
1041
- yield this.client.executeAction(shellAction, commandKey);
1025
+ yield this.client.sendMessageWithTaskId({ actions: shellAction }, 'command', commandKey, timeout);
1042
1026
  console.log(`📤 [Shell] 命令已发送: ${commandKey}`);
1043
1027
  }
1044
1028
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shellx-ai",
3
- "version": "1.0.10",
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",