UNPKG

shellx-ai

Version:

shellx is a powerful WebSocket-based client for controlling shell commands and UI automation on remote devices.

694 lines (693 loc) 26.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ShellX = exports.createShellXWithShellMonitoring = exports.WebSocketTaskClient = exports.DEFAULT_CONFIG = void 0; const uuid_1 = require("uuid"); const utils_1 = require("./utils"); // 定义默认配置 exports.DEFAULT_CONFIG = { timeout: 5000, reconnect: true, reconnectMaxAttempts: 5, reconnectInterval: 1000, pingInterval: 2000, onOpen: () => { }, onClose: () => { }, onError: () => { }, onReconnectFailed: () => { }, }; const cbor_1 = require("cbor"); // 安全地获取环境变量,兼容浏览器和Node.js环境 let authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY'); /** * 获取适合当前环境的WebSocket构造函数 */ function getWebSocket() { return __awaiter(this, void 0, void 0, function* () { // 检查是否已有全局WebSocket(浏览器环境或Node.js 20+) if (typeof globalThis.WebSocket !== 'undefined') { return globalThis.WebSocket; } // Node.js环境下动态导入ws模块 try { // @ts-ignore - Dynamic import may not have types const wsModule = yield Promise.resolve().then(() => __importStar(require('ws'))); return wsModule.default || wsModule; } catch (error) { console.error('❌ [WebSocket] ws模块不可用,请安装: npm install ws'); throw new Error('WebSocket not available. Please install ws module: npm install ws'); } }); } /** * Enhanced WebSocket Task Client with protocol-aware task methods */ class WebSocketTaskClient { constructor(wsUrl, config = {}) { this.ws = null; // Use any to avoid WebSocket type issues this.shellxConnected = false; // ShellX.ai 连接状态 this.wsConnected = false; // WebSocket 连接状态 this.authenticated = false; // 认证状态 this.pendingTasks = new Map(); this.messageQueue = []; this.reconnectAttempts = 0; this.pingIntervalId = null; this.initializationPromise = null; this.shellx = null; // 关联的 ShellX 实例 this.wsUrl = wsUrl; this.config = Object.assign(Object.assign({}, exports.DEFAULT_CONFIG), config); this.initializationPromise = this.init(); } init() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; try { console.log('Initializing WebSocket client...' + this.wsUrl); // 获取适合当前环境的WebSocket构造函数 const WebSocketConstructor = yield getWebSocket(); this.ws = new WebSocketConstructor(this.wsUrl); // 设置二进制类型(如果支持) if (this.ws.binaryType !== undefined) { this.ws.binaryType = 'arraybuffer'; } this.ws.onopen = () => { console.log('🔗 [ShellX] WebSocket连接已建立,发送认证消息...' + this.wsUrl); this.wsConnected = true; this.reconnectAttempts = 0; // 连接建立后立即发送认证消息 this.sendAuthenticationMessage(); }; this.ws.onmessage = (event) => { this.handleMessage(event); }; this.ws.onclose = (event) => { var _a, _b; console.log('❌ [ShellX] WebSocket连接已关闭,正在尝试重新连接...' + event.code + ' ' + this.wsUrl); this.wsConnected = false; this.shellxConnected = false; this.authenticated = false; (_b = (_a = this.config).onClose) === null || _b === void 0 ? void 0 : _b.call(_a, event); this.stopPing(); this.reconnect(); }; this.ws.onerror = (error) => { var _a, _b; (_b = (_a = this.config).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error); }; } catch (error) { console.error('❌ [WebSocket] 初始化失败:', error); (_b = (_a = this.config).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error); throw error; } }); } /** * 等待WebSocket初始化完成 */ waitForInitialization() { return __awaiter(this, void 0, void 0, function* () { if (this.initializationPromise) { yield this.initializationPromise; } }); } /** * 发送认证消息 */ sendAuthenticationMessage() { return __awaiter(this, void 0, void 0, function* () { try { authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY'); if (!authKey) { console.error('❌ [Auth] SHELLX_AUTH_KEY 环境变量未设置'); return; } console.log('🔑 [Auth] 发送认证消息...'); // 从URL中提取认证密钥(如果URL包含认证信息) // 发送认证消息 const authMessage = { authenticate: authKey }; console.log('📤 [Auth] 发送认证消息:', { authenticate: authKey }); if (this.ws && this.wsConnected) { this.ws.send((0, cbor_1.encode)(authMessage)); } else { console.error('❌ [Auth] WebSocket未连接,无法发送认证消息'); } } catch (error) { console.error('❌ [Auth] 发送认证消息失败:', error); } }); } handleMessage(event) { let serverMessage; try { // Handle CBOR decoding let binaryData; if (event.data instanceof ArrayBuffer) { binaryData = new Uint8Array(event.data); } else if (event.data instanceof Uint8Array) { binaryData = event.data; } else { // JSON fallback serverMessage = JSON.parse(event.data); this.processServerMessage(serverMessage); return; } serverMessage = (0, cbor_1.decode)(binaryData); this.processServerMessage(serverMessage); } catch (cborError) { console.log('CBOR decode failed, trying JSON fallback:', cborError); try { let textData; if (event.data instanceof ArrayBuffer) { textData = new TextDecoder().decode(event.data); } else { textData = event.data; } serverMessage = JSON.parse(textData); this.processServerMessage(serverMessage); } catch (jsonError) { console.error('Failed to parse message:', { cborError, jsonError }); } } } processServerMessage(message) { var _a, _b, _c, _d; //console.log('📨 [ShellX] 收到服务器消息:', message); this.authenticated = true; // 只有在认证成功后才处理其他消息并认为连接成功 if (this.authenticated && !this.shellxConnected) { this.shellxConnected = true; console.log('✅ [ShellX] ShellX.ai服务连接成功!'); (_b = (_a = this.config).onOpen) === null || _b === void 0 ? void 0 : _b.call(_a); this.flushQueue(); this.startPing(); } // Call user-defined message handler (_d = (_c = this.config).onMessage) === null || _d === void 0 ? void 0 : _d.call(_c, message); // Handle specific message types and resolve pending tasks if (message.jsonData) { this.handleJsonDataResponse(message.jsonData); } // Handle other server message types (hello, users, shells, etc.) if (message.hello !== undefined) { console.log('👋 [ShellX] 服务器问候,用户ID:', message.hello); } if (message.pong !== undefined) { //console.log('🏓 [ShellX] 收到心跳响应:', message.pong); } if (message.error) { console.error('❌ [ShellX] 服务器错误:', message.error); } } handleJsonDataResponse(jsonData) { // Match responses to pending tasks based on response type for (const [taskId, task] of this.pendingTasks.entries()) { let responseData = null; let shouldResolve = false; switch (task.type) { case 'findElement': if (jsonData.findElement) { responseData = jsonData.findElement; shouldResolve = true; } break; case 'waitElement': if (jsonData.waitElement) { responseData = jsonData.waitElement; shouldResolve = true; } break; case 'screenShot': if (jsonData.screenShot) { responseData = jsonData.screenShot; shouldResolve = true; } break; case 'screenInfo': if (jsonData.screenInfo) { responseData = jsonData.screenInfo; shouldResolve = true; } break; case 'appList': if (jsonData.appList) { responseData = jsonData.appList; shouldResolve = true; } break; case 'action': /* if (jsonData.action_event) { responseData = jsonData.action_event; shouldResolve = true; }*/ // 命令,按键,滑动,点击,等待,查找,等待,屏幕截图,屏幕信息,屏幕变化,应用列表, 需要自己处理 break; } if (shouldResolve) { clearTimeout(task.timer); this.pendingTasks.delete(taskId); if ((responseData === null || responseData === void 0 ? void 0 : responseData.success) === false) { task.reject(new Error(responseData.errorMessage || 'Task failed')); } else { task.resolve(responseData); } break; // Only resolve the first matching task } } } sendMessage(message, taskType, commandType) { return new Promise((resolve, reject) => { const taskId = (0, uuid_1.v4)(); if (taskType) { const timer = setTimeout(() => { this.pendingTasks.delete(taskId); reject(new Error(`Task ${taskType} timeout (${this.config.timeout}ms)`)); }, this.config.timeout); this.pendingTasks.set(taskId, { resolve, reject, timer, type: taskType, commandType }); console.log(`📋 [ShellX] 创建任务: ${taskId}, 类型: ${taskType}}`); } if (this.shellxConnected && this.ws) { try { console.log('📤 [ShellX] 发送消息:', message); this.ws.send((0, cbor_1.encode)(message)); if (!taskType) resolve(undefined); // For fire-and-forget messages } catch (error) { if (taskType) { this.pendingTasks.delete(taskId); } reject(error); } } else { console.log('⏳ [ShellX] 连接未就绪,消息已加入队列'); this.messageQueue.push(message); if (!taskType) resolve(undefined); } }); } // ===== PROTOCOL-AWARE TASK METHODS ===== /** * Find UI elements on the screen */ findElement(selector, options) { return __awaiter(this, void 0, void 0, function* () { const findAction = { type: 'find', selector, options }; return this.sendMessage({ findElement: findAction }, 'findElement'); }); } /** * Wait for UI element to appear */ waitElement(selector, options) { return __awaiter(this, void 0, void 0, function* () { const waitAction = { type: 'wait', selector, options }; return this.sendMessage({ waitElement: waitAction }, 'waitElement'); }); } /** * Take a screenshot */ screenShot(options) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ screenShot: options || { "format": "png", "quality": 30, "scale": 1.0, "region": { "left": 0, "top": 0, "width": 1080, "height": 2340 } } }, 'screenShot'); }); } /** * Get screen information */ getScreenInfo() { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ screenInfo: {} }, 'screenInfo'); }); } /** * Get list of installed apps */ getAppList(options) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ appList: options || {} }, 'appList'); }); } /** * Get specific app information */ getAppInfo(packageName) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ appInfo: { packageName } }, 'appInfo'); }); } /** * Execute an action sequence */ executeAction(actionSequence, taskId) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessageWithTaskId({ actions: actionSequence }, 'action', taskId); }); } /** * Execute promptflow actions */ executePromptFlow(actionSequence) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ promptflowx: actionSequence }); }); } /** * Listen for display changes */ screenChange(options) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ screenChange: options || { enable: true } }); }); } /** * Switch to a different node */ switchNode(nodeId) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ switchNode: nodeId }); }); } /** * Send authentication data */ authenticate(authData) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ authenticate: authData }); }); } /** * Set user name */ setName(name) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ setName: name }); }); } /** * Send chat message */ sendChat(message) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage({ chat: message }); }); } /** * Send raw message (for custom integrations) */ sendRawMessage(message) { return __awaiter(this, void 0, void 0, function* () { return this.sendMessage(message); }); } /** * 发送消息并返回 taskId,允许手动控制任务完成 * @param message 要发送的消息 * @param taskType 任务类型 * @returns Promise<{taskId: string, promise: Promise<any>}> */ sendMessageWithTaskId(message, taskType, definedTaskId) { return new Promise((resolve) => { let taskId = definedTaskId; if (taskId == null) { taskId = (0, uuid_1.v4)(); } if (taskType) { const timer = setTimeout(() => { if (taskId != null) { this.pendingTasks.delete(taskId); } console.log(`⏰ [ShellX] 任务超时: ${taskId}, 类型: ${taskType}`); }, this.config.timeout); this.pendingTasks.set(taskId, { resolve: () => { }, reject: () => { }, timer, type: taskType }); console.log(`📋 [ShellX] 创建可手动控制的任务: ${taskId}, 类型: ${taskType}}`); } if (this.shellxConnected && this.ws) { try { console.log('📤 [ShellX] 发送消息:', message); this.ws.send((0, cbor_1.encode)(message)); const promise = new Promise((promiseResolve, promiseReject) => { if (taskType) { if (taskId != null) { const task = this.pendingTasks.get(taskId); if (task) { task.resolve = promiseResolve; task.reject = promiseReject; } } } else { promiseResolve(undefined); } }); resolve({ taskId, promise }); } catch (error) { if (taskType) { this.pendingTasks.delete(taskId); } resolve({ taskId, promise: Promise.reject(error) }); } } else { console.log('⏳ [ShellX] 连接未就绪,消息已加入队列'); this.messageQueue.push(message); resolve({ taskId, promise: Promise.resolve(undefined) }); } }); } // ===== CONNECTION MANAGEMENT ===== startPing() { this.stopPing(); console.log('🏓 [ShellX] 开始心跳检测...'); this.pingIntervalId = setInterval(() => { if (this.ws && this.shellxConnected) { this.ws.send((0, cbor_1.encode)({ ping: BigInt(Date.now()) })); } }, this.config.pingInterval); } stopPing() { if (this.pingIntervalId !== null) { clearInterval(this.pingIntervalId); this.pingIntervalId = null; } } reconnect() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; // TODO: 本地的需要重连 console.log(`🔄 [ShellX] 重连中... (${this.reconnectAttempts}/${this.config.reconnectMaxAttempts})` + this.wsUrl); if (this.reconnectAttempts >= this.config.reconnectMaxAttempts) { (_b = (_a = this.config).onReconnectFailed) === null || _b === void 0 ? void 0 : _b.call(_a); return; } this.reconnectAttempts++; yield (0, utils_1.wait)(this.config.reconnectInterval * this.reconnectAttempts); this.init(); }); } flushQueue() { while (this.messageQueue.length > 0 && this.ws) { const message = this.messageQueue.shift(); if (message) { try { console.log('flushQueue Sending message:', message); this.ws.send((0, cbor_1.encode)(message)); } catch (error) { console.error('Failed to send queued message:', error); } } } } close() { var _a; this.shellxConnected = false; this.wsConnected = false; this.authenticated = false; (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close(); this.pendingTasks.clear(); this.messageQueue = []; this.stopPing(); } // Getters for debugging/monitoring get isConnected() { return this.shellxConnected; } get isWebSocketConnected() { return this.wsConnected; } get isAuthenticated() { return this.authenticated; } get pendingTaskCount() { return this.pendingTasks.size; } get queuedMessageCount() { return this.messageQueue.length; } /** * 设置关联的 ShellX 实例 */ setShellX(shellx) { this.shellx = shellx; console.log('🔗 [ShellX] 已关联 ShellX 实例'); } /** * 获取关联的 ShellX 实例 */ getShellX() { return this.shellx; } /** * 手动完成指定 taskId 的任务 * @param taskId 任务ID * @param result 任务结果 * @param success 是否成功 */ completeTask(taskId, result, success = true) { const task = this.pendingTasks.get(taskId); if (!task) { console.log(`⚠️ [ShellX] 未找到任务: ${taskId}`); return false; } console.log(`✅ [ShellX] 手动完成任务: ${taskId}, 类型: ${task.type}, 成功: ${success}`); // 清除超时定时器 clearTimeout(task.timer); // 从待处理任务中移除 this.pendingTasks.delete(taskId); // 根据成功状态调用相应的回调 if (success) { task.resolve(result || { success: true, taskId }); } else { task.reject(new Error(`Task ${taskId} manually failed`)); } return true; } /** * 获取所有待处理任务的ID列表 */ getPendingTaskIds() { return Array.from(this.pendingTasks.keys()); } /** * 获取指定任务的信息 */ getTaskInfo(taskId) { const task = this.pendingTasks.get(taskId); if (!task) { return null; } return { type: task.type, commandType: task.commandType }; } /** * 批量完成指定类型的任务 * @param taskType 任务类型 * @param result 任务结果 * @param success 是否成功 */ completeTasksByType(taskType, result, success = true) { const taskIds = this.getPendingTaskIds(); let completedCount = 0; for (const taskId of taskIds) { const task = this.pendingTasks.get(taskId); if (task && task.type === taskType) { this.completeTask(taskId, result, success); completedCount++; } } console.log(`📦 [ShellX] 批量完成 ${completedCount} 个 ${taskType} 类型的任务`); return completedCount; } } exports.WebSocketTaskClient = WebSocketTaskClient; exports.default = WebSocketTaskClient; // PromptFlow 模块已移至 examples/ 目录作为示例实现 var shellx_1 = require("./shellx"); Object.defineProperty(exports, "createShellXWithShellMonitoring", { enumerable: true, get: function () { return shellx_1.createShellXWithShellMonitoring; } }); Object.defineProperty(exports, "ShellX", { enumerable: true, get: function () { return shellx_1.ShellX; } });