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
JavaScript
"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; } });