From 8ca86c5d5500e720e4b5537338b59e37b96b8d7b Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:44:42 +0800 Subject: [PATCH 1/7] refactor(devtool-mcp-server)!: use new connector --- .changeset/petite-bats-smell.md | 7 + .changeset/three-geese-study.md | 5 + .../devtool-connector/eslint.config.js | 63 + .../devtool-connector/package.json | 59 + .../devtool-connector/rslib.config.ts | 26 + .../devtool-connector/src/AGENTS.md | 132 ++ .../devtool-connector/src/index.ts | 483 +++++ .../devtool-connector/src/streams/cdp.ts | 62 + .../src/streams/customized.ts | 157 ++ .../devtool-connector/src/streams/peertalk.ts | 84 + .../devtool-connector/src/streams/utils.ts | 29 + .../devtool-connector/src/takeover.ts | 39 + .../src/transport/android.ts | 183 ++ .../src/transport/desktop.ts | 81 + .../devtool-connector/src/transport/index.ts | 7 + .../devtool-connector/src/transport/ios.ts | 91 + .../src/transport/transport.ts | 53 + .../devtool-connector/src/types.ts | 134 ++ .../devtool-connector/tsconfig.json | 8 + .../debug-router-connector.d.ts | 4 - .../debug-router-connector.js | 4 - .../devtool-mcp-server/package.json | 9 +- .../devtool-mcp-server/src/McpContext.ts | 15 +- .../devtool-mcp-server/src/McpResponse.ts | 45 +- .../devtool-mcp-server/src/connector.ts | 507 ------ .../devtool-mcp-server/src/index.ts | 61 +- .../devtool-mcp-server/src/main.ts | 10 +- .../devtool-mcp-server/src/schema/index.ts | 7 +- .../src/tools/DOM/GetBoxModel.ts | 2 +- .../src/tools/DOM/GetDocument.ts | 2 +- .../src/tools/Debugger/GetScriptSource.ts | 5 +- .../src/tools/Debugger/ListScripts.ts | 45 +- .../src/tools/Device/ClosePage.ts | 21 + .../src/tools/Device/ListClients.ts | 40 +- .../src/tools/Device/ListDevices.ts | 18 +- .../src/tools/Device/ListSessions.ts | 6 +- .../src/tools/Device/OpenPage.ts | 74 +- .../src/tools/Device/Reconnect.ts | 21 - .../src/tools/Page/TakeScreenshot.ts | 81 +- .../src/tools/Runtime/ListConsole.ts | 79 +- .../src/tools/defineTool.ts | 17 +- .../devtool-mcp-server/tsconfig.json | 3 +- pnpm-lock.yaml | 1600 ++--------------- pnpm-workspace.yaml | 11 + 44 files changed, 2177 insertions(+), 2213 deletions(-) create mode 100644 .changeset/petite-bats-smell.md create mode 100644 .changeset/three-geese-study.md create mode 100644 packages/mcp-servers/devtool-connector/eslint.config.js create mode 100644 packages/mcp-servers/devtool-connector/package.json create mode 100644 packages/mcp-servers/devtool-connector/rslib.config.ts create mode 100644 packages/mcp-servers/devtool-connector/src/AGENTS.md create mode 100644 packages/mcp-servers/devtool-connector/src/index.ts create mode 100644 packages/mcp-servers/devtool-connector/src/streams/cdp.ts create mode 100644 packages/mcp-servers/devtool-connector/src/streams/customized.ts create mode 100644 packages/mcp-servers/devtool-connector/src/streams/peertalk.ts create mode 100644 packages/mcp-servers/devtool-connector/src/streams/utils.ts create mode 100644 packages/mcp-servers/devtool-connector/src/takeover.ts create mode 100644 packages/mcp-servers/devtool-connector/src/transport/android.ts create mode 100644 packages/mcp-servers/devtool-connector/src/transport/desktop.ts create mode 100644 packages/mcp-servers/devtool-connector/src/transport/index.ts create mode 100644 packages/mcp-servers/devtool-connector/src/transport/ios.ts create mode 100644 packages/mcp-servers/devtool-connector/src/transport/transport.ts create mode 100644 packages/mcp-servers/devtool-connector/src/types.ts create mode 100644 packages/mcp-servers/devtool-connector/tsconfig.json delete mode 100644 packages/mcp-servers/devtool-mcp-server/debug-router-connector.d.ts delete mode 100644 packages/mcp-servers/devtool-mcp-server/debug-router-connector.js delete mode 100644 packages/mcp-servers/devtool-mcp-server/src/connector.ts create mode 100644 packages/mcp-servers/devtool-mcp-server/src/tools/Device/ClosePage.ts delete mode 100644 packages/mcp-servers/devtool-mcp-server/src/tools/Device/Reconnect.ts diff --git a/.changeset/petite-bats-smell.md b/.changeset/petite-bats-smell.md new file mode 100644 index 0000000000..eba7dd401d --- /dev/null +++ b/.changeset/petite-bats-smell.md @@ -0,0 +1,7 @@ +--- +"@lynx-js/devtool-mcp-server": minor +--- + +Use `@lynx-js/devtool-connector` instead of `@lynx-js/debug-router-connector`. + +The new connector avoid using keep-alive connections, which make the connection much more reliable. diff --git a/.changeset/three-geese-study.md b/.changeset/three-geese-study.md new file mode 100644 index 0000000000..aab27fc7b1 --- /dev/null +++ b/.changeset/three-geese-study.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/devtool-mcp-server": minor +--- + +**BREAKING CHANGE**: Remove the `./debug-router-connector` exports. diff --git a/packages/mcp-servers/devtool-connector/eslint.config.js b/packages/mcp-servers/devtool-connector/eslint.config.js new file mode 100644 index 0000000000..e9e34c46be --- /dev/null +++ b/packages/mcp-servers/devtool-connector/eslint.config.js @@ -0,0 +1,63 @@ +// Copyright 2025 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +import eslint from '@eslint/js'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import headers from 'eslint-plugin-headers'; +import tseslint from 'typescript-eslint'; + +export default defineConfig( + globalIgnores([ + '.rslib/**', + 'dist/**', + 'node_modules/**', + ]), + eslint.configs.recommended, + tseslint.configs.recommended, + { + languageOptions: { + parserOptions: { + tsconfigRootDir: import.meta.dirname, + projectService: { + allowDefaultProject: ['*.js', 'rslib.config.ts'], + defaultProject: './tsconfig.json', + }, + }, + }, + }, + { + plugins: { + headers, + }, + rules: { + 'headers/header-format': [ + 'error', + { + source: 'string', + style: 'line', + content: [ + 'Copyright (year) {authors}. All rights reserved.', + 'Licensed under the (license) that can be found in the', + 'LICENSE file in the root directory of this source tree.', + ].join('\n'), + variables: { + authors: 'The Lynx Authors', + }, + patterns: { + year: { + pattern: '\\d{4}', + defaultValue: new Date().getFullYear().toString(), + }, + license: { + pattern: [ + 'Apache License Version 2.0', + ].join('|'), + defaultValue: 'Apache License Version 2.0', + }, + }, + }, + ], + }, + }, +); diff --git a/packages/mcp-servers/devtool-connector/package.json b/packages/mcp-servers/devtool-connector/package.json new file mode 100644 index 0000000000..ffce805f04 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/package.json @@ -0,0 +1,59 @@ +{ + "name": "@lynx-js/devtool-connector", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts", + "default": "./src/index.ts" + }, + "./transport": { + "types": "./src/transport/index.ts", + "import": "./src/transport/index.ts", + "default": "./src/transport/index.ts" + }, + "./package.json": "./package.json" + }, + "main": "./src/index.ts", + "types": "./src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "rslib build", + "test": "node --test" + }, + "devDependencies": { + "@types/debug": "^4.1.12", + "@types/node": "^24.10.0", + "@yume-chan/adb": "catalog:adb", + "@yume-chan/adb-server-node-tcp": "catalog:adb", + "@yume-chan/stream-extra": "catalog:adb", + "commander": "^13.1.0", + "debug": "^4.4.3", + "rsbuild-plugin-publint": "^0.3.3", + "typescript": "^5.9.3", + "usbmux-client": "^0.2.1" + }, + "engines": { + "node": ">=18.19" + }, + "publishConfig": { + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + }, + "./transport": { + "types": "./dist/transport/index.d.ts", + "import": "./dist/transport/index.js", + "default": "./dist/transport/index.js" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" + } +} diff --git a/packages/mcp-servers/devtool-connector/rslib.config.ts b/packages/mcp-servers/devtool-connector/rslib.config.ts new file mode 100644 index 0000000000..40e9cae78f --- /dev/null +++ b/packages/mcp-servers/devtool-connector/rslib.config.ts @@ -0,0 +1,26 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { defineConfig } from '@rslib/core'; +import { pluginPublint } from 'rsbuild-plugin-publint'; + +export default defineConfig({ + plugins: [ + pluginPublint({ throwOn: 'suggestion' }), + ], + source: { + entry: { + index: './src/index.ts', + 'transport/index': './src/transport/index.ts', + }, + }, + lib: [ + { + format: 'esm', + syntax: 'es2022', + dts: { + bundle: false, + }, + }, + ], +}); diff --git a/packages/mcp-servers/devtool-connector/src/AGENTS.md b/packages/mcp-servers/devtool-connector/src/AGENTS.md new file mode 100644 index 0000000000..893fb47c07 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/AGENTS.md @@ -0,0 +1,132 @@ +# Connector Design Document + +## 1. Context & Goals + +The new Connector is designed to address stability issues in the previous long-lived connection solution. The core idea is to adopt a stateless short-lived connection design, bypass higher-level wrappers, and directly control the lifecycle of the underlying connections. + +### Key Objectives + +- **Stability first**: Use short-lived connections to avoid the fragility of long-lived ones. +- **Stateless design**: Each request (for example a CDP request) runs its own `connect -> send -> receive -> close` flow. +- **Cross-platform**: Provide a unified abstraction for Android (ADB) and iOS (Usbmuxd). + +## 2. Architecture Layering + +The Connector adopts a layered architecture to separate concerns: + +### Transport layer + +- **Responsibility**: Establish raw byte-stream channels, without awareness of higher-level protocols. +- **Implementation**: + - `AndroidTransport` (Android): Based on `@yume-chan/adb`, uses `adb shell nc` to create a tunnel directly to the device port. + - `iOSTransport` (iOS): Based on `usbmux-client`, uses `createDeviceTunnel` to connect directly to the device port. +- **Interface**: Provides primitive operations such as `connect` and `listDevices`. + +### Protocol layer + +- **Responsibility**: Handle Peertalk framing and CDP message encoding/decoding. +- **Components**: + - `MessageToPeertalkTransformStream`: Message -> Peertalk frame. + - `PeertalkToMessageTransformStream`: Peertalk frame -> Message. +- **Characteristics**: Pure functional transforms, stateless. + +### Connector layer (core) + +- **Responsibility**: Expose a unified interface and coordinate the Transport and Protocol layers. +- **Implementation**: `Connector` class in `src/connector/index.ts`. +- **Mechanics**: + - **Short-lived requests**: Each call to `sendMessageWithTransport` establishes a new connection, transfers data, and then closes the connection immediately. + - **Device/client discovery**: + - Android: Scan ports 8901-8910 and send an `Initialize` handshake message to confirm the presence of a Client. + - All results are snapshots of the current moment. + +## 3. Core Concepts + +In the stateless model, the lifecycle of core objects changes: + +- **Device**: A physical device, discovered dynamically via low-level tools (ADB/Usbmuxd). +- **Client**: A listening port of `debug-router` on the device (for example 8901-8910). + - **Dynamic nature**: We no longer hold client objects for a long time; instead, we scan ports and use Initialize/Register handshakes to confirm their existence. + - **Identifier**: `ClientId` contains the device ID and port number. +- **Session**: A page/view session. Only fetched via `ListSession` when needed and treated as a temporary snapshot. + +## 4. Code Structure + +- `src/connector/index.ts`: Connector layer. Main entry, manages all transports. +- `src/connector/transport.ts`: Transport interface definition. +- `src/connector/android.ts`: `AndroidTransport` implementation. +- `src/connector/ios.ts`: `iOSTransport` implementation. +- `src/connector/peertalk.ts` / `cdp.ts`: Protocol layer stream transformers. +- `src/connector/types.ts`: Core type definitions. + +## 5. Development Guide + +- **Adding a new transport**: Implement the `Transport` interface and register it in the `Connector`. +- **Debugging**: Use `DEBUG=devtool-mcp-server:connector*` to enable detailed logs. +- **Notes**: Any new APIs should prefer short-lived connection implementations and avoid introducing persistent state dependencies. + +## 6. API & Usage Examples + +The Connector is the single entry point for upper-layer tools to communicate with devices. + +### 6.1 Initialization + +```typescript +import { AndroidTransport } from './connector/android.js'; +import { Connector } from './connector/index.js'; +import { iOSTransport } from './connector/ios.js'; + +// Initialize the Connector with support for both Android and iOS +const connector = new Connector([ + new AndroidTransport(), + new iOSTransport(), +]); +``` + +### 6.2 Device discovery and connection + +```typescript +// 1. Get all connected devices (Android + iOS) +const devices = await connector.listDevices(); +// Output: [{ id: "emulator-5554", os: "Android" }, { id: "00008101-...", os: "iOS" }] + +// 2. Get all active debug clients on a device (debug-router ports) +// Note: this automatically scans device ports and performs a handshake check +const clients = await connector.listClients(); +// Output: [{ id: "emulator-5554:8901", info: { ... } }, ...] +``` + +### 6.3 Sending CDP commands + +This is the most common operation. Because connections are short-lived, each call is atomic. + +```typescript +const clientId = 'emulator-5554:8901'; + +// 1. Get the list of sessions (pages) under a client +const sessions = await connector.sendListSessionMessage(clientId); +if (sessions.length > 0) { + const sessionId = sessions[0].session_id; + + // 2. Send a CDP command (for example, fetch the DOM) + const result = await connector.sendCDPMessage( + clientId, + sessionId, + 'DOM.getDocument', + { depth: -1 }, + ); + console.log(result); +} +``` + +### 6.4 App control + +```typescript +const deviceId = 'emulator-5554'; + +// List installed debuggable apps +const apps = await connector.listAvailableApps(deviceId); + +// Open an app +await connector.openApp(deviceId, 'com.lynx.explorer'); +``` diff --git a/packages/mcp-servers/devtool-connector/src/index.ts b/packages/mcp-servers/devtool-connector/src/index.ts new file mode 100644 index 0000000000..2dc102903e --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/index.ts @@ -0,0 +1,483 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { randomInt } from 'node:crypto'; +import { ReadableStream, type TransformStream } from 'node:stream/web'; + +import createDebug from 'debug'; + +import { + CDPOutputTransformStream, + CDPRequestTransformStream, + CDPResponseTransformStream, +} from './streams/cdp.ts'; +import { + AppResponseTransformStream, + CustomizedRequestTransformStream, + CustomizedResponseTransformStream, + GlobalSwitchRequestTransformStream, +} from './streams/customized.ts'; +import { + MessageToPeertalkTransformStream, + PeertalkToMessageTransformStream, +} from './streams/peertalk.ts'; +import { FilterTransformStream, InspectStream } from './streams/utils.ts'; +import { takeoverDebugRouterLock } from './takeover.ts'; +import type { + App, + Client, + Device, + OpenAppOptions, + Transport, + TransportConnectOptions, +} from './transport/transport.ts'; +import { isInitializeResponse, isListSessionResponse } from './types.ts'; +import type { + AppInfo, + CDPRequestMessage, + GetGlobalSwitchResponse, + GlobalKeys, + InitializeRequest, + InitializeResponse, + ListSessionRequest, + ListSessionResponse, + Session, +} from './types.ts'; + +export { MessageToPeertalkTransformStream, PeertalkToMessageTransformStream }; + +const debug = createDebug('devtool-mcp-server:connector'); + +interface OutputStream extends AsyncDisposable, ReadableStream {} + +export class ClientId { + static serialize(deviceId: string, port: number): string { + return `${encodeURIComponent(deviceId)}:${port}`; + } + + static deserialize( + clientId: string, + ): { deviceId: string; port: number } | null { + try { + const lastColonIndex = clientId.lastIndexOf(':'); + if (lastColonIndex === -1) return null; + + const port = Number.parseInt(clientId.substring(lastColonIndex + 1), 10); + if (Number.isNaN(port)) return null; + + return { + deviceId: decodeURIComponent(clientId.substring(0, lastColonIndex)), + port, + }; + } catch { + return null; + } + } +} + +interface Pipeline { + input: TransformStream[]; + output: TransformStream[]; +} + +export class Connector { + #transports: Transport[]; + + constructor(transports: Transport[]) { + this.#transports = transports; + } + + async listClients(): Promise { + const transportDevices = await Promise.allSettled( + this.#transports.map(async (transport) => ({ + transport, + devices: await transport.listDevices(), + })), + ); + + const results = await Promise.allSettled( + transportDevices + .filter(r => r.status === 'fulfilled') + .map(r => r.value) + .flatMap(({ transport, devices }) => + devices.flatMap(({ id }) => this.#listClientsForDevice(transport, id)) + ), + ); + + return results + .filter((r) => r.status === 'fulfilled') + .flatMap((r) => r.value); + } + + async listDevices(): Promise { + const results = await Promise.allSettled( + this.#transports.map(t => t.listDevices()), + ); + + return results + .filter(result => result.status === 'fulfilled') + .flatMap(({ value }) => value); + } + + async listAvailableApps(deviceId: string): Promise { + const transport = await this.#findTransportWithDeviceId(deviceId); + + return await transport.listAvailableApps(deviceId); + } + + async openApp( + deviceId: string, + packageName: string, + options?: OpenAppOptions, + ): Promise { + const transport = await this.#findTransportWithDeviceId(deviceId); + + await transport.openApp(deviceId, packageName, options); + + const signal = AbortSignal.any([ + options?.signal, + AbortSignal.timeout(60_000), + ].filter(i => i !== undefined)); + + const { setTimeout } = await import('node:timers/promises'); + while (!signal.aborted) { + try { + const clients = await this.#listClientsForDevice(transport, deviceId); + + if ( + clients.some(({ info }) => + /** Android */ info.AppProcessName === packageName + /** iOS */ || info.bundleId === packageName + /** OpenHarmony */ || info.bundleName === packageName + ) + ) { + break; + } + } catch (err) { + // ignore error + debug(`openApp ${deviceId} ${packageName} client not found %o`, err); + } + await setTimeout(1_000); + } + } + + async sendMessage(clientId: string, message: T): Promise { + return await this.#sendMessage(clientId, message); + } + + async sendAppMessage( + clientId: string, + method: string, + params?: Params, + ): Promise { + const { port } = this.#resolveClientId(clientId); + const id = randomInt(10_000, 50_000); + + return await this.#sendMessage, Output>(clientId, { + method, + params: /** App message requires params to be an object */ { ...params }, + }, { + input: [ + new CustomizedRequestTransformStream({ + type: 'App', + port, + sessionId: -1, + messageBuilder: (message) => ({ id, ...message }), + }), + ], + output: [ + new CustomizedResponseTransformStream('App', id), + new AppResponseTransformStream(method), + ], + }); + } + + async sendCDPMessage( + clientId: string, + sessionId: number, + method: string, + params?: Params, + ): Promise { + const { port } = this.#resolveClientId(clientId); + const id = randomInt(10_000, 50_000); + + return await this.#sendMessage, Output>(clientId, { + method, + params, + sessionId, + }, { + input: [ + new CDPRequestTransformStream(port, id), + ], + output: [ + new CDPResponseTransformStream(id), + new CDPOutputTransformStream(), + ], + }); + } + + async sendListSessionMessage( + clientId: string, + ): Promise { + const options = this.#resolveClientId(clientId); + const { data: { data: sessions } } = await this.#sendMessage< + ListSessionRequest, + ListSessionResponse + >( + clientId, + { + event: 'Customized', + data: { + type: 'ListSession', + sender: options.port, + data: {}, + }, + }, + { + input: [], + output: [ + new FilterTransformStream(isListSessionResponse), + ], + }, + ); + + return sessions; + } + + async getGlobalSwitch( + clientId: string, + key: GlobalKeys, + ): Promise { + const options = this.#resolveClientId(clientId); + const { + data: { data: { message } }, + } = await this.#sendMessage<{ key: GlobalKeys }, GetGlobalSwitchResponse>( + clientId, + { key }, + { + input: [ + new GlobalSwitchRequestTransformStream( + 'GetGlobalSwitch', + options.port, + ), + ], + output: [], + }, + ); + + if (typeof message === 'object') { + return message?.global_value === 'true' || message?.global_value === true; + } else { + return message === 'true' || message === true; + } + } + + async setGlobalSwitch( + clientId: string, + key: GlobalKeys, + value: boolean, + ): Promise { + const options = this.#resolveClientId(clientId); + await this.#sendMessage(clientId, { key, value }, { + input: [ + new GlobalSwitchRequestTransformStream('SetGlobalSwitch', options.port), + ], + output: [], + }); + } + + async sendStream( + clientId: string, + inputStream: ReadableStream, + { signal }: { signal?: AbortSignal }, + ): Promise> { + const { deviceId, port } = this.#resolveClientId(clientId); + const transport = await this.#findTransportWithDeviceId(deviceId); + + return await this.#connect( + transport, + { deviceId, port, signal }, + inputStream, + { input: [], output: [] }, + ); + } + + async sendCDPStream( + clientId: string, + inputStream: ReadableStream, + { signal }: { signal?: AbortSignal } = {}, + ): Promise> { + const { deviceId, port } = this.#resolveClientId(clientId); + const transport = await this.#findTransportWithDeviceId(deviceId); + + return await this.#connect( + transport, + { deviceId, port, signal }, + inputStream, + { + input: [ + new CDPRequestTransformStream(port), + ], + output: [ + new CDPResponseTransformStream(), + ], + }, + ); + } + + #resolveClientId(clientId: string): TransportConnectOptions { + const parsed = ClientId.deserialize(clientId); + if (!parsed) { + throw new Error(`Invalid clientId: ${clientId}`); + } + return parsed; + } + + async #findTransportWithDeviceId(deviceId: string): Promise { + return await Promise.any( + this.#transports.map(async (t) => { + const devices = await t.listDevices(); + if (devices.some(({ id }) => id === deviceId)) return t; + throw new Error('Not found in this transport'); + }), + ).catch(() => { + throw new Error(`Device with id: ${deviceId} not found`); + }); + } + + async #connect( + transport: Transport, + options: TransportConnectOptions, + inputStream: ReadableStream, + pipeline: Pipeline, + ): Promise> { + const { deviceId, port } = options; + + await takeoverDebugRouterLock(); + + const conn = await transport.connect(options); + + void [ + ...pipeline.input, + new InspectStream((msg) => + debug(`connect ${deviceId}:${port} input stream send %O`, msg) + ), + new MessageToPeertalkTransformStream(), + ].reduce((stream, transform) => stream.pipeThrough(transform), inputStream) + .pipeTo(conn.writable, { preventClose: true }) + .catch((err) => { + debug(`connect ${deviceId}:${port} input stream err %O`, err); + void conn[Symbol.asyncDispose](); + }); + + const outputStream = [ + new PeertalkToMessageTransformStream(), + new InspectStream((msg) => + debug(`connect ${deviceId}:${port} output stream receive %O`, msg) + ), + ...pipeline.output, + ].reduce( + (stream, transform) => stream.pipeThrough(transform), + conn.readable, + ); + + return Object.assign(outputStream, { + async [Symbol.asyncDispose]() { + debug(`connect ${deviceId}:${port} close connection`); + return await conn[Symbol.asyncDispose](); + }, + }); + } + + async #sendMessage( + clientId: string, + input: I, + pipeline: Pipeline = { input: [], output: [] }, + ): Promise { + const { deviceId, port } = this.#resolveClientId(clientId); + const transport = await this.#findTransportWithDeviceId(deviceId); + + const signal = AbortSignal.timeout(5000); + + return this.#sendMessageWithTransport( + transport, + { deviceId, port, signal }, + input, + pipeline, + ); + } + + async #sendMessageWithTransport( + transport: Transport, + options: TransportConnectOptions, + input: I, + pipeline: Pipeline, + ): Promise { + await using outputStream = await this.#connect( + transport, + options, + ReadableStream.from([input]), + pipeline, + ); + for await (const response of outputStream) { + return response; + } + + const { deviceId, port } = options; + throw new Error( + `No response found for deviceId: ${deviceId} port: ${port}`, + ); + } + + async #listClientsForDevice( + transport: Transport, + deviceId: string, + ): Promise<{ id: string; info: AppInfo; port: number }[]> { + const MIN_PORT = 8901; + const PORTS = Array.from({ length: 10 }, (_, i) => MIN_PORT + i); + const results = await Promise.allSettled(PORTS.map(async (port) => { + const { data: { info } } = await this.#sendMessageWithTransport< + InitializeRequest, + InitializeResponse + >( + transport, + { deviceId, port, signal: AbortSignal.timeout(5_000) }, + { event: 'Initialize', data: port }, + { + input: [], + output: [ + new FilterTransformStream(isInitializeResponse), + ], + }, + ); + + try { + await this.#sendMessageWithTransport< + { key: GlobalKeys; value: boolean }, + never + >( + transport, + { deviceId, port, signal: AbortSignal.timeout(3_000) }, + { key: 'enable_devtool', value: true }, + { + input: [ + new GlobalSwitchRequestTransformStream('SetGlobalSwitch', port), + ], + output: [], + }, + ); + } catch (err) { + debug( + `listClientsForDevice ${deviceId}:${port} enable_devtool failed %O`, + err, + ); + } + + return { id: ClientId.serialize(deviceId, port), info, port }; + })); + + return results + .filter(result => result.status === 'fulfilled') + .map(result => result.value); + } +} diff --git a/packages/mcp-servers/devtool-connector/src/streams/cdp.ts b/packages/mcp-servers/devtool-connector/src/streams/cdp.ts new file mode 100644 index 0000000000..f8bb6e48c7 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/streams/cdp.ts @@ -0,0 +1,62 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { randomInt } from 'node:crypto'; + +import type { CDPRequestMessage, CDPResponseMessage } from '../types.ts'; +import { + CustomizedRequestTransformStream, + CustomizedResponseTransformStream, + ResponseParserTransformStream, +} from './customized.ts'; + +export class CDPRequestTransformStream extends CustomizedRequestTransformStream< + CDPRequestMessage & { sessionId: number } +> { + constructor(port: number, fixedId?: number) { + super({ + type: 'CDP', + port, + sessionId: (chunk) => chunk.sessionId, + messageBuilder: (chunk) => { + const id = fixedId ?? randomInt(10_000, 50_000); + const { method, params } = chunk; + return { id, method, params }; + }, + }); + } +} + +export class CDPResponseTransformStream + extends CustomizedResponseTransformStream<'CDP', Output> +{ + constructor(id?: number) { + super('CDP', id); + } +} + +export class CDPOutputTransformStream + extends ResponseParserTransformStream +{ + constructor() { + super({ + checkError: (message) => { + if ('error' in message) { + return new Error( + `CDP request error: ${message.error.message}`, + { cause: message }, + ); + } + return null; + }, + parseResult: (message) => { + if ('result' in message) { + return message.result as Output; + } + throw new Error('No result in CDP response message', { + cause: message, + }); + }, + }); + } +} diff --git a/packages/mcp-servers/devtool-connector/src/streams/customized.ts b/packages/mcp-servers/devtool-connector/src/streams/customized.ts new file mode 100644 index 0000000000..0903772813 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/streams/customized.ts @@ -0,0 +1,157 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { TransformStream } from 'node:stream/web'; + +import { isCustomizedResponseWithType } from '../types.ts'; +import type { + AppResponseMessage, + CustomizedResponseMap, + CustomizedResponseMessageMap, + Response, +} from '../types.ts'; + +export class CustomizedRequestTransformStream + extends TransformStream +{ + protected port: number; + + constructor(options: { + type: string; + port: number; + sessionId?: number | ((chunk: T) => number); + messageBuilder: (chunk: T) => unknown; + }) { + const { type, port, sessionId = -1, messageBuilder } = options; + super({ + transform(chunk, controller) { + const sid = typeof sessionId === 'function' + ? sessionId(chunk) + : sessionId; + controller.enqueue({ + event: 'Customized', + data: { + type, + data: { + client_id: port, + session_id: sid, + message: messageBuilder(chunk), + }, + sender: port, + }, + }); + }, + }); + this.port = port; + } +} + +export class CustomizedResponseTransformStream< + T extends keyof CustomizedResponseMap, + Output = CustomizedResponseMessageMap[T], +> extends TransformStream { + constructor(type: T, id?: number) { + super({ + transform(response, controller) { + if (!isCustomizedResponseWithType(response, type)) { + return; + } + + try { + const message = JSON.parse(response.data.data.message) as Output & { + id?: number; + }; + if (id === undefined || message?.id === id) { + controller.enqueue(message); + } + } catch (err) { + controller.error( + new Error(`Failed to parse response for type ${type}`, { + cause: err, + }), + ); + } + }, + }); + } +} + +/** + * Common response parser that handles JSON parsing and error checking in the payload. + */ +export class ResponseParserTransformStream + extends TransformStream +{ + constructor(options: { + parseResult: (input: Input) => Output; + checkError: (input: Input) => Error | null; + }) { + const { parseResult, checkError } = options; + super({ + transform(chunk, controller) { + const error = checkError(chunk); + if (error) { + controller.error(error); + return; + } + + try { + controller.enqueue(parseResult(chunk)); + } catch (err) { + controller.error(err); + } + }, + }); + } +} + +export class AppResponseTransformStream + extends ResponseParserTransformStream +{ + constructor(method: string) { + super({ + checkError: (message) => { + try { + const result = JSON.parse(message.result) as { + code: number | string; + message: string; + }; + if ( + /** Android */ result.code !== 0 && /** iOS */ result.code !== '0' + ) { + return new Error(`App request ${method} error: ${result.message}`, { + cause: message, + }); + } + return null; + } catch (err) { + return new Error('Failed to parse App response message', { + cause: err, + }); + } + }, + parseResult: (message) => { + return JSON.parse(message.result) as Output; + }, + }); + } +} + +export class GlobalSwitchRequestTransformStream + extends CustomizedRequestTransformStream<{ + key: string; + value?: boolean; + }> +{ + constructor(type: 'SetGlobalSwitch' | 'GetGlobalSwitch', port: number) { + super({ + type, + port, + sessionId: -1, + messageBuilder: ({ key, value }) => ({ + global_key: key, + global_value: value, + }), + }); + } +} diff --git a/packages/mcp-servers/devtool-connector/src/streams/peertalk.ts b/packages/mcp-servers/devtool-connector/src/streams/peertalk.ts new file mode 100644 index 0000000000..02282a1483 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/streams/peertalk.ts @@ -0,0 +1,84 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { TransformStream } from 'node:stream/web'; + +import type { Response } from '../types.ts'; + +export class PeertalkToMessageTransformStream + extends TransformStream +{ + constructor() { + let buffer = new Uint8Array(0); // Buffer for partial frames + const decoder = new TextDecoder(); // Reuse decoder instance + + super({ + transform: (chunk, c) => { + // 1. Simple buffer merge + const n = new Uint8Array(buffer.length + chunk.length); + n.set(buffer); + n.set(chunk, buffer.length); + buffer = n; + + // 2. Parse in a loop + // Offset 16 is the length field (fourth I in !IIIII) + while (buffer.length >= 20) { + const v = new DataView(buffer.buffer, buffer.byteOffset); + const len = v.getUint32(16); // DataView is big-endian by default, no flag needed + + if (buffer.length < 20 + len) break; // Wait for a full frame + + // 3. Extract payload and emit + try { + c.enqueue( + JSON.parse(decoder.decode(buffer.subarray(20, 20 + len))), + ); + } catch (e) { + c.error(e); + } + + // 4. Slice consumed bytes + buffer = buffer.subarray(20 + len); + } + }, + }); + } +} + +export class MessageToPeertalkTransformStream + extends TransformStream< + TMessage, + Uint8Array + > +{ + constructor() { + const encoder = new TextEncoder(); // Reuse encoder instance + + super({ + transform(chunk, controller) { + // 1. Encode message body (UTF-8) + // chunk is a single message; trim here if your messages can contain newlines + const body = encoder.encode(JSON.stringify(chunk)); + const len = body.length; + + // 2. Allocate buffer (20-byte header + body length) + const data = new Uint8Array(20 + len); + const view = new DataView(data.buffer); + + // 3. Write header (DataView is big-endian by default) + // Layout: [1, 101, 0, len + 4, len] + view.setUint32(0, 1); // Version + view.setUint32(4, 101); // Type + view.setUint32(8, 0); // Tag + view.setUint32(12, len + 4); // Total Length (Reference logic) + view.setUint32(16, len); // Payload Length + + // 4. Write message body + data.set(body, 20); + + // 5. Push downstream + controller.enqueue(data); + }, + }); + } +} diff --git a/packages/mcp-servers/devtool-connector/src/streams/utils.ts b/packages/mcp-servers/devtool-connector/src/streams/utils.ts new file mode 100644 index 0000000000..63ee11c5e0 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/streams/utils.ts @@ -0,0 +1,29 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { TransformStream } from 'node:stream/web'; + +export class FilterTransformStream + extends TransformStream +{ + constructor(filter: (chunk: T) => chunk is P) { + super({ + transform(chunk, controller) { + if (filter(chunk)) { + controller.enqueue(chunk); + } + }, + }); + } +} + +export class InspectStream extends TransformStream { + constructor(callback: (message: T) => void) { + super({ + transform(chunk, controller) { + callback(chunk); + controller.enqueue(chunk); + }, + }); + } +} diff --git a/packages/mcp-servers/devtool-connector/src/takeover.ts b/packages/mcp-servers/devtool-connector/src/takeover.ts new file mode 100644 index 0000000000..974ded0989 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/takeover.ts @@ -0,0 +1,39 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +import createDebug from 'debug'; + +const debug = createDebug('devtool-mcp-server:takeover'); + +const DEBUG_ROUTER_DIR = path.join(os.homedir(), '.DebugRouterConnector'); +const DEBUG_ROUTER_LOCK_DIR = path.join(DEBUG_ROUTER_DIR, 'lockfile'); +const DEBUG_ROUTER_LATEST_FILE = path.join( + DEBUG_ROUTER_DIR, + 'LatestDriverProcess', +); + +export async function takeoverDebugRouterLock(): Promise { + try { + await fs.mkdir(DEBUG_ROUTER_DIR, { recursive: true }); + + await fs.rm(DEBUG_ROUTER_LOCK_DIR, { recursive: true, force: true }); + + await fs.mkdir(DEBUG_ROUTER_LOCK_DIR, { recursive: true }); + + await fs.writeFile(DEBUG_ROUTER_LATEST_FILE, `${process.pid}`, 'utf-8'); + debug(`wrote PID=${process.pid}`); + } catch (err) { + debug('skipped due to filesystem error %O', err); + } finally { + try { + await fs.rm(DEBUG_ROUTER_LOCK_DIR, { recursive: true, force: true }); + } catch (_cleanupError) { + debug('failed to remove lock directory %O', _cleanupError); + } + } +} diff --git a/packages/mcp-servers/devtool-connector/src/transport/android.ts b/packages/mcp-servers/devtool-connector/src/transport/android.ts new file mode 100644 index 0000000000..d434c8b02a --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/transport/android.ts @@ -0,0 +1,183 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type { SocketConnectOpts } from 'node:net'; + +import { type Adb, AdbServerClient } from '@yume-chan/adb'; +import { AdbServerNodeTcpConnector } from '@yume-chan/adb-server-node-tcp'; +import createDebug from 'debug'; + +import type { + App, + Connection, + Device, + OpenAppOptions, + Transport, + TransportConnectOptions, +} from './transport.ts'; + +const debug = createDebug('devtool-mcp-server:connector:android'); + +const KNOWNS_APPS: App[] = [ + { packageName: 'com.ss.android.ugc.aweme', name: 'Douyin' }, + { packageName: 'com.lynx.uiapp', name: 'Lynx Example' }, + { packageName: 'com.lynx.explorer', name: 'Lynx Explorer' }, +]; + +export class AndroidTransport implements Transport { + #client: AdbServerClient; + + constructor(spec: SocketConnectOpts = { port: 5037 }) { + this.#client = new AdbServerClient(new AdbServerNodeTcpConnector(spec)); + } + + async #createAdb(deviceId: string): Promise { + const adb = await this.#client.createAdb({ serial: deviceId }); + return Object.assign(adb, { + async [Symbol.asyncDispose]() { + await adb.close(); + }, + }); + } + + close(): Promise { + // noop + debug('Android transport closed'); + return Promise.resolve(); + } + + async connect( + { deviceId, port, signal }: TransportConnectOptions, + ): Promise { + const adb = await this.#client.createAdb({ serial: deviceId }); + + debug(`connect: create connection to deviceId: ${deviceId}, port: ${port}`); + + signal?.throwIfAborted(); + + const service = `tcp:${port}`; + + let socket: Awaited>; + try { + socket = await adb.createSocket(service); + } catch (err) { + await adb.close(); + debug(`connect: create socket to ${service} failed with err: %o`, err); + throw err; + } + + if (signal?.aborted) { + await socket.close(); + await adb.close(); + signal.throwIfAborted(); + } + + const abortHandler = () => { + void Promise.resolve(socket.close()).catch((err: unknown) => { + debug(`connect: socket ${service} close on abort err: %o`, err); + }); + }; + signal?.addEventListener('abort', abortHandler, { once: true }); + + void Promise.resolve(socket.closed).catch((err: unknown) => { + debug(`connect: socket ${service} closed with err: %o`, err); + }); + + return { + readable: socket.readable as never, + writable: socket.writable as never, + async [Symbol.asyncDispose]() { + signal?.removeEventListener('abort', abortHandler); + debug( + `connect: close connection to deviceId: ${deviceId}, port: ${port}`, + ); + try { + await socket.close(); + } finally { + await adb.close(); + } + }, + }; + } + + async withConnection( + options: TransportConnectOptions, + callback: (conn: Connection) => Promise, + ): Promise { + await using conn = await this.connect(options); + return await callback(conn); + } + + async listDevices(): Promise { + const devices = await this.#client.getDevices(); + + debug('listDevices: devices %o', devices); + + return devices.map(({ serial }) => ({ + os: 'Android', + id: serial, + })); + } + + async listAvailableApps(deviceId: string): Promise { + await using adb = await this.#createAdb(deviceId); + const output = await adb.subprocess.noneProtocol.spawnWaitText([ + // adb shell pm list packages + 'pm', + 'list', + 'packages', + '-3', // third-party apps only + ]); + const packages = new Set( + output + .split('\n') + .map((line) => line.replace('package:', '').trim()) + .filter(i => i !== ''), + ); + debug(`listAvailableApps all packages: %o`, packages); + + return KNOWNS_APPS.filter((app) => packages.has(app.packageName)); + } + + async openApp( + deviceId: string, + packageName: string, + { withDataCleared }: OpenAppOptions = {}, + ): Promise { + const apps = await this.listAvailableApps(deviceId); + await using adb = await this.#createAdb(deviceId); + + if (!apps.some((app) => app.packageName === packageName)) { + throw new Error(`package ${packageName} not found`); + } + + if (withDataCleared) { + const output = await adb.subprocess.noneProtocol.spawnWaitText([ + // adb shell pm clear + 'pm', + 'clear', + packageName, + ]); + debug(`openApp clear data output ${output}`); + } + + const output = await adb.subprocess.noneProtocol.spawnWaitText([ + // adb shell monkey -p -c android.intent.category.LAUNCHER 1 + 'monkey', + '-p', + packageName, + '-c', + 'android.intent.category.LAUNCHER', + '1', + ]); + debug(`openApp LAUNCHER output ${output}`); + if (output.includes('No activities found')) { + throw new Error( + `No launchable activity found for package ${packageName}.`, + ); + } + if (output.includes('monkey aborted')) { + throw new Error(`Failed to open app ${packageName}.`); + } + } +} diff --git a/packages/mcp-servers/devtool-connector/src/transport/desktop.ts b/packages/mcp-servers/devtool-connector/src/transport/desktop.ts new file mode 100644 index 0000000000..2bff36bd90 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/transport/desktop.ts @@ -0,0 +1,81 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +import net from 'node:net'; +import { Duplex } from 'node:stream'; + +import createDebug from 'debug'; + +import type { + App, + Connection, + Device, + Transport, + TransportConnectOptions, +} from './transport.ts'; + +const debug = createDebug('devtool-mcp-server:connector:desktop'); + +export class DesktopTransport implements Transport { + close(): Promise { + debug('Desktop transport closed'); + return Promise.resolve(); + } + + listDevices(): Promise { + return Promise.resolve([{ id: 'localhost', os: 'Desktop' }]); + } + + listAvailableApps(): Promise { + return Promise.resolve([]); + } + + openApp(): Promise { + throw new Error('openApp is not supported on DesktopTransport'); + } + + async connect({ + deviceId, + port, + signal, + }: TransportConnectOptions): Promise { + if (deviceId !== 'localhost') { + throw new Error( + `DesktopTransport only supports 'localhost' deviceId, got: ${deviceId}`, + ); + } + + debug(`connect: connecting to 127.0.0.1:${port}`); + + const socket = net.createConnection({ host: '127.0.0.1', port, signal }); + + try { + if (socket.connecting) { + await new Promise((resolve, reject) => { + socket.once('connect', resolve); + socket.once('error', reject); + }); + } else { + // already connected or failed immediately + } + + debug(`connect: connected to 127.0.0.1:${port}`); + + const { readable, writable } = Duplex.toWeb(socket); + return { + readable, + writable, + [Symbol.asyncDispose]() { + debug(`connect: closing connection to 127.0.0.1:${port}`); + socket.destroy(); + return Promise.resolve(); + }, + }; + } catch (err) { + debug(`connect: error connecting to 127.0.0.1:${port} %O`, err); + socket.destroy(); + throw err; + } + } +} diff --git a/packages/mcp-servers/devtool-connector/src/transport/index.ts b/packages/mcp-servers/devtool-connector/src/transport/index.ts new file mode 100644 index 0000000000..67e7a5249e --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/transport/index.ts @@ -0,0 +1,7 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +export * from './android.ts'; +export * from './desktop.ts'; +export * from './ios.ts'; +export * from './transport.ts'; diff --git a/packages/mcp-servers/devtool-connector/src/transport/ios.ts b/packages/mcp-servers/devtool-connector/src/transport/ios.ts new file mode 100644 index 0000000000..6417f2002e --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/transport/ios.ts @@ -0,0 +1,91 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +import type { NetConnectOpts } from 'node:net'; +import { Duplex } from 'node:stream'; +import { ReadableStream } from 'node:stream/web'; +import type { WritableStream } from 'node:stream/web'; + +import createDebug from 'debug'; +import { UsbmuxClient } from 'usbmux-client'; + +import type { + App, + Connection, + Device, + Transport, + TransportConnectOptions, +} from './transport.ts'; + +const debug = createDebug('devtool-mcp-server:connector:ios'); + +export class iOSTransport implements Transport { + #client: UsbmuxClient; + + constructor(options?: NetConnectOpts) { + this.#client = new UsbmuxClient(options); + } + + async close(): Promise { + await this.#client.close(); + debug('iOS transport closed'); + } + + async connect( + { deviceId, port, signal }: TransportConnectOptions, + ): Promise { + debug(`connect: create connection to deviceId: ${deviceId}, port: ${port}`); + signal?.throwIfAborted(); + + const conn = await this.#client.createDeviceTunnel(deviceId, port); + if (signal?.aborted) { + conn.destroy(); + } + signal?.throwIfAborted(); + + const abortHandler = () => { + conn.destroy(); + }; + signal?.addEventListener('abort', abortHandler, { once: true }); + + const { readable, writable } = Duplex.toWeb(conn); + return { + readable: ReadableStream.from(readable), + writable: writable as WritableStream, + [Symbol.asyncDispose]() { + signal?.removeEventListener('abort', abortHandler); + debug( + `connect: close connection to deviceId: ${deviceId}, port: ${port}`, + ); + conn.destroy(); + return Promise.resolve(); + }, + }; + } + + async withConnection( + options: TransportConnectOptions, + callback: (conn: Connection) => Promise, + ): Promise { + await using conn = await this.connect(options); + return await callback(conn); + } + + async listDevices(): Promise { + const devices = await this.#client.getDevices(); + debug('listDevices: devices %o', devices); + return Object.values(devices).map(({ DeviceID }) => ({ + os: 'iOS', + id: `${DeviceID}`, + })); + } + + listAvailableApps(): Promise { + throw new Error('Not implemented'); + } + + openApp(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/mcp-servers/devtool-connector/src/transport/transport.ts b/packages/mcp-servers/devtool-connector/src/transport/transport.ts new file mode 100644 index 0000000000..a54de330d5 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/transport/transport.ts @@ -0,0 +1,53 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import type { ReadableStream, WritableStream } from 'node:stream/web'; + +import type { AppInfo } from '../types.ts'; + +export interface TransportConnectOptions { + deviceId: string; + port: number; + signal?: AbortSignal | undefined; +} + +export interface OpenAppOptions { + signal?: AbortSignal; + withDataCleared?: boolean | undefined; +} + +export interface Transport { + close(): Promise; + + listDevices(): Promise; + + listAvailableApps(deviceId: string): Promise; + + openApp( + deviceId: string, + packageName: string, + options?: OpenAppOptions, + ): Promise; + + connect(options: TransportConnectOptions): Promise; +} + +export interface Device { + id: string; + os: 'iOS' | 'Android' | 'Desktop' | 'OpenHarmony'; +} + +export interface Client { + id: string; + info: AppInfo; +} + +export interface App { + packageName: string; + name: string; +} + +export interface Connection extends AsyncDisposable { + readable: ReadableStream; + writable: WritableStream; +} diff --git a/packages/mcp-servers/devtool-connector/src/types.ts b/packages/mcp-servers/devtool-connector/src/types.ts new file mode 100644 index 0000000000..fb67180680 --- /dev/null +++ b/packages/mcp-servers/devtool-connector/src/types.ts @@ -0,0 +1,134 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +interface Event { + event: E; + data: D; +} + +type CustomizedEvent = Event<'Customized', { + type: TType; + sender: number; + data: TData; +}>; + +export interface AppInfo { + App: string; + AppVersion: string; + /** Android only */ + AppProcessName?: string; + /** iOS only */ + bundleId?: string; + /** OpenHarmony only */ + bundleName?: string; + debugRouterId: string; + debugRouterVersion: string; + deviceModel: string; + network: string; + osVersion: string; + sdkVersion: string; +} +export type InitializeRequest = Event<'Initialize', number>; +export type InitializeResponse = Event<'Register', { + id: number; + info: AppInfo; +}>; + +export type AppResponse = CustomizedEvent<'App', { + /** JSON string. See {@link AppResponseMessage} for parsed result. */ + message: string; +}>; +export interface AppResponseMessage { + id: number; + /** JSON string */ + result: string; +} +export interface CDPRequestMessage { + method: string; + params?: T | undefined; +} +export type CDPRequest = CustomizedEvent<'CDP', { + client_id: number; + session_id: number; + message: CDPRequestMessage & { id: number }; +}>; +export type CDPResponse = CustomizedEvent<'CDP', { + /** JSON string. See {@link CDPResponseMessage} for parsed result. */ + message: string; +}>; +export type CDPResponseMessage = + & { id: number } + & ({ result: unknown } | { error: { code: number; message: string } }); + +export interface Session { + session_id: number; + type: ''; + url: string; +} +export type ListSessionRequest = CustomizedEvent< + 'ListSession', + Record +>; +export type ListSessionResponse = CustomizedEvent<'SessionList', Session[]>; + +export type GlobalKeys = 'enable_devtool'; +export type GetGlobalSwitchRequest = CustomizedEvent<'GetGlobalSwitch', { + client_id: number; + session_id: number; + message: { global_key: GlobalKeys }; +}>; +export type GetGlobalSwitchResponse = CustomizedEvent<'GetGlobalSwitch', { + client_id: number; + session_id: number; + message: string | boolean | { global_value: string | boolean }; +}>; +export type SetGlobalSwitchRequest = CustomizedEvent<'SetGlobalSwitch', { + client_id: number; + session_id: number; + message: { global_key: GlobalKeys; global_value: boolean }; +}>; +export type SetGlobalSwitchResponse = CustomizedEvent<'SetGlobalSwitch', { + client_id: number; + session_id: number; + /** JSON string */ + message: string; +}>; + +export interface CustomizedResponseMap { + App: AppResponse; + CDP: CDPResponse; +} +export interface CustomizedResponseMessageMap { + App: AppResponseMessage; + CDP: CDPResponseMessage; +} + +export type Response = + | InitializeResponse + | ListSessionResponse + | AppResponse + | CDPResponse + | GetGlobalSwitchResponse + | SetGlobalSwitchResponse; + +export function isInitializeResponse( + response: Response, +): response is InitializeResponse { + return response.event === 'Register'; +} + +export function isListSessionResponse( + response: Response, +): response is ListSessionResponse { + return response.event === 'Customized' + && response.data.type === 'SessionList'; +} + +export function isCustomizedResponseWithType< + T extends keyof CustomizedResponseMap, +>( + response: Response, + type: T, +): response is CustomizedResponseMap[T] { + return response.event === 'Customized' && response.data.type === type; +} diff --git a/packages/mcp-servers/devtool-connector/tsconfig.json b/packages/mcp-servers/devtool-connector/tsconfig.json new file mode 100644 index 0000000000..c734bac23f --- /dev/null +++ b/packages/mcp-servers/devtool-connector/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "allowImportingTsExtensions": true, + }, + "include": ["src"], +} diff --git a/packages/mcp-servers/devtool-mcp-server/debug-router-connector.d.ts b/packages/mcp-servers/devtool-mcp-server/debug-router-connector.d.ts deleted file mode 100644 index e1b3e4f33c..0000000000 --- a/packages/mcp-servers/devtool-mcp-server/debug-router-connector.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. -// Licensed under the Apache License Version 2.0 that can be found in the -// LICENSE file in the root directory of this source tree. -export * from '@lynx-js/debug-router-connector'; diff --git a/packages/mcp-servers/devtool-mcp-server/debug-router-connector.js b/packages/mcp-servers/devtool-mcp-server/debug-router-connector.js deleted file mode 100644 index e1b3e4f33c..0000000000 --- a/packages/mcp-servers/devtool-mcp-server/debug-router-connector.js +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. -// Licensed under the Apache License Version 2.0 that can be found in the -// LICENSE file in the root directory of this source tree. -export * from '@lynx-js/debug-router-connector'; diff --git a/packages/mcp-servers/devtool-mcp-server/package.json b/packages/mcp-servers/devtool-mcp-server/package.json index 33e46cbc78..5280f20e51 100644 --- a/packages/mcp-servers/devtool-mcp-server/package.json +++ b/packages/mcp-servers/devtool-mcp-server/package.json @@ -15,26 +15,19 @@ "import": "./dist/index.js", "default": "./dist/index.js" }, - "./debug-router-connector.js": { - "types": "./debug-router-connector.d.ts", - "import": "./debug-router-connector.js", - "default": "./debug-router-connector.js" - }, "./package.json": "./package.json" }, "main": "./dist/index.js", "types": "./dist/index.d.ts", "bin": "./dist/main.js", "files": [ - "debug-router-connector.js", - "debug-router-connector.d.ts", "dist" ], "scripts": { "build": "rslib build" }, "dependencies": { - "@lynx-js/debug-router-connector": "0.0.6" + "@lynx-js/devtool-connector": "workspace:*" }, "devDependencies": { "@microsoft/api-extractor": "^7.57.6", diff --git a/packages/mcp-servers/devtool-mcp-server/src/McpContext.ts b/packages/mcp-servers/devtool-mcp-server/src/McpContext.ts index 1c4a31e74a..b40827fd6c 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/McpContext.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/McpContext.ts @@ -1,23 +1,22 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. -import type { DebugRouterConnector } from './connector.ts'; +import type { Connector } from '@lynx-js/devtool-connector'; + import type { Context } from './tools/defineTool.ts'; export class McpContext implements Context { - #connector: DebugRouterConnector; + #connector: Connector; - constructor(connector: DebugRouterConnector) { + constructor(connector: Connector) { this.#connector = connector; } - static withConnector( - connector: DebugRouterConnector, - ): Promise { + static withConnector(connector: Connector): Promise { return Promise.resolve(new McpContext(connector)); } - connector(): DebugRouterConnector { + connector(): Connector { return this.#connector; } } diff --git a/packages/mcp-servers/devtool-mcp-server/src/McpResponse.ts b/packages/mcp-servers/devtool-mcp-server/src/McpResponse.ts index caf303a1a1..7c35adaa43 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/McpResponse.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/McpResponse.ts @@ -1,25 +1,16 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type { McpContext } from './McpContext.ts'; import type { ImageContentData, Response } from './tools/defineTool.ts'; export class McpResponse implements Response { - #includeDevices = false; - setIncludeDevices(value: boolean): void { - this.#includeDevices = value; - } - - #deviceId: string | undefined; - - #includeClients = false; - setIncludeClients(value: boolean, deviceId?: string): void { - this.#includeClients = value; - this.#deviceId = deviceId; + #includeResponseTitle = true; + setIncludeResponseTitle(value: boolean): void { + this.#includeResponseTitle = value; } #additionalLines: string[] = []; @@ -32,31 +23,11 @@ export class McpResponse implements Response { this.#images.push(value); } - async handle( - toolName: string, - context: McpContext, - ): Promise> { - const connector = context.connector(); - - const responses: string[] = [`# ${toolName} response`]; - - if (this.#includeDevices) { - const devices = Array.from(connector.devices.entries()) - .map(([id, device]) => - `- ${id}: ${device.info.os}, ${device.info.title}` - ); - - responses.push('## Devices', devices.join('\n')); - } + async handle(toolName: string): Promise> { + const responses: string[] = []; - if (this.#includeClients) { - const clients = (this.#deviceId - ? (await connector.getDeviceUsbClients(this.#deviceId)) - : connector.getAllUsbClients()) - .map(client => - `- ${client.clientId()}: ${client.info.query.app} (${client.info.query.device})` - ); - responses.push('## Clients', clients.join('\n')); + if (this.#includeResponseTitle) { + responses.push(`# ${toolName} response`); } return [ diff --git a/packages/mcp-servers/devtool-mcp-server/src/connector.ts b/packages/mcp-servers/devtool-mcp-server/src/connector.ts deleted file mode 100644 index 71c5b3cd00..0000000000 --- a/packages/mcp-servers/devtool-mcp-server/src/connector.ts +++ /dev/null @@ -1,507 +0,0 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. -// Licensed under the Apache License Version 2.0 that can be found in the -// LICENSE file in the root directory of this source tree. -import { - DebugRouterConnector as BaseDebugRouterConnector, - MultiOpenStatus, - SocketEvent, -} from '@lynx-js/debug-router-connector'; -import createDebug from 'debug'; -import EventEmitter from 'node:events'; - -const debug = createDebug('lynx-devtool-mcp:cdp'); - -type ConsoleAPI = { - args: { type: 'string'; value: string }[]; - consoleId: number; - executionContextId: number; - stackTrace: { - callFrames: { - columnNumber: number; - functionName: string; - lineNumber: number; - scriptId: string; - url: string; - }[]; - }; - timestamp: number; - type: 'log' | 'info' | 'warning' | 'error' | 'debug'; - url: string; - viewId: number; -}; - -type Session = { - type: string; - session_id: number; - url: string; -}; -type SessionListResponse = { - type: 'SessionList'; - data: Session[]; - sender: number; -}; -type CDPResponse = { - type: 'CDP'; - data: { - client_id: number; - session_id: number; - message: string; - }; - sender: number; -}; -type GetGlobalSwitchResponse = { - type: 'GetGlobalSwitch'; - data: string | boolean | { global_value: string | boolean }; - sender: number; -}; -type SetGlobalSwitchResponse = { - type: 'SetGlobalSwitch'; - data: { global_value: boolean; global_key: GlobalKey }; - sender: number; -}; -type CustomizeResponse = { - event: 'Customized'; - data: - | CDPResponse - | SessionListResponse - | GetGlobalSwitchResponse - | SetGlobalSwitchResponse; -}; - -type ParsedSource = { - endColumn: number; - endLine: number; - executionContextId: number; - hasSourceURL: boolean; - hash: string; - length: number; - scriptId: string; - scriptLanguage: 'JavaScript'; - sourceMapURL: string; - startColumn: number; - startLine: number; - url: string; - viewId: number; -}; - -type EventMap = { - SessionList: [Session[]]; - GetGlobalSwitch: [string | boolean | { global_value: string | boolean }]; - SetGlobalSwitch: [{ global_value: boolean; global_key: GlobalKey }]; - 'CDP-event-Runtime.consoleAPICalled': [ - error: string | undefined, - ConsoleAPI, - clientId: number, - sessionId: number, - ]; - 'CDP-event-Debugger.scriptParsed': [ - error: string | undefined, - ParsedSource, - clientId: number, - sessionId: number, - ]; - [CDPId: `CDP-${number}` | `CDP-event-${string}`]: [ - error: string | undefined, - unknown, - clientId: number, - sessionId: number, - ]; -}; - -type GlobalKey = 'enable_devtool'; - -export class DebugRouterConnector extends BaseDebugRouterConnector { - #messageEmitter: EventEmitter = new EventEmitter(); - #consoleCollectors: Map< - /** clientId */ number, - Map - > = new Map(); - #scriptSourceCollectors: Map< - /** clientId */ number, - Map - > = new Map(); - - constructor() { - super({ - manualConnect: true, - enableWebSocket: true, - enableDesktop: true, - }); - - this.#listenClientMessages(); - this.#collectConsoleMessages(); - this.#collectScriptSources(); - } - - async getGlobalSwitch(clientId: number, key: GlobalKey): Promise { - const { promise, resolve } = Promise.withResolvers(); - - this.#messageEmitter.once('GetGlobalSwitch', (result) => { - if (typeof result === 'object') { - resolve( - result?.global_value === 'true' || result?.global_value === true, - ); - } else { - resolve(result === 'true' || result === true); - } - }); - - this.sendMessageToApp( - clientId, - JSON.stringify({ - event: 'Customized', - data: { - type: 'GetGlobalSwitch', - data: { - client_id: clientId, - message: JSON.stringify({ - global_key: key, - id: 10000, - }), - session_id: -1, - }, - sender: clientId, - }, - from: clientId, - }), - ); - - return promise; - } - - async setGlobalSwitch( - clientId: number, - key: GlobalKey, - value: boolean, - ): Promise { - const { promise, resolve } = Promise.withResolvers(); - - this.#messageEmitter.once('SetGlobalSwitch', () => { - resolve(); - }); - - this.sendMessageToApp( - clientId, - JSON.stringify({ - event: 'Customized', - data: { - type: 'SetGlobalSwitch', - data: { - client_id: clientId, - message: JSON.stringify({ - global_key: key, - global_value: value, - id: 10000, - }), - session_id: -1, - }, - sender: clientId, - }, - from: clientId, - }), - ); - - return promise; - } - - async sendSessionListMessage(clientId: number): Promise { - const { promise, resolve } = Promise.withResolvers(); - - this.#messageEmitter.once('SessionList', (sessions) => { - resolve(sessions); - }); - - this.sendMessageToApp( - clientId, - JSON.stringify({ - event: 'Customized', - data: { - type: 'ListSession', - data: [], - sender: clientId, - }, - from: clientId, - }), - ); - - return promise; - } - - #currentCDPId = 0; - async sendCDPMessage( - clientId: number, - sessionId: number, - method: string, - params?: Record, - ): Promise { - const { promise, resolve, reject } = Promise.withResolvers(); - - const id = this.#currentCDPId++; - this.#messageEmitter.once(`CDP-${id}`, (error, data) => { - if (error !== undefined) { - reject(new Error(error)); - return; - } - - resolve(data as Response); - }); - - this.sendMessageToApp( - clientId, - JSON.stringify({ - event: 'Customized', - data: { - type: 'CDP', - data: { - client_id: clientId, - session_id: sessionId, - message: { - method, - id, - params, - }, - }, - sender: clientId, - }, - from: clientId, - }), - ); - - return promise; - } - - async waitForCDPEvent(event: string): Promise { - const { promise, resolve, reject } = Promise.withResolvers(); - this.#messageEmitter.once(`CDP-event-${event}`, (error, data) => { - if (error !== undefined) { - reject(new Error(error)); - return; - } - - resolve(data as Response); - }); - return promise; - } - - #listenClientMessages() { - this.on('usb-client-message', ({ message, id }) => { - const { data, event } = JSON.parse(message) as CustomizeResponse; - - if (event !== SocketEvent.Customized) { - // We do not care about non-customized messages - // E.g.: register messages - return; - } - - data.sender = id; - switch (data.type) { - case 'CDP': - data.data.client_id = id; - this.#handleCDPResponse(data); - break; - case 'SessionList': - this.#handleSessionListResponse(data); - break; - case 'GetGlobalSwitch': - this.#handleGetGlobalSwitchResponse(data); - break; - case 'SetGlobalSwitch': - this.#handleSetGlobalSwitchResponse(data); - break; - default: - debug(`Unknown message type: %O, event: %s`, data, event); - } - }); - } - - getConsole(clientId: number, sessionId: number): ConsoleAPI[] { - const sessionConsoleCollectors = this.#consoleCollectors.get(clientId); - if (!sessionConsoleCollectors) { - throw new Error('No console collectors for clientId:' + clientId); - } - const consoleMessages = sessionConsoleCollectors.get(sessionId); - if (!consoleMessages) { - throw new Error('No console messages for sessionId:' + sessionId); - } - return consoleMessages || []; - } - - #collectConsoleMessages() { - this.onCDPEvent( - 'Runtime.consoleAPICalled', - (_, data, clientId, sessionId) => { - if (!this.#consoleCollectors.has(clientId)) { - this.#consoleCollectors.set(clientId, new Map()); - } - const sessionConsoleCollectors = this.#consoleCollectors.get(clientId)!; - if (!sessionConsoleCollectors.has(sessionId)) { - sessionConsoleCollectors.set(sessionId, []); - } - const consoleMessages = sessionConsoleCollectors.get(sessionId)!; - consoleMessages.push(data); - }, - ); - } - - getSource( - clientId: number, - sessionId: number, - scriptId?: string, - ): ParsedSource[] { - const sessionScriptSourceCollectors = this.#scriptSourceCollectors.get( - clientId, - ); - if (!sessionScriptSourceCollectors) { - throw new Error('No script source collectors for clientId:' + clientId); - } - const scriptSources = sessionScriptSourceCollectors.get(sessionId); - if (!scriptSources) { - throw new Error('No script sources for sessionId:' + sessionId); - } - - if (scriptId) { - return scriptSources.filter(source => source.scriptId === scriptId); - } - - return scriptSources; - } - - #collectScriptSources() { - this.onCDPEvent('Debugger.scriptParsed', (_, data, clientId, sessionId) => { - if (!this.#scriptSourceCollectors.has(clientId)) { - this.#scriptSourceCollectors.set(clientId, new Map()); - } - const sessionScriptSourceCollectors = this.#scriptSourceCollectors.get( - clientId, - )!; - if (!sessionScriptSourceCollectors.has(sessionId)) { - sessionScriptSourceCollectors.set(sessionId, []); - } - const scriptSources = sessionScriptSourceCollectors.get(sessionId)!; - scriptSources.push(data); - }); - } - - #handleGetGlobalSwitchResponse({ data }: GetGlobalSwitchResponse) { - this.#messageEmitter.emit('GetGlobalSwitch', data); - } - - #handleSetGlobalSwitchResponse({ data }: SetGlobalSwitchResponse) { - this.#messageEmitter.emit('SetGlobalSwitch', data); - } - - #handleCDPResponse({ data }: CDPResponse) { - interface CDPDetailedMessage { - id: number; - result: unknown; - error?: { - code: number; - message: string; - }; - } - interface CDPEvent { - method: string; - params: Record; - error?: { - code: number; - message: string; - }; - } - const response = JSON.parse(data.message) as CDPDetailedMessage | CDPEvent; - if ('error' in response) { - debug(`Error %s`, response.error.message); - } else { - debug( - `Response ${data.client_id} ${data.session_id} %O`, - JSON.parse(data.message), - ); - } - if ('id' in response) { - // Response - this.#messageEmitter.emit( - `CDP-${response.id}`, - response.error?.message, - response.result, - data.client_id, - data.session_id, - ); - } else { - // Event - this.#messageEmitter.emit( - `CDP-event-${response.method}`, - response.error?.message, - response.params, - data.client_id, - data.session_id, - ); - } - } - - #handleSessionListResponse({ data, sender }: SessionListResponse) { - this.#messageEmitter.emit('SessionList', data); - for (const { session_id } of data) { - this.sendCDPMessage(sender, session_id, 'Runtime.enable', {}); - } - } - - onCDPEvent( - event: EventName, - listener: ( - ...args: `CDP-event-${EventName}` extends `CDP-event-${infer E}` - ? EventMap[`CDP-event-${E}`] - : unknown[] - ) => void, - ) { - this.#messageEmitter.on(`CDP-event-${event}`, listener as never); - } -} - -let connector: DebugRouterConnector | null = null; - -export async function ensureLynxConnected(): Promise { - if (connector === null) { - connector = new DebugRouterConnector(); - connector.setMultiOpenCallback({ - statusChanged(status) { - if (status === MultiOpenStatus.unattached) { - connector = null; - } - }, - }); - await connector.startWSServer(); - - connector.on('device-connected', (device) => { - device.startWatchClient(); - }); - - connector.on('client-connected', async client => { - const devtoolEnabled = await connector?.getGlobalSwitch( - client.clientId(), - 'enable_devtool', - ); - - if (!devtoolEnabled) { - await connector?.setGlobalSwitch( - client.clientId(), - 'enable_devtool', - true, - ); - } - await connector?.sendSessionListMessage(client.clientId()); - }); - } - - if (connector.devices.size === 0) { - const devices = await connector.connectDevices(1000); - if (devices.length === 0) { - throw new Error('Failed to connect to Lynx: no device found.'); - } - } - - return connector; -} - -export async function reconnect(): Promise { - connector = null; - await ensureLynxConnected(); -} diff --git a/packages/mcp-servers/devtool-mcp-server/src/index.ts b/packages/mcp-servers/devtool-mcp-server/src/index.ts index bc2e8ce946..1296faf172 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/index.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/index.ts @@ -1,10 +1,18 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import 'core-js/modules/es.promise.with-resolvers.js'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import { ensureLynxConnected } from './connector.ts'; + +import { Connector } from '@lynx-js/devtool-connector'; +import { + AndroidTransport, + DesktopTransport, + iOSTransport, +} from '@lynx-js/devtool-connector/transport'; +import type { Transport } from '@lynx-js/devtool-connector/transport'; + import { McpContext } from './McpContext.ts'; import { McpResponse } from './McpResponse.ts'; import { GetBackgroundColors } from './tools/CSS/GetBackgroundColors.ts'; @@ -15,11 +23,11 @@ import { GetStyleSheetText } from './tools/CSS/GetStyleSheetText.ts'; import { GetScriptSource } from './tools/Debugger/GetScriptSource.ts'; import { ListScripts } from './tools/Debugger/ListScripts.ts'; import type { ToolDefinition } from './tools/defineTool.ts'; +import { ClosePage } from './tools/Device/ClosePage.ts'; import { ListClients } from './tools/Device/ListClients.ts'; import { ListDevices } from './tools/Device/ListDevices.ts'; import { ListSessions } from './tools/Device/ListSessions.ts'; import { OpenPage } from './tools/Device/OpenPage.ts'; -import { Reconnect } from './tools/Device/Reconnect.ts'; import { GetAttributes } from './tools/DOM/GetAttributes.ts'; import { GetBoxModel } from './tools/DOM/GetBoxModel.ts'; import { GetDocument } from './tools/DOM/GetDocument.ts'; @@ -52,11 +60,11 @@ const TOOLS = [ ListScripts, // Device + ClosePage, ListClients, ListDevices, ListSessions, OpenPage, - Reconnect, // DOM GetAttributes, @@ -85,7 +93,14 @@ const TOOLS = [ ListConsole, ] as unknown as ToolDefinition[]; -export function registerTool(mcpServer: McpServer, tool: ToolDefinition): void { +export function registerTool( + mcpServer: McpServer, + tool: ToolDefinition, + transports: Transport[], +): void { + if (!tool.schema) { + throw new Error('Tool schema is required'); + } mcpServer.registerTool( tool.name, { @@ -93,13 +108,13 @@ export function registerTool(mcpServer: McpServer, tool: ToolDefinition): void { inputSchema: tool.schema, annotations: tool.annotations, }, - async (params): Promise => { + async (params, extra): Promise => { const response = new McpResponse(); - const connector = await ensureLynxConnected(); + const connector = new Connector(transports); const context = await McpContext.withConnector(connector); try { - await tool.handler({ params }, response, context); - const content = await response.handle(tool.name, context); + await tool.handler({ params, extra }, response, context); + const content = await response.handle(tool.name); return { content }; } catch (error) { @@ -118,13 +133,35 @@ export function registerTool(mcpServer: McpServer, tool: ToolDefinition): void { ); } -export function setupServer(mcpServer: McpServer): void { +export function setupServer( + mcpServer: McpServer, + transports?: Transport[], +): void { for (const tool of TOOLS) { - registerTool(mcpServer, tool); + registerTool(mcpServer, tool, transports ?? createDefaultTransports()); } return; } -export { defineTool, type ToolDefinition } from './tools/defineTool.ts'; +function createDefaultTransports(): Transport[] { + return [ + new iOSTransport(), + new AndroidTransport({ + host: '127.0.0.1', // Node.js 18 does not support ::1 + port: 5037, + }), + new DesktopTransport(), + ]; +} + +// TODO: export from '@lynx-js/devtool-mcp-server/transport' +export { + AndroidTransport, + DesktopTransport, + iOSTransport, +} from '@lynx-js/devtool-connector/transport'; +export type { Transport } from '@lynx-js/devtool-connector/transport'; + export * as Schema from './schema/index.ts'; +export { defineTool, type ToolDefinition } from './tools/defineTool.ts'; diff --git a/packages/mcp-servers/devtool-mcp-server/src/main.ts b/packages/mcp-servers/devtool-mcp-server/src/main.ts index 4ada48384e..2456a484e1 100755 --- a/packages/mcp-servers/devtool-mcp-server/src/main.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/main.ts @@ -1,16 +1,14 @@ #!/usr/bin/env node -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. -import { defaultLogger } from '@lynx-js/debug-router-connector'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import pkg from '../package.json' with { type: 'json' }; async function main() { - defaultLogger.setLevel('silent'); const { setupServer } = await import('./index.ts'); const mcpServer = new McpServer({ name: 'Lynx DevTool', @@ -27,9 +25,9 @@ Glossary: Tool selection guidance: 1. All tools have name '_'. is the domain of the tool, e.g., 'CSS', 'DOM', 'Debugger', 'Runtime', etc. Most of the tools would have the same functionality with Chrome DevTools Protocol. See documentation at: https://chromedevtools.github.io/devtools-protocol/tot//#method-. -Tool usage guidance: - 1. Most of the tools would require a 'clientId' and a 'sessionId' parameter to identify the target client and session. You can get the list of connected devices, clients and sessions using the 'Device.listDevices', 'Device.listClients' and 'Device.listSessions' tools. -`, + Tool usage guidance: + 1. Most of the tools would require a 'clientId' and a 'sessionId' parameter to identify the target client and session. You can get the list of connected devices, clients and sessions using the 'Device_listDevices', 'Device_listClients' and 'Device_listSessions' tools. + `, }); setupServer(mcpServer); diff --git a/packages/mcp-servers/devtool-mcp-server/src/schema/index.ts b/packages/mcp-servers/devtool-mcp-server/src/schema/index.ts index 3e0dc91a0f..545d54f598 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/schema/index.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/schema/index.ts @@ -1,18 +1,17 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import * as z from 'zod'; export const clientId = z - .number() + .string() .describe( 'The clientId to list sessions. Use `Device_listClients` to get the ID.', ); export const deviceId = z.string() - .optional() .describe( - 'The deviceId. Use the `Device_listDevices` to get ID for a devices. Omit to get all clients.', + 'The deviceId. Use `Device_listDevices` to get the ID for a device.', ); // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#type-NodeId diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetBoxModel.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetBoxModel.ts index 193bcda216..4bfacd5288 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetBoxModel.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetBoxModel.ts @@ -6,7 +6,7 @@ import { defineTool } from '../defineTool.ts'; export const GetBoxModel = /*#__PURE__*/ defineTool({ name: 'DOM_getBoxModel', - description: 'Get the box model of a Lynx element.', + description: 'Get the box model of an element.', schema: { clientId, sessionId, diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetDocument.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetDocument.ts index a73852b6d5..a64e0e747a 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetDocument.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/DOM/GetDocument.ts @@ -6,7 +6,7 @@ import { defineTool } from '../defineTool.ts'; export const GetDocument = /*#__PURE__*/ defineTool({ name: 'DOM_getDocument', - description: 'Get the document tree of the Lynx page.', + description: 'Get the document tree of the page.', schema: { clientId, sessionId, diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/GetScriptSource.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/GetScriptSource.ts index ae7bcd2bb7..5dc904a5d0 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/GetScriptSource.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/GetScriptSource.ts @@ -1,4 +1,4 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import fs from 'node:fs/promises'; @@ -31,7 +31,8 @@ export const GetScriptSource = /*#__PURE__*/ defineTool({ const connector = context.connector(); const { scriptSource } = await connector.sendCDPMessage< - { scriptSource: string } + { scriptSource: string }, + { scriptId: string } >( params.clientId, params.sessionId, diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/ListScripts.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/ListScripts.ts index c8eb999d62..1b52176e30 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/ListScripts.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Debugger/ListScripts.ts @@ -1,6 +1,9 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. +import { ReadableStream } from 'node:stream/web'; +import { setTimeout } from 'node:timers/promises'; + import { clientId, sessionId } from '../../schema/index.ts'; import { defineTool } from '../defineTool.ts'; @@ -15,10 +18,46 @@ export const ListScripts = /*#__PURE__*/ defineTool({ annotations: { readOnlyHint: true, }, - async handler({ params }, response, context) { + async handler({ params, extra }, response, context) { const connector = context.connector(); - const scripts = connector.getSource(params.clientId, params.sessionId); + await using stream = await connector.sendCDPStream( + params.clientId, + ReadableStream.from([{ + sessionId: params.sessionId, + method: 'Debugger.enable', + }]), + { signal: extra.signal }, + ); + + const scripts: { scriptId: string; url: string }[] = []; + + const reader = stream.getReader(); + const IDLE_TIMEOUT = 200; + const MAX_TOTAL_TIME = 2000; + const startTime = Date.now(); + + try { + while (Date.now() - startTime < MAX_TOTAL_TIME) { + const result = await Promise.race([ + reader.read(), + setTimeout(IDLE_TIMEOUT, 'timeout' as const), + ]); + if (result === 'timeout') { + await reader.cancel(); + break; + } + + const { done, value } = result; + if (done) break; + + if (value.method === 'Debugger.scriptParsed') { + scripts.push(value.params as never); + } + } + } finally { + reader.releaseLock(); + } response.appendLines( ...scripts.map(({ scriptId, url }) => diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ClosePage.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ClosePage.ts new file mode 100644 index 0000000000..97ba155264 --- /dev/null +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ClosePage.ts @@ -0,0 +1,21 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +import { clientId } from '../../schema/index.ts'; +import { defineTool } from '../defineTool.ts'; + +export const ClosePage = /*#__PURE__*/ defineTool({ + name: 'Device_closePage', + description: 'Close the current page', + schema: { + clientId, + }, + annotations: { + readOnlyHint: false, + }, + async handler({ params }, _, context) { + const connector = context.connector(); + + await connector.sendAppMessage(params.clientId, 'App.closePage'); + }, +}); diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListClients.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListClients.ts index 7f4f868665..670c6ccc7e 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListClients.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListClients.ts @@ -1,48 +1,22 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. -import { deviceId } from '../../schema/index.ts'; import { defineTool } from '../defineTool.ts'; export const ListClients = /*#__PURE__*/ defineTool({ name: 'Device_listClients', description: - 'List all connected clients. This tool may timeout if no clients are connected or just started. Use `Devices_reconnect` and retry in a few seconds if that happens.', - schema: { - deviceId, - }, + 'List all connected clients. This tool may timeout if no clients are connected or DevTool just started; if that happens, try again after a few seconds.', + schema: {}, annotations: { readOnlyHint: true, }, - async handler({ params }, response, context) { + async handler(_, response, context) { const connector = context.connector(); - response.setIncludeClients(true, params.deviceId); - - if (connector.usbClients.size > 0) { - return; - } - - // Wait until client connected - const { promise, resolve, reject } = Promise.withResolvers(); - connector.on('client-connected', onClientConnected); - setTimeout(() => { - connector.off('client-connected', onClientConnected); - if (connector.usbClients.size === 0) { - reject( - new Error( - 'List clients timeout. Please open App with Lynx Engine and try again.', - ), - ); - } else { - resolve(); - } - }, 1000); - function onClientConnected() { - connector.off('client-connected', onClientConnected); - resolve(); - } + const clients = await connector.listClients(); - return promise; + response.setIncludeResponseTitle(false); + response.appendLines(JSON.stringify(clients, null, 2)); }, }); diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListDevices.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListDevices.ts index e2c8bd2f75..6b3f29044a 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListDevices.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListDevices.ts @@ -1,17 +1,25 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import { defineTool } from '../defineTool.ts'; export const ListDevices = /*#__PURE__*/ defineTool({ name: 'Device_listDevices', - description: 'List all connected devices', + description: 'List all connected devices.', schema: {}, annotations: { readOnlyHint: true, }, - handler(_, response) { - response.setIncludeDevices(true); - return Promise.resolve(); + async handler(_, response, context) { + const connector = context.connector(); + + const devices = await connector.listDevices(); + + response.setIncludeResponseTitle(false); + response.appendLines(JSON.stringify( + devices.map(({ id }) => id), + null, + 2, + )); }, }); diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListSessions.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListSessions.ts index e78010dc46..1145d495e0 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListSessions.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/ListSessions.ts @@ -1,4 +1,4 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import { clientId } from '../../schema/index.ts'; @@ -6,7 +6,7 @@ import { defineTool } from '../defineTool.ts'; export const ListSessions = /*#__PURE__*/ defineTool({ name: 'Device_listSessions', - description: 'List all opened Lynx pages', + description: 'List all opened sessions', schema: { clientId, }, @@ -16,7 +16,7 @@ export const ListSessions = /*#__PURE__*/ defineTool({ async handler({ params }, response, context) { const connector = context.connector(); - const sessions = await connector.sendSessionListMessage(params.clientId); + const sessions = await connector.sendListSessionMessage(params.clientId); response.appendLines(JSON.stringify(sessions)); }, diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/OpenPage.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/OpenPage.ts index bf9cf2876f..a352b05ab9 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/OpenPage.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/OpenPage.ts @@ -1,17 +1,17 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. -import type { DebugRouterConnector } from '@lynx-js/debug-router-connector'; import * as z from 'zod'; + import { clientId } from '../../schema/index.ts'; import { defineTool } from '../defineTool.ts'; export const OpenPage = /*#__PURE__*/ defineTool({ name: 'Device_openPage', - description: 'Open a Lynx page', + description: 'Open a page', schema: { - url: z.string().describe('The URL of the Lynx page'), - clientId: clientId.optional(), + url: z.string().describe('The URL of the page'), + clientId, }, annotations: { readOnlyHint: false, @@ -19,53 +19,23 @@ export const OpenPage = /*#__PURE__*/ defineTool({ async handler({ params }, _, context) { const connector = context.connector(); - const clients = params.clientId - ? (() => { - const client = connector.usbClients.get(params.clientId); - if (!client) { - throw new Error(`Lynx client not found for id: ${params.clientId}`); - } - return [client]; - })() - : connector.getAllUsbClients(); - - if (clients.length === 0) { - throw new Error('No Lynx client found'); - } - - for (const client of clients) { - if ( - params.url.startsWith('http://') || params.url.startsWith('https://') - ) { - // This is used to open URL in LynxExplorer, which does not support `App.openPage`. - open(connector, client.clientId(), params.url); - } else { - await client.sendClientMessage('App.openPage', { - url: params.url, - }); - } + try { + await connector.sendAppMessage(params.clientId, 'App.openPage', { + url: params.url, + }); + } catch { + await connector.sendMessage(params.clientId, { + event: 'Customized', + data: { + type: 'OpenCard', + data: { + type: 'url', + url: params.url, + }, + sender: params.clientId, + }, + from: params.clientId, + }); } }, }); - -function open( - connector: DebugRouterConnector, - clientId: number, - url: string, -) { - connector.sendMessageToApp( - clientId, - JSON.stringify({ - event: 'Customized', - data: { - type: 'OpenCard', - data: { - type: 'url', - url: url, - }, - sender: clientId, - }, - from: clientId, - }), - ); -} diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/Reconnect.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Device/Reconnect.ts deleted file mode 100644 index ffd31378e2..0000000000 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Device/Reconnect.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. -// Licensed under the Apache License Version 2.0 that can be found in the -// LICENSE file in the root directory of this source tree. -import { reconnect } from '../../connector.ts'; -import { defineTool } from '../defineTool.ts'; - -export const Reconnect = /*#__PURE__*/ defineTool({ - name: 'Device_reconnect', - description: - 'Reconnect all devices. This may be useful when no device or client found.', - schema: {}, - annotations: { - readOnlyHint: true, - }, - async handler(_, response) { - await reconnect(); - - response.setIncludeDevices(true); - response.setIncludeClients(true); - }, -}); diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Page/TakeScreenshot.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Page/TakeScreenshot.ts index 75bb838916..0a91028ac5 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Page/TakeScreenshot.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Page/TakeScreenshot.ts @@ -1,9 +1,11 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import fs from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; +import { ReadableStream } from 'node:stream/web'; + import { clientId, sessionId } from '../../schema/index.ts'; import { defineTool } from '../defineTool.ts'; @@ -17,56 +19,47 @@ export const TakeScreenshot = /*#__PURE__*/ defineTool({ annotations: { readOnlyHint: true, }, - async handler({ params }, response, context) { + async handler({ params, extra }, response, context) { const connector = context.connector(); - await connector.sendCDPMessage( - params.clientId, - params.sessionId, - 'Page.startScreencast', - { - 'format': 'jpeg', - 'quality': 20, - 'mode': 'fullscreen', - }, - ); - - interface Response { - data: string; - metadata: { - deviceHeight: number; - deviceWidth: number; - offsetTop: number; - pageScaleFactor: number; - scrollOffsetX: number; - scrollOffsetY: number; - timestamp: number; - }; - } + const timeoutSignal = AbortSignal.timeout(10_000); + const signal = extra.signal + ? AbortSignal.any([extra.signal, timeoutSignal]) + : timeoutSignal; - const { data, metadata } = await connector.waitForCDPEvent( - 'Page.screencastFrame', + await using stream = await connector.sendCDPStream( + params.clientId, + ReadableStream.from([ + { method: 'Lynx.getScreenshot', sessionId: params.sessionId }, + ]), + { signal }, ); - response.appendLines(JSON.stringify(metadata)); - response.attachImage({ - data, - mimeType: 'image/jpeg', - }); + for await (const { method, params: eventParams } of stream) { + if (method === 'Lynx.screenshotCaptured') { + const { data } = eventParams as { data: string }; + if (data) { + response.attachImage({ + data, + mimeType: 'image/jpeg', + }); - if (data.length > 10 * 1024) { - const tmp = await fs.mkdtemp(path.join(tmpdir(), 'lynx-devtool-mcp-')); - await fs.writeFile( - path.join(tmp, 'screenshot.jpeg'), - Buffer.from(data, 'base64'), - ); - response.appendLines(`Screenshot saved to ${tmp}/screenshot.jpeg`); + if (data.length > 10 * 1024) { + const tmp = await fs.mkdtemp( + path.join(tmpdir(), 'lynx-devtool-mcp-'), + ); + const fileName = `screenshot-Lynx_getScreenshot.jpeg`; + await fs.writeFile( + path.join(tmp, fileName), + Buffer.from(data, 'base64'), + ); + response.appendLines(`Screenshot saved to ${tmp}/${fileName}`); + } + return; + } + } } - await connector.sendCDPMessage( - params.clientId, - params.sessionId, - 'Page.stopScreencast', - ); + throw new Error('Failed to capture screenshot'); }, }); diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/Runtime/ListConsole.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/Runtime/ListConsole.ts index 3841cd0361..afa86c1eec 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/Runtime/ListConsole.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/Runtime/ListConsole.ts @@ -1,10 +1,35 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. +import { ReadableStream } from 'node:stream/web'; +import { setTimeout } from 'node:timers/promises'; + import * as z from 'zod'; + import { clientId, sessionId } from '../../schema/index.ts'; import { defineTool } from '../defineTool.ts'; +interface ConsoleCallFrame { + url: string; + lineNumber: number; + columnNumber: number; +} + +interface ConsoleStackTrace { + callFrames: ConsoleCallFrame[]; +} + +interface ConsoleArg { + value?: unknown; +} + +interface ConsoleMessage { + type: string; + args: ConsoleArg[]; + stackTrace?: ConsoleStackTrace; + url?: string; +} + export const ListConsole = /*#__PURE__*/ defineTool({ name: 'Runtime_listConsole', description: 'List all console messages.', @@ -34,29 +59,63 @@ export const ListConsole = /*#__PURE__*/ defineTool({ const { offset = 0, - limit = Number.POSITIVE_INFINITY, + limit = 100, includeStackTraces = false, level = ['info', 'log', 'warning', 'error'], } = params; - const consoleMessages = connector.getConsole( + await using stream = await connector.sendCDPStream( params.clientId, - params.sessionId, + ReadableStream.from([{ + sessionId: params.sessionId, + method: 'Runtime.enable', + }]), ); + const messages: ConsoleMessage[] = []; + + const reader = stream.getReader(); + const IDLE_TIMEOUT = 200; + const MAX_TOTAL_TIME = 2000; + const startTime = Date.now(); + + try { + while (Date.now() - startTime < MAX_TOTAL_TIME) { + const result = await Promise.race([ + reader.read(), + setTimeout(IDLE_TIMEOUT, 'timeout' as const), + ]); + if (result === 'timeout') { + await reader.cancel(); + break; + } + + const { done, value } = result; + if (done) break; + + if (value.method === 'Runtime.consoleAPICalled') { + messages.push(value.params as ConsoleMessage); + } + } + } finally { + reader.releaseLock(); + } + response.appendLines( - ...consoleMessages + ...messages + .filter(msg => (level as string[]).includes(msg.type)) .slice(offset, offset + limit) - .filter(msg => level.includes(msg.type)) .map(({ args, type, url, stackTrace }) => - `- [${type}] ${args.map(i => i.value).join(' ')} ${ - (includeStackTraces || type === 'error') - ? stackTrace.callFrames.map(frame => + `- [${type}] ${args.map((i) => i.value).join(' ')} ${ + (includeStackTraces || type === 'error') && stackTrace + ? stackTrace.callFrames.map((frame) => `\n - ${frame.url}:${frame.lineNumber}:${frame.columnNumber}` ).join( '', ) - : `(at ${url})` + : (url + ? `(at ${url})` + : '') }` ), ); diff --git a/packages/mcp-servers/devtool-mcp-server/src/tools/defineTool.ts b/packages/mcp-servers/devtool-mcp-server/src/tools/defineTool.ts index ad1734c45e..78047f4b5c 100644 --- a/packages/mcp-servers/devtool-mcp-server/src/tools/defineTool.ts +++ b/packages/mcp-servers/devtool-mcp-server/src/tools/defineTool.ts @@ -1,23 +1,30 @@ -// Copyright 2025 The Lynx Authors. All rights reserved. +// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ServerNotification, + ServerRequest, +} from '@modelcontextprotocol/sdk/types.js'; import type * as z from 'zod'; -import type { DebugRouterConnector } from '../connector.ts'; + +import type { Connector } from '@lynx-js/devtool-connector'; export interface Request { params: z.objectOutputType; + extra: RequestHandlerExtra; } export interface Response { - setIncludeClients(value: boolean, deviceId?: string): void; - setIncludeDevices(value: boolean): void; + setIncludeResponseTitle(value: boolean): void; appendLines(...lines: string[]): void; attachImage(value: ImageContentData): void; } export interface Context { - connector(): DebugRouterConnector; + connector(): Connector; } export interface ImageContentData { diff --git a/packages/mcp-servers/devtool-mcp-server/tsconfig.json b/packages/mcp-servers/devtool-mcp-server/tsconfig.json index abd958e72d..77e352e1f2 100644 --- a/packages/mcp-servers/devtool-mcp-server/tsconfig.json +++ b/packages/mcp-servers/devtool-mcp-server/tsconfig.json @@ -4,14 +4,13 @@ "compilerOptions": { "allowImportingTsExtensions": true, - "lib": ["ES2024.Promise"], + "lib": ["ES2024.Promise", "ES2022"], "module": "nodenext", "isolatedDeclarations": false, "noEmit": true, }, "include": [ - "debug-router-connector.*", "src", ], } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cacbb74b81..86c8aaed2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,16 @@ settings: excludeLinksFromLockfile: false catalogs: + adb: + '@yume-chan/adb': + specifier: ^2.5.1 + version: 2.5.1 + '@yume-chan/adb-server-node-tcp': + specifier: ^2.5.2 + version: 2.5.2 + '@yume-chan/stream-extra': + specifier: ^2.1.0 + version: 2.1.0 default: '@microsoft/api-extractor': specifier: 7.57.6 @@ -265,7 +275,7 @@ importers: version: 3.7.0 '@rsbuild/plugin-babel': specifier: 1.1.0 - version: 1.1.0(@rsbuild/core@1.7.3) + version: 1.1.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) '@types/react': specifier: ^18.3.28 version: 18.3.28 @@ -417,11 +427,44 @@ importers: specifier: 0.3.4 version: 0.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + packages/mcp-servers/devtool-connector: + devDependencies: + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 + '@types/node': + specifier: ^24.10.0 + version: 24.10.13 + '@yume-chan/adb': + specifier: catalog:adb + version: 2.5.1 + '@yume-chan/adb-server-node-tcp': + specifier: catalog:adb + version: 2.5.2 + '@yume-chan/stream-extra': + specifier: catalog:adb + version: 2.1.0 + commander: + specifier: ^13.1.0 + version: 13.1.0 + debug: + specifier: ^4.4.3 + version: 4.4.3 + rsbuild-plugin-publint: + specifier: ^0.3.3 + version: 0.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + usbmux-client: + specifier: ^0.2.1 + version: 0.2.1 + packages/mcp-servers/devtool-mcp-server: dependencies: - '@lynx-js/debug-router-connector': - specifier: 0.0.6 - version: 0.0.6 + '@lynx-js/devtool-connector': + specifier: workspace:* + version: link:../devtool-connector devDependencies: '@microsoft/api-extractor': specifier: ^7.57.6 @@ -948,10 +991,10 @@ importers: version: link:../../web-platform/web-elements '@rsbuild/plugin-less': specifier: 1.6.0 - version: 1.6.0(@rsbuild/core@1.7.3) + version: 1.6.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) '@rsbuild/plugin-sass': specifier: 1.5.0 - version: 1.5.0(@rsbuild/core@1.7.3) + version: 1.5.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) commander: specifier: ^13.1.0 version: 13.1.0 @@ -966,7 +1009,7 @@ importers: version: 1.1.1 rsbuild-plugin-tailwindcss: specifier: 0.2.4 - version: 0.2.4(@rsbuild/core@1.7.3)(tailwindcss@3.4.19) + version: 0.2.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(tailwindcss@3.4.19) rslog: specifier: ^1.3.2 version: 1.3.2 @@ -1792,13 +1835,13 @@ importers: version: 7.33.4(@types/node@24.10.13) '@rsbuild/plugin-sass': specifier: 1.5.0 - version: 1.5.0(@rsbuild/core@1.7.3) + version: 1.5.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) '@rsbuild/plugin-type-check': specifier: 1.3.4 - version: 1.3.4(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3) + version: 1.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3) '@rsbuild/plugin-typed-css-modules': specifier: 1.2.2 - version: 1.2.2(@rsbuild/core@1.7.3) + version: 1.2.2(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) '@rspress/core': specifier: 2.0.3 version: 2.0.3(@types/react@19.2.14)(core-js@3.48.0) @@ -1911,169 +1954,6 @@ packages: resolution: {integrity: sha512-Hb4o6h1Pf6yRUAX07DR4JVY7dmQw+RVQMW5/m55GoiAT/VRoKCWBtIUPPOnqDVhbx1Cjfil9b6EDrgJsUAujEQ==} engines: {node: '>= 10'} - '@aws-crypto/crc32@5.2.0': - resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/crc32c@5.2.0': - resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} - - '@aws-crypto/sha1-browser@5.2.0': - resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} - - '@aws-crypto/sha256-browser@5.2.0': - resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - - '@aws-crypto/sha256-js@5.2.0': - resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/supports-web-crypto@5.2.0': - resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - - '@aws-crypto/util@5.2.0': - resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - - '@aws-sdk/client-s3@3.669.0': - resolution: {integrity: sha512-diqkwSqWjgAZ5NdYOtpYI/JlwsFDmo7x2Q+QOHevFaCEp4vgZOwJyXE3DQbKoO1X10fMH2vOUULgonPJd4ClUQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso-oidc@3.669.0': - resolution: {integrity: sha512-E7uYOS3Axhe36Zeq6iLC9kjF1mMEyCQ4fXud11h22rbjq7PFUtN2Omekrch37eUx3BFj1jMePnuTnT98t5LWnw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.669.0 - - '@aws-sdk/client-sso@3.669.0': - resolution: {integrity: sha512-WNpfNYIHzehLv98F+KolJglXNjJOTbOvIbSZ2XAnebVLmXCWeEEd1r4dIH0oI2dHtLbQ/h3uqaeQhsVQjLAxpw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sts@3.669.0': - resolution: {integrity: sha512-1XdOBtHKCVxVkEDiy+oktJNSsySj3ADyh58KpHaqgvCQKV3vmfkr0YO5dG4kqq+TflNwdfl1YgMuOUiCulOWeQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/core@3.667.0': - resolution: {integrity: sha512-pMcDVI7Tmdsc8R3sDv0Omj/4iRParGY+uJtAfF669WnZfDfaBQaix2Mq7+Mu08vdjqO9K3gicFvjk9S1VLmOKA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-env@3.667.0': - resolution: {integrity: sha512-zZbrkkaPc54WXm+QAnpuv0LPNfsts0HPPd+oCECGs7IQRaFsGj187cwvPg9RMWDFZqpm64MdBDoA8OQHsqzYCw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-http@3.667.0': - resolution: {integrity: sha512-sjtybFfERZWiqTY7fswBxKQLvUkiCucOWyqh3IaPo/4nE1PXRnaZCVG0+kRBPrYIxWqiVwytvZzMJy8sVZcG0A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-ini@3.669.0': - resolution: {integrity: sha512-YHhfH7w29BmMPnOK0BrBhEy2IRFRSRGSCyz3jtqpG883CZ2Lxan/AzaJDfKRdz350KPgbMMBwbPkIrqNIsg8iw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.669.0 - - '@aws-sdk/credential-provider-node@3.669.0': - resolution: {integrity: sha512-O506azQcq6N1gnDX870MXXL9LHlhX0k6BlLMM6IDClxVDnlNkK3+n2cAEKSy8HwZJcRlekcsKz/AS2CxjPY+fg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-process@3.667.0': - resolution: {integrity: sha512-HZHnvop32fKgsNHkdhVaul7UzQ25sEc0j9yqA4bjhtbk0ECl42kj3f1pJ+ZU/YD9ut8lMJs/vVqiOdNThVdeBw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-sso@3.669.0': - resolution: {integrity: sha512-HvpMJQ8xZuEGjadARVOfORPZx4U23PC5Jf6Fj+/NWK4VxEXhvf8J037fvGp3xRds5wUeuBBbhWX+Cbt0lbLCwQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.667.0': - resolution: {integrity: sha512-t8CFlZMD/1p/8Cli3rvRiTJpjr/8BO64gw166AHgFZYSN2h95L2l1tcW0jpsc3PprA32nLg1iQVKYt4WGM4ugw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.667.0 - - '@aws-sdk/middleware-bucket-endpoint@3.667.0': - resolution: {integrity: sha512-XGz4jMAkDoTyFdtLz7ZF+C05IAhCTC1PllpvTBaj821z/L0ilhbqVhrT/f2Buw8Id/K5A390csGXgusXyrFFjA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-expect-continue@3.667.0': - resolution: {integrity: sha512-0TiSL9S5DSG95NHGIz6qTMuV7GDKVn8tvvGSrSSZu/wXO3JaYSH0AElVpYfc4PtPRqVpEyNA7nnc7W56mMCLWQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-flexible-checksums@3.669.0': - resolution: {integrity: sha512-01UQLoUzVwWMf+b+AEuwJ2lluBD+Cp8AcbyEHqvEaPdjGKHIS4BCvnY70mZYnAfRtL8R2h9tt7iI61oWU3Gjkg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-host-header@3.667.0': - resolution: {integrity: sha512-Z7fIAMQnPegs7JjAQvlOeWXwpMRfegh5eCoIP6VLJIeR6DLfYKbP35JBtt98R6DXslrN2RsbTogjbxPEDQfw1w==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-location-constraint@3.667.0': - resolution: {integrity: sha512-ob85H3HhT3/u5O+x0o557xGZ78vSNeSSwMaSitxdsfs2hOuoUl1uk+OeLpi1hkuJnL41FPpokV7TVII2XrFfmg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-logger@3.667.0': - resolution: {integrity: sha512-PtTRNpNm/5c746jRgZCNg4X9xEJIwggkGJrF0GP9AB1ANg4pc/sF2Fvn1NtqPe9wtQ2stunJprnm5WkCHN7QiA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-recursion-detection@3.667.0': - resolution: {integrity: sha512-U5glWD3ehFohzpUpopLtmqAlDurGWo2wRGPNgi4SwhWU7UDt6LS7E/UvJjqC0CUrjlzOw+my2A+Ncf+fisMhxQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-sdk-s3@3.669.0': - resolution: {integrity: sha512-b2QUQ7DcIcVCUFhvmFEDI90BemvQhO0ntIajllLqQSy88PSNdLDCVx5mIzfxaaK/1tdY/UsEDRRm1kMQHJDQpg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-ssec@3.667.0': - resolution: {integrity: sha512-1wuAUZIkmZIvOmGg5qNQU821CGFHhkuKioxXgNh0DpUxZ9+AeiV7yorJr+bqkb2KBFv1i1TnzGRecvKf/KvZIQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-user-agent@3.669.0': - resolution: {integrity: sha512-K8ScPi45zjJrj5Y2gRqVsvKKQCQbvQBfYGcBw9ZOx9TTavH80bOCBjWg/GFnvs4f37tqVc1wMN2oGvcTF6HveQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/region-config-resolver@3.667.0': - resolution: {integrity: sha512-iNr+JhhA902JMKHG9IwT9YdaEx6KGl6vjAL5BRNeOjfj4cZYMog6Lz/IlfOAltMtT0w88DAHDEFrBd2uO0l2eg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.669.0': - resolution: {integrity: sha512-TVwlWAxfBHnFjnfTBQWUhzVJzjwVhkq1+KR0JZV7JrfqeyBOdZjAaV9ie3VNY9HUouecq1fDuKaSwe4JiWQsHg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/token-providers@3.667.0': - resolution: {integrity: sha512-ZecJlG8p6D4UTYlBHwOWX6nknVtw/OBJ3yPXTSajBjhUlj9lE2xvejI8gl4rqkyLXk7z3bki+KR4tATbMaM9yg==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sso-oidc': ^3.667.0 - - '@aws-sdk/types@3.667.0': - resolution: {integrity: sha512-gYq0xCsqFfQaSL/yT1Gl1vIUjtsg7d7RhnUfsXaHt8xTxOKRTdH9GjbesBjXOzgOvB0W0vfssfreSNGFlOOMJg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-arn-parser@3.568.0': - resolution: {integrity: sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-endpoints@3.667.0': - resolution: {integrity: sha512-X22SYDAuQJWnkF1/q17pkX3nGw5XMD9YEUbmt87vUnRq7iyJ3JOpl6UKOBeUBaL838wA5yzdbinmCITJ/VZ1QA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-locate-window@3.893.0': - resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/util-user-agent-browser@3.667.0': - resolution: {integrity: sha512-y1pKlNzNpxzddM0QSnfIfIbi3Z9LTag1VDjYyZRbEGGSVip2J00qKsET+979nRezWMyJgw5GPBQR3Y+rN+jh0Q==} - - '@aws-sdk/util-user-agent-node@3.669.0': - resolution: {integrity: sha512-9jxCYrgggy2xd44ZASqI7AMiRVaSiFp+06Kg8BQSU0ijKpBJlwcsqIS8pDT/n6LxuOw2eV5ipvM2C0r1iKzrGA==} - engines: {node: '>=16.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/xml-builder@3.662.0': - resolution: {integrity: sha512-ikLkXn0igUpnJu2mCZjklvmcDGWT9OaLRv3JyC/cRkTaaSrblPjPM7KKsltxdMTLQ+v7fjCN0TsJpxphMfaOPA==} - engines: {node: '>=16.0.0'} - '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -2525,19 +2405,6 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@devicefarmer/adbkit-logcat@2.1.3': - resolution: {integrity: sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==} - engines: {node: '>= 4'} - - '@devicefarmer/adbkit-monkey@1.2.1': - resolution: {integrity: sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==} - engines: {node: '>= 0.10.4'} - - '@devicefarmer/adbkit@3.2.4': - resolution: {integrity: sha512-8Iq5aGMD5jUvg86rJnU4zbsYfIaWMl7Ch+BhGnVRD1BqaVs0fQAUOMQzqGrFe01GlbBx3GX5T0ppNQC5IizL3g==} - engines: {node: '>= 0.10.4'} - hasBin: true - '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -2987,6 +2854,9 @@ packages: peerDependencies: hono: ^4 + '@httptoolkit/util@0.1.9': + resolution: {integrity: sha512-MRRf7UAHcb/bXfytFDmuCo+XRH6HqxXpQpTMT8KUd95YCLDiofPOBE+2SV3M6rG4YYeEf3WxNzQiidn3qo1akQ==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -3190,9 +3060,6 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} - '@lynx-js/debug-router-connector@0.0.6': - resolution: {integrity: sha512-ZJAFRfG/zzfAF7+GxtWuT/VTRNaTt0FDvlDiJJdHhHY44YenCvuPSuDnKnqO3U8TREtLrJVDBKgrAvTik6NgRA==} - '@lynx-js/lynx-core@0.1.3': resolution: {integrity: sha512-uWzKKYJUK4Q09ZRZxWSAFINnmZb9piWPbvWF9SkLn+3snBl9u/BJa4ekPRcKWAhBmpbtxWH1x27fxe3Q3p5l3Q==} @@ -3894,212 +3761,6 @@ packages: '@sinclair/typebox@0.34.37': resolution: {integrity: sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==} - '@smithy/abort-controller@3.1.9': - resolution: {integrity: sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==} - engines: {node: '>=16.0.0'} - - '@smithy/chunked-blob-reader-native@3.0.1': - resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} - - '@smithy/chunked-blob-reader@4.0.0': - resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} - - '@smithy/config-resolver@3.0.13': - resolution: {integrity: sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg==} - engines: {node: '>=16.0.0'} - - '@smithy/core@2.5.7': - resolution: {integrity: sha512-8olpW6mKCa0v+ibCjoCzgZHQx1SQmZuW/WkrdZo73wiTprTH6qhmskT60QLFdT9DRa5mXxjz89kQPZ7ZSsoqqg==} - engines: {node: '>=16.0.0'} - - '@smithy/credential-provider-imds@3.2.8': - resolution: {integrity: sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-codec@3.1.10': - resolution: {integrity: sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ==} - - '@smithy/eventstream-serde-browser@3.0.14': - resolution: {integrity: sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-config-resolver@3.0.11': - resolution: {integrity: sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-node@3.0.13': - resolution: {integrity: sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-universal@3.0.13': - resolution: {integrity: sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw==} - engines: {node: '>=16.0.0'} - - '@smithy/fetch-http-handler@3.2.9': - resolution: {integrity: sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==} - - '@smithy/fetch-http-handler@4.1.3': - resolution: {integrity: sha512-6SxNltSncI8s689nvnzZQc/dPXcpHQ34KUj6gR/HBroytKOd/isMG3gJF/zBE1TBmTT18TXyzhg3O3SOOqGEhA==} - - '@smithy/hash-blob-browser@3.1.10': - resolution: {integrity: sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA==} - - '@smithy/hash-node@3.0.11': - resolution: {integrity: sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA==} - engines: {node: '>=16.0.0'} - - '@smithy/hash-stream-node@3.1.10': - resolution: {integrity: sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q==} - engines: {node: '>=16.0.0'} - - '@smithy/invalid-dependency@3.0.11': - resolution: {integrity: sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ==} - - '@smithy/is-array-buffer@2.2.0': - resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} - engines: {node: '>=14.0.0'} - - '@smithy/is-array-buffer@3.0.0': - resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} - engines: {node: '>=16.0.0'} - - '@smithy/md5-js@3.0.11': - resolution: {integrity: sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ==} - - '@smithy/middleware-content-length@3.0.13': - resolution: {integrity: sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-endpoint@3.2.8': - resolution: {integrity: sha512-OEJZKVUEhMOqMs3ktrTWp7UvvluMJEvD5XgQwRePSbDg1VvBaL8pX8mwPltFn6wk1GySbcVwwyldL8S+iqnrEQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-retry@3.0.34': - resolution: {integrity: sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-serde@3.0.11': - resolution: {integrity: sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-stack@3.0.11': - resolution: {integrity: sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA==} - engines: {node: '>=16.0.0'} - - '@smithy/node-config-provider@3.1.12': - resolution: {integrity: sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ==} - engines: {node: '>=16.0.0'} - - '@smithy/node-http-handler@3.3.3': - resolution: {integrity: sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==} - engines: {node: '>=16.0.0'} - - '@smithy/property-provider@3.1.11': - resolution: {integrity: sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==} - engines: {node: '>=16.0.0'} - - '@smithy/protocol-http@4.1.8': - resolution: {integrity: sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-builder@3.0.11': - resolution: {integrity: sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-parser@3.0.11': - resolution: {integrity: sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw==} - engines: {node: '>=16.0.0'} - - '@smithy/service-error-classification@3.0.11': - resolution: {integrity: sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==} - engines: {node: '>=16.0.0'} - - '@smithy/shared-ini-file-loader@3.1.12': - resolution: {integrity: sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==} - engines: {node: '>=16.0.0'} - - '@smithy/signature-v4@4.2.4': - resolution: {integrity: sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==} - engines: {node: '>=16.0.0'} - - '@smithy/smithy-client@3.7.0': - resolution: {integrity: sha512-9wYrjAZFlqWhgVo3C4y/9kpc68jgiSsKUnsFPzr/MSiRL93+QRDafGTfhhKAb2wsr69Ru87WTiqSfQusSmWipA==} - engines: {node: '>=16.0.0'} - - '@smithy/types@3.7.2': - resolution: {integrity: sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==} - engines: {node: '>=16.0.0'} - - '@smithy/url-parser@3.0.11': - resolution: {integrity: sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw==} - - '@smithy/util-base64@3.0.0': - resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-body-length-browser@3.0.0': - resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} - - '@smithy/util-body-length-node@3.0.0': - resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-buffer-from@3.0.0': - resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-config-provider@3.0.0': - resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-defaults-mode-browser@3.0.34': - resolution: {integrity: sha512-FumjjF631lR521cX+svMLBj3SwSDh9VdtyynTYDAiBDEf8YPP5xORNXKQ9j0105o5+ARAGnOOP/RqSl40uXddA==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-defaults-mode-node@3.0.34': - resolution: {integrity: sha512-vN6aHfzW9dVVzkI0wcZoUXvfjkl4CSbM9nE//08lmUMyf00S75uuCpTrqF9uD4bD9eldIXlt53colrlwKAT8Gw==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-endpoints@2.1.7': - resolution: {integrity: sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==} - engines: {node: '>=16.0.0'} - - '@smithy/util-hex-encoding@3.0.0': - resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-middleware@3.0.11': - resolution: {integrity: sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==} - engines: {node: '>=16.0.0'} - - '@smithy/util-retry@3.0.11': - resolution: {integrity: sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-stream@3.3.4': - resolution: {integrity: sha512-SGhGBG/KupieJvJSZp/rfHHka8BFgj56eek9px4pp7lZbOF+fRiVr4U7A3y3zJD8uGhxq32C5D96HxsTC9BckQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-uri-escape@3.0.0': - resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} - engines: {node: '>=16.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@3.0.0': - resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-waiter@3.2.0': - resolution: {integrity: sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg==} - engines: {node: '>=16.0.0'} - '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -4412,9 +4073,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - '@types/webpack-sources@3.2.3': resolution: {integrity: sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==} @@ -4743,12 +4401,37 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@yume-chan/adb-server-node-tcp@2.5.2': + resolution: {integrity: sha512-ga9icFBRUk1n6lO+ymlj83qE+90kMLV8sQA3w9mZAisfdxxEEIq1cvXitFQJvDgL0NuFQruVI4JoZA/wrgpKOw==} + + '@yume-chan/adb@2.5.1': + resolution: {integrity: sha512-Nm9ZA0cOxfINE+BlZ7whgqjtoLpaZg1xcV8e0C7YbpG/KOHnQGseGTwbfqJFR6xdqbRvvrNL4jIGGpxvn31ykw==} + + '@yume-chan/async@4.1.3': + resolution: {integrity: sha512-0vzhNJMkWUPyjKzUK4rqHEeCU6YQtF78RsB1kFRB6Y2BLupmEQNxcSb0mjKabPL9jZpCCiLa5KL8oTOJClUVaw==} + + '@yume-chan/event@2.0.0': + resolution: {integrity: sha512-z56MDOcX1QlgLUCuA6th3r10negVb7A3gzY//TwSC9ZOvzuRlrAqXcxZf1T3hHfNMk/NFO9RIgQgegXYSfaqLw==} + + '@yume-chan/no-data-view@2.0.0': + resolution: {integrity: sha512-0GRJrrt6wtZlbiE92jocHOnaAvjQ+Y7xwwhwOPqLkwf90Kj1JIHJ5Zh4wJVQSQIkzfRSOpM+jeEQdC2K15snlA==} + + '@yume-chan/stream-extra@2.1.0': + resolution: {integrity: sha512-Sq1mDCLTIOu+TbI3VmFhcKL5hi+qZkDA3S+JezZb/AJm4ESC3hB7+LgQIQs3QEK/ZeRwMg37w4IOYHSh30R7BQ==} + + '@yume-chan/struct@2.3.2': + resolution: {integrity: sha512-afoCnSKV+5HRK7e4innVd9YTYDyNWdjA1CVQa1j8rWYnmr7HGZfdkHMQr+AESLk6GxWFMzOIPQIG6nYOTGMFIw==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -4782,10 +4465,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - address@1.2.2: - resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} - engines: {node: '>= 10.0.0'} - agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -5011,9 +4690,6 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -5035,9 +4711,6 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - bowser@2.12.1: - resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -5072,9 +4745,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bufferpack@0.0.6: - resolution: {integrity: sha512-MTWvLHElqczrIVhge9qHtqgNigJFyh0+tCDId5yCbFAfuekHWIG+uAgvoHVflwrDPuY/e47JE1ki5qcM7w4uLg==} - builtin-modules@5.0.0: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} @@ -5537,15 +5207,6 @@ packages: supports-color: optional: true - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -5660,11 +5321,6 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - detect-port@1.6.1: - resolution: {integrity: sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==} - engines: {node: '>= 4.0.0'} - hasBin: true - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -5729,9 +5385,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexer2@0.1.4: - resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -6167,10 +5820,6 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-xml-parser@4.4.1: - resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} - hasBin: true - fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -6727,9 +6376,6 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -7592,9 +7238,6 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -7953,9 +7596,9 @@ packages: engines: {node: '>=18'} hasBin: true - plist@3.0.6: - resolution: {integrity: sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==} - engines: {node: '>=6'} + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} @@ -8254,14 +7897,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - q@1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} - deprecated: |- - You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) - qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -8984,9 +8619,6 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} - split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -9107,9 +8739,6 @@ packages: resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} engines: {node: '>=0.10.0'} - strnum@1.1.2: - resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} - style-to-js@1.1.16: resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} @@ -9552,9 +9181,6 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - unzipper@0.12.3: - resolution: {integrity: sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==} - upath@2.0.1: resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} engines: {node: '>=4'} @@ -9571,8 +9197,8 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - utf8@3.0.0: - resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} + usbmux-client@0.2.1: + resolution: {integrity: sha512-VXiLllRT/PFRKPRtVs34VVB2UQ6Uf6GRqfHVngTZLem8kUCvvua2q7oVoa4h50wESAAfFpmmd4PvtpyHafJFEw==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -9585,10 +9211,6 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - v8-to-istanbul@9.3.0: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} @@ -10046,518 +9668,13 @@ snapshots: '@ast-grep/napi-win32-ia32-msvc': 0.37.0 '@ast-grep/napi-win32-x64-msvc': 0.37.0 - '@aws-crypto/crc32@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 - tslib: 2.8.1 - - '@aws-crypto/crc32c@5.2.0': + '@babel/code-frame@7.26.2': dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 - tslib: 2.8.1 + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@aws-crypto/sha1-browser@5.2.0': - dependencies: - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-locate-window': 3.893.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-crypto/sha256-browser@5.2.0': - dependencies: - '@aws-crypto/sha256-js': 5.2.0 - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-locate-window': 3.893.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-crypto/sha256-js@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 - tslib: 2.8.1 - - '@aws-crypto/supports-web-crypto@5.2.0': - dependencies: - tslib: 2.8.1 - - '@aws-crypto/util@5.2.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-sdk/client-s3@3.669.0': - dependencies: - '@aws-crypto/sha1-browser': 5.2.0 - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.669.0(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/client-sts': 3.669.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/credential-provider-node': 3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/middleware-bucket-endpoint': 3.667.0 - '@aws-sdk/middleware-expect-continue': 3.667.0 - '@aws-sdk/middleware-flexible-checksums': 3.669.0 - '@aws-sdk/middleware-host-header': 3.667.0 - '@aws-sdk/middleware-location-constraint': 3.667.0 - '@aws-sdk/middleware-logger': 3.667.0 - '@aws-sdk/middleware-recursion-detection': 3.667.0 - '@aws-sdk/middleware-sdk-s3': 3.669.0 - '@aws-sdk/middleware-ssec': 3.667.0 - '@aws-sdk/middleware-user-agent': 3.669.0 - '@aws-sdk/region-config-resolver': 3.667.0 - '@aws-sdk/signature-v4-multi-region': 3.669.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-endpoints': 3.667.0 - '@aws-sdk/util-user-agent-browser': 3.667.0 - '@aws-sdk/util-user-agent-node': 3.669.0 - '@aws-sdk/xml-builder': 3.662.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/eventstream-serde-browser': 3.0.14 - '@smithy/eventstream-serde-config-resolver': 3.0.11 - '@smithy/eventstream-serde-node': 3.0.13 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-blob-browser': 3.1.10 - '@smithy/hash-node': 3.0.11 - '@smithy/hash-stream-node': 3.1.10 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/md5-js': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 - '@smithy/util-waiter': 3.2.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0)': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.669.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/credential-provider-node': 3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/middleware-host-header': 3.667.0 - '@aws-sdk/middleware-logger': 3.667.0 - '@aws-sdk/middleware-recursion-detection': 3.667.0 - '@aws-sdk/middleware-user-agent': 3.669.0 - '@aws-sdk/region-config-resolver': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-endpoints': 3.667.0 - '@aws-sdk/util-user-agent-browser': 3.667.0 - '@aws-sdk/util-user-agent-node': 3.669.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso@3.669.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/middleware-host-header': 3.667.0 - '@aws-sdk/middleware-logger': 3.667.0 - '@aws-sdk/middleware-recursion-detection': 3.667.0 - '@aws-sdk/middleware-user-agent': 3.669.0 - '@aws-sdk/region-config-resolver': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-endpoints': 3.667.0 - '@aws-sdk/util-user-agent-browser': 3.667.0 - '@aws-sdk/util-user-agent-node': 3.669.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sts@3.669.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.669.0(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/core': 3.667.0 - '@aws-sdk/credential-provider-node': 3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/middleware-host-header': 3.667.0 - '@aws-sdk/middleware-logger': 3.667.0 - '@aws-sdk/middleware-recursion-detection': 3.667.0 - '@aws-sdk/middleware-user-agent': 3.669.0 - '@aws-sdk/region-config-resolver': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-endpoints': 3.667.0 - '@aws-sdk/util-user-agent-browser': 3.667.0 - '@aws-sdk/util-user-agent-node': 3.669.0 - '@smithy/config-resolver': 3.0.13 - '@smithy/core': 2.5.7 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.11 - '@smithy/invalid-dependency': 3.0.11 - '@smithy/middleware-content-length': 3.0.13 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-retry': 3.0.34 - '@smithy/middleware-serde': 3.0.11 - '@smithy/middleware-stack': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/node-http-handler': 3.3.3 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.34 - '@smithy/util-defaults-mode-node': 3.0.34 - '@smithy/util-endpoints': 2.1.7 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/core': 2.5.7 - '@smithy/node-config-provider': 3.1.12 - '@smithy/property-provider': 3.1.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-middleware': 3.0.11 - fast-xml-parser: 4.4.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.667.0': - dependencies: - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.667.0': - dependencies: - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.3.3 - '@smithy/property-provider': 3.1.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-stream': 3.3.4 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))(@aws-sdk/client-sts@3.669.0)': - dependencies: - '@aws-sdk/client-sts': 3.669.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/credential-provider-env': 3.667.0 - '@aws-sdk/credential-provider-http': 3.667.0 - '@aws-sdk/credential-provider-process': 3.667.0 - '@aws-sdk/credential-provider-sso': 3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0)) - '@aws-sdk/credential-provider-web-identity': 3.667.0(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/types': 3.667.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - - '@aws-sdk/credential-provider-node@3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))(@aws-sdk/client-sts@3.669.0)': - dependencies: - '@aws-sdk/credential-provider-env': 3.667.0 - '@aws-sdk/credential-provider-http': 3.667.0 - '@aws-sdk/credential-provider-ini': 3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/credential-provider-process': 3.667.0 - '@aws-sdk/credential-provider-sso': 3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0)) - '@aws-sdk/credential-provider-web-identity': 3.667.0(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/types': 3.667.0 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - - aws-crt - - '@aws-sdk/credential-provider-process@3.667.0': - dependencies: - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-sso@3.669.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))': - dependencies: - '@aws-sdk/client-sso': 3.669.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/token-providers': 3.667.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0)) - '@aws-sdk/types': 3.667.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - - '@aws-sdk/credential-provider-web-identity@3.667.0(@aws-sdk/client-sts@3.669.0)': - dependencies: - '@aws-sdk/client-sts': 3.669.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-bucket-endpoint@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-expect-continue@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-flexible-checksums@3.669.0': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@aws-crypto/crc32c': 5.2.0 - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@smithy/is-array-buffer': 3.0.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-host-header@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-location-constraint@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-logger@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-recursion-detection@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-s3@3.669.0': - dependencies: - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/core': 2.5.7 - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-ssec@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-user-agent@3.669.0': - dependencies: - '@aws-sdk/core': 3.667.0 - '@aws-sdk/types': 3.667.0 - '@aws-sdk/util-endpoints': 3.667.0 - '@smithy/core': 2.5.7 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/region-config-resolver@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.11 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.669.0': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.669.0 - '@aws-sdk/types': 3.667.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/signature-v4': 4.2.4 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.667.0(@aws-sdk/client-sso-oidc@3.669.0(@aws-sdk/client-sts@3.669.0))': - dependencies: - '@aws-sdk/client-sso-oidc': 3.669.0(@aws-sdk/client-sts@3.669.0) - '@aws-sdk/types': 3.667.0 - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/types@3.667.0': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/util-arn-parser@3.568.0': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-endpoints@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/types': 3.7.2 - '@smithy/util-endpoints': 2.1.7 - tslib: 2.8.1 - - '@aws-sdk/util-locate-window@3.893.0': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-browser@3.667.0': - dependencies: - '@aws-sdk/types': 3.667.0 - '@smithy/types': 3.7.2 - bowser: 2.12.1 - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-node@3.669.0': - dependencies: - '@aws-sdk/middleware-user-agent': 3.669.0 - '@aws-sdk/types': 3.667.0 - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@aws-sdk/xml-builder@3.662.0': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/code-frame@7.29.0': + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 @@ -11117,7 +10234,7 @@ snapshots: '@codspeed/core@5.2.0': dependencies: - axios: 1.11.0(debug@4.3.4) + axios: 1.11.0 find-up: 6.3.0 form-data: 4.0.4 node-gyp-build: 4.8.4 @@ -11155,22 +10272,6 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@devicefarmer/adbkit-logcat@2.1.3': {} - - '@devicefarmer/adbkit-monkey@1.2.1': {} - - '@devicefarmer/adbkit@3.2.4': - dependencies: - '@devicefarmer/adbkit-logcat': 2.1.3 - '@devicefarmer/adbkit-monkey': 1.2.1 - bluebird: 3.7.2 - commander: 9.5.0 - debug: 4.3.7 - node-forge: 1.3.1 - split: 1.0.1 - transitivePeerDependencies: - - supports-color - '@discoveryjs/json-ext@0.5.7': {} '@dprint/darwin-arm64@0.51.1': @@ -11461,6 +10562,8 @@ snapshots: dependencies: hono: 4.11.7 + '@httptoolkit/util@0.1.9': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -11692,33 +10795,6 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@lynx-js/debug-router-connector@0.0.6': - dependencies: - '@aws-sdk/client-s3': 3.669.0 - '@devicefarmer/adbkit': 3.2.4 - '@types/uuid': 9.0.8 - axios: 1.11.0(debug@4.3.4) - bufferpack: 0.0.6 - chalk: 4.1.2 - debug: 4.3.4 - detect-port: 1.6.1 - express: 5.2.1 - fs-extra: 11.3.3 - ip: 2.0.1 - plist: 3.0.6 - q: 1.5.1 - signal-exit: 4.1.0 - unzipper: 0.12.3 - utf8: 3.0.0 - uuid: 9.0.1 - ws: 8.19.0 - yargs: 15.4.1 - transitivePeerDependencies: - - aws-crt - - bufferutil - - supports-color - - utf-8-validate - '@lynx-js/lynx-core@0.1.3': {} '@lynx-js/preact-devtools@5.0.1': @@ -12119,13 +11195,13 @@ snapshots: optionalDependencies: core-js: 3.48.0 - '@rsbuild/plugin-babel@1.1.0(@rsbuild/core@1.7.3)': + '@rsbuild/plugin-babel@1.1.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@rsbuild/core': 1.7.3 + '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) '@types/babel__core': 7.20.5 deepmerge: 4.3.1 reduce-configs: 1.1.1 @@ -12165,9 +11241,9 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 - '@rsbuild/plugin-less@1.6.0(@rsbuild/core@1.7.3)': + '@rsbuild/plugin-less@1.6.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': dependencies: - '@rsbuild/core': 1.7.3 + '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) deepmerge: 4.3.1 reduce-configs: 1.1.1 @@ -12188,6 +11264,15 @@ snapshots: reduce-configs: 1.1.1 sass-embedded: 1.97.3 + '@rsbuild/plugin-sass@1.5.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': + dependencies: + '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) + deepmerge: 4.3.1 + loader-utils: 2.0.4 + postcss: 8.5.6 + reduce-configs: 1.1.1 + sass-embedded: 1.97.3 + '@rsbuild/plugin-source-build@1.0.4(@rsbuild/core@1.7.3)': dependencies: fast-glob: 3.3.3 @@ -12196,19 +11281,6 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 - '@rsbuild/plugin-type-check@1.3.4(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3)': - dependencies: - deepmerge: 4.3.1 - json5: 2.2.3 - reduce-configs: 1.1.1 - ts-checker-rspack-plugin: 1.3.0(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3) - optionalDependencies: - '@rsbuild/core': 1.7.3 - transitivePeerDependencies: - - '@rspack/core' - - tslib - - typescript - '@rsbuild/plugin-type-check@1.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3)': dependencies: deepmerge: 4.3.1 @@ -12226,6 +11298,10 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 + '@rsbuild/plugin-typed-css-modules@1.2.2(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': + optionalDependencies: + '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) + '@rsdoctor/client@1.2.3': {} '@rsdoctor/core@1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.105.2)': @@ -12235,7 +11311,7 @@ snapshots: '@rsdoctor/sdk': 1.2.3(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.105.2) '@rsdoctor/types': 1.2.3(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.105.2) '@rsdoctor/utils': 1.2.3(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.105.2) - axios: 1.11.0(debug@4.3.4) + axios: 1.11.0 browserslist-load-config: 1.0.0 enhanced-resolve: 5.12.0 filesize: 10.1.6 @@ -12677,345 +11753,6 @@ snapshots: '@sinclair/typebox@0.34.37': {} - '@smithy/abort-controller@3.1.9': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/chunked-blob-reader-native@3.0.1': - dependencies: - '@smithy/util-base64': 3.0.0 - tslib: 2.8.1 - - '@smithy/chunked-blob-reader@4.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/config-resolver@3.0.13': - dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.11 - tslib: 2.8.1 - - '@smithy/core@2.5.7': - dependencies: - '@smithy/middleware-serde': 3.0.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-stream': 3.3.4 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/credential-provider-imds@3.2.8': - dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/property-provider': 3.1.11 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - tslib: 2.8.1 - - '@smithy/eventstream-codec@3.1.10': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 3.7.2 - '@smithy/util-hex-encoding': 3.0.0 - tslib: 2.8.1 - - '@smithy/eventstream-serde-browser@3.0.14': - dependencies: - '@smithy/eventstream-serde-universal': 3.0.13 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/eventstream-serde-config-resolver@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/eventstream-serde-node@3.0.13': - dependencies: - '@smithy/eventstream-serde-universal': 3.0.13 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/eventstream-serde-universal@3.0.13': - dependencies: - '@smithy/eventstream-codec': 3.1.10 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@3.2.9': - dependencies: - '@smithy/protocol-http': 4.1.8 - '@smithy/querystring-builder': 3.0.11 - '@smithy/types': 3.7.2 - '@smithy/util-base64': 3.0.0 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@4.1.3': - dependencies: - '@smithy/protocol-http': 4.1.8 - '@smithy/querystring-builder': 3.0.11 - '@smithy/types': 3.7.2 - '@smithy/util-base64': 3.0.0 - tslib: 2.8.1 - - '@smithy/hash-blob-browser@3.1.10': - dependencies: - '@smithy/chunked-blob-reader': 4.0.0 - '@smithy/chunked-blob-reader-native': 3.0.1 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/hash-node@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/hash-stream-node@3.1.10': - dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/invalid-dependency@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/is-array-buffer@2.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/is-array-buffer@3.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/md5-js@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/middleware-content-length@3.0.13': - dependencies: - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@3.2.8': - dependencies: - '@smithy/core': 2.5.7 - '@smithy/middleware-serde': 3.0.11 - '@smithy/node-config-provider': 3.1.12 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - '@smithy/url-parser': 3.0.11 - '@smithy/util-middleware': 3.0.11 - tslib: 2.8.1 - - '@smithy/middleware-retry@3.0.34': - dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/protocol-http': 4.1.8 - '@smithy/service-error-classification': 3.0.11 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-retry': 3.0.11 - tslib: 2.8.1 - uuid: 9.0.1 - - '@smithy/middleware-serde@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/middleware-stack@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/node-config-provider@3.1.12': - dependencies: - '@smithy/property-provider': 3.1.11 - '@smithy/shared-ini-file-loader': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/node-http-handler@3.3.3': - dependencies: - '@smithy/abort-controller': 3.1.9 - '@smithy/protocol-http': 4.1.8 - '@smithy/querystring-builder': 3.0.11 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/property-provider@3.1.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/protocol-http@4.1.8': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/querystring-builder@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - '@smithy/util-uri-escape': 3.0.0 - tslib: 2.8.1 - - '@smithy/querystring-parser@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/service-error-classification@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - - '@smithy/shared-ini-file-loader@3.1.12': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/signature-v4@4.2.4': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.11 - '@smithy/util-uri-escape': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/smithy-client@3.7.0': - dependencies: - '@smithy/core': 2.5.7 - '@smithy/middleware-endpoint': 3.2.8 - '@smithy/middleware-stack': 3.0.11 - '@smithy/protocol-http': 4.1.8 - '@smithy/types': 3.7.2 - '@smithy/util-stream': 3.3.4 - tslib: 2.8.1 - - '@smithy/types@3.7.2': - dependencies: - tslib: 2.8.1 - - '@smithy/url-parser@3.0.11': - dependencies: - '@smithy/querystring-parser': 3.0.11 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/util-base64@3.0.0': - dependencies: - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/util-body-length-browser@3.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-body-length-node@3.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-buffer-from@2.2.0': - dependencies: - '@smithy/is-array-buffer': 2.2.0 - tslib: 2.8.1 - - '@smithy/util-buffer-from@3.0.0': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - tslib: 2.8.1 - - '@smithy/util-config-provider@3.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-defaults-mode-browser@3.0.34': - dependencies: - '@smithy/property-provider': 3.1.11 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - bowser: 2.12.1 - tslib: 2.8.1 - - '@smithy/util-defaults-mode-node@3.0.34': - dependencies: - '@smithy/config-resolver': 3.0.13 - '@smithy/credential-provider-imds': 3.2.8 - '@smithy/node-config-provider': 3.1.12 - '@smithy/property-provider': 3.1.11 - '@smithy/smithy-client': 3.7.0 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/util-endpoints@2.1.7': - dependencies: - '@smithy/node-config-provider': 3.1.12 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/util-hex-encoding@3.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-middleware@3.0.11': - dependencies: - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/util-retry@3.0.11': - dependencies: - '@smithy/service-error-classification': 3.0.11 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - - '@smithy/util-stream@3.3.4': - dependencies: - '@smithy/fetch-http-handler': 4.1.3 - '@smithy/node-http-handler': 3.3.3 - '@smithy/types': 3.7.2 - '@smithy/util-base64': 3.0.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.8.1 - - '@smithy/util-uri-escape@3.0.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-utf8@2.3.0': - dependencies: - '@smithy/util-buffer-from': 2.2.0 - tslib: 2.8.1 - - '@smithy/util-utf8@3.0.0': - dependencies: - '@smithy/util-buffer-from': 3.0.0 - tslib: 2.8.1 - - '@smithy/util-waiter@3.2.0': - dependencies: - '@smithy/abort-controller': 3.1.9 - '@smithy/types': 3.7.2 - tslib: 2.8.1 - '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.0.0': {} @@ -13347,8 +12084,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/uuid@9.0.8': {} - '@types/webpack-sources@3.2.3': dependencies: '@types/node': 24.10.13 @@ -13732,10 +12467,45 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@xmldom/xmldom@0.8.11': {} + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} + '@yume-chan/adb-server-node-tcp@2.5.2': + dependencies: + '@yume-chan/adb': 2.5.1 + '@yume-chan/async': 4.1.3 + '@yume-chan/stream-extra': 2.1.0 + '@yume-chan/struct': 2.3.2 + + '@yume-chan/adb@2.5.1': + dependencies: + '@yume-chan/async': 4.1.3 + '@yume-chan/event': 2.0.0 + '@yume-chan/no-data-view': 2.0.0 + '@yume-chan/stream-extra': 2.1.0 + '@yume-chan/struct': 2.3.2 + + '@yume-chan/async@4.1.3': {} + + '@yume-chan/event@2.0.0': + dependencies: + '@yume-chan/async': 4.1.3 + + '@yume-chan/no-data-view@2.0.0': {} + + '@yume-chan/stream-extra@2.1.0': + dependencies: + '@yume-chan/async': 4.1.3 + '@yume-chan/struct': 2.3.2 + + '@yume-chan/struct@2.3.2': + dependencies: + '@yume-chan/async': 4.1.3 + '@yume-chan/no-data-view': 2.0.0 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -13764,8 +12534,6 @@ snapshots: acorn@8.15.0: {} - address@1.2.2: {} - agent-base@7.1.3: {} aggregate-error@3.1.0: @@ -13931,9 +12699,9 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - axios@1.11.0(debug@4.3.4): + axios@1.11.0: dependencies: - follow-redirects: 1.15.6(debug@4.3.4) + follow-redirects: 1.15.6 form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -14008,8 +12776,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - bluebird@3.7.2: {} - body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -14067,8 +12833,6 @@ snapshots: boolbase@1.0.0: {} - bowser@2.12.1: {} - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -14111,8 +12875,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bufferpack@0.0.6: {} - builtin-modules@5.0.0: {} bundle-name@4.1.0: @@ -14290,7 +13052,8 @@ snapshots: commander@7.2.0: {} - commander@9.5.0: {} + commander@9.5.0: + optional: true comment-json@4.2.5: dependencies: @@ -14565,10 +13328,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.4: - dependencies: - ms: 2.1.2 - debug@4.3.7: dependencies: ms: 2.1.3 @@ -14647,13 +13406,6 @@ snapshots: detect-node@2.1.0: {} - detect-port@1.6.1: - dependencies: - address: 1.2.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -14724,10 +13476,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexer2@0.1.4: - dependencies: - readable-stream: 2.3.8 - duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -15376,10 +14124,6 @@ snapshots: fast-uri@3.1.0: {} - fast-xml-parser@4.4.1: - dependencies: - strnum: 1.1.2 - fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -15495,9 +14239,7 @@ snapshots: flexsearch@0.8.212: {} - follow-redirects@1.15.6(debug@4.3.4): - optionalDependencies: - debug: 4.3.4 + follow-redirects@1.15.6: {} for-each@0.3.5: dependencies: @@ -15949,7 +14691,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.6(debug@4.3.4) + follow-redirects: 1.15.6 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -16045,8 +14787,6 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ip@2.0.1: {} - ipaddr.js@1.9.1: {} ipaddr.js@2.3.0: {} @@ -17231,8 +15971,6 @@ snapshots: ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} multicast-dns@7.2.5: @@ -17585,8 +16323,9 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - plist@3.0.6: + plist@3.1.0: dependencies: + '@xmldom/xmldom': 0.8.11 base64-js: 1.5.1 xmlbuilder: 15.1.1 @@ -17858,8 +16597,6 @@ snapshots: punycode@2.3.1: {} - q@1.5.1: {} - qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -18257,12 +16994,6 @@ snapshots: optionalDependencies: '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) - rsbuild-plugin-tailwindcss@0.2.4(@rsbuild/core@1.7.3)(tailwindcss@3.4.19): - dependencies: - tailwindcss: 3.4.19 - optionalDependencies: - '@rsbuild/core': 1.7.3 - rsbuild-plugin-tailwindcss@0.2.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(tailwindcss@3.4.19): dependencies: tailwindcss: 3.4.19 @@ -18740,10 +17471,6 @@ snapshots: transitivePeerDependencies: - supports-color - split@1.0.1: - dependencies: - through: 2.3.8 - sprintf-js@1.0.3: {} stable-hash-x@0.1.1: {} @@ -18864,8 +17591,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - strnum@1.1.2: {} - style-to-js@1.1.16: dependencies: style-to-object: 1.0.8 @@ -19382,14 +18107,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - unzipper@0.12.3: - dependencies: - bluebird: 3.7.2 - duplexer2: 0.1.4 - fs-extra: 11.3.3 - graceful-fs: 4.2.11 - node-int64: 0.4.0 - upath@2.0.1: {} update-browserslist-db@1.2.3(browserslist@4.28.1): @@ -19404,7 +18121,10 @@ snapshots: dependencies: punycode: 2.3.1 - utf8@3.0.0: {} + usbmux-client@0.2.1: + dependencies: + '@httptoolkit/util': 0.1.9 + plist: 3.1.0 util-deprecate@1.0.2: {} @@ -19412,8 +18132,6 @@ snapshots: uuid@8.3.2: {} - uuid@9.0.1: {} - v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.29 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ffbaf146fc..da97660805 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -35,6 +35,17 @@ catalog: "@microsoft/api-extractor": "7.57.6" catalogs: + adb: + "@yume-chan/adb": ^2.5.1 + "@yume-chan/adb-scrcpy": ^2.3.2 + "@yume-chan/adb-server-node-tcp": ^2.5.2 + "@yume-chan/async": ^2.1.0 + "@yume-chan/fetch-scrcpy-server": ^1.0.0 + "@yume-chan/scrcpy": ^2.3.0 + "@yume-chan/scrcpy-decoder-webcodecs": ^2.5.0 + "@yume-chan/stream-extra": ^2.1.0 + "@yume-chan/struct": ^2.1.0 + # Rsbuild monorepo packages rsbuild: "@rsbuild/core": "1.7.3" From 806202abd32fc14b404d2a881adab47aacc6af38 Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:51:00 +0800 Subject: [PATCH 2/7] pnpm dedupe --- pnpm-lock.yaml | 54 ++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86c8aaed2a..86ad4a115c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -275,7 +275,7 @@ importers: version: 3.7.0 '@rsbuild/plugin-babel': specifier: 1.1.0 - version: 1.1.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + version: 1.1.0(@rsbuild/core@1.7.3) '@types/react': specifier: ^18.3.28 version: 18.3.28 @@ -991,10 +991,10 @@ importers: version: link:../../web-platform/web-elements '@rsbuild/plugin-less': specifier: 1.6.0 - version: 1.6.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + version: 1.6.0(@rsbuild/core@1.7.3) '@rsbuild/plugin-sass': specifier: 1.5.0 - version: 1.5.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + version: 1.5.0(@rsbuild/core@1.7.3) commander: specifier: ^13.1.0 version: 13.1.0 @@ -1009,7 +1009,7 @@ importers: version: 1.1.1 rsbuild-plugin-tailwindcss: specifier: 0.2.4 - version: 0.2.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(tailwindcss@3.4.19) + version: 0.2.4(@rsbuild/core@1.7.3)(tailwindcss@3.4.19) rslog: specifier: ^1.3.2 version: 1.3.2 @@ -1835,13 +1835,13 @@ importers: version: 7.33.4(@types/node@24.10.13) '@rsbuild/plugin-sass': specifier: 1.5.0 - version: 1.5.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + version: 1.5.0(@rsbuild/core@1.7.3) '@rsbuild/plugin-type-check': specifier: 1.3.4 - version: 1.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3) + version: 1.3.4(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3) '@rsbuild/plugin-typed-css-modules': specifier: 1.2.2 - version: 1.2.2(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) + version: 1.2.2(@rsbuild/core@1.7.3) '@rspress/core': specifier: 2.0.3 version: 2.0.3(@types/react@19.2.14)(core-js@3.48.0) @@ -11195,13 +11195,13 @@ snapshots: optionalDependencies: core-js: 3.48.0 - '@rsbuild/plugin-babel@1.1.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': + '@rsbuild/plugin-babel@1.1.0(@rsbuild/core@1.7.3)': dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) + '@rsbuild/core': 1.7.3 '@types/babel__core': 7.20.5 deepmerge: 4.3.1 reduce-configs: 1.1.1 @@ -11241,9 +11241,9 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 - '@rsbuild/plugin-less@1.6.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': + '@rsbuild/plugin-less@1.6.0(@rsbuild/core@1.7.3)': dependencies: - '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) + '@rsbuild/core': 1.7.3 deepmerge: 4.3.1 reduce-configs: 1.1.1 @@ -11264,15 +11264,6 @@ snapshots: reduce-configs: 1.1.1 sass-embedded: 1.97.3 - '@rsbuild/plugin-sass@1.5.0(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': - dependencies: - '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) - deepmerge: 4.3.1 - loader-utils: 2.0.4 - postcss: 8.5.6 - reduce-configs: 1.1.1 - sass-embedded: 1.97.3 - '@rsbuild/plugin-source-build@1.0.4(@rsbuild/core@1.7.3)': dependencies: fast-glob: 3.3.3 @@ -11281,6 +11272,19 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 + '@rsbuild/plugin-type-check@1.3.4(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3)': + dependencies: + deepmerge: 4.3.1 + json5: 2.2.3 + reduce-configs: 1.1.1 + ts-checker-rspack-plugin: 1.3.0(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3) + optionalDependencies: + '@rsbuild/core': 1.7.3 + transitivePeerDependencies: + - '@rspack/core' + - tslib + - typescript + '@rsbuild/plugin-type-check@1.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.9.3)': dependencies: deepmerge: 4.3.1 @@ -11298,10 +11302,6 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.7.3 - '@rsbuild/plugin-typed-css-modules@1.2.2(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))': - optionalDependencies: - '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) - '@rsdoctor/client@1.2.3': {} '@rsdoctor/core@1.2.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(webpack@5.105.2)': @@ -16994,6 +16994,12 @@ snapshots: optionalDependencies: '@rsbuild/core': 2.0.0-beta.3(core-js@3.48.0) + rsbuild-plugin-tailwindcss@0.2.4(@rsbuild/core@1.7.3)(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + optionalDependencies: + '@rsbuild/core': 1.7.3 + rsbuild-plugin-tailwindcss@0.2.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0))(tailwindcss@3.4.19): dependencies: tailwindcss: 3.4.19 From d9ce6d15fd04ca4458005215f2040d9207b62b74 Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:09:07 +0800 Subject: [PATCH 3/7] docs: fix incorrect paths in devtool-connector AGENTS.md --- .../devtool-connector/src/AGENTS.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/mcp-servers/devtool-connector/src/AGENTS.md b/packages/mcp-servers/devtool-connector/src/AGENTS.md index 893fb47c07..675175035d 100644 --- a/packages/mcp-servers/devtool-connector/src/AGENTS.md +++ b/packages/mcp-servers/devtool-connector/src/AGENTS.md @@ -33,7 +33,7 @@ The Connector adopts a layered architecture to separate concerns: ### Connector layer (core) - **Responsibility**: Expose a unified interface and coordinate the Transport and Protocol layers. -- **Implementation**: `Connector` class in `src/connector/index.ts`. +- **Implementation**: `Connector` class in `src/index.ts`. - **Mechanics**: - **Short-lived requests**: Each call to `sendMessageWithTransport` establishes a new connection, transfers data, and then closes the connection immediately. - **Device/client discovery**: @@ -52,12 +52,12 @@ In the stateless model, the lifecycle of core objects changes: ## 4. Code Structure -- `src/connector/index.ts`: Connector layer. Main entry, manages all transports. -- `src/connector/transport.ts`: Transport interface definition. -- `src/connector/android.ts`: `AndroidTransport` implementation. -- `src/connector/ios.ts`: `iOSTransport` implementation. -- `src/connector/peertalk.ts` / `cdp.ts`: Protocol layer stream transformers. -- `src/connector/types.ts`: Core type definitions. +- `src/index.ts`: Connector layer. Main entry, manages all transports. +- `src/transport/transport.ts`: Transport interface definition. +- `src/transport/android.ts`: `AndroidTransport` implementation. +- `src/transport/ios.ts`: `iOSTransport` implementation. +- `src/streams/peertalk.ts` / `src/streams/cdp.ts`: Protocol layer stream transformers. +- `src/types.ts`: Core type definitions. ## 5. Development Guide @@ -72,9 +72,9 @@ The Connector is the single entry point for upper-layer tools to communicate wit ### 6.1 Initialization ```typescript -import { AndroidTransport } from './connector/android.js'; -import { Connector } from './connector/index.js'; -import { iOSTransport } from './connector/ios.js'; +import { AndroidTransport } from './transport/android.js'; +import { Connector } from './index.js'; +import { iOSTransport } from './transport/ios.js'; // Initialize the Connector with support for both Android and iOS const connector = new Connector([ From 49ce77cafc114fed63bc3b13218e5cf9a647f4de Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:10:32 +0800 Subject: [PATCH 4/7] docs: fix grammar in changeset for devtool-mcp-server --- .changeset/petite-bats-smell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/petite-bats-smell.md b/.changeset/petite-bats-smell.md index eba7dd401d..5565f70efe 100644 --- a/.changeset/petite-bats-smell.md +++ b/.changeset/petite-bats-smell.md @@ -4,4 +4,4 @@ Use `@lynx-js/devtool-connector` instead of `@lynx-js/debug-router-connector`. -The new connector avoid using keep-alive connections, which make the connection much more reliable. +The new connector avoids using keep-alive connections, which makes the connection much more reliable. From 71a04f72b748b281bb00542f211483cbc5dd2bf9 Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:11:52 +0800 Subject: [PATCH 5/7] Remove unnecessary ReadableStream.from() wrapping. --- .../mcp-servers/devtool-connector/src/transport/android.ts | 2 -- packages/mcp-servers/devtool-connector/src/transport/ios.ts | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/mcp-servers/devtool-connector/src/transport/android.ts b/packages/mcp-servers/devtool-connector/src/transport/android.ts index d434c8b02a..1879fa173b 100644 --- a/packages/mcp-servers/devtool-connector/src/transport/android.ts +++ b/packages/mcp-servers/devtool-connector/src/transport/android.ts @@ -19,8 +19,6 @@ import type { const debug = createDebug('devtool-mcp-server:connector:android'); const KNOWNS_APPS: App[] = [ - { packageName: 'com.ss.android.ugc.aweme', name: 'Douyin' }, - { packageName: 'com.lynx.uiapp', name: 'Lynx Example' }, { packageName: 'com.lynx.explorer', name: 'Lynx Explorer' }, ]; diff --git a/packages/mcp-servers/devtool-connector/src/transport/ios.ts b/packages/mcp-servers/devtool-connector/src/transport/ios.ts index 6417f2002e..602e432a99 100644 --- a/packages/mcp-servers/devtool-connector/src/transport/ios.ts +++ b/packages/mcp-servers/devtool-connector/src/transport/ios.ts @@ -4,7 +4,6 @@ import type { NetConnectOpts } from 'node:net'; import { Duplex } from 'node:stream'; -import { ReadableStream } from 'node:stream/web'; import type { WritableStream } from 'node:stream/web'; import createDebug from 'debug'; @@ -51,7 +50,7 @@ export class iOSTransport implements Transport { const { readable, writable } = Duplex.toWeb(conn); return { - readable: ReadableStream.from(readable), + readable, writable: writable as WritableStream, [Symbol.asyncDispose]() { signal?.removeEventListener('abort', abortHandler); From 9d842570b8fcd6029cbe0847239bf3f85468be4c Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:06:24 +0800 Subject: [PATCH 6/7] fix(devtool-connector): fix race condition in connect abort handling --- .../devtool-connector/src/transport/android.ts | 12 ++++++------ .../devtool-connector/src/transport/ios.ts | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/mcp-servers/devtool-connector/src/transport/android.ts b/packages/mcp-servers/devtool-connector/src/transport/android.ts index 1879fa173b..5a9fe76c3f 100644 --- a/packages/mcp-servers/devtool-connector/src/transport/android.ts +++ b/packages/mcp-servers/devtool-connector/src/transport/android.ts @@ -64,12 +64,6 @@ export class AndroidTransport implements Transport { throw err; } - if (signal?.aborted) { - await socket.close(); - await adb.close(); - signal.throwIfAborted(); - } - const abortHandler = () => { void Promise.resolve(socket.close()).catch((err: unknown) => { debug(`connect: socket ${service} close on abort err: %o`, err); @@ -77,6 +71,12 @@ export class AndroidTransport implements Transport { }; signal?.addEventListener('abort', abortHandler, { once: true }); + if (signal?.aborted) { + await socket.close(); + await adb.close(); + signal.throwIfAborted(); + } + void Promise.resolve(socket.closed).catch((err: unknown) => { debug(`connect: socket ${service} closed with err: %o`, err); }); diff --git a/packages/mcp-servers/devtool-connector/src/transport/ios.ts b/packages/mcp-servers/devtool-connector/src/transport/ios.ts index 602e432a99..df72a49a8b 100644 --- a/packages/mcp-servers/devtool-connector/src/transport/ios.ts +++ b/packages/mcp-servers/devtool-connector/src/transport/ios.ts @@ -38,16 +38,17 @@ export class iOSTransport implements Transport { signal?.throwIfAborted(); const conn = await this.#client.createDeviceTunnel(deviceId, port); - if (signal?.aborted) { - conn.destroy(); - } - signal?.throwIfAborted(); const abortHandler = () => { conn.destroy(); }; signal?.addEventListener('abort', abortHandler, { once: true }); + if (signal?.aborted) { + conn.destroy(); + signal.throwIfAborted(); + } + const { readable, writable } = Duplex.toWeb(conn); return { readable, From 7aa85419c7164eddcc99b4aa38835329115ed31a Mon Sep 17 00:00:00 2001 From: Qingyu Wang <40660121+colinaaa@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:21:17 +0800 Subject: [PATCH 7/7] fix: resolve sherif dependency version conflicts --- packages/mcp-servers/devtool-connector/package.json | 4 ++-- pnpm-lock.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mcp-servers/devtool-connector/package.json b/packages/mcp-servers/devtool-connector/package.json index ffce805f04..11d28973e4 100644 --- a/packages/mcp-servers/devtool-connector/package.json +++ b/packages/mcp-servers/devtool-connector/package.json @@ -26,13 +26,13 @@ }, "devDependencies": { "@types/debug": "^4.1.12", - "@types/node": "^24.10.0", + "@types/node": "^24.10.13", "@yume-chan/adb": "catalog:adb", "@yume-chan/adb-server-node-tcp": "catalog:adb", "@yume-chan/stream-extra": "catalog:adb", "commander": "^13.1.0", "debug": "^4.4.3", - "rsbuild-plugin-publint": "^0.3.3", + "rsbuild-plugin-publint": "0.3.4", "typescript": "^5.9.3", "usbmux-client": "^0.2.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86ad4a115c..022c1203df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -433,7 +433,7 @@ importers: specifier: ^4.1.12 version: 4.1.12 '@types/node': - specifier: ^24.10.0 + specifier: ^24.10.13 version: 24.10.13 '@yume-chan/adb': specifier: catalog:adb @@ -451,7 +451,7 @@ importers: specifier: ^4.4.3 version: 4.4.3 rsbuild-plugin-publint: - specifier: ^0.3.3 + specifier: 0.3.4 version: 0.3.4(@rsbuild/core@2.0.0-beta.3(core-js@3.48.0)) typescript: specifier: ^5.9.3