mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 12:15:48 +08:00
[feat] bundle MCP and DSH into one installable package
This commit is contained in:
1 parent
02a25a759f
commit
2a9faf1eb1
15 files changed
+875
-590
No files matched your search
@@ -142,3 +142,29 @@ jobs:
|
||||
name: npm-${{ matrix.target }}
|
||||
path: build/npm/*.tgz
|
||||
if-no-files-found: error
|
||||
- name: Build MCP and DSH plugin
|
||||
shell: bash
|
||||
run: |
|
||||
npm --prefix apps/mcp ci
|
||||
npm --prefix apps/mcp run build
|
||||
- name: Package MCP with native CLI
|
||||
shell: bash
|
||||
run: |
|
||||
native_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$native_dir"' EXIT
|
||||
native_archives=(build/npm/chunkdash-crossdesk-cli-*-*.tgz)
|
||||
if [[ ${#native_archives[@]} -ne 1 || ! -f "${native_archives[0]}" ]]; then
|
||||
echo 'Expected exactly one native platform package' >&2
|
||||
exit 1
|
||||
fi
|
||||
tar -xzf "${native_archives[0]}" -C "$native_dir"
|
||||
native_package_dir="$native_dir/package"
|
||||
if [[ "$RUNNER_OS" == Windows ]]; then
|
||||
native_package_dir=$(cygpath -m "$native_package_dir")
|
||||
fi
|
||||
npm --prefix apps/mcp run pack:npm -- --native-package-dir "$native_package_dir"
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: crossdesk-mcp-${{ matrix.target }}
|
||||
path: build/npm-mcp/*.tgz
|
||||
if-no-files-found: error
|
||||
@@ -57,7 +57,7 @@ CrossDesk 让你从电脑、浏览器、手机或终端(桌面 CLI)连接远
|
||||
| 鸿蒙手机 / 平板(开发版) | **仅控制**;API 20+,arm64;使用 DevEco 构建并签名 HAP,仍在进行真机验收 |
|
||||
| Web | **仅控制**;支持 WebRTC 的浏览器;无需在控制端安装客户端 |
|
||||
| 终端 CLI(开发版) | **仅控制**;分发目标为 macOS / Linux(x64、arm64)和 Windows(x64);通过 [npm 安装](#cli-npm) 或从源码构建,平台验收范围见 [CLI 说明](apps/cli/README.md) |
|
||||
| MCP(开发版) | **仅控制**;本机 Node.js 22+、兼容的 Agent 宿主及原生 CLI;支持浏览器、终端和 MCP Apps 监看,见 [源码运行说明](apps/mcp/README.md) |
|
||||
| MCP(开发版) | **仅控制**;在 Codex 或 DSH 中操作远端并监看画面;DSH 安装一个插件包,Codex 需 Node.js 22+,见 [安装与使用](apps/mcp/README.md) |
|
||||
|
||||
系统要求按当前源码与构建配置整理,具体安装包以对应 Release 说明为准。各客户端的功能范围见 [终端 CLI](#cli) 与 [移动端与鸿蒙](#mobile)。
|
||||
|
||||
@@ -283,14 +283,14 @@ pi -e ./apps/cli/plugins/pi/index.ts
|
||||
|
||||
## Agent 与 MCP
|
||||
|
||||
[`apps/mcp`](apps/mcp/README.md) 提供本机 MCP 服务,让 Agent 通过 CrossDesk 连接远端电脑。控制端需要 Node.js 22+ 和兼容的原生 CLI,无需安装 CrossDesk 桌面 GUI;被控端仍需运行兼容的 CrossDesk,信令服务需支持 `cli` 注册。可以使用维护者提供的本地安装包或从源码构建;仓库中的包版本不代表已发布到 npm。
|
||||
[CrossDesk MCP](apps/mcp/README.md) 让你在 Codex 或 DeepSeek Harness 对话中安排远程任务,并在右侧查看远端画面。远端电脑运行 CrossDesk;本机按下表安装后,在面板中选择设备并授权。
|
||||
|
||||
| 使用环境 | 安装与配置 | 远端画面 |
|
||||
| --- | --- | --- |
|
||||
| Codex 桌面版 | [Codex 安装指南](apps/mcp/README.md#install-codex):注册 MCP,无需额外插件 | Agent 打开右侧内置浏览器 |
|
||||
| DeepSeek Harness 桌面版(DSH) | [DSH 安装指南](apps/mcp/README.md#install-dsh):注册 MCP,并安装 CrossDesk Sidebar 插件 | 插件自动打开当前对话侧栏 |
|
||||
| DeepSeek Harness 桌面版(DSH) | [DSH 安装指南](apps/mcp/README.md#install-dsh):只安装一个对应平台的 MCP 插件包,自动注册服务 | 插件自动打开当前对话侧栏 |
|
||||
|
||||
1. 选择 [本地安装包](apps/mcp/README.md#install-package) 或 [源码构建](apps/mcp/README.md#从源码运行),再按上表配置宿主。完整指南包含配置示例、原生依赖和故障排查。
|
||||
1. [下载与你的本机对应的平台安装包](apps/mcp/README.md#install-package),再按上表完成安装。
|
||||
2. 让 Agent 打开 CrossDesk,在监看面板选择设备、输入连接密码,并选择仅查看或允许控制。密码在本地面板中输入;保存密码与允许以后自动连接分别设置。
|
||||
3. Agent 通过截图、键鼠和结构化桌面工具执行任务;用户可实时监看、暂停 Agent、人工接管或断开连接。
|
||||
|
||||
@@ -344,7 +344,8 @@ PC 端可控制和被控制,复用桌面界面与桌面墙;手机和平板
|
||||
## 构建与反馈
|
||||
|
||||
- [终端客户端 CLI](apps/cli/README.md):独立构建、终端使用、Pi 扩展、桥接协议和 npm 打包。
|
||||
- [Agent / MCP](apps/mcp/README.md):安装包与源码运行、Codex / DSH 配置、授权与监看、自动化工具及兼容范围。
|
||||
- [Agent / MCP 用户指南](apps/mcp/README.md):下载安装、Codex / DSH 配置、连接授权、日常使用与常见问题。
|
||||
- [MCP 开发参考](apps/mcp/DEVELOPMENT.md):源码构建、宿主集成、工具接口、打包与验证范围。
|
||||
- [源码构建](docs/BUILD.md):桌面端依赖、编译参数与打包;移动端请使用上方各平台的构建指南。
|
||||
- [GUI 架构](docs/gui-architecture.md):桌面界面、平台实现与工程结构。
|
||||
- [连接模式架构](docs/connection-architecture.md):ID 连接与 IP 直连的寻址、认证、协商、数据路径及生命周期。
|
||||
|
||||
+6
-5
@@ -57,7 +57,7 @@ Get a package matching your OS and CPU architecture from the [website](https://w
|
||||
| HarmonyOS phones / tablets (development) | **Control only**. API 20+, arm64; build and sign a HAP with DevEco; physical-device validation is ongoing |
|
||||
| Web | **Control only**. A WebRTC-capable browser; no client installation on the controlling device |
|
||||
| Terminal CLI (development) | **Control only**. Distribution targets: macOS / Linux (x64, arm64) and Windows (x64). [Install from npm](#cli-npm) or build from source; see the [CLI guide](apps/cli/README.md) (Chinese) for platform validation status |
|
||||
| MCP (development) | **Control only**. Local Node.js 22+, a compatible Agent host, and the native CLI; browser, terminal, and MCP Apps monitoring. See [source setup](apps/mcp/README.md) (Chinese) |
|
||||
| MCP (development) | **Control only**. Operate and watch a remote computer in Codex or DSH. DSH needs one plugin package; Codex also needs Node.js 22+. See [installation and usage](apps/mcp/README.md) (Chinese) |
|
||||
|
||||
Requirements follow the current source and build configuration. Check the release notes for the downloaded package. Client capabilities are summarized under [Terminal CLI](#cli) and [Mobile and HarmonyOS](#mobile).
|
||||
|
||||
@@ -283,14 +283,14 @@ The interactive CLI provides ID-based viewing and input, without direct IP conne
|
||||
|
||||
## Agents and MCP
|
||||
|
||||
[`apps/mcp`](apps/mcp/README.md) provides a local MCP service for Agents to connect to remote computers through CrossDesk. The controller needs Node.js 22+ and a compatible native CLI, without the CrossDesk desktop GUI. The remote host still needs a compatible CrossDesk client and a signaling server supporting `cli` registration. Use a local package supplied by the maintainer or build from source; package versions in the repository do not establish npm availability.
|
||||
[CrossDesk MCP](apps/mcp/README.md) lets you request remote tasks in a Codex or DeepSeek Harness chat and watch the remote screen on the right. Run CrossDesk on the remote computer, follow the installation guide for your local app below, then choose and authorize the device in the panel.
|
||||
|
||||
| Host | Installation and configuration | Remote video |
|
||||
| --- | --- | --- |
|
||||
| Codex desktop | [Codex setup](apps/mcp/README.md#install-codex) (Chinese): register the MCP server; no extra plugin | The Agent opens the built-in browser on the right |
|
||||
| DeepSeek Harness desktop (DSH) | [DSH setup](apps/mcp/README.md#install-dsh) (Chinese): register the MCP server and install the CrossDesk Sidebar plugin | The plugin opens the current chat's browser sidebar automatically |
|
||||
| DeepSeek Harness desktop (DSH) | [DSH setup](apps/mcp/README.md#install-dsh) (Chinese): install one platform plugin package; MCP registration is automatic | The plugin opens the current chat's browser sidebar automatically |
|
||||
|
||||
1. Choose a [local package](apps/mcp/README.md#install-package) or [source build](apps/mcp/README.md#从源码运行), then configure your host using the guides above. These guides include configuration examples, native dependencies, and troubleshooting.
|
||||
1. [Download the platform package for your local computer](apps/mcp/README.md#install-package), then follow the guide for your app above.
|
||||
2. Ask the Agent to open CrossDesk. Choose a device and enter its password in the local monitoring panel, then grant view-only or control access. Saving a password and allowing future automatic connections are separate choices.
|
||||
3. The Agent uses screenshots, keyboard and mouse actions, and structured desktop tools. You can watch live, pause the Agent, take over manually, or disconnect.
|
||||
|
||||
@@ -343,7 +343,8 @@ Host your own signaling and TURN services, then enable them under **Settings →
|
||||
|
||||
## Build and feedback
|
||||
|
||||
- [Agent / MCP](apps/mcp/README.md) (Chinese): package and source installation, Codex / DSH setup, authorization, monitoring, automation tools, and compatibility.
|
||||
- [Agent / MCP user guide](apps/mcp/README.md) (Chinese): downloads, Codex / DSH setup, device authorization, everyday usage, and troubleshooting.
|
||||
- [MCP developer reference](apps/mcp/DEVELOPMENT.md) (Chinese): source builds, host integration, tool interfaces, packaging, and validation scope.
|
||||
- [Terminal client CLI](apps/cli/README.md) (Chinese): independent builds, terminal usage, Pi extension, bridge protocol, and npm packaging.
|
||||
- [Build from source](docs/BUILD_EN.md): desktop dependencies, build options, and packaging. Use the platform guides above for mobile builds.
|
||||
- [GUI architecture](docs/gui-architecture.md) (Chinese): desktop UI, platform implementation, and project structure.
|
||||
|
||||
@@ -0,0 +1,512 @@
|
||||
# CrossDesk MCP 开发与集成参考
|
||||
|
||||
本文面向开发者与宿主集成者,记录构建、工具接口、显示机制和验证范围。安装与日常使用请参阅 [用户指南](README.md)。
|
||||
|
||||
导航:[源码构建](#从源码运行) · [DSH 开发配置](#harness-development) · [工具接口](#模型工具) · [远程操作](#远程操作) · [实时视频](#实时视频与按需截图) · [打包](#打包)
|
||||
|
||||
## 版本与依赖
|
||||
|
||||
MCP 包 `@chunkdash/crossdesk-mcp` 0.1.0 绑定原生 CLI 0.2.0,内置 DSH 插件随 MCP 同包分发。源码版本和本地打包不代表版本已经发布到 npm;内含原生程序的平台安装包可独立分发。
|
||||
|
||||
当前提供 31 个模型工具和 11 个 MCP Apps 面板专用接口。本机 MCP 接入使用 stdio,不提供 MCP Streamable HTTP 或通用远程 shell 工具。控制端原生 CLI、被控端和信令服务器需要支持对应自动化协议。
|
||||
|
||||
## 从源码运行
|
||||
|
||||
仓库根目录构建原生程序(已有工作区可保留原有桌面构建选项):
|
||||
|
||||
```sh
|
||||
sh apps/cli/scripts/prepare-deps.sh
|
||||
xmake f -y --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n -m release
|
||||
xmake build crossdesk-cli
|
||||
cd apps/mcp
|
||||
npm ci
|
||||
npm run build
|
||||
```
|
||||
|
||||
完整编译环境见 [源码构建指南](../../docs/BUILD.md)。`npm run build` 同时构建 MCP、页面和 DSH 配套插件。源码目录的 `npm ci` 只安装开发依赖,**源码运行必须显式指定原生程序路径**;生成的发布包才声明固定版本的平台依赖。
|
||||
|
||||
例如,macOS arm64 Release 构建后可先检查:
|
||||
|
||||
```sh
|
||||
/absolute/path/crossdesk/build/macosx/arm64/release/crossdesk-cli doctor --json
|
||||
```
|
||||
|
||||
Windows、Linux、x64 或 Debug 构建使用自己的实际产物路径。`--native-binary` 必须指向可执行文件,不接受 shell 命令;也可用 `CROSSDESK_NATIVE_BINARY` 指定。原生程序需要提供 MCP 所需的 automation、terminal、video 协议;仅能运行交互式 CLI 不代表版本兼容。
|
||||
|
||||
两种安装方式在下文使用的路径不同:
|
||||
|
||||
| 项目 | 本地安装包 | 源码构建 |
|
||||
| --- | --- | --- |
|
||||
| MCP 入口 | `<安装目录>/node_modules/@chunkdash/crossdesk-mcp/dist/main.js` | `<工程目录>/apps/mcp/dist/main.js` |
|
||||
| DSH 插件目录 | `<安装目录>/node_modules/@chunkdash/crossdesk-mcp`(也可直接安装 `.tgz`) | `<工程目录>/apps/mcp` |
|
||||
| 原生程序 | 平台包内置,通用包使用固定版本平台依赖,或显式指定路径 | 显式指定构建产物路径 |
|
||||
|
||||
服务启动不连接远端、不打开浏览器;Agent 调用打开或连接工具后才进入授权与连接流程。
|
||||
|
||||
### Codex 源码配置
|
||||
|
||||
按 [Codex 安装指南](README.md#install-codex) 配置服务时,源码用户将 `args` 替换为以下内容;原生路径示例对应 macOS arm64 Release,其他构建替换为自己的产物路径:
|
||||
|
||||
```toml
|
||||
args = [
|
||||
"/absolute/path/crossdesk/apps/mcp/dist/main.js",
|
||||
"serve", "--stdio",
|
||||
"--panel=browser", "--browser-launch=host",
|
||||
"--native-binary", "/absolute/path/crossdesk/build/macosx/arm64/release/crossdesk-cli"
|
||||
]
|
||||
```
|
||||
|
||||
<a id="harness-development"></a>
|
||||
|
||||
## DSH 插件开发与配置
|
||||
|
||||
源码用户先完成上方 [源码构建](#从源码运行),再安装 **MCP 根目录**:
|
||||
|
||||
```sh
|
||||
dsh plugin --profile desktop add /absolute/path/crossdesk/apps/mcp
|
||||
```
|
||||
|
||||
源码目录不内含原生程序,需要在 `~/.dsh/profiles/desktop/cordis.patch.yml` 中为 `crossdesk` 添加配置;使用自定义 `DSH_HOME` 或 profile 时改为对应路径。将条目合并到现有列表,原生路径替换为本机实际构建产物:
|
||||
|
||||
```yaml
|
||||
- id: crossdesk
|
||||
config:
|
||||
nativeBinary: /absolute/path/crossdesk/build/macosx/arm64/release/crossdesk-cli
|
||||
```
|
||||
|
||||
插件配置只需在修改默认值时填写:
|
||||
|
||||
| 字段 | 默认值 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `nativeBinary` | 包内原生程序,或固定版本的平台依赖 | 源码开发或显式使用兼容 CLI,必须为绝对路径 |
|
||||
| `configDir` | `~/.config/crossdesk/mcp/` | 指定设备配置目录,必须为绝对路径 |
|
||||
| `serverName` | `crossdesk` | MCP 工具命名空间;侧栏会同步使用该名称 |
|
||||
|
||||
用户配置覆盖整个 `config` 时需保留仍然需要的字段。Windows 路径可用 `C:/.../crossdesk-cli.exe`。集成机制参考 [Harness bundle](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/boot/app-boot#profiles) 与 [MCP Client](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/mcp/mcp-client)。
|
||||
|
||||
DSH 源码位于 `integrations/deepseek-harness/src/`:`plugin.js` 管理原生检查和 MCP 生命周期,`host.js` 交接工具结果,`client.js` 打开对应对话的侧栏。构建入口生成 `dist/harness.js` 和 `dist/harness-client.js`,包根目录的 `cordis.patch.yml` 注册 `crossdesk` 组件。宿主提供 MCP Client,复用其工具权限、命名空间保留、图像处理和连接清理行为。
|
||||
|
||||
## 通用包与原生依赖
|
||||
|
||||
如果已获得维护者或 CI 构建的 `chunkdash-crossdesk-mcp-0.1.0.tgz`,在固定目录安装。可在 [Build MCP 工作流](https://github.com/kunkundi/crossdesk/actions/workflows/build-mcp.yml) 的成功运行中查找 `mcp-package` 产物;Actions 下载件是外层 ZIP,先解压,再将其中的 `.tgz` 交给 npm;不要把 ZIP 直接交给 npm。没有可用安装包时,使用下方源码构建流程。
|
||||
|
||||
macOS / Linux:
|
||||
|
||||
```sh
|
||||
mkdir -p "$HOME/crossdesk-mcp"
|
||||
cd "$HOME/crossdesk-mcp"
|
||||
npm init -y
|
||||
npm install /absolute/path/chunkdash-crossdesk-mcp-0.1.0.tgz
|
||||
node node_modules/@chunkdash/crossdesk-mcp/dist/main.js --help
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force "$env:USERPROFILE/crossdesk-mcp"
|
||||
Set-Location "$env:USERPROFILE/crossdesk-mcp"
|
||||
npm init -y
|
||||
npm install "C:/Downloads/chunkdash-crossdesk-mcp-0.1.0.tgz"
|
||||
node node_modules/@chunkdash/crossdesk-mcp/dist/main.js --help
|
||||
```
|
||||
|
||||
安装包已经包含页面和 DSH 插件构建产物,无需再执行 `npm run build`。从 CLI 构建工作流取得的 `crossdesk-mcp-<平台>` 产物还内含对应平台的原生 CLI,安装这类包无需另装 CLI;以下补充步骤只针对不带原生程序的通用 `mcp-package`。安装通用包时保留 optional dependencies:包会尝试安装固定版本的平台原生依赖,支持 macOS arm64/x64、glibc Linux arm64/x64 和 Windows x64。当前 MCP 0.1.0 绑定原生包 0.2.0;如果对应原生包尚未发布或下载失败,需要在**同一安装目录**安装维护者提供的对应平台包,例如 macOS arm64:
|
||||
|
||||
```sh
|
||||
npm install /absolute/path/chunkdash-crossdesk-cli-darwin-arm64-0.2.0.tgz
|
||||
```
|
||||
|
||||
也可在下文宿主配置中添加 `--native-binary`,指向已经构建好的兼容 `crossdesk-cli` 可执行文件(Windows 为 `.exe`)。全局安装 CLI 不会被 MCP 自动发现:未指定路径时先使用包内对应平台的原生程序,否则解析固定版本的平台依赖,不搜索 PATH。`--help` 仅验证 Node 入口;普通 stdio 服务在连接时检查原生协议,DSH 合并插件则在启动时提前检查。
|
||||
|
||||
如果使用 npm 仓库,先确认所需版本已发布,再将上述安装命令替换为 `npm install @chunkdash/crossdesk-mcp@<已发布版本>`;仓库里的版本号不代表 npm 上已有该包。
|
||||
|
||||
## 用户连接流程
|
||||
|
||||
1. 对 Agent 说“打开 CrossDesk”。`crossdesk_open` 创建 `view_id`,返回面板选择和等待用户状态。
|
||||
2. 在面板选择已保存设备,或输入 9 位设备 ID;密码填写在面板里。可选择仅查看或允许 Agent 控制。
|
||||
3. “保存设备”“记住密码”“允许以后自动连接”分别控制设备簿、系统凭据库、持久权限。后两项要求保存设备,默认均关闭。保存密码不自动授予控制权限。
|
||||
4. 有待执行的远程任务时,Agent 在打开面板后立即调用 `crossdesk_get_session({view_id, wait_ms: 25000})` 等待连接。连接就绪后,工具直接返回会话 ID 和权限状态,Agent 继续任务;用户无需回到对话发送连接确认,也无需点击继续按钮。浏览器和 Apps 面板使用同一流程。
|
||||
5. Agent 获取独立截图,用截图的 `observation_id` 和图像坐标提交动作。面板预览不消耗模型截图序列。
|
||||
|
||||
`crossdesk_open` 立即返回,让 Apps 宿主有机会渲染面板;等待连接在后续 `crossdesk_get_session` 调用中完成。按 `view_id` 查询默认最多等待 25 秒,`wait_ms: 0` 则立即查询;未就绪时返回 `wait_timed_out: true`,Agent 在原任务仍待执行时继续等待。按 `session_id` 查询默认立即返回,传入正数 `wait_ms` 可等待正在进行的连接恢复;带 `unlock_request_id` 时等待对应解锁请求。取消等待只取消本次查询,不断开用户正在建立或使用的连接。用户主动断开后返回 `closed`,Agent 停止等待,不自动重连。
|
||||
|
||||
面板发起的连接失败后,按 `view_id` 等待也会返回 `failed` 和错误码,避免 Agent 一直停留在等待用户状态。断开正在建立的连接会取消整个连接尝试。多个面板和 Agent 同时查询状态时共用一次原生查询,单次查询取消或暂时繁忙不会断开会话。`crossdesk_connect` 在连接成功但面板初始化失败时仍返回有效的 `session_id`,并附加 `panel_error`。
|
||||
|
||||
初次信令注册使用原生连接的整体 30 秒期限,期间允许 MiniRTC 按退避策略恢复临时传输异常。期限覆盖所有初始重试。证书校验失败、登录被拒绝,以及设备连接请求发出后的信令中断会结束当前尝试。连接失败结果使用 `error.code` 表示错误类别,并以 `error.reason` 提供已识别的原生原因,例如 `signaling_timeout`、`signaling_disconnected` 或 `device_offline_or_unavailable`。面板显示对应说明,终端错误包含原因标识。`logs/<session_id>/transport.log` 记录连接阶段、受控的传输错误分类与数字码、连接关闭码和重试退避信息。
|
||||
|
||||
自动继续依赖宿主保持当前 Agent 任务及工具调用流程。它不通过网页伪造用户消息,也不承诺从已经结束的对话中主动唤醒 Agent;此类唤醒由宿主负责。
|
||||
|
||||
人工暂停由原生层锁定,模型不能通过 `crossdesk_set_control({session_id, enabled: true})` 恢复;只有当前授权面板的恢复按钮可以恢复。浏览器和 Apps 面板还支持人工接管键鼠,并由用户交还 Agent。仅查看连接不能升级为控制连接,需重新连接并授权。关闭/隐藏面板会停止预览并释放面板持有的按键,远程会话保留;断开按钮或 `crossdesk_disconnect` 才结束会话。
|
||||
|
||||
同一 MCP 进程支持最多八个并行设备连接,面板按设备绑定会话。相同配置目录中的进程用设备锁避免同时连接同一目标;它不协调另一个系统用户、另一份配置目录或独立桌面客户端。
|
||||
|
||||
## 面板策略
|
||||
|
||||
`--panel` 优先于设备配置中的 `panel`,默认 `auto`;`--browser-launch` 同样默认 `auto`。MCP 根据客户端标识内置 Codex 和 DeepSeek Harness 的默认策略,不依赖个人 Codex 配置或 `AGENTS.md`。已有配置中显式指定的面板模式和启动方式仍然有效。
|
||||
|
||||
| 模式 | 行为 |
|
||||
| --- | --- |
|
||||
| `auto` | Codex 和 DeepSeek Harness 默认选择宿主侧栏浏览器;其他客户端有本机 GUI 时使用系统浏览器,无 GUI 且有终端环境时提供 CLI 画面入口,两者均无则使用 `off` |
|
||||
| `apps` | 只使用 MCP Apps;能力不匹配返回 `PANEL_UNSUPPORTED` |
|
||||
| `browser` | 使用本地浏览器页面;启动方式为 `auto` 时 Codex 和 DeepSeek Harness 使用宿主侧栏,其他客户端使用系统浏览器;可显式选择 `--browser-launch=host` 或 `--browser-launch=system`,不附加 Apps 工具资源元数据 |
|
||||
| `terminal` | 提供独立终端 `view` 命令,共用当前 MCP 会话,不启动网页服务 |
|
||||
| `off` | 不启动网页服务,保留截图和操作工具,凭据通过独立 `setup` 配置 |
|
||||
|
||||
`auto` 不选择 MCP Apps;需要宿主内嵌面板时显式使用 `--panel=apps`。Apps 使用 `extensions["io.modelcontextprotocol/ui"].mimeTypes` 中的 `text/html;profile=mcp-app` 判断能力。旧协议读取 initialize 能力;2026-07-28 协议读取当前请求的能力,不按宿主产品名猜测支持情况。Apps 的卡片、侧栏或窗口形式由宿主决定。
|
||||
|
||||
除 Codex 和 DeepSeek Harness 外,客户端使用 `auto` 时,Linux 根据 `DISPLAY` / `WAYLAND_DISPLAY` 判断 GUI;macOS 检查当前用户的桌面登录会话,SSH 会话不自动打开远端 Mac 的浏览器;Windows 排除 SSH 和 Services 会话。终端判断包含 TTY、`SSH_TTY` 和非 `dumb` 的 `TERM`,因为 MCP 的 stdin/stdout 通常是管道。需要终端或无面板运行时,显式设置 `--panel=terminal` 或 `--panel=off`。
|
||||
|
||||
终端模式的 `crossdesk_open` / `crossdesk_connect` 返回 `terminal_command`。用户在同一主机的另一个交互终端执行(源码运行时将 `crossdesk-mcp` 替换为 `node /absolute/path/apps/mcp/dist/main.js`):
|
||||
|
||||
```sh
|
||||
crossdesk-mcp view --view <view_id> --config-dir <相同的配置目录>
|
||||
```
|
||||
|
||||
该命令提供设备选择、隐藏密码输入和本次控制授权;已建立会话则直接显示画面。终端通过受限配置目录中的运行描述符和认证本地 IPC 复用 MCP 的原生连接,不启动第二个远程连接,也不占用 Agent 的终端或 MCP stdio。Agent 应展示命令供用户执行,再通过 `crossdesk_get_session` 等待;不能在工具输出捕获通道中运行 `view`。无交互终端时 `view` 返回 `VIEW_REQUIRES_TERMINAL`。
|
||||
|
||||
终端画面采用原生 CLI 的 ANSI 四分块渲染器,目标 8 FPS,最多 160 列 × 60 行,根据终端环境使用 8/256/真彩色。默认字符宽高比 0.5,可用 `--cell-aspect`(0.2–2)调整。此入口暂不使用 Kitty/Sixel。`p` 暂停 Agent、`r` 恢复已授权控制、`d` 断开、`q` / Ctrl+C 仅关闭画面并恢复终端;关闭画面后会话保留。暂停仍由原生层锁定,Agent 不能自行恢复。
|
||||
|
||||
浏览器/Apps 与终端均绘制 Agent 最后成功发送的鼠标位置,支持移动、点击、滚动和拖拽;坐标来自原生层对截图缩放及裁剪的映射。暂停或关闭控制时隐藏,切换显示器或图像尺寸变化时清除。该指针是 Agent 的操作位置提示,不代表远端输入执行 ACK,也不跟踪远端用户自己的鼠标。它不写入模型截图或源视频。
|
||||
|
||||
浏览器及 Apps 的实时视频光标会以 60–140 毫秒的短缓动追踪最新坐标,动画独立于视频帧刷新;连续位置更新从当前显示位置接续,不排队回放旧轨迹。首次显示、连接代际或显示几何变化时直接定位,隐藏或停止播放时取消动画,并遵守系统的“减少动态效果”设置。平滑效果只作用于页面提示光标,原生鼠标输入及点击坐标不受影响,因此不表示远端鼠标实际经过了补间路径。
|
||||
|
||||
实时视频光标采用 CrossDesk 主色(`#3564e3`)的圆角箭头、白色描边和同色柔光,与客户端及页面配色一致。移动时尖端朝向页面上实际移动的方向,以尖端为旋转锚点;方向按画面像素计算,适配宽屏与竖屏。停止后保留最后朝向,忽略小于半个 CSS 像素的位移对朝向的影响;首次显示及连接、显示几何变化时恢复默认朝向。
|
||||
|
||||
Apps 资源为 `ui://crossdesk/viewer.html`,工具只在允许使用 Apps 且当前请求支持时暴露 UI 元数据和面板专用接口。页面回报 `crossdesk_ui_ready` 后才标记就绪;10 秒未回报显示 `not_confirmed`,不自动弹第二个浏览器。用户明确要求回退时调用 `crossdesk_open_browser`;该工具不附带 Apps 元数据,`apps`/`off` 策略拒绝覆盖。
|
||||
|
||||
浏览器面板只监听随机端口的 `127.0.0.1`,与 MCP 服务同一进程。它不是 MCP Streamable HTTP,也不向局域网提供 MCP 接入。MCP、CLI、用户浏览器应在同一控制端电脑上运行;SSH 容器/远程宿主的浏览器转发不属于此本地接入模式。
|
||||
|
||||
页面以 Agent 会话监看为中心:首次连接显示授权卡片,连接后远程画面占满可用区域,设备、权限和恢复设置收进「会话详情」抽屉。工具栏显示设备与实际控制状态,仅在具有控制授权时保留「暂停 Agent / 恢复 Agent」;本面板人工接管时改为「交还 Agent」。画面放大、人工接管、声音与音量、断开连接收进「会话选项」,仅查看会话不显示人工接管入口。等待系统解锁时显示独立提示及密码入口,关闭解锁弹窗后仍可再次打开。状态区分仅查看、控制已启用、已暂停、人工接管和恢复连接,不将控制权限解释为 Agent 正在执行任务。
|
||||
|
||||
页面标题为 CrossDesk MCP,页内使用 CrossDesk 正式标志和名称。配色与桌面客户端的 WorkbenchStyle / ButtonStyle 一致,采用浅蓝灰底色、蓝色主操作、6 像素控件圆角及 12 像素卡片圆角;工具栏按钮统一为 32 像素高,图标按钮保留可访问名称与悬停提示。布局填满浏览器或宿主面板的可用宽高,远程画面保持原比例缩放,底部集中显示视频状态、分辨率和帧率;授权卡片在高度不足时独立滚动,窄侧栏沿用相同的监看布局。「放大画面」隐藏页眉并缩小外边距;退出菜单、详情和非人工接管状态下的放大模式支持 Esc。界面资源均可离线使用,保存设备与持久授权仍由用户分别选择。
|
||||
|
||||
首次打开使用 URL fragment 中的短期一次性 bootstrap,页面立即清除 fragment,并换取 HttpOnly/SameSite cookie 和 CSRF 令牌。原始 bootstrap 不进入工具结果;`browser_url` 不含配对凭据,`browser_launch_url` 则是短期配对入口,只用于交给宿主导航,不应读取或发布。复制无凭据地址到另一浏览器不能获得会话控制权。
|
||||
|
||||
无法自动打开浏览器时,在自己的终端运行工具结果 `pairing_command` 中的命令和参数。例如:
|
||||
|
||||
```sh
|
||||
crossdesk-mcp setup --open-panel --view <view_id> --config-dir <相同的配置目录>
|
||||
```
|
||||
|
||||
通过源码运行时用 `node /absolute/path/apps/mcp/dist/main.js` 替换 `crossdesk-mcp`。配对通过本用户配置目录中的受限运行描述符和本地 IPC 完成,不允许匿名 HTTP 请求签发控制凭证。`setup` 使用用户终端的桌面环境打开浏览器;仍无法打开时,只在该交互终端显示 60 秒有效的一次性地址,不将它返回 MCP 或发送到聊天。面板最多保留 32 个视图;再次打开时会清理超过 30 分钟未使用的视图。浏览器会话最长 12 小时,失效后重新打开。
|
||||
|
||||
### Codex 默认侧栏浏览器
|
||||
|
||||
安装、配置示例及首次使用见 [Codex 用户安装与使用](README.md#install-codex)。任何用户在 Codex 中直接启动 MCP,默认都会返回侧栏浏览器入口;以下说明宿主识别和配对机制。
|
||||
|
||||
MCP 识别 Codex 的客户端名称 `codex-mcp-client` 和 DeepSeek Harness 的 `dsh-mcp-client`:旧协议读取 initialize 中的 `clientInfo`,2026-07-28 协议读取当前请求的 `io.modelcontextprotocol/clientInfo`。该标识仅选择显示方式,不授予任何设备权限。`--panel=auto` 在这两个宿主中选择 `browser`,`--browser-launch=auto` 选择 `host`;其他客户端仍沿用环境检测和系统浏览器。
|
||||
|
||||
`host` 不启动系统浏览器,也不生成 MCP App 卡片;它要求宿主浏览器与 MCP 运行在同一台电脑。Codex 使用本机 `file://` 启动页;DeepSeek Harness 使用下述 HTTP 启动入口。显式使用 `--panel=browser --browser-launch=host` 可以强制宿主浏览器,使用 `--panel=browser --browser-launch=system` 则强制系统浏览器。显式将 `--browser-launch=host` 与 `apps`、`terminal` 或 `off` 组合会报错,这些模式单独使用不受浏览器默认值影响。
|
||||
|
||||
服务指令、工具说明和返回值均内置 Codex 的打开流程:Agent 拿到 `browser_launch_url` 后立即打开宿主浏览器,即使连接已就绪也不省略此步骤。无需用户再次要求打开侧栏,也无需添加个人 `AGENTS.md`。实际打开仍由宿主浏览器工具执行,MCP 协议本身不能强制所有客户端展示侧栏;缺少该工具时(例如没有侧栏浏览器的 Codex CLI)应明确说明限制并展示本地配对命令。
|
||||
|
||||
|
||||
`crossdesk_open`、`crossdesk_connect` 和 `crossdesk_open_browser` 在此模式返回 `browser_launch_url`、`browser_url`、`browser_host`、剩余有效时间及绝对到期时间。Agent 应将 `browser_launch_url` 直接交给宿主的浏览器打开工具;在 Codex 中调用 `open_in_codex`,设置 `target.type` 为 `browser`、`target.url` 为该地址、`placement` 为 `right`。不要读取、粘贴或上传启动页内容。
|
||||
|
||||
Codex 启动页位于系统临时目录的独立目录中,POSIX 权限为目录 `0700`、文件 `0600`,内含 60 秒有效的一次性配对地址。浏览器在本机跳转到播放器并换取自己的 HttpOnly/SameSite cookie 和 CSRF 令牌;工具结果不包含原始 bootstrap 票据。票据绑定视图及代际,消费、到期或正常关闭服务时清理启动页。启动地址过期时,对已有 `view_id` 调用 `crossdesk_open_browser` 重新签发,不要重新连接远端。
|
||||
|
||||
`browser_url` 是不带配对凭据的本机 HTTP 地址,用于已配对浏览器重新打开;直接复制到另一浏览器不能获得当前会话权限。没有内置浏览器的宿主可由用户执行原有 `pairing_command`,在系统浏览器完成配对。
|
||||
|
||||
侧栏中的网页沿用本机 WebSocket → WebCodecs/VideoFrame 播放路径,媒体数据不经过 MCP 工具结果,也不需要新增公网中转。它与 MCP Apps iframe 是不同的运行环境;是否能播放及实际帧率需要在目标宿主验证。
|
||||
|
||||
2026-11-11 在本机 Codex 侧栏浏览器完成实测:通过上述启动页连接 macOS 远端,页面显示“实时”、1920 × 1080、约 26 FPS,画布帧计数持续递增。原生端确认 `h264-annexb/1` 经 `loopback-websocket/1` 传输,1 个活动观看者、发送队列无积压;验证期间仅查看画面,未发送远端输入。此结果验证当前环境下的侧栏实时播放,实际帧率仍取决于远端、网络和解码性能。
|
||||
|
||||
2026-11-12 使用工程外的全新临时配置完成 16 项 stdio 协议检查:Codex 新旧协议均在不设置面板参数时返回宿主浏览器启动页,其他客户端保留原有行为,显式面板及启动方式覆盖有效,重新签发及退出清理正常;另以模拟会话验证已就绪连接和解锁面板的 Codex 路由。此检查不读取个人 Codex 配置,不连接真实远端。
|
||||
|
||||
### DeepSeek Harness 桌面侧栏
|
||||
|
||||
Harness 桌面版的 Sidebar Browser 只接受 HTTP(S) 地址,不能打开 Codex 的 `file://` 启动页;其内置侧栏也未提供供 Agent 调用的打开工具。安装合并的 CrossDesk MCP 插件后,工具调用会自动打开当前 Harness 会话的浏览器侧栏,不需要手动点击链接。代码结构见 [DSH 集成说明](integrations/deepseek-harness/README.md)。
|
||||
|
||||
完整安装与配置见 [DeepSeek Harness 用户安装与使用](README.md#install-dsh)。合并插件管理 MCP 进程和页面显示,不增加设备权限,也不会发起第二次远端连接。
|
||||
|
||||
Harness 的 `browser_launch_url` 为本机 `/launch/<随机标识>`,在 60 秒内仅可导航一次,再跳转到原有配对流程;原始 bootstrap 令牌不进入工具结果。启动地址本身是短期配对入口,不应发布、抓取或记录其内容。消费、过期、视图代际变化或服务关闭后均不可重用;过期时为已有 `view_id` 重新签发。现有 Host/Origin 校验、HttpOnly/SameSite Cookie、CSRF 和禁止 iframe 嵌入的策略保持有效。
|
||||
|
||||
侧栏适配只在 Harness 桌面版启用;Harness 网页版的 iframe 不支持此路径,不会为此开放任意跨域嵌入或公网转发。未安装插件时,Agent 应说明安装方式,或提供入口链接供用户选择“在侧栏打开”,不能声称已自动打开。侧栏导航成功与配对/视频就绪分别确认,Agent 仍须等待 `crossdesk_get_session`。
|
||||
|
||||
2026-11-12 在 macOS 的 DeepSeek Harness 0.2.2-rc.1 完成实测:安装配套插件后,`crossdesk_connect` 以仅查看模式连接已保存的 Mac mini,插件自动打开当前会话右侧浏览器并完成配对;页面显示实时画面、1920 × 1080、约 27–29 FPS。验证过程中未发送远端键鼠输入。另在工程外临时目录通过 23 项协议/配对/宿主桥接检查及 6 项客户端生命周期检查,覆盖 Codex 与 Harness 新旧协议、单次消费、过期、代际失效、会话隔离、多窗口领取和标签复用。
|
||||
|
||||
同日完成合并包验证:使用 Harness 自带 CLI 在工程外全新 profile 中仅安装一个 macOS arm64 平台包,确认 bundle 和配置 schema 可加载;使用真实 Harness 运行时、Cordis 和 MCP Client 验证自动注册 31 个工具、侧栏请求交接、重复命名空间冲突、停用清理、重新启用及自定义名称。验证安装与卸载、无效原生程序拒绝、打包协议校验,以及构建后的侧栏客户端生命周期;Codex 路由回归通过。本次合并验证未连接远端,其他平台仍需 CI 与实机验收。
|
||||
|
||||
## 无面板配置与凭据
|
||||
|
||||
在用户自己的交互终端执行(不能在宿主 stdio 通道中交互输入密码):
|
||||
|
||||
```sh
|
||||
crossdesk-mcp setup
|
||||
```
|
||||
|
||||
配置默认存放于 `~/.config/crossdesk/mcp/`,可用 `--config-dir` 或 `CROSSDESK_MCP_CONFIG_DIR` 覆盖。`devices.json` 包含设备、面板/视频选项及凭据引用;密码保存到 macOS Keychain、Windows 凭据库或 Linux Secret Service。凭据库不可用就返回明确错误,没有明文文件降级。面板的临时连接仍可使用用户现场输入的密码。持久保存失败会保留已建立连接,并在面板显示错误。
|
||||
|
||||
也可在 `setup` 中指定外部密码环境变量名称,服务读取该变量作为凭据提供器。示例配置(没有密码值):
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"panel": "off",
|
||||
"server": "api.crossdesk.cn",
|
||||
"port": 9099,
|
||||
"devices": [{
|
||||
"alias": "work-pc",
|
||||
"device_id": "123456789",
|
||||
"server": "api.crossdesk.cn",
|
||||
"port": 9099,
|
||||
"authorization": "control",
|
||||
"credential": "environment",
|
||||
"environment_variable": "MY_CROSSDESK_PASSWORD"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
授权范围和可用凭据缺一不可。模型不能提供任意 `device_id` 绕过面板确认;模型侧 `crossdesk_connect` 仅接受已授权设备别名。不要把密码发给 Agent,也不要把包含密码的命令行写进宿主配置。
|
||||
|
||||
Apps 依赖宿主遵守 app-only 工具可见性和 `_meta` 的模型不可见约定;服务另检查每个视图的随机 UI 能力令牌和 generation。app-only 标记本身不是可信身份凭据。能够读取同用户进程/文件或执行任意本机代码的 Agent 不受此边界隔离。
|
||||
|
||||
## 模型工具
|
||||
|
||||
以下 31 个工具面向 Agent,定义见 [src/server.ts](src/server.ts)。远端能力可通过会话状态中的 `capabilities` 查询,窗口与元素操作的具体平台能力通过 `crossdesk_desktop({session_id, operation: "capabilities"})` 查询。
|
||||
|
||||
| 工具 | 作用 |
|
||||
| --- | --- |
|
||||
| `crossdesk_open` | 打开设备选择/连接面板,返回 `view_id` |
|
||||
| `crossdesk_open_browser` | 用户显式请求的浏览器入口/回退 |
|
||||
| `crossdesk_list_devices` | 本机设备簿和授权摘要,不提供云发现 |
|
||||
| `crossdesk_connect` | 通过别名连接已经授权且有凭据的设备 |
|
||||
| `crossdesk_get_session` | `session_id`、`view_id` 严格二选一;查询状态或等待连接、恢复、指定解锁请求,`wait_ms` 范围 0–25000 |
|
||||
| `crossdesk_list_sessions` | 列出活动会话及设备、显示器和控制状态,最多八个并行连接 |
|
||||
| `crossdesk_set_recovery` | 开关有界断线恢复,尝试次数可设为 1–5,默认 3;需要持久授权和可用凭据 |
|
||||
| `crossdesk_disconnect` | 断开并释放输入状态、子进程和设备锁 |
|
||||
| `crossdesk_screenshot` | 返回 PNG image content 和观测元数据 |
|
||||
| `crossdesk_wait` | 根据已有观测等待画面区域变化或稳定,满足条件后返回新截图 |
|
||||
| `crossdesk_action` | 结构化输入,可附带动作之后的截图 |
|
||||
| `crossdesk_release_inputs` | 释放当前会话持有的所有按键和鼠标按钮,并取消当前输入动作 |
|
||||
| `crossdesk_set_control` | 在已授权范围内启停 Agent 输入 |
|
||||
| `crossdesk_select_display` | 切换显示器,使旧观测失效 |
|
||||
| `crossdesk_capture_displays` | 顺序截取最多八个显示器,返回多屏图像及 `capture_id`,随后恢复原显示器 |
|
||||
| `crossdesk_drag_across` | 使用多屏截图坐标跨显示器拖拽 |
|
||||
| `crossdesk_video_settings` | 运行时调整预览帧率、分辨率、质量和码率 |
|
||||
| `crossdesk_desktop` | 查询平台能力、窗口清单或可访问性树,支持按进程和元素读取子树 |
|
||||
| `crossdesk_desktop_action` | 激活窗口、按下/聚焦元素、设置值、启动应用或打开 HTTP(S) 地址 |
|
||||
| `crossdesk_wait_ui` | 按文本和角色等待窗口或可访问性元素出现、消失 |
|
||||
| `crossdesk_audio` | 开关远端桌面音频接收,默认关闭 |
|
||||
| `crossdesk_audio_capture` | 采集 100–10000 毫秒远端音频,返回单声道 48 kHz WAV 音频内容块 |
|
||||
| `crossdesk_clipboard_get` | 读取远端文本剪贴板,需要已授权控制会话 |
|
||||
| `crossdesk_clipboard_set` | 写入或清空远端文本剪贴板 |
|
||||
| `crossdesk_files_list` | 分页浏览远端目录或文件系统根目录 |
|
||||
| `crossdesk_files_transfer` | 上传或下载文件、文件夹,返回可查询的传输任务 |
|
||||
| `crossdesk_files_status` | 查询传输进度、逐文件结果及完成/失败/取消状态 |
|
||||
| `crossdesk_files_cancel` | 取消传输并清理未完成的暂存文件,保留已完成文件 |
|
||||
| `crossdesk_system_status` | 查询远端系统锁定状态及安全桌面服务信息 |
|
||||
| `crossdesk_system_command` | 锁定远端系统,或通过 Windows 服务发送安全注意序列 `send_sas` |
|
||||
| `crossdesk_unlock` | 使用独立的系统锁屏凭据解锁,或在本地面板请求输入密码 |
|
||||
|
||||
## MCP Apps 面板专用接口
|
||||
|
||||
以下 11 个接口仅在 `--panel=apps` 且当前请求支持 MCP Apps 时暴露为 app-only 工具,由经过视图令牌验证的面板调用。浏览器和终端面板通过各自经过认证的本地通道使用会话功能。
|
||||
|
||||
| 接口 | 作用 |
|
||||
| --- | --- |
|
||||
| `crossdesk_ui_ready` | 回报面板已初始化 |
|
||||
| `crossdesk_ui_list_devices` | 获取设备簿及默认服务器配置 |
|
||||
| `crossdesk_ui_get_session` | 查询当前面板绑定的会话 |
|
||||
| `crossdesk_ui_connect` | 提交用户选择的设备、连接凭据及授权选项 |
|
||||
| `crossdesk_video_open` | 获取当前视图的实时视频连接票据 |
|
||||
| `crossdesk_ui_preview` | 实时视频受限时获取当前视图的有界 PNG 预览,仅通过面板元数据返回 |
|
||||
| `crossdesk_viewer_control` | 人工暂停、恢复、接管或断开 |
|
||||
| `crossdesk_ui_input` | 人工接管期间发送面板键鼠输入 |
|
||||
| `crossdesk_ui_unlock` | 提交或取消锁屏密码输入,可选择保存凭据 |
|
||||
| `crossdesk_ui_recovery` | 设置断线恢复开关和尝试次数 |
|
||||
| `crossdesk_ui_audio` | 开关面板会话的音频接收 |
|
||||
|
||||
## 远程操作
|
||||
|
||||
### 键鼠输入
|
||||
|
||||
`crossdesk_action` 支持 `move`、`click`、`double_click`、`scroll`、`drag`、`key_press`、`hotkey`、`type_text`,以及可跨动作保持的 `key_down`、`key_up`、`mouse_down`、`mouse_up`。按键使用 Windows VK 对应的标准名称,覆盖 `A`–`Z`、`0`–`9`、`F1`–`F24`、数字键盘、`CTRL`、`ALT`、`SHIFT`、`META`、`ENTER`、`ESC`、方向键、菜单、锁定键和媒体键等;应用快捷键及实际效果仍需根据远端系统选择。
|
||||
|
||||
`move`、`click`、`double_click`、`scroll`、`drag` 和 `key_press` 支持可选的 `modifiers: ["CTRL", "ALT", "SHIFT", "META"]`(选择需要的键,不能重复),用于组合点击、选择、拖拽和快捷键。修饰键仅在本次动作期间保持,结束、取消或人工暂停时释放。`drag` 可指定 `button: "left" | "right" | "middle"`,默认左键。标点键支持 `SEMICOLON`、`EQUAL`、`COMMA`、`MINUS`、`PERIOD`、`SLASH`、`BACKQUOTE`、`BRACKET_LEFT`、`BACKSLASH`、`BRACKET_RIGHT`、`QUOTE`;这些是 VK 键位名称,实际字符取决于远端键盘布局,输入文字应使用 `type_text`。例如:
|
||||
|
||||
```json
|
||||
{"session_id":"…","operation_id":"select-item-1","observation_id":"…","action":{"type":"click","x":120,"y":80,"modifiers":["SHIFT"]}}
|
||||
```
|
||||
|
||||
`key_down`、`key_up`、`mouse_down` 和 `mouse_up` 的按下状态可跨动作保持,例如按住 Ctrl 后移动或拖动鼠标。最后一次动作完成后,按住状态的有效期为十五秒,超时自动释放。`crossdesk_release_inputs` 可立即释放本会话的全部按键和按钮,并取消当前输入动作。人工暂停、断开及输入错误同样释放按住状态。
|
||||
|
||||
`key_press`、`hotkey` 支持 `duration_ms`,最长三十秒;`drag` 时长上限同样为三十秒。状态返回 `held_key_codes` 和 `held_mouse_flags`。文本输入前应释放持续按住的键鼠状态。
|
||||
|
||||
### 减少 Agent 操作往返
|
||||
|
||||
`crossdesk_action` 支持 `action.type: "sequence"`,在一次调用中依次执行 1–8 个固定动作,最后只返回一次截图。例如已聚焦远程浏览器时,可以将聚焦地址栏、输入网址、回车合并:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "current_session",
|
||||
"operation_id": "open_bilibili",
|
||||
"observation_id": "current_observation",
|
||||
"action": {
|
||||
"type": "sequence",
|
||||
"actions": [
|
||||
{ "type": "hotkey", "keys": ["META", "L"] },
|
||||
{ "type": "type_text", "text": "https://www.bilibili.com" },
|
||||
{ "type": "key_press", "key": "ENTER" }
|
||||
]
|
||||
},
|
||||
"wait": { "condition": "changed", "timeout_ms": 1500 }
|
||||
}
|
||||
```
|
||||
|
||||
该例适用于 macOS;其他系统按实际快捷键选择修饰键。只有无需中途观察判断的动作才能合并,所有坐标仍对应起始观测。序列禁止嵌套和跨动作保持按下状态,累计文本不超过 16 KiB,计划输入时长(含间隔)不超过 8 秒,执行总期限为 25 秒。`interval_ms` 为动作间最小间隔,默认 100 毫秒,可设为 0–500;它不代表应用完成加载。原生层在首次输入前验证整组动作,每步等待支持的输入回执;回执失败、超时、人工暂停、取消或几何变化都会停止余下动作。旧被控端没有回执时保留 `remote_execution: "unsupported"`,最终画面仍需检查。`sequence.finished_actions` 表示已结束输入阶段的动作数,错误时的 `stopped_at` 是从 0 开始的停止位置,不代表应用业务结果。
|
||||
|
||||
连接和状态结果的 `automation` 汇总本机序列支持、远端输入回执及桌面自动化能力。Agent 应复用当前会话和有效观测,支持桌面自动化时优先用 `crossdesk_desktop_action` 直接打开网址、应用或操作可访问性元素;未声明支持时直接使用截图输入路径。旧 CLI 不支持序列时在任何输入前返回 `NATIVE_SEQUENCE_UNSUPPORTED`,需更新本机 CLI。远端能力未声明时会给出兼容性提示,不自动断开连接或尝试不支持的操作。
|
||||
|
||||
动作结果的 `timing_ms` 包含原生输入阶段、MCP 到 CLI 的输入往返、等待及截图阶段和工具内总耗时,可用来区分工具执行与工具调用之外的宿主、模型或语音延迟。
|
||||
|
||||
### 截图与条件等待
|
||||
|
||||
截图默认最长边 1280,最大 2048;允许指定源帧像素区域。返回 `source_size`、`source_rect`、`image_size`、`generation`、`geometry_epoch`、`frame_id`、`frame_age_ms` 和 `observation_id`。动作坐标使用返回图像坐标,不使用浏览器 CSS 像素。观测保留最多 32 个,30 秒过期;切换显示器、连接代际或图像几何变化立即失效。截图使用不超过 2 秒的帧,PNG 不超过 2 MiB,必要时继续缩小输出并返回真实尺寸。
|
||||
|
||||
`timeout_ms` 限制等待可用帧的时间,PNG 编码独立执行;`0` 表示只使用当前已就绪的新鲜帧,无帧返回 `FRAME_TIMEOUT`,编码器繁忙返回 `BUSY`。截图按到达顺序处理,并优先于预览。视频超过 2 秒未更新时拒绝新的键鼠输入,恢复新鲜画面后可继续;已提交操作的去重查询仍可读取原结果。
|
||||
|
||||
键鼠动作的 `operation_id` 在当前原生会话中缓存最近 128 个操作,至少在未被容量淘汰的 5 分钟窗口内支持同参数查询;不同参数复用 ID 返回冲突,整组序列同样受此规则约束。`submitted` 表示输入已发送,远端原生输入处理状态见 `remote_execution`,应用界面的实际结果仍需后续观察确认。超时、断网、取消或发送失败可能返回 `outcome_unknown`/`side_effect_possible`,禁止自动重放。
|
||||
|
||||
`observe_after` 默认开启:在同一次调用内最多等待 1000 毫秒的画面变化,再返回最终截图及 `wait.matched` / `wait.wait_timed_out`。纯移动和单独按下/释放默认只取新帧,避免等待本来就不会变化的像素;仍可显式指定 `wait`。显式 `wait` 可选择 `changed`、`stable`,或指定 `condition: "ui"` 与 `text` / `role` 等待远端可访问性元素出现;最长等待 5000 毫秒。视觉等待可以提供同一会话的预先裁剪观测 `wait.observation_id`,避免视频、动画或时钟等无关区域干扰。新版 CLI 的视觉等待只接受动作结束帧之后的帧,旧 CLI 返回 `wait.frame_bound: false`。等待超时或截图错误不会覆盖已经提交的输入结果,也不会重放动作;变化或稳定不等于应用任务成功。`observe_after: false` 只返回输入结果,不能同时指定 `wait`。
|
||||
|
||||
更新后的被控端通过 `capabilities.input_ack` 声明输入处理回执。动作结果的 `remote_execution` 表示远端原生输入处理状态,应用界面的实际结果由后续观察确认。旧被控端返回 `unsupported`。回执等待有明确期限,超时动作保留结果不确定状态。
|
||||
|
||||
`crossdesk_wait` 使用已有截图的 `observation_id`,等待对应区域变化(`changed`)或持续稳定(`stable`)。可设置 `stable_ms` 和最长 25000 毫秒的 `timeout_ms`。满足条件后返回新截图,切换显示器、断开和取消会结束等待。
|
||||
|
||||
### 锁屏解锁与密码保存
|
||||
|
||||
Agent 识别到远端系统锁屏后,先聚焦画面中的系统密码框并重新截图,再调用 `crossdesk_unlock`,传入 `session_id`、`operation_id` 和截图的 `observation_id`。默认 `use_saved: true` 会读取该设备独立保存的锁屏凭据。缺少凭据时,浏览器或 Apps 面板通过悬浮弹窗显示隐藏输入框,终端观看器显示隐藏输入提示。弹窗可关闭,并通过左侧连接信息中的“输入密码”重新打开。提交结果显示为六秒后收起的悬浮提示,连接信息持续展示解锁状态和锁屏凭据配置状态。
|
||||
|
||||
用户可在解锁输入框勾选“保存密码并允许以后自动解锁”。连接表单的“保存与授权”和本机 `crossdesk-mcp setup` 也提供锁屏密码配置。持久保存要求设备已在本地设备簿中,系统凭据库使用独立账户保存访问密码和锁屏密码。设备列表返回 `unlock_credential_provider` 摘要,配置文件保存 `unlock_credential` 引用。环境变量方式使用 `unlock_credential: "environment"` 和 `unlock_environment_variable`,旧设备配置继续有效。
|
||||
|
||||
等待输入时,Agent 使用 `crossdesk_get_session`,提供 `session_id` 或 `view_id`、`unlock_request_id` 和 `wait_ms: 25000`。`wait_timed_out` 后可继续等待,等待取消仅结束当前查询。解锁请求有效期为五分钟,等待期间保留实时画面,常规 Agent 输入和显示器切换会返回 `UNLOCK_PENDING`。用户取消、人工暂停、连接结束或画面代际变化会终止请求。
|
||||
|
||||
提交前重新获取同一显示器代际的截图,原生端执行选择密码框内容、文本输入和回车。对支持 `capabilities.system` 的被控端,服务在发送密码前先查询锁定状态,已经解锁时返回 `unlock.state: "verified"` 和 `unlock.result.status: "already_unlocked"`,不再输入密码。提交后会进入 `verifying`,在八秒验证窗口内查询系统状态;确认解锁时返回 `verified`,无法确认时保留 `submitted` 和验证信息。旧被控端成功发送后返回 `submitted`。Agent 随后获取新截图,确认桌面状态后继续原任务。
|
||||
|
||||
用户在解锁弹窗选择保存密码时,支持系统状态的被控端仅在验证为已解锁后写入凭据库;未确认时返回 `persistence_error: "UNLOCK_NOT_CONFIRMED"`。旧被控端仍在成功发送后保存,密码正确性需通过画面确认。密码被拒绝时,使用新请求并设置 `use_saved: false` 让用户更正,结果不确定时先观察画面。相同 `operation_id` 与参数查询原请求结果。
|
||||
|
||||
锁屏密码通过经过认证的本地面板或终端 IPC 进入原生输入通道。模型侧工具参数和结果只包含请求状态,原生操作缓存仅保存解锁请求标识及结果。解锁遵守现有控制授权和人工暂停状态,依赖远端文本输入能力。`off` 模式可使用预先配置的锁屏凭据,缺少凭据时返回 `UNLOCK_CREDENTIAL_REQUIRED`。原生 CLI 通过 `unlock_protocol: "password/1"` 声明支持,更新源码后需重建 CLI 和 MCP 并重新启动服务。
|
||||
|
||||
### 远端剪贴板
|
||||
|
||||
`crossdesk_clipboard_get` 读取远端文本剪贴板,`crossdesk_clipboard_set` 设置文本,空字符串清空。读取上限为 128 KiB,单次工具写入上限为 16 KiB UTF-8。写入需要 `operation_id`,相同请求查询原结果。工具使用已授权控制会话,并遵守人工暂停及解锁等待状态。更新后的被控端通过 `capabilities.clipboard` 声明支持,写入成功由系统剪贴板更新回执确认。
|
||||
|
||||
### 文件浏览与传输
|
||||
|
||||
`crossdesk_files_list` 浏览远端目录,空路径选择远端主目录,`@roots` 返回文件系统根目录。每页最多 256 项,使用 `request_id` 和 `next_offset` 继续读取。超时返回可继续查询的请求标识。
|
||||
|
||||
`crossdesk_files_transfer` 支持上传和下载文件、文件夹,源路径为绝对路径,目标目录需要已存在。传输使用 `operation_id` 去重,通过 `crossdesk_files_status` 查询每个文件的字节进度和终态,通过 `crossdesk_files_cancel` 取消并清理未完成文件。每个连接同时处理一个传输任务,多文件按现有传输引擎排队。已完成任务保留四份历史记录,状态中的文件清单分页返回。目标同名文件按现有冲突命名规则另存。
|
||||
|
||||
目录浏览及新建传输需要已授权且未暂停的控制会话,并要求被控端声明 `capabilities.files`。下载请求绑定目标目录及传输标识,接收器接受已为该请求预留的文件。人工暂停后仍可查询或取消已有任务。
|
||||
|
||||
### 系统状态与安全桌面
|
||||
|
||||
`crossdesk_system_status` 查询系统锁定状态。Windows 同时报告服务可用性和交互桌面阶段。`crossdesk_system_command` 支持 `lock`,Windows 可通过已运行的服务执行 `send_sas`。命令需要控制授权和独立的 `operation_id`,相同请求查询原结果。系统接受命令后,通过状态及画面观察确认实际界面。
|
||||
|
||||
### 多会话与显示管理
|
||||
|
||||
同一 MCP 实例支持最多八个并行设备连接。`crossdesk_list_sessions` 返回各连接的设备、显示器及控制状态。面板按设备绑定会话,断开一个连接会释放该连接的资源。每个连接的诊断日志位于 `logs/<session_id>/transport.log`。
|
||||
|
||||
`crossdesk_capture_displays` 按顺序截取最多八个显示器并恢复原显示器,通过一次工具结果展示多屏画面。`capture_id` 有效期三十秒,供 `crossdesk_drag_across` 使用各屏截图坐标完成跨屏拖拽。拖拽保持按下状态跨越显示器切换,在完成、取消或异常时释放按钮。普通输入操作使用重新选择显示器后取得的新截图。
|
||||
|
||||
`crossdesk_video_settings` 在运行时调整预览帧率、分辨率、质量和码率,返回远端接受状态。设置受对端能力限制。多屏采集及跨屏拖拽期间,同一会话的其他输入请求返回忙碌状态。
|
||||
|
||||
### 有界断线恢复
|
||||
|
||||
`crossdesk_set_recovery` 和面板的“断线自动恢复”开关用于启用恢复。默认关闭,启用要求设备已保存连接凭据及持久授权。后台监测覆盖临时信令中断、连接丢失和原生进程异常,使用退避策略,在六十秒预算内最多执行配置次数的重连。每次重连重新核对设备身份和授权,人工暂停、关闭恢复开关和主动断开会停止恢复。
|
||||
|
||||
恢复期间返回 `reconnecting`,可以用 `crossdesk_get_session` 携带 `session_id` 和 `wait_ms` 等待。恢复成功后返回新的 `session_id`,既有监看面板跟随新连接。旧会话标识可用于查询替代连接,后续操作使用新会话及新截图。截图标识包含所属会话,避免不同原生进程的观测编号相互混淆。先前的输入、解锁和文件任务保持原有结果边界,恢复流程只重建连接并恢复观看配置。
|
||||
|
||||
### 面板人工接管
|
||||
|
||||
浏览器及 Apps 面板提供“人工接管”和“交还 Agent”。接管后面板可操作鼠标、滚轮、键盘快捷键和中文输入法,连接信息展示当前控制权。输入绑定面板、控制批次和画面尺寸,交接会释放按住状态并使旧批次失效。画面超过两秒未更新时停止发送新输入,窗口失焦或隐藏时释放按键。原生层保留十五秒的输入租约作为断线清理保障。
|
||||
|
||||
Agent 在人工接管期间仍可观察画面,键鼠、显示设置和其他要求控制权的操作等待用户在面板交还控制。连接需预先授予控制权限,并使用支持 `events/1` 的原生 CLI。
|
||||
|
||||
### 远端声音
|
||||
|
||||
`crossdesk_audio` 开关远端桌面音频接收,默认关闭。面板的“播放声音”按钮同时启用接收和本地播放,音量在本面板调节。播放使用已授权的视频 WebSocket 通道,PCM 队列与浏览器播放时延均设有上限,拥塞时丢弃过时音频。浏览器首次播放需要用户点击按钮。
|
||||
|
||||
`crossdesk_audio_capture` 采集接下来 100 至 10000 毫秒的音频,通过 MCP 音频内容块返回单声道 48 kHz WAV。采集期间临时开启接收,结束或取消后恢复之前的设置,未收到足够音频时返回 `AUDIO_UNAVAILABLE`。系统需允许被控端捕获桌面声音。自动恢复连接会恢复用户选择的音频接收设置。
|
||||
|
||||
### 窗口、应用与可访问性元素
|
||||
|
||||
`crossdesk_desktop` 查询平台能力、窗口清单及可访问性树。可按进程或先前快照中的元素继续读取子树,节点包含角色、名称、可读取值和系统原生桌面坐标。每份快照最多 128 个节点,响应限制为 48 KiB 左右,超出范围返回 `truncated`。元素引用绑定连接,有效期三十秒,最多保留八份快照。
|
||||
|
||||
`crossdesk_desktop_action` 支持激活窗口、按下或聚焦元素、设置文本值、启动应用以及打开 HTTP(S) 地址。元素操作使用快照和 `element_id`,执行后该快照失效。所有修改操作需要控制授权及 `operation_id`,相同请求查询已有结果。安全密码字段隐藏值并拒绝通过结构化接口修改,系统解锁使用本地密码流程。操作结果表示系统接口接受情况,随后使用画面或新树确认界面变化。
|
||||
|
||||
`crossdesk_wait_ui` 根据文本及角色等待元素出现或消失。读取范围不完整时无法确认消失,会在超时结果中返回 `search_incomplete`。
|
||||
|
||||
macOS 使用 AX,需要系统辅助功能权限。Windows 使用 UI Automation,窗口激活遵守系统前台窗口及权限约束。Linux 构建时若检测到 `atspi-2` 开发包,会启用 AT-SPI 元素访问,否则提供 X11 窗口查询和激活。Wayland 的元素访问依赖桌面和应用提供 AT-SPI。应用启动参数接受 macOS bundle ID 或绝对 `.app` 路径、Windows 绝对 `.exe` 路径、Linux 绝对可执行文件路径。Linux 接口的所有权和超时处理依据官方文档。[AT-SPI 接口文档](https://docs.gtk.org/atspi2/)
|
||||
|
||||
## 实时视频与按需截图
|
||||
|
||||
实时视频提供两条路径,默认 `browser`,两条路径均由原生 CLI 的本机 WebSocket 直接发送给浏览器。Node 只处理会话、授权票据和 MCP 工具,不读取或转发实时视频字节;不再创建视频管道,不经过 MCP stdio 或模型上下文,不逐帧转 PNG、不重新编码视频。
|
||||
|
||||
| `video.decode_path` | 画面路径 |
|
||||
| --- | --- |
|
||||
| `browser` | MiniRTC 输出原始 H.264 Annex B → 浏览器 WebCodecs 解码 → canvas |
|
||||
| `minirtc` | MiniRTC 解码 → 紧凑 NV12 原始帧 → 浏览器 VideoFrame / canvas;浏览器不再解码 H.264 |
|
||||
|
||||
`video.decoder` 默认 `auto`:优先使用可用硬件解码器,不支持时使用软件。`software` 禁用本程序的硬件选择与硬件回退。浏览器路径使用 WebCodecs 的 `prefer-hardware` / `prefer-software`;这些值是浏览器 API 的偏好,浏览器可以忽略,不能据此宣称已确认物理硬件或软件后端。原生路径由 MiniRTC 的解码器工厂选择,本机 macOS 使用 VideoToolbox 或 OpenH264;其他平台使用 MiniRTC 已支持的后端。状态中的 `native_video_decoder` 返回 MiniRTC 报告的后端名称(统计可用前为空),不代表浏览器后端。
|
||||
|
||||
在 `~/.config/crossdesk/mcp/devices.json` 的根对象中加入:
|
||||
|
||||
```json
|
||||
"video": {
|
||||
"decode_path": "minirtc",
|
||||
"decoder": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
也可在 Agent 宿主的 MCP 启动参数中配置,命令行优先于文件:
|
||||
|
||||
```sh
|
||||
crossdesk-mcp serve --stdio --video-path browser --video-decoder auto
|
||||
crossdesk-mcp serve --stdio --video-path minirtc --video-decoder software
|
||||
```
|
||||
|
||||
修改后重启 MCP,再建立连接。选项适用于整个 MCP 进程,两条路径均将解码选项传给原生端;浏览器路径中的原生解码用于截图缓存。PNG 编码按截图请求触发,包括单屏截图、多屏采集及动作或等待后的截图,WebSocket 实时视频不触发 PNG 编码;Apps 的兼容预览按需触发 PNG 编码。这里配置的是控制端解码器,不更改远端编码器。原有配置文件无需新增字段即可继续使用。
|
||||
|
||||
Agent 模式初始请求高帧率档位,初始分辨率上限为 1920 × 1080。连接后可通过 `crossdesk_video_settings` 调整帧率、分辨率、质量和码率;工具接受的分辨率最大为 3840 × 2160,实际分辨率和帧率由远端能力、网络和本机解码性能决定。面板显示实际尺寸与呈现 FPS。浏览器路径需要 WebCodecs H.264 解码能力;MiniRTC 路径仅需浏览器支持从 NV12 创建 VideoFrame 并绘制。浏览器面板能力不足时明确报错;Apps 面板无法连接实时通道或不支持解码时切换到下述有界 PNG 预览。NV12 不压缩,1080p / 30 FPS 约需 93 MB/s 本机传输,4K / 30 FPS 约需 373 MB/s;它不增加远端到控制端的 H.264 网络码率。移除 Node 转发没有消除 NV12 自身的本机传输与浏览器像素处理开销;这不是 GPU 零拷贝。
|
||||
|
||||
视频 WebSocket 在原生 CLI 的独立 I/O 线程中运行,只监听随机 `127.0.0.1` 端口,与 Node 面板 HTTP 端口不同。浏览器面板通过 cookie/CSRF 授权获取视频票据,Apps 通过 app-only `crossdesk_video_open` 和视图令牌获取。票据 15 秒内一次性有效,绑定视图、会话和代际,通过 WebSocket 第一条消息提交,不写入 URL、日志或模型可见工具结果。WebSocket 提供画面、已启用的 PCM 音频、收帧 ACK 和关键帧恢复请求,不能执行远端输入操作。最多 8 个活动观看者。H.264 丢帧后等待关键帧恢复;NV12 每帧独立,每个观看者最多一帧未确认,原生发送队列只保留最新画面;H.264 最多 8 帧未确认并受字节阈值限制。10 秒未确认最早一帧则关闭该视频通道,不阻塞其他观看者或 MCP 控制。视图撤销、显示器/连接代际变化、断开和进程退出会关闭对应视频连接;暂停 Agent 控制仍继续播放。页面隐藏或卸载时释放视频订阅,恢复可见时重新订阅。
|
||||
|
||||
浏览器页面和 MCP Apps 资源的 CSP 允许原生服务使用的 `ws://127.0.0.1:*`(仅动态端口),HTTP 控制仍限制到 Node 面板的确切地址。原生服务另外校验确切 Host、Origin 和一次性票据。宿主仍可能限制 loopback、WebSocket、WebCodecs 或 iframe 权限。已观察到 Codex 内嵌宿主过滤 HTTP/WS 白名单,最终以 `connect-src 'self'` 拦截原生视频连接;添加同义 CSP 字段无法解决协议限制。播放器区分 CSP 拦截,并在 Apps 实时视频失败时通过已授权的 MCP 通道提供兼容预览。
|
||||
|
||||
兼容预览显示“预览”和实际 FPS,最高每秒 1 帧、最长边 960 像素、PNG 每帧不超过 256 KiB,不含声音。`crossdesk_ui_preview` 仅供面板使用,验证视图令牌、代际及绑定会话,每个视图最多一个进行中的请求,并在响应前重新检查会话是否改变。图像只放在工具结果 `_meta` 中,不进入模型可见内容,也不占用 Agent 的操作截图历史。前端直接解码 PNG 字节,无需新增网络白名单;隐藏或卸载面板时停止刷新,迟到响应不再绘制。需要流畅视频及声音时使用 `--panel=browser`;`--panel=apps` 仍禁止自动打开外部浏览器。
|
||||
|
||||
2026-11-11 在本机 Codex MCP Apps 中以仅查看模式连接 macOS 远端,确认 WebSocket 被 CSP 拦截后自动显示兼容预览,画布帧计数持续递增,实测约 0.5–1 FPS。该画面在 256 KiB 上限内自适应缩小为 540 × 303;此结果验证了内嵌预览,不代表宿主已支持本机 WebSocket 实时视频。
|
||||
|
||||
## 打包
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
npm run pack:npm -- --out-dir /absolute/path/artifacts
|
||||
```
|
||||
|
||||
默认生成通用 tarball,包含 MCP、DSH 插件入口与 bundle 声明、面板、源代码、许可和固定版本的五个平台可选依赖。CI 的 `MCP package` 工作流上传此通用 `mcp-package`,不发布 npm。使用通用包前需要对应版本的原生 npm 依赖已经可用。
|
||||
|
||||
为 DSH 生成只需安装一次的平台包时,将 CLI 工作流生成的原生 `.tgz` 解压到工程外目录,再传入其 `package` 目录:
|
||||
|
||||
```sh
|
||||
native_package_dir=$(mktemp -d)
|
||||
tar -xzf /absolute/path/chunkdash-crossdesk-cli-darwin-arm64-0.2.0.tgz -C "$native_package_dir"
|
||||
npm run pack:npm -- --native-package-dir "$native_package_dir/package" --out-dir /absolute/path/platform-artifacts
|
||||
rm -rf "$native_package_dir"
|
||||
```
|
||||
|
||||
平台包保留原生包的程序、源码、许可和构建信息,设置对应 OS / CPU / libc 限制,不再下载原生平台依赖;其他 JavaScript 依赖仍由包管理器安装。CLI 构建工作流为每个平台生成 `crossdesk-mcp-<平台>` 产物;平台安装包要求实际传入的原生包与 MCP 的依赖版本一致。通用包和平台包名称、版本相同,应放在不同输出目录,不要将平台包当成通用版本发布到 npm。
|
||||
|
||||
原生 `doctor` 必须同时声明自动化协议和 `automation_video_protocols: ["h264-annexb/1", "nv12/1"]`、`automation_video_decoders: ["auto", "software"]`,以及 `automation_video_transport: "loopback-websocket/1"`、`automation_terminal_protocol: "ansi/1"`。通用 npm 分发需要先发布对应 0.2.0 平台包,再发布 MCP;内含原生程序的平台包不受此顺序限制。构建通过不等于各宿主和设备上的联调完成。
|
||||
|
||||
从发布 tarball 重建 TypeScript/面板时,将包中的 `build-package.json` 复制为 `package.json`,运行 `npm ci && npm run build`。原生源码和依赖来源随平台包提供,详见 [源码来源](../cli/docs/source-provenance.md)。
|
||||
|
||||
## 本地审阅验证(2026-11-03)
|
||||
|
||||
动作参数校验和输入步骤生成集中在 `apps/cli/automation_protocol.*`,主循环负责调度、取消和权限检查。截图按色度网格对齐后仅转换所需区域,PNG 在容量受限的缓冲区中单次压缩;原生帧仅在没有其他读取者时复用内存。Node 按行累计响应字节,在完整响应到达时解码,避免反复扫描和复制大截图。
|
||||
|
||||
本机 macOS arm64 完成 TypeScript/面板构建、原生 CLI 构建、现有 wire/keyboard-state 测试,以及工程外的 MCP、会话和原生动作回归检查。ASan/UBSan 检查覆盖动作调度和输入清理;140 组随机图像覆盖完整帧、奇偶裁剪边界、缩放及容量回退,优化前后 PNG 字节一致。
|
||||
|
||||
使用 1920 × 1080 合成 NV12、最长边 1280、每项 6 次的 `-O2` 本地基准:完整画面编码约 22.5 → 11.5 ms,321 × 241 裁剪约 2.1 → 0.9 ms。数字仅衡量图像转换与编码,不包含网络、解码、远端执行或宿主渲染;未连接真实远端或完成 Windows/Linux 验收。额外测试代码、脚本、数据和产物均存放于工程外系统临时目录。
|
||||
+157
-530
@@ -1,34 +1,91 @@
|
||||
# CrossDesk MCP
|
||||
|
||||
让本机 Agent 通过 MCP 观察并操作远端 CrossDesk,同时为用户提供浏览器、终端或 MCP Apps 监看面板。控制端不需要安装 CrossDesk 桌面客户端;被控端仍需运行兼容的 CrossDesk,并连接支持 `cli` 注册的信令服务器。
|
||||
让 Codex 或 DeepSeek Harness 在你的授权下查看、操作远程电脑,并在右侧显示远端画面。你可以用自然语言安排任务,随时暂停操作、接管鼠标键盘或断开连接。
|
||||
|
||||
此目录实现 MCP 包 `@chunkdash/crossdesk-mcp` 0.1.0,绑定原生 CLI 包 0.2.0。**源码实现和本地打包不代表这些版本已经发布到 npm。** 发布后的安装示例需要以实际可用版本为准。
|
||||
|
||||
当前提供 31 个模型工具和 11 个 MCP Apps 面板专用接口,覆盖设备与会话管理、截图与条件等待、键鼠输入、窗口与可访问性元素、多显示器、实时视频与音频、文本剪贴板、文件传输、系统锁定与解锁、断线恢复及人工接管。具体能力取决于原生 CLI、被控端版本和系统权限;本机 MCP 接入使用 stdio,不提供 MCP Streamable HTTP 或通用远程 shell 工具。
|
||||
|
||||
安装入口:[安装前准备](#install) · [本地安装包](#install-package) · [源码构建](#从源码运行) · [Codex 用户](#install-codex) · [DeepSeek Harness 用户](#install-dsh) · [首次连接](#用户连接流程) · [安装排查](#install-troubleshooting)
|
||||
[准备工作](#install) · [下载安装包](#install-package) · [DSH 安装](#install-dsh) · [Codex 安装](#install-codex) · [连接设备](#用户连接流程) · [日常使用](#使用示例) · [常见问题](#install-troubleshooting)
|
||||
|
||||
<a id="install"></a>
|
||||
|
||||
## 安装前准备
|
||||
## 准备工作
|
||||
|
||||
控制端安装 Node.js 22 或以上,使用支持图像工具结果的 Agent。MCP、原生 CLI 和显示画面的宿主浏览器必须运行在同一台控制端电脑上;远端电脑运行兼容的 CrossDesk,并保持在线。
|
||||
需要准备两台电脑:
|
||||
|
||||
| 使用环境 | 需要安装 | 画面打开方式 |
|
||||
- **本机**:运行 Codex 桌面版或 DeepSeek Harness(DSH)桌面版,安装 CrossDesk MCP。
|
||||
- **远端电脑**:安装并运行 [CrossDesk](https://www.crossdesk.cn/),保持在线,准备好客户端显示的 9 位设备 ID 和访问密码。按系统提示授予屏幕录制、辅助功能等权限,详见 [客户端使用指南](../../docs/USER_GUIDE.md)。
|
||||
|
||||
| 你使用的应用 | 本机需要准备什么 | 画面在哪里 |
|
||||
| --- | --- | --- |
|
||||
| Codex 桌面版 | CrossDesk MCP 和兼容的原生 CLI | Agent 打开右侧内置浏览器,无需额外插件 |
|
||||
| DeepSeek Harness 桌面版(DSH) | CrossDesk MCP、兼容的原生 CLI、CrossDesk Sidebar 插件 | 插件自动打开当前对话的右侧浏览器 |
|
||||
| Codex CLI / 没有内置浏览器的宿主 | CrossDesk MCP 和兼容的原生 CLI | 系统浏览器或终端,不提供桌面侧栏 |
|
||||
| DeepSeek Harness 桌面版 | 一个 CrossDesk MCP 平台插件包 | 当前对话的右侧浏览器 |
|
||||
| Codex 桌面版 | Node.js 22 或以上,以及 CrossDesk MCP 平台安装包 | 右侧内置浏览器 |
|
||||
| Codex CLI | Node.js 22 或以上,以及 CrossDesk MCP 平台安装包 | 系统浏览器或独立终端 |
|
||||
|
||||
DSH 侧栏已在 macOS / Harness 0.2.2-rc.1 验证;Harness 网页版不支持此侧栏方案。其他平台的原生运行要求见 [CLI 分发文档](../cli/docs/npm-distribution.md)。
|
||||
安装时需要联网下载依赖。MCP 和用于显示画面的应用应在同一台本机上运行。远端电脑使用自建服务器时,在连接面板的“高级设置”中填写相同的服务器和端口。
|
||||
|
||||
下文配置中的 `/absolute/path/...` 均需替换为自己的**绝对路径**。macOS / Linux 可用 `command -v node` 查找 Node;Windows PowerShell 使用 `(Get-Command node).Source`。桌面应用不一定继承终端的 PATH,不要假设配置中的 `~` 会展开。
|
||||
目前侧栏已在 macOS 的 Codex 和 DSH 0.2.2-rc.1 验证;Harness 网页版不支持此侧栏。其他平台的验证范围见 [开发参考](DEVELOPMENT.md)。
|
||||
|
||||
<a id="install-package"></a>
|
||||
|
||||
## 使用本地安装包
|
||||
## 下载安装包
|
||||
|
||||
如果已获得维护者或 CI 构建的 `chunkdash-crossdesk-mcp-0.1.0.tgz`,在固定目录安装。可在 [Build MCP 工作流](https://github.com/kunkundi/crossdesk/actions/workflows/build-mcp.yml) 的成功运行中查找 `mcp-package` 产物;所选构建需包含本次宿主适配。Actions 下载件是外层 ZIP,先解压,再将其中的 `.tgz` 交给 npm;不要把 ZIP 直接交给 npm。没有可用安装包时,使用下方源码构建流程。
|
||||
DSH 和 Codex 都使用下表中的**平台安装包**,其中已包含所需的原生程序。
|
||||
|
||||
1. 登录 GitHub,打开 [CrossDesk 构建页面](https://github.com/kunkundi/crossdesk/actions/workflows/build-cli.yml)。
|
||||
2. 选择一次成功的运行,在页面的 **Artifacts** 区域下载与你的本机对应的文件。
|
||||
3. 解压下载的 `.zip`,取出里面的 `.tgz` 安装包,例如 `chunkdash-crossdesk-mcp-0.1.0.tgz`。保留 `.tgz`,安装时直接使用它。
|
||||
|
||||
GitHub 要求登录后才能下载构建产物,操作说明见 [下载构建产物](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts)。
|
||||
|
||||
| 运行 Codex / DSH 的电脑 | 下载的产物名称 |
|
||||
| --- | --- |
|
||||
| Apple Silicon Mac(M 系列芯片) | `crossdesk-mcp-darwin-arm64` |
|
||||
| Intel Mac | `crossdesk-mcp-darwin-x64` |
|
||||
| Linux x64(glibc) | `crossdesk-mcp-linux-x64` |
|
||||
| Linux arm64(glibc) | `crossdesk-mcp-linux-arm64` |
|
||||
| Windows x64 | `crossdesk-mcp-win32-x64` |
|
||||
|
||||
Mac 可在“关于本机”中查看芯片;Windows 可在“设置 → 系统 → 系统信息”中查看系统类型;Linux 可运行 `uname -m` 查看架构,`x86_64` 对应 x64,`aarch64` 对应 arm64。
|
||||
|
||||
不同平台解压后的 `.tgz` 文件名相同,请以下载时的产物名称区分。如果某次运行没有表中对应的产物,选择另一次提供该产物的运行;也可以按 [源码构建说明](DEVELOPMENT.md#从源码运行) 自行构建。
|
||||
|
||||
下文以 `0.1.0` 为例。**所有 `/absolute/path/...` 都需要换成自己电脑上的完整路径**,版本号也应与实际下载的文件一致。路径中有空格时保留引号。
|
||||
|
||||
<a id="install-dsh"></a>
|
||||
|
||||
## 在 DeepSeek Harness 中安装
|
||||
|
||||
### 1. 安装插件
|
||||
|
||||
在 Harness 的插件页面选择本地 `.tgz` 安装包,或在终端执行:
|
||||
|
||||
```sh
|
||||
dsh plugin --profile desktop add "/absolute/path/chunkdash-crossdesk-mcp-0.1.0.tgz"
|
||||
```
|
||||
|
||||
这个插件包会自动提供 MCP 工具和侧栏画面,无需另外安装 Node.js、原生 CLI 或填写 MCP 启动配置。
|
||||
|
||||
如果 macOS 终端提示找不到 `dsh`,可使用应用自带的命令:
|
||||
|
||||
```sh
|
||||
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop add "/absolute/path/chunkdash-crossdesk-mcp-0.1.0.tgz"
|
||||
```
|
||||
|
||||
上述命令对应默认安装位置和 `desktop` 配置。使用自定义安装位置或 profile 时替换为自己的值。
|
||||
|
||||
### 2. 打开 CrossDesk
|
||||
|
||||
重启 Harness,在插件页面确认 CrossDesk(`@chunkdash/crossdesk-mcp`)已启用。然后在对话中发送:
|
||||
|
||||
> 打开 CrossDesk,让我选择远程电脑。
|
||||
|
||||
右侧出现设备连接面板,就可以继续 [连接设备](#用户连接流程)。安装插件本身不会连接任何远端电脑。
|
||||
|
||||
<a id="install-codex"></a>
|
||||
|
||||
## 在 Codex 中安装
|
||||
|
||||
### 1. 安装到固定目录
|
||||
|
||||
先安装 Node.js 22 或以上。打开终端,按自己的系统执行以下命令,并替换安装包路径。
|
||||
|
||||
macOS / Linux:
|
||||
|
||||
@@ -36,8 +93,7 @@ macOS / Linux:
|
||||
mkdir -p "$HOME/crossdesk-mcp"
|
||||
cd "$HOME/crossdesk-mcp"
|
||||
npm init -y
|
||||
npm install /absolute/path/chunkdash-crossdesk-mcp-0.1.0.tgz
|
||||
node node_modules/@chunkdash/crossdesk-mcp/dist/main.js --help
|
||||
npm install "/absolute/path/chunkdash-crossdesk-mcp-0.1.0.tgz"
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
@@ -47,59 +103,19 @@ New-Item -ItemType Directory -Force "$env:USERPROFILE/crossdesk-mcp"
|
||||
Set-Location "$env:USERPROFILE/crossdesk-mcp"
|
||||
npm init -y
|
||||
npm install "C:/Downloads/chunkdash-crossdesk-mcp-0.1.0.tgz"
|
||||
```
|
||||
|
||||
安装完成后,在同一目录运行:
|
||||
|
||||
```sh
|
||||
node node_modules/@chunkdash/crossdesk-mcp/dist/main.js --help
|
||||
```
|
||||
|
||||
安装包已经包含页面和 DSH 插件构建产物,无需再执行 `npm run build`。安装时保留 optional dependencies:包会尝试安装固定版本的平台原生依赖,支持 macOS arm64/x64、glibc Linux arm64/x64 和 Windows x64。当前 MCP 0.1.0 绑定原生包 0.2.0;如果对应原生包尚未发布或下载失败,需要在**同一安装目录**安装维护者提供的对应平台包,例如 macOS arm64:
|
||||
出现命令帮助说明 Node 入口可以运行。请保留安装目录,后续 Codex 会从这里启动 CrossDesk。
|
||||
|
||||
```sh
|
||||
npm install /absolute/path/chunkdash-crossdesk-cli-darwin-arm64-0.2.0.tgz
|
||||
```
|
||||
### 2. 添加 MCP 配置
|
||||
|
||||
也可在下文宿主配置中添加 `--native-binary`,指向已经构建好的兼容 `crossdesk-cli` 可执行文件(Windows 为 `.exe`)。全局安装 CLI 不会被 MCP 自动发现:未指定路径时只解析固定版本的平台依赖,不搜索 PATH。`--help` 仅验证 Node 入口,原生协议在连接时才检查。
|
||||
|
||||
如果使用 npm 仓库,先确认所需版本已发布,再将上述安装命令替换为 `npm install @chunkdash/crossdesk-mcp@<已发布版本>`;仓库里的版本号不代表 npm 上已有该包。
|
||||
|
||||
## 从源码运行
|
||||
|
||||
仓库根目录构建原生程序(已有工作区可保留原有桌面构建选项):
|
||||
|
||||
```sh
|
||||
sh apps/cli/scripts/prepare-deps.sh
|
||||
xmake f -y --CROSSDESK_BUILD_DESKTOP=n --CROSSDESK_BUILD_CLI=y --USE_CUDA=n -m release
|
||||
xmake build crossdesk-cli
|
||||
cd apps/mcp
|
||||
npm ci
|
||||
npm run build
|
||||
```
|
||||
|
||||
完整编译环境见 [源码构建指南](../../docs/BUILD.md)。`npm run build` 同时构建 MCP、页面和 DSH 配套插件。源码目录的 `npm ci` 只安装开发依赖,**源码运行必须显式指定原生程序路径**;生成的发布包才声明固定版本的平台依赖。
|
||||
|
||||
例如,macOS arm64 Release 构建后可先检查:
|
||||
|
||||
```sh
|
||||
/absolute/path/crossdesk/build/macosx/arm64/release/crossdesk-cli doctor --json
|
||||
```
|
||||
|
||||
Windows、Linux、x64 或 Debug 构建使用自己的实际产物路径。`--native-binary` 必须指向可执行文件,不接受 shell 命令;也可用 `CROSSDESK_NATIVE_BINARY` 指定。原生程序需要提供 MCP 所需的 automation、terminal、video 协议;仅能运行交互式 CLI 不代表版本兼容。
|
||||
|
||||
两种安装方式在下文使用的路径不同:
|
||||
|
||||
| 项目 | 本地安装包 | 源码构建 |
|
||||
| --- | --- | --- |
|
||||
| MCP 入口 | `<安装目录>/node_modules/@chunkdash/crossdesk-mcp/dist/main.js` | `<工程目录>/apps/mcp/dist/main.js` |
|
||||
| DSH 插件目录 | `<安装目录>/node_modules/@chunkdash/crossdesk-mcp/integrations/deepseek-harness` | `<工程目录>/apps/mcp/integrations/deepseek-harness` |
|
||||
| 原生程序 | 固定版本平台依赖,或显式指定路径 | 显式指定构建产物路径 |
|
||||
|
||||
服务启动不连接远端、不打开浏览器;Agent 调用打开或连接工具后才进入授权与连接流程。
|
||||
|
||||
<a id="install-codex"></a>
|
||||
|
||||
## Codex 用户安装与使用
|
||||
|
||||
### 1. 注册 MCP 服务
|
||||
|
||||
先完成上方的安装包安装或源码构建,再将以下内容合并到 `~/.codex/config.toml`。已有 `[mcp_servers.crossdesk]` 时直接更新该段,保留其他服务配置。该配置由 Codex 桌面版和 CLI 共用;字段说明见 [Codex 官方 MCP 文档](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)。
|
||||
打开用户目录中的 `.codex/config.toml`(macOS / Linux 为 `~/.codex/config.toml`),加入以下配置。已有 `[mcp_servers.crossdesk]` 时更新该段,保留其他服务配置。
|
||||
|
||||
```toml
|
||||
[mcp_servers.crossdesk]
|
||||
@@ -114,509 +130,120 @@ startup_timeout_sec = 30
|
||||
tool_timeout_sec = 90
|
||||
```
|
||||
|
||||
**源码用户**将上述 `args` 数组替换为:
|
||||
需要替换两处路径:
|
||||
|
||||
```toml
|
||||
args = [
|
||||
"/absolute/path/crossdesk/apps/mcp/dist/main.js",
|
||||
"serve", "--stdio",
|
||||
"--panel=browser", "--browser-launch=host",
|
||||
"--native-binary", "/absolute/path/crossdesk/build/macosx/arm64/release/crossdesk-cli"
|
||||
]
|
||||
```
|
||||
- `command`:Node 可执行文件的完整路径。macOS / Linux 用 `command -v node` 查询;Windows PowerShell 用 `(Get-Command node).Source` 查询。
|
||||
- `args` 第一项:上一步安装目录下的 `node_modules/@chunkdash/crossdesk-mcp/dist/main.js` 完整路径。
|
||||
|
||||
该原生路径仅为 macOS arm64 Release 示例。Windows 使用实际的 `node.exe`、`crossdesk-cli.exe` 路径;配置路径可统一使用 `/`,如 `C:/Users/you/crossdesk-mcp/...`。
|
||||
配置路径不要使用 `~`。Windows 可使用正斜杠,例如 `C:/Program Files/nodejs/node.exe`。保留 `--panel=browser --browser-launch=host`,让 Codex 桌面版打开右侧浏览器。配置格式可参考 [Codex 官方文档](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)。
|
||||
|
||||
显式保留 `--panel=browser --browser-launch=host`,使 Codex 桌面版采用右侧内置浏览器。新版 MCP 的 `auto` 也包含相同默认策略,无需另装插件或添加个人 `AGENTS.md`。这里配置的是本地 stdio 进程,不需要填写 HTTP MCP 地址。
|
||||
### 3. 打开 CrossDesk
|
||||
|
||||
### 2. 加载并确认工具
|
||||
重启 Codex,在对话中发送:
|
||||
|
||||
保存配置后,重启 CrossDesk MCP 或 Codex,再开启一次对话。安装了 Codex CLI 时,可用 `codex mcp list` 确认 `crossdesk` 已启用。随后对 Agent 说:
|
||||
> 打开 CrossDesk,让我选择远程电脑。
|
||||
|
||||
> 使用 CrossDesk 列出已保存设备。
|
||||
右侧出现设备连接面板后,按下方步骤授权。首次安装没有已保存设备是正常的。
|
||||
|
||||
首次安装返回空列表是正常的。若没有 CrossDesk 工具或服务启动失败,先检查 Node、入口路径和 [安装排查](#install-troubleshooting)。
|
||||
|
||||
### 3. 打开侧栏并连接
|
||||
|
||||
> 打开 CrossDesk,让我选择远程电脑;连接后先查看画面。
|
||||
|
||||
Agent 会将启动地址交给 Codex 的内置浏览器,打开右侧面板。在面板中选择或添加设备、输入密码、授予仅查看或控制权限。连接后可以继续下达任务;若希望以后按设备别名连接,按需保存设备、密码与自动连接授权,例如:
|
||||
|
||||
> 使用 CrossDesk 连接已授权的“办公室电脑”,打开浏览器搜索天气。
|
||||
|
||||
密码只在本地授权面板输入。详细权限行为见 [用户连接流程](#用户连接流程)。如果使用的是 **Codex CLI** 而非桌面版,将 `--browser-launch=host` 改为 `--browser-launch=system`;无图形环境可使用 `--panel=terminal` 并删除 `--browser-launch=host`,按工具返回的命令在另一个交互终端打开画面。
|
||||
|
||||
<a id="install-dsh"></a>
|
||||
|
||||
## DeepSeek Harness 用户安装与使用
|
||||
|
||||
以下针对 **DeepSeek Harness 桌面版**,简称 DSH。需要同时完成“注册 MCP 服务”和“安装侧栏插件”:MCP 提供远控工具,插件负责自动打开当前对话的侧栏;安装插件不会自动注册 MCP 服务。
|
||||
|
||||
### 1. 注册 MCP 服务
|
||||
|
||||
先完成上方的安装包安装或源码构建。默认桌面 profile 配置为 `~/.dsh/profiles/desktop/cordis.patch.yml`;使用自定义 `DSH_HOME` 或 profile 时改为对应目录。将下列条目合并到现有 YAML 列表,不要覆盖已有插件或 MCP 配置。若已通过界面配置 `crossdesk`,编辑已有服务即可,不要重复添加。
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
- id: mcp-crossdesk
|
||||
name: '@deepseek-ai/dsh-mcp-client'
|
||||
config:
|
||||
serverName: crossdesk
|
||||
transport: stdio
|
||||
command: /absolute/path/node
|
||||
args:
|
||||
- /absolute/path/crossdesk-mcp/node_modules/@chunkdash/crossdesk-mcp/dist/main.js
|
||||
- serve
|
||||
- --stdio
|
||||
- --panel=browser
|
||||
- --browser-launch=host
|
||||
toolCallTimeoutMs: 90000
|
||||
```
|
||||
|
||||
**源码用户**将入口改为 `/absolute/path/crossdesk/apps/mcp/dist/main.js`,并在 `args` 列表末尾追加以下两项,原生路径替换为当前平台的实际构建产物:
|
||||
|
||||
```yaml
|
||||
- --native-binary
|
||||
- /absolute/path/crossdesk/build/macosx/arm64/release/crossdesk-cli
|
||||
```
|
||||
|
||||
服务名称 `serverName: crossdesk` 要与侧栏插件匹配。参数格式见 [Harness 官方 MCP Client 文档](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/mcp/mcp-client)。保留 `--panel=browser --browser-launch=host` 或新版的 `auto` 默认值;旧配置中的 `--panel=apps`、`--panel=off`、`--browser-launch=system` 会改变显示方式。
|
||||
|
||||
### 2. 安装 CrossDesk Sidebar 插件
|
||||
|
||||
本地安装包用户执行:
|
||||
|
||||
```sh
|
||||
dsh plugin --profile desktop add /absolute/path/crossdesk-mcp/node_modules/@chunkdash/crossdesk-mcp/integrations/deepseek-harness
|
||||
```
|
||||
|
||||
源码用户完成 `npm run build` 后执行:
|
||||
|
||||
```sh
|
||||
dsh plugin --profile desktop add /absolute/path/crossdesk/apps/mcp/integrations/deepseek-harness
|
||||
```
|
||||
|
||||
若终端找不到 `dsh`,可在 Harness 插件页面通过本地路径安装上述目录。macOS 默认安装位置也可使用应用自带的 CLI,将命令开头的 `dsh` 替换为:
|
||||
|
||||
```sh
|
||||
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh"
|
||||
```
|
||||
|
||||
在 Harness 插件页面确认 `crossdesk-sidebar` 组件已启用并运行。如果 MCP 服务改了名称,按 [插件说明](integrations/deepseek-harness/README.md) 配置相同的 `serverName`。
|
||||
|
||||
### 3. 重启并首次使用
|
||||
|
||||
重启 CrossDesk MCP 和 Harness,使新入口、工具说明及插件一起生效。然后在 Harness 对话中依次发送:
|
||||
|
||||
> 使用 CrossDesk 列出已保存设备。
|
||||
|
||||
> 打开 CrossDesk,让我选择远程电脑;连接后先查看画面。
|
||||
|
||||
工具名称形如 `mcp__crossdesk__crossdesk_list_devices`。打开工具返回后,插件会自动打开**发起调用的对话**右侧浏览器,无需手动点击链接。用户在页面中输入设备信息并授权,Agent 等待连接;页面显示“实时”、分辨率和帧率后,可监看远端任务。已有保存设备及相应授权时,也可以直接要求连接设备别名。
|
||||
|
||||
插件只负责导航,不读取启动页或转发视频。不要将 `browser_launch_url` 改成普通 `browser_url` 来完成首次配对,也不要读取或复制启动页内容。已有会话的启动地址过期时,让 Agent 为同一 `view_id` 调用 `crossdesk_open_browser`,无需重连远端。
|
||||
|
||||
### 更新与卸载
|
||||
|
||||
安装包用户在原安装目录安装新版 `.tgz`;源码用户更新后重新运行 `npm ci` 和 `npm run build`。保持插件目录路径稳定,再重启 MCP 和 Harness;目录变更时重新执行对应的 `dsh plugin ... add` 命令,并同步修改 MCP 入口。
|
||||
|
||||
卸载侧栏插件:
|
||||
|
||||
```sh
|
||||
dsh plugin --profile desktop remove @chunkdash/dsh-crossdesk-sidebar
|
||||
```
|
||||
|
||||
该命令不会移除 MCP 服务配置。只取消自动侧栏、继续使用 MCP 时,可将服务参数改为 `--panel=browser --browser-launch=system`;完全停用则另行禁用或删除 `crossdesk` MCP 配置。
|
||||
|
||||
<a id="install-troubleshooting"></a>
|
||||
|
||||
## 安装与侧栏排查
|
||||
|
||||
| 现象 | 处理方式 |
|
||||
| --- | --- |
|
||||
| 找不到 `node`、服务启动失败或没有工具 | 使用 Node 和 `dist/main.js` 的绝对路径;先在终端运行 `node <入口> --help`,确认服务已启用,再重启宿主 |
|
||||
| `NATIVE_PACKAGE_MISSING` | 在 MCP 安装目录安装对应平台的原生 0.2.0 包,或配置 `--native-binary`;全局 CLI 不会被自动解析 |
|
||||
| `NATIVE_VERSION_MISMATCH` / `NATIVE_PROTOCOL_MISMATCH` | 使用与 MCP 匹配的原生版本,运行 `crossdesk-cli doctor --json` 检查协议;不要仅依据交互式 CLI 能启动判断兼容 |
|
||||
| DSH 能调用工具,但没有自动侧栏 | 确认 Sidebar 插件启用、`serverName` 一致,MCP 已更新并重启,且未强制使用 `apps` / `off` / `system` |
|
||||
| DSH 返回 `file://` 启动地址 | 检查是否仍运行旧版 MCP 或旧入口,更新为包含 Harness 适配的构建后重启;不要复制启动页内容 |
|
||||
| 页面配对失败或启动地址过期 | 为已有 `view_id` 调用 `crossdesk_open_browser` 重新签发;普通 `browser_url` 不能为新浏览器完成配对 |
|
||||
| Codex CLI 没有右侧画面 | CLI 不提供桌面侧栏,使用系统浏览器或终端模式 |
|
||||
| `DEVICE_BUSY` | 先断开另一 MCP 进程对该设备的会话;使用同一配置目录的 Codex 与 DSH 不应同时连接同一目标 |
|
||||
**使用 Codex CLI 时**,将配置中的 `--browser-launch=host` 改为 `--browser-launch=system`,画面会在本机系统浏览器显示。无图形环境的终端用法见 [显示方式配置](DEVELOPMENT.md#面板策略)。
|
||||
|
||||
## 用户连接流程
|
||||
|
||||
1. 对 Agent 说“打开 CrossDesk”。`crossdesk_open` 创建 `view_id`,返回面板选择和等待用户状态。
|
||||
2. 在面板选择已保存设备,或输入 9 位设备 ID;密码填写在面板里。可选择仅查看或允许 Agent 控制。
|
||||
3. “保存设备”“记住密码”“允许以后自动连接”分别控制设备簿、系统凭据库、持久权限。后两项要求保存设备,默认均关闭。保存密码不自动授予控制权限。
|
||||
4. 有待执行的远程任务时,Agent 在打开面板后立即调用 `crossdesk_get_session({view_id, wait_ms: 25000})` 等待连接。连接就绪后,工具直接返回会话 ID 和权限状态,Agent 继续任务;用户无需回到对话发送连接确认,也无需点击继续按钮。浏览器和 Apps 面板使用同一流程。
|
||||
5. Agent 获取独立截图,用截图的 `observation_id` 和图像坐标提交动作。面板预览不消耗模型截图序列。
|
||||
1. 在右侧面板选择已保存设备,或选择“连接新设备”。
|
||||
2. 输入远端电脑的 **设备 ID** 和 **访问密码**。密码只在面板中填写,不发送到对话。
|
||||
3. 选择授权范围:**仅查看画面**适合观察和检查;需要打开网页、输入文字、操作文件时,选择 **允许 Agent 操作**。
|
||||
4. 点击 **授权并连接**。连接成功后,面板会显示远端桌面、分辨率和帧率。
|
||||
5. 回到对话下达任务。如果连接前已经安排了任务,Agent 会在连接就绪后继续执行。
|
||||
|
||||
`crossdesk_open` 立即返回,让 Apps 宿主有机会渲染面板;等待连接在后续 `crossdesk_get_session` 调用中完成。按 `view_id` 查询默认最多等待 25 秒,`wait_ms: 0` 则立即查询;未就绪时返回 `wait_timed_out: true`,Agent 在原任务仍待执行时继续等待。按 `session_id` 查询默认立即返回,传入正数 `wait_ms` 可等待正在进行的连接恢复;带 `unlock_request_id` 时等待对应解锁请求。取消等待只取消本次查询,不断开用户正在建立或使用的连接。用户主动断开后返回 `closed`,Agent 停止等待,不自动重连。
|
||||
### 下次如何直接连接
|
||||
|
||||
面板发起的连接失败后,按 `view_id` 等待也会返回 `failed` 和错误码,避免 Agent 一直停留在等待用户状态。断开正在建立的连接会取消整个连接尝试。多个面板和 Agent 同时查询状态时共用一次原生查询,单次查询取消或暂时繁忙不会断开会话。`crossdesk_connect` 在连接成功但面板初始化失败时仍返回有效的 `session_id`,并附加 `panel_error`。
|
||||
展开连接面板中的 **保存与授权**,按需设置:
|
||||
|
||||
初次信令注册使用原生连接的整体 30 秒期限,期间允许 MiniRTC 按退避策略恢复临时传输异常。期限覆盖所有初始重试。证书校验失败、登录被拒绝,以及设备连接请求发出后的信令中断会结束当前尝试。连接失败结果使用 `error.code` 表示错误类别,并以 `error.reason` 提供已识别的原生原因,例如 `signaling_timeout`、`signaling_disconnected` 或 `device_offline_or_unavailable`。面板显示对应说明,终端错误包含原因标识。`logs/<session_id>/transport.log` 记录连接阶段、受控的传输错误分类与数字码、连接关闭码和重试退避信息。
|
||||
|
||||
自动继续依赖宿主保持当前 Agent 任务及工具调用流程。它不通过网页伪造用户消息,也不承诺从已经结束的对话中主动唤醒 Agent;此类唤醒由宿主负责。
|
||||
|
||||
人工暂停由原生层锁定,模型不能通过 `crossdesk_set_control({session_id, enabled: true})` 恢复;只有当前授权面板的恢复按钮可以恢复。浏览器和 Apps 面板还支持人工接管键鼠,并由用户交还 Agent。仅查看连接不能升级为控制连接,需重新连接并授权。关闭/隐藏面板会停止预览并释放面板持有的按键,远程会话保留;断开按钮或 `crossdesk_disconnect` 才结束会话。
|
||||
|
||||
同一 MCP 进程支持最多八个并行设备连接,面板按设备绑定会话。相同配置目录中的进程用设备锁避免同时连接同一目标;它不协调另一个系统用户、另一份配置目录或独立桌面客户端。
|
||||
|
||||
## 面板策略
|
||||
|
||||
`--panel` 优先于设备配置中的 `panel`,默认 `auto`;`--browser-launch` 同样默认 `auto`。MCP 根据客户端标识内置 Codex 和 DeepSeek Harness 的默认策略,不依赖个人 Codex 配置或 `AGENTS.md`。已有配置中显式指定的面板模式和启动方式仍然有效。
|
||||
|
||||
| 模式 | 行为 |
|
||||
| 选项 | 作用 |
|
||||
| --- | --- |
|
||||
| `auto` | Codex 和 DeepSeek Harness 默认选择宿主侧栏浏览器;其他客户端有本机 GUI 时使用系统浏览器,无 GUI 且有终端环境时提供 CLI 画面入口,两者均无则使用 `off` |
|
||||
| `apps` | 只使用 MCP Apps;能力不匹配返回 `PANEL_UNSUPPORTED` |
|
||||
| `browser` | 使用本地浏览器页面;启动方式为 `auto` 时 Codex 和 DeepSeek Harness 使用宿主侧栏,其他客户端使用系统浏览器;可显式选择 `--browser-launch=host` 或 `--browser-launch=system`,不附加 Apps 工具资源元数据 |
|
||||
| `terminal` | 提供独立终端 `view` 命令,共用当前 MCP 会话,不启动网页服务 |
|
||||
| `off` | 不启动网页服务,保留截图和操作工具,凭据通过独立 `setup` 配置 |
|
||||
| 保存设备 | 记住设备信息,可填写“工作电脑”等备注 |
|
||||
| 记住密码 | 将访问密码保存在本机系统凭据库 |
|
||||
| 允许下次自动连接 | 允许 Agent 在以后的任务中沿用本次授权范围连接 |
|
||||
|
||||
`auto` 不再优先选择 MCP Apps;需要宿主内嵌面板时显式使用 `--panel=apps`。Apps 使用 `extensions["io.modelcontextprotocol/ui"].mimeTypes` 中的 `text/html;profile=mcp-app` 判断能力。旧协议读取 initialize 能力;2026-07-28 协议读取当前请求的能力,不按宿主产品名猜测支持情况。Apps 的卡片、侧栏或窗口形式由宿主决定。
|
||||
后两项需要先勾选“保存设备”。希望以后通过设备备注直接连接时,同时保存密码并允许下次自动连接;仅保存设备不会自动授权控制。
|
||||
|
||||
除 Codex 和 DeepSeek Harness 外,客户端使用 `auto` 时,Linux 根据 `DISPLAY` / `WAYLAND_DISPLAY` 判断 GUI;macOS 检查当前用户的桌面登录会话,SSH 会话不自动打开远端 Mac 的浏览器;Windows 排除 SSH 和 Services 会话。终端判断包含 TTY、`SSH_TTY` 和非 `dumb` 的 `TERM`,因为 MCP 的 stdin/stdout 通常是管道。需要终端或无面板运行时,显式设置 `--panel=terminal` 或 `--panel=off`。
|
||||
例如:
|
||||
|
||||
终端模式的 `crossdesk_open` / `crossdesk_connect` 返回 `terminal_command`。用户在同一主机的另一个交互终端执行(源码运行时将 `crossdesk-mcp` 替换为 `node /absolute/path/apps/mcp/dist/main.js`):
|
||||
> 使用 CrossDesk 连接“工作电脑”,帮我打开浏览器搜索天气。
|
||||
|
||||
```sh
|
||||
crossdesk-mcp view --view <view_id> --config-dir <相同的配置目录>
|
||||
```
|
||||
如果本次只授权查看,之后需要操作时,请断开后重新连接并选择“允许 Agent 操作”。
|
||||
|
||||
该命令提供设备选择、隐藏密码输入和本次控制授权;已建立会话则直接显示画面。终端通过受限配置目录中的运行描述符和认证本地 IPC 复用 MCP 的原生连接,不启动第二个远程连接,也不占用 Agent 的终端或 MCP stdio。Agent 应展示命令供用户执行,再通过 `crossdesk_get_session` 等待;不能在工具输出捕获通道中运行 `view`。无交互终端时 `view` 返回 `VIEW_REQUIRES_TERMINAL`。
|
||||
## 使用示例
|
||||
|
||||
终端画面采用原生 CLI 的 ANSI 四分块渲染器,目标 8 FPS,最多 160 列 × 60 行,根据终端环境使用 8/256/真彩色。默认字符宽高比 0.5,可用 `--cell-aspect`(0.2–2)调整。此入口暂不使用 Kitty/Sixel。`p` 暂停 Agent、`r` 恢复已授权控制、`d` 断开、`q` / Ctrl+C 仅关闭画面并恢复终端;关闭画面后会话保留。暂停仍由原生层锁定,Agent 不能自行恢复。
|
||||
连接后直接向 Agent 描述要做的事情:
|
||||
|
||||
浏览器/Apps 与终端均绘制 Agent 最后成功发送的鼠标位置,支持移动、点击、滚动和拖拽;坐标来自原生层对截图缩放及裁剪的映射。暂停或关闭控制时隐藏,切换显示器或图像尺寸变化时清除。该指针是 Agent 的操作位置提示,不代表远端输入执行 ACK,也不跟踪远端用户自己的鼠标。它不写入模型截图或源视频。
|
||||
|
||||
浏览器及 Apps 的实时视频光标会以 60–140 毫秒的短缓动追踪最新坐标,动画独立于视频帧刷新;连续位置更新从当前显示位置接续,不排队回放旧轨迹。首次显示、连接代际或显示几何变化时直接定位,隐藏或停止播放时取消动画,并遵守系统的“减少动态效果”设置。平滑效果只作用于页面提示光标,原生鼠标输入及点击坐标不受影响,因此不表示远端鼠标实际经过了补间路径。
|
||||
|
||||
实时视频光标采用 CrossDesk 主色(`#3564e3`)的圆角箭头、白色描边和同色柔光,与客户端及页面配色一致。移动时尖端朝向页面上实际移动的方向,以尖端为旋转锚点;方向按画面像素计算,适配宽屏与竖屏。停止后保留最后朝向,忽略小于半个 CSS 像素的位移对朝向的影响;首次显示及连接、显示几何变化时恢复默认朝向。
|
||||
|
||||
Apps 资源为 `ui://crossdesk/viewer.html`,工具只在允许使用 Apps 且当前请求支持时暴露 UI 元数据和面板专用接口。页面回报 `crossdesk_ui_ready` 后才标记就绪;10 秒未回报显示 `not_confirmed`,不自动弹第二个浏览器。用户明确要求回退时调用 `crossdesk_open_browser`;该工具不附带 Apps 元数据,`apps`/`off` 策略拒绝覆盖。
|
||||
|
||||
浏览器面板只监听随机端口的 `127.0.0.1`,与 MCP 服务同一进程。它不是 MCP Streamable HTTP,也不向局域网提供 MCP 接入。MCP、CLI、用户浏览器应在同一控制端电脑上运行;SSH 容器/远程宿主的浏览器转发不属于此本地接入模式。
|
||||
|
||||
页面以 Agent 会话监看为中心:首次连接显示授权卡片,连接后远程画面占满可用区域,设备、权限和恢复设置收进「会话详情」抽屉。工具栏显示设备与实际控制状态,仅在具有控制授权时保留「暂停 Agent / 恢复 Agent」;本面板人工接管时改为「交还 Agent」。画面放大、人工接管、声音与音量、断开连接收进「会话选项」,仅查看会话不显示人工接管入口。等待系统解锁时显示独立提示及密码入口,关闭解锁弹窗后仍可再次打开。状态区分仅查看、控制已启用、已暂停、人工接管和恢复连接,不将控制权限解释为 Agent 正在执行任务。
|
||||
|
||||
页面标题为 CrossDesk MCP,页内使用 CrossDesk 正式标志和名称。配色与桌面客户端的 WorkbenchStyle / ButtonStyle 一致,采用浅蓝灰底色、蓝色主操作、6 像素控件圆角及 12 像素卡片圆角;工具栏按钮统一为 32 像素高,图标按钮保留可访问名称与悬停提示。布局填满浏览器或宿主面板的可用宽高,远程画面保持原比例缩放,底部集中显示视频状态、分辨率和帧率;授权卡片在高度不足时独立滚动,窄侧栏沿用相同的监看布局。「放大画面」隐藏页眉并缩小外边距;退出菜单、详情和非人工接管状态下的放大模式支持 Esc。界面资源均可离线使用,保存设备与持久授权仍由用户分别选择。
|
||||
|
||||
首次打开使用 URL fragment 中的短期一次性 bootstrap,页面立即清除 fragment,并换取 HttpOnly/SameSite cookie 和 CSRF 令牌。原始 bootstrap 不进入工具结果;`browser_url` 不含配对凭据,`browser_launch_url` 则是短期配对入口,只用于交给宿主导航,不应读取或发布。复制无凭据地址到另一浏览器不能获得会话控制权。
|
||||
|
||||
无法自动打开浏览器时,在自己的终端运行工具结果 `pairing_command` 中的命令和参数。例如:
|
||||
|
||||
```sh
|
||||
crossdesk-mcp setup --open-panel --view <view_id> --config-dir <相同的配置目录>
|
||||
```
|
||||
|
||||
通过源码运行时用 `node /absolute/path/apps/mcp/dist/main.js` 替换 `crossdesk-mcp`。配对通过本用户配置目录中的受限运行描述符和本地 IPC 完成,不允许匿名 HTTP 请求签发控制凭证。`setup` 使用用户终端的桌面环境打开浏览器;仍无法打开时,只在该交互终端显示 60 秒有效的一次性地址,不将它返回 MCP 或发送到聊天。面板最多保留 32 个视图;再次打开时会清理超过 30 分钟未使用的视图。浏览器会话最长 12 小时,失效后重新打开。
|
||||
|
||||
### Codex 默认侧栏浏览器
|
||||
|
||||
安装、配置示例及首次使用见 [Codex 用户安装与使用](#install-codex)。任何用户在 Codex 中直接启动新版 MCP,默认都会返回侧栏浏览器入口;以下说明宿主识别和配对机制。
|
||||
|
||||
MCP 识别 Codex 的客户端名称 `codex-mcp-client` 和 DeepSeek Harness 的 `dsh-mcp-client`:旧协议读取 initialize 中的 `clientInfo`,2026-07-28 协议读取当前请求的 `io.modelcontextprotocol/clientInfo`。该标识仅选择显示方式,不授予任何设备权限。`--panel=auto` 在这两个宿主中选择 `browser`,`--browser-launch=auto` 选择 `host`;其他客户端仍沿用环境检测和系统浏览器。
|
||||
|
||||
`host` 不启动系统浏览器,也不生成 MCP App 卡片;它要求宿主浏览器与 MCP 运行在同一台电脑。Codex 使用本机 `file://` 启动页;DeepSeek Harness 使用下述 HTTP 启动入口。显式使用 `--panel=browser --browser-launch=host` 可以强制宿主浏览器,使用 `--panel=browser --browser-launch=system` 则强制系统浏览器。显式将 `--browser-launch=host` 与 `apps`、`terminal` 或 `off` 组合会报错,这些模式单独使用不受浏览器默认值影响。
|
||||
|
||||
服务指令、工具说明和返回值均内置 Codex 的打开流程:Agent 拿到 `browser_launch_url` 后立即打开宿主浏览器,即使连接已就绪也不省略此步骤。无需用户再次要求打开侧栏,也无需添加个人 `AGENTS.md`。实际打开仍由宿主浏览器工具执行,MCP 协议本身不能强制所有客户端展示侧栏;缺少该工具时(例如没有侧栏浏览器的 Codex CLI)应明确说明限制并展示本地配对命令。
|
||||
|
||||
升级后重启 MCP 服务即可使用新默认值;原有 `--panel=apps`、`--panel=terminal`、`--panel=off` 等参数或设备配置中的显式策略会继续覆盖默认值,需要侧栏时删除旧覆盖或显式使用 `--panel=browser --browser-launch=host`。这项行为随 MCP 包分发,尚未升级到包含此改动的版本不会自动变化。
|
||||
|
||||
`crossdesk_open`、`crossdesk_connect` 和 `crossdesk_open_browser` 在此模式返回 `browser_launch_url`、`browser_url`、`browser_host`、剩余有效时间及绝对到期时间。Agent 应将 `browser_launch_url` 直接交给宿主的浏览器打开工具;在 Codex 中调用 `open_in_codex`,设置 `target.type` 为 `browser`、`target.url` 为该地址、`placement` 为 `right`。不要读取、粘贴或上传启动页内容。
|
||||
|
||||
Codex 启动页位于系统临时目录的独立目录中,POSIX 权限为目录 `0700`、文件 `0600`,内含 60 秒有效的一次性配对地址。浏览器在本机跳转到播放器并换取自己的 HttpOnly/SameSite cookie 和 CSRF 令牌;工具结果不包含原始 bootstrap 票据。票据绑定视图及代际,消费、到期或正常关闭服务时清理启动页。启动地址过期时,对已有 `view_id` 调用 `crossdesk_open_browser` 重新签发,不要重新连接远端。
|
||||
|
||||
`browser_url` 是不带配对凭据的本机 HTTP 地址,用于已配对浏览器重新打开;直接复制到另一浏览器不能获得当前会话权限。没有内置浏览器的宿主可由用户执行原有 `pairing_command`,在系统浏览器完成配对。
|
||||
|
||||
侧栏中的网页沿用本机 WebSocket → WebCodecs/VideoFrame 播放路径,媒体数据不经过 MCP 工具结果,也不需要新增公网中转。它与 MCP Apps iframe 是不同的运行环境;是否能播放及实际帧率需要在目标宿主验证。
|
||||
|
||||
2026-11-11 在本机 Codex 侧栏浏览器完成实测:通过上述启动页连接 macOS 远端,页面显示“实时”、1920 × 1080、约 26 FPS,画布帧计数持续递增。原生端确认 `h264-annexb/1` 经 `loopback-websocket/1` 传输,1 个活动观看者、发送队列无积压;验证期间仅查看画面,未发送远端输入。此结果验证当前环境下的侧栏实时播放,实际帧率仍取决于远端、网络和解码性能。
|
||||
|
||||
2026-11-12 使用工程外的全新临时配置完成 16 项 stdio 协议检查:Codex 新旧协议均在不设置面板参数时返回宿主浏览器启动页,其他客户端保留原有行为,显式面板及启动方式覆盖有效,重新签发及退出清理正常;另以模拟会话验证已就绪连接和解锁面板的 Codex 路由。此检查不读取个人 Codex 配置,不连接真实远端。
|
||||
|
||||
### DeepSeek Harness 桌面侧栏
|
||||
|
||||
Harness 桌面版的 Sidebar Browser 只接受 HTTP(S) 地址,不能打开 Codex 的 `file://` 启动页;其内置侧栏也未提供供 Agent 调用的打开工具。安装配套 [CrossDesk Sidebar 插件](integrations/deepseek-harness/README.md) 后,MCP 工具调用会自动打开当前 Harness 会话的浏览器侧栏,不需要手动点击链接。插件源代码及构建产物随 MCP 包分发。
|
||||
|
||||
完整安装与配置见 [DeepSeek Harness 用户安装与使用](#install-dsh)。插件只负责页面显示,不增加设备权限,也不会发起第二次远端连接。
|
||||
|
||||
Harness 的 `browser_launch_url` 为本机 `/launch/<随机标识>`,在 60 秒内仅可导航一次,再跳转到原有配对流程;原始 bootstrap 令牌不进入工具结果。启动地址本身是短期配对入口,不应发布、抓取或记录其内容。消费、过期、视图代际变化或服务关闭后均不可重用;过期时为已有 `view_id` 重新签发。现有 Host/Origin 校验、HttpOnly/SameSite Cookie、CSRF 和禁止 iframe 嵌入的策略保持有效。
|
||||
|
||||
插件只在 Harness 桌面版启用;Harness 网页版的 iframe 不支持此路径,不会为此开放任意跨域嵌入或公网转发。未安装插件时,Agent 应说明安装方式,或提供入口链接供用户选择“在侧栏打开”,不能声称已自动打开。侧栏导航成功与配对/视频就绪分别确认,Agent 仍须等待 `crossdesk_get_session`。
|
||||
|
||||
2026-11-12 在 macOS 的 DeepSeek Harness 0.2.2-rc.1 完成实测:安装配套插件后,`crossdesk_connect` 以仅查看模式连接已保存的 Mac mini,插件自动打开当前会话右侧浏览器并完成配对;页面显示实时画面、1920 × 1080、约 27–29 FPS。验证过程中未发送远端键鼠输入。另在工程外临时目录通过 23 项协议/配对/宿主桥接检查及 6 项客户端生命周期检查,覆盖 Codex 与 Harness 新旧协议、单次消费、过期、代际失效、会话隔离、多窗口领取和标签复用。
|
||||
|
||||
## 无面板配置与凭据
|
||||
|
||||
在用户自己的交互终端执行(不能在宿主 stdio 通道中交互输入密码):
|
||||
|
||||
```sh
|
||||
crossdesk-mcp setup
|
||||
```
|
||||
|
||||
配置默认存放于 `~/.config/crossdesk/mcp/`,可用 `--config-dir` 或 `CROSSDESK_MCP_CONFIG_DIR` 覆盖。`devices.json` 包含设备、面板/视频选项及凭据引用;密码保存到 macOS Keychain、Windows 凭据库或 Linux Secret Service。凭据库不可用就返回明确错误,没有明文文件降级。面板的临时连接仍可使用用户现场输入的密码。持久保存失败会保留已建立连接,并在面板显示错误。
|
||||
|
||||
也可在 `setup` 中指定外部密码环境变量名称,服务读取该变量作为凭据提供器。示例配置(没有密码值):
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"panel": "off",
|
||||
"server": "api.crossdesk.cn",
|
||||
"port": 9099,
|
||||
"devices": [{
|
||||
"alias": "work-pc",
|
||||
"device_id": "123456789",
|
||||
"server": "api.crossdesk.cn",
|
||||
"port": 9099,
|
||||
"authorization": "control",
|
||||
"credential": "environment",
|
||||
"environment_variable": "MY_CROSSDESK_PASSWORD"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
授权范围和可用凭据缺一不可。模型不能提供任意 `device_id` 绕过面板确认;模型侧 `crossdesk_connect` 仅接受已授权设备别名。不要把密码发给 Agent,也不要把包含密码的命令行写进宿主配置。
|
||||
|
||||
Apps 依赖宿主遵守 app-only 工具可见性和 `_meta` 的模型不可见约定;服务另检查每个视图的随机 UI 能力令牌和 generation。app-only 标记本身不是可信身份凭据。能够读取同用户进程/文件或执行任意本机代码的 Agent 不受此边界隔离。
|
||||
|
||||
## 模型工具
|
||||
|
||||
以下 31 个工具面向 Agent,定义见 [src/server.ts](src/server.ts)。远端能力可通过会话状态中的 `capabilities` 查询,窗口与元素操作的具体平台能力通过 `crossdesk_desktop({session_id, operation: "capabilities"})` 查询。
|
||||
|
||||
| 工具 | 作用 |
|
||||
| 任务 | 可以这样说 |
|
||||
| --- | --- |
|
||||
| `crossdesk_open` | 打开设备选择/连接面板,返回 `view_id` |
|
||||
| `crossdesk_open_browser` | 用户显式请求的浏览器入口/回退 |
|
||||
| `crossdesk_list_devices` | 本机设备簿和授权摘要,不提供云发现 |
|
||||
| `crossdesk_connect` | 通过别名连接已经授权且有凭据的设备 |
|
||||
| `crossdesk_get_session` | `session_id`、`view_id` 严格二选一;查询状态或等待连接、恢复、指定解锁请求,`wait_ms` 范围 0–25000 |
|
||||
| `crossdesk_list_sessions` | 列出活动会话及设备、显示器和控制状态,最多八个并行连接 |
|
||||
| `crossdesk_set_recovery` | 开关有界断线恢复,尝试次数可设为 1–5,默认 3;需要持久授权和可用凭据 |
|
||||
| `crossdesk_disconnect` | 断开并释放输入状态、子进程和设备锁 |
|
||||
| `crossdesk_screenshot` | 返回 PNG image content 和观测元数据 |
|
||||
| `crossdesk_wait` | 根据已有观测等待画面区域变化或稳定,满足条件后返回新截图 |
|
||||
| `crossdesk_action` | 结构化输入,可附带动作之后的截图 |
|
||||
| `crossdesk_release_inputs` | 释放当前会话持有的所有按键和鼠标按钮,并取消当前输入动作 |
|
||||
| `crossdesk_set_control` | 在已授权范围内启停 Agent 输入 |
|
||||
| `crossdesk_select_display` | 切换显示器,使旧观测失效 |
|
||||
| `crossdesk_capture_displays` | 顺序截取最多八个显示器,返回多屏图像及 `capture_id`,随后恢复原显示器 |
|
||||
| `crossdesk_drag_across` | 使用多屏截图坐标跨显示器拖拽 |
|
||||
| `crossdesk_video_settings` | 运行时调整预览帧率、分辨率、质量和码率 |
|
||||
| `crossdesk_desktop` | 查询平台能力、窗口清单或可访问性树,支持按进程和元素读取子树 |
|
||||
| `crossdesk_desktop_action` | 激活窗口、按下/聚焦元素、设置值、启动应用或打开 HTTP(S) 地址 |
|
||||
| `crossdesk_wait_ui` | 按文本和角色等待窗口或可访问性元素出现、消失 |
|
||||
| `crossdesk_audio` | 开关远端桌面音频接收,默认关闭 |
|
||||
| `crossdesk_audio_capture` | 采集 100–10000 毫秒远端音频,返回单声道 48 kHz WAV 音频内容块 |
|
||||
| `crossdesk_clipboard_get` | 读取远端文本剪贴板,需要已授权控制会话 |
|
||||
| `crossdesk_clipboard_set` | 写入或清空远端文本剪贴板 |
|
||||
| `crossdesk_files_list` | 分页浏览远端目录或文件系统根目录 |
|
||||
| `crossdesk_files_transfer` | 上传或下载文件、文件夹,返回可查询的传输任务 |
|
||||
| `crossdesk_files_status` | 查询传输进度、逐文件结果及完成/失败/取消状态 |
|
||||
| `crossdesk_files_cancel` | 取消传输并清理未完成的暂存文件,保留已完成文件 |
|
||||
| `crossdesk_system_status` | 查询远端系统锁定状态及安全桌面服务信息 |
|
||||
| `crossdesk_system_command` | 锁定远端系统,或通过 Windows 服务发送安全注意序列 `send_sas` |
|
||||
| `crossdesk_unlock` | 使用独立的系统锁屏凭据解锁,或在本地面板请求输入密码 |
|
||||
| 查看屏幕 | “看一下远程电脑现在显示什么。” |
|
||||
| 操作浏览器 | “在远程浏览器搜索一头牛,打开相关视频。” |
|
||||
| 查找文件 | “帮我看看远端下载文件夹里有哪些 PDF。” |
|
||||
| 下载文件 | “把远端桌面的报告下载到本机 Downloads 文件夹。” |
|
||||
| 切换显示器 | “切换到远程电脑的第二块显示器。” |
|
||||
| 调整画面 | “把远端预览分辨率降低一些,让画面更流畅。” |
|
||||
|
||||
## MCP Apps 面板专用接口
|
||||
具体操作取决于设备授权、远端系统权限和应用支持。实时画面用于监看;Agent 的操作速度还取决于它观察和决策所需的时间。
|
||||
|
||||
以下 11 个接口仅在 `--panel=apps` 且当前请求支持 MCP Apps 时暴露为 app-only 工具,由经过视图令牌验证的面板调用。浏览器和终端面板通过各自经过认证的本地通道使用会话功能。
|
||||
### 暂停、接管和结束任务
|
||||
|
||||
| 接口 | 作用 |
|
||||
| 你想做什么 | 面板中的操作 |
|
||||
| --- | --- |
|
||||
| `crossdesk_ui_ready` | 回报面板已初始化 |
|
||||
| `crossdesk_ui_list_devices` | 获取设备簿及默认服务器配置 |
|
||||
| `crossdesk_ui_get_session` | 查询当前面板绑定的会话 |
|
||||
| `crossdesk_ui_connect` | 提交用户选择的设备、连接凭据及授权选项 |
|
||||
| `crossdesk_video_open` | 获取当前视图的实时视频连接票据 |
|
||||
| `crossdesk_ui_preview` | 实时视频受限时获取当前视图的有界 PNG 预览,仅通过面板元数据返回 |
|
||||
| `crossdesk_viewer_control` | 人工暂停、恢复、接管或断开 |
|
||||
| `crossdesk_ui_input` | 人工接管期间发送面板键鼠输入 |
|
||||
| `crossdesk_ui_unlock` | 提交或取消锁屏密码输入,可选择保存凭据 |
|
||||
| `crossdesk_ui_recovery` | 设置断线恢复开关和尝试次数 |
|
||||
| `crossdesk_ui_audio` | 开关面板会话的音频接收 |
|
||||
| 暂停 Agent 操作 | 点击 **暂停 Agent**;需要继续时由你点击 **恢复 Agent** |
|
||||
| 自己操作远端 | 打开 **会话选项 → 人工接管**,结束后点击 **交还 Agent** |
|
||||
| 听远端声音 | 打开 **会话选项 → 播放声音**,按需调整音量 |
|
||||
| 放大画面 | 打开 **会话选项 → 放大画面** |
|
||||
| 结束远程会话 | 打开 **会话选项 → 断开连接**,或让 Agent 断开 |
|
||||
|
||||
## 远程操作
|
||||
人工接管需要本次连接已授权操作。**关闭侧栏不会断开远端连接**;结束使用时请选择“断开连接”。
|
||||
|
||||
### 键鼠输入
|
||||
## 更新与卸载
|
||||
|
||||
`crossdesk_action` 支持 `move`、`click`、`double_click`、`scroll`、`drag`、`key_press`、`hotkey`、`type_text`,以及可跨动作保持的 `key_down`、`key_up`、`mouse_down`、`mouse_up`。按键使用 Windows VK 对应的标准名称,覆盖 `A`–`Z`、`0`–`9`、`F1`–`F24`、数字键盘、`CTRL`、`ALT`、`SHIFT`、`META`、`ENTER`、`ESC`、方向键、菜单、锁定键和媒体键等;应用快捷键及实际效果仍需根据远端系统选择。
|
||||
先结束正在进行的任务,并断开远端连接,再更新或卸载。
|
||||
|
||||
`move`、`click`、`double_click`、`scroll`、`drag` 和 `key_press` 支持可选的 `modifiers: ["CTRL", "ALT", "SHIFT", "META"]`(选择需要的键,不能重复),用于组合点击、选择、拖拽和快捷键。修饰键仅在本次动作期间保持,结束、取消或人工暂停时释放。`drag` 可指定 `button: "left" | "right" | "middle"`,默认左键。标点键支持 `SEMICOLON`、`EQUAL`、`COMMA`、`MINUS`、`PERIOD`、`SLASH`、`BACKQUOTE`、`BRACKET_LEFT`、`BACKSLASH`、`BRACKET_RIGHT`、`QUOTE`;这些是 VK 键位名称,实际字符取决于远端键盘布局,输入文字应使用 `type_text`。例如:
|
||||
|
||||
```json
|
||||
{"session_id":"…","operation_id":"select-item-1","observation_id":"…","action":{"type":"click","x":120,"y":80,"modifiers":["SHIFT"]}}
|
||||
```
|
||||
|
||||
`key_down`、`key_up`、`mouse_down` 和 `mouse_up` 的按下状态可跨动作保持,例如按住 Ctrl 后移动或拖动鼠标。最后一次动作完成后,按住状态的有效期为十五秒,超时自动释放。`crossdesk_release_inputs` 可立即释放本会话的全部按键和按钮,并取消当前输入动作。人工暂停、断开及输入错误同样释放按住状态。
|
||||
|
||||
`key_press`、`hotkey` 支持 `duration_ms`,最长三十秒;`drag` 时长上限同样为三十秒。状态返回 `held_key_codes` 和 `held_mouse_flags`。文本输入前应释放持续按住的键鼠状态。
|
||||
|
||||
### 减少 Agent 操作往返
|
||||
|
||||
`crossdesk_action` 支持 `action.type: "sequence"`,在一次调用中依次执行 1–8 个固定动作,最后只返回一次截图。例如已聚焦远程浏览器时,可以将聚焦地址栏、输入网址、回车合并:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "current_session",
|
||||
"operation_id": "open_bilibili",
|
||||
"observation_id": "current_observation",
|
||||
"action": {
|
||||
"type": "sequence",
|
||||
"actions": [
|
||||
{ "type": "hotkey", "keys": ["META", "L"] },
|
||||
{ "type": "type_text", "text": "https://www.bilibili.com" },
|
||||
{ "type": "key_press", "key": "ENTER" }
|
||||
]
|
||||
},
|
||||
"wait": { "condition": "changed", "timeout_ms": 1500 }
|
||||
}
|
||||
```
|
||||
|
||||
该例适用于 macOS;其他系统按实际快捷键选择修饰键。只有无需中途观察判断的动作才能合并,所有坐标仍对应起始观测。序列禁止嵌套和跨动作保持按下状态,累计文本不超过 16 KiB,计划输入时长(含间隔)不超过 8 秒,执行总期限为 25 秒。`interval_ms` 为动作间最小间隔,默认 100 毫秒,可设为 0–500;它不代表应用完成加载。原生层在首次输入前验证整组动作,每步等待支持的输入回执;回执失败、超时、人工暂停、取消或几何变化都会停止余下动作。旧被控端没有回执时保留 `remote_execution: "unsupported"`,最终画面仍需检查。`sequence.finished_actions` 表示已结束输入阶段的动作数,错误时的 `stopped_at` 是从 0 开始的停止位置,不代表应用业务结果。
|
||||
|
||||
连接和状态结果的 `automation` 汇总本机序列支持、远端输入回执及桌面自动化能力。Agent 应复用当前会话和有效观测,支持桌面自动化时优先用 `crossdesk_desktop_action` 直接打开网址、应用或操作可访问性元素;未声明支持时直接使用截图输入路径。旧 CLI 不支持序列时在任何输入前返回 `NATIVE_SEQUENCE_UNSUPPORTED`,需更新本机 CLI。远端能力未声明时会给出兼容性提示,不自动断开连接或尝试不支持的操作。
|
||||
|
||||
动作结果的 `timing_ms` 包含原生输入阶段、MCP 到 CLI 的输入往返、等待及截图阶段和工具内总耗时,可用来区分工具执行与工具调用之外的宿主、模型或语音延迟。
|
||||
|
||||
### 截图与条件等待
|
||||
|
||||
截图默认最长边 1280,最大 2048;允许指定源帧像素区域。返回 `source_size`、`source_rect`、`image_size`、`generation`、`geometry_epoch`、`frame_id`、`frame_age_ms` 和 `observation_id`。动作坐标使用返回图像坐标,不使用浏览器 CSS 像素。观测保留最多 32 个,30 秒过期;切换显示器、连接代际或图像几何变化立即失效。截图使用不超过 2 秒的帧,PNG 不超过 2 MiB,必要时继续缩小输出并返回真实尺寸。
|
||||
|
||||
`timeout_ms` 限制等待可用帧的时间,PNG 编码独立执行;`0` 表示只使用当前已就绪的新鲜帧,无帧返回 `FRAME_TIMEOUT`,编码器繁忙返回 `BUSY`。截图按到达顺序处理,并优先于预览。视频超过 2 秒未更新时拒绝新的键鼠输入,恢复新鲜画面后可继续;已提交操作的去重查询仍可读取原结果。
|
||||
|
||||
键鼠动作的 `operation_id` 在当前原生会话中缓存最近 128 个操作,至少在未被容量淘汰的 5 分钟窗口内支持同参数查询;不同参数复用 ID 返回冲突,整组序列同样受此规则约束。`submitted` 表示输入已发送,远端原生输入处理状态见 `remote_execution`,应用界面的实际结果仍需后续观察确认。超时、断网、取消或发送失败可能返回 `outcome_unknown`/`side_effect_possible`,禁止自动重放。
|
||||
|
||||
`observe_after` 默认开启:在同一次调用内最多等待 1000 毫秒的画面变化,再返回最终截图及 `wait.matched` / `wait.wait_timed_out`。纯移动和单独按下/释放默认只取新帧,避免等待本来就不会变化的像素;仍可显式指定 `wait`。显式 `wait` 可选择 `changed`、`stable`,或指定 `condition: "ui"` 与 `text` / `role` 等待远端可访问性元素出现;最长等待 5000 毫秒。视觉等待可以提供同一会话的预先裁剪观测 `wait.observation_id`,避免视频、动画或时钟等无关区域干扰。新版 CLI 的视觉等待只接受动作结束帧之后的帧,旧 CLI 返回 `wait.frame_bound: false`。等待超时或截图错误不会覆盖已经提交的输入结果,也不会重放动作;变化或稳定不等于应用任务成功。`observe_after: false` 只返回输入结果,不能同时指定 `wait`。
|
||||
|
||||
更新后的被控端通过 `capabilities.input_ack` 声明输入处理回执。动作结果的 `remote_execution` 表示远端原生输入处理状态,应用界面的实际结果由后续观察确认。旧被控端返回 `unsupported`。回执等待有明确期限,超时动作保留结果不确定状态。
|
||||
|
||||
`crossdesk_wait` 使用已有截图的 `observation_id`,等待对应区域变化(`changed`)或持续稳定(`stable`)。可设置 `stable_ms` 和最长 25000 毫秒的 `timeout_ms`。满足条件后返回新截图,切换显示器、断开和取消会结束等待。
|
||||
|
||||
### 锁屏解锁与密码保存
|
||||
|
||||
Agent 识别到远端系统锁屏后,先聚焦画面中的系统密码框并重新截图,再调用 `crossdesk_unlock`,传入 `session_id`、`operation_id` 和截图的 `observation_id`。默认 `use_saved: true` 会读取该设备独立保存的锁屏凭据。缺少凭据时,浏览器或 Apps 面板通过悬浮弹窗显示隐藏输入框,终端观看器显示隐藏输入提示。弹窗可关闭,并通过左侧连接信息中的“输入密码”重新打开。提交结果显示为六秒后收起的悬浮提示,连接信息持续展示解锁状态和锁屏凭据配置状态。
|
||||
|
||||
用户可在解锁输入框勾选“保存密码并允许以后自动解锁”。连接表单的“保存与授权”和本机 `crossdesk-mcp setup` 也提供锁屏密码配置。持久保存要求设备已在本地设备簿中,系统凭据库使用独立账户保存访问密码和锁屏密码。设备列表返回 `unlock_credential_provider` 摘要,配置文件保存 `unlock_credential` 引用。环境变量方式使用 `unlock_credential: "environment"` 和 `unlock_environment_variable`,旧设备配置继续有效。
|
||||
|
||||
等待输入时,Agent 使用 `crossdesk_get_session`,提供 `session_id` 或 `view_id`、`unlock_request_id` 和 `wait_ms: 25000`。`wait_timed_out` 后可继续等待,等待取消仅结束当前查询。解锁请求有效期为五分钟,等待期间保留实时画面,常规 Agent 输入和显示器切换会返回 `UNLOCK_PENDING`。用户取消、人工暂停、连接结束或画面代际变化会终止请求。
|
||||
|
||||
提交前重新获取同一显示器代际的截图,原生端执行选择密码框内容、文本输入和回车。对支持 `capabilities.system` 的被控端,服务在发送密码前先查询锁定状态,已经解锁时返回 `unlock.state: "verified"` 和 `unlock.result.status: "already_unlocked"`,不再输入密码。提交后会进入 `verifying`,在八秒验证窗口内查询系统状态;确认解锁时返回 `verified`,无法确认时保留 `submitted` 和验证信息。旧被控端成功发送后返回 `submitted`。Agent 随后获取新截图,确认桌面状态后继续原任务。
|
||||
|
||||
用户在解锁弹窗选择保存密码时,支持系统状态的被控端仅在验证为已解锁后写入凭据库;未确认时返回 `persistence_error: "UNLOCK_NOT_CONFIRMED"`。旧被控端仍在成功发送后保存,密码正确性需通过画面确认。密码被拒绝时,使用新请求并设置 `use_saved: false` 让用户更正,结果不确定时先观察画面。相同 `operation_id` 与参数查询原请求结果。
|
||||
|
||||
锁屏密码通过经过认证的本地面板或终端 IPC 进入原生输入通道。模型侧工具参数和结果只包含请求状态,原生操作缓存仅保存解锁请求标识及结果。解锁遵守现有控制授权和人工暂停状态,依赖远端文本输入能力。`off` 模式可使用预先配置的锁屏凭据,缺少凭据时返回 `UNLOCK_CREDENTIAL_REQUIRED`。原生 CLI 通过 `unlock_protocol: "password/1"` 声明支持,更新源码后需重建 CLI 和 MCP 并重新启动服务。
|
||||
|
||||
### 远端剪贴板
|
||||
|
||||
`crossdesk_clipboard_get` 读取远端文本剪贴板,`crossdesk_clipboard_set` 设置文本,空字符串清空。读取上限为 128 KiB,单次工具写入上限为 16 KiB UTF-8。写入需要 `operation_id`,相同请求查询原结果。工具使用已授权控制会话,并遵守人工暂停及解锁等待状态。更新后的被控端通过 `capabilities.clipboard` 声明支持,写入成功由系统剪贴板更新回执确认。
|
||||
|
||||
### 文件浏览与传输
|
||||
|
||||
`crossdesk_files_list` 浏览远端目录,空路径选择远端主目录,`@roots` 返回文件系统根目录。每页最多 256 项,使用 `request_id` 和 `next_offset` 继续读取。超时返回可继续查询的请求标识。
|
||||
|
||||
`crossdesk_files_transfer` 支持上传和下载文件、文件夹,源路径为绝对路径,目标目录需要已存在。传输使用 `operation_id` 去重,通过 `crossdesk_files_status` 查询每个文件的字节进度和终态,通过 `crossdesk_files_cancel` 取消并清理未完成文件。每个连接同时处理一个传输任务,多文件按现有传输引擎排队。已完成任务保留四份历史记录,状态中的文件清单分页返回。目标同名文件按现有冲突命名规则另存。
|
||||
|
||||
目录浏览及新建传输需要已授权且未暂停的控制会话,并要求被控端声明 `capabilities.files`。下载请求绑定目标目录及传输标识,接收器接受已为该请求预留的文件。人工暂停后仍可查询或取消已有任务。
|
||||
|
||||
### 系统状态与安全桌面
|
||||
|
||||
`crossdesk_system_status` 查询系统锁定状态。Windows 同时报告服务可用性和交互桌面阶段。`crossdesk_system_command` 支持 `lock`,Windows 可通过已运行的服务执行 `send_sas`。命令需要控制授权和独立的 `operation_id`,相同请求查询原结果。系统接受命令后,通过状态及画面观察确认实际界面。
|
||||
|
||||
### 多会话与显示管理
|
||||
|
||||
同一 MCP 实例支持最多八个并行设备连接。`crossdesk_list_sessions` 返回各连接的设备、显示器及控制状态。面板按设备绑定会话,断开一个连接会释放该连接的资源。每个连接的诊断日志位于 `logs/<session_id>/transport.log`。
|
||||
|
||||
`crossdesk_capture_displays` 按顺序截取最多八个显示器并恢复原显示器,通过一次工具结果展示多屏画面。`capture_id` 有效期三十秒,供 `crossdesk_drag_across` 使用各屏截图坐标完成跨屏拖拽。拖拽保持按下状态跨越显示器切换,在完成、取消或异常时释放按钮。普通输入操作使用重新选择显示器后取得的新截图。
|
||||
|
||||
`crossdesk_video_settings` 在运行时调整预览帧率、分辨率、质量和码率,返回远端接受状态。设置受对端能力限制。多屏采集及跨屏拖拽期间,同一会话的其他输入请求返回忙碌状态。
|
||||
|
||||
### 有界断线恢复
|
||||
|
||||
`crossdesk_set_recovery` 和面板的“断线自动恢复”开关用于启用恢复。默认关闭,启用要求设备已保存连接凭据及持久授权。后台监测覆盖临时信令中断、连接丢失和原生进程异常,使用退避策略,在六十秒预算内最多执行配置次数的重连。每次重连重新核对设备身份和授权,人工暂停、关闭恢复开关和主动断开会停止恢复。
|
||||
|
||||
恢复期间返回 `reconnecting`,可以用 `crossdesk_get_session` 携带 `session_id` 和 `wait_ms` 等待。恢复成功后返回新的 `session_id`,既有监看面板跟随新连接。旧会话标识可用于查询替代连接,后续操作使用新会话及新截图。截图标识包含所属会话,避免不同原生进程的观测编号相互混淆。先前的输入、解锁和文件任务保持原有结果边界,恢复流程只重建连接并恢复观看配置。
|
||||
|
||||
### 面板人工接管
|
||||
|
||||
浏览器及 Apps 面板提供“人工接管”和“交还 Agent”。接管后面板可操作鼠标、滚轮、键盘快捷键和中文输入法,连接信息展示当前控制权。输入绑定面板、控制批次和画面尺寸,交接会释放按住状态并使旧批次失效。画面超过两秒未更新时停止发送新输入,窗口失焦或隐藏时释放按键。原生层保留十五秒的输入租约作为断线清理保障。
|
||||
|
||||
Agent 在人工接管期间仍可观察画面,键鼠、显示设置和其他要求控制权的操作等待用户在面板交还控制。连接需预先授予控制权限,并使用支持 `events/1` 的原生 CLI。
|
||||
|
||||
### 远端声音
|
||||
|
||||
`crossdesk_audio` 开关远端桌面音频接收,默认关闭。面板的“播放声音”按钮同时启用接收和本地播放,音量在本面板调节。播放使用已授权的视频 WebSocket 通道,PCM 队列与浏览器播放时延均设有上限,拥塞时丢弃过时音频。浏览器首次播放需要用户点击按钮。
|
||||
|
||||
`crossdesk_audio_capture` 采集接下来 100 至 10000 毫秒的音频,通过 MCP 音频内容块返回单声道 48 kHz WAV。采集期间临时开启接收,结束或取消后恢复之前的设置,未收到足够音频时返回 `AUDIO_UNAVAILABLE`。系统需允许被控端捕获桌面声音。自动恢复连接会恢复用户选择的音频接收设置。
|
||||
|
||||
### 窗口、应用与可访问性元素
|
||||
|
||||
`crossdesk_desktop` 查询平台能力、窗口清单及可访问性树。可按进程或先前快照中的元素继续读取子树,节点包含角色、名称、可读取值和系统原生桌面坐标。每份快照最多 128 个节点,响应限制为 48 KiB 左右,超出范围返回 `truncated`。元素引用绑定连接,有效期三十秒,最多保留八份快照。
|
||||
|
||||
`crossdesk_desktop_action` 支持激活窗口、按下或聚焦元素、设置文本值、启动应用以及打开 HTTP(S) 地址。元素操作使用快照和 `element_id`,执行后该快照失效。所有修改操作需要控制授权及 `operation_id`,相同请求查询已有结果。安全密码字段隐藏值并拒绝通过结构化接口修改,系统解锁使用本地密码流程。操作结果表示系统接口接受情况,随后使用画面或新树确认界面变化。
|
||||
|
||||
`crossdesk_wait_ui` 根据文本及角色等待元素出现或消失。读取范围不完整时无法确认消失,会在超时结果中返回 `search_incomplete`。
|
||||
|
||||
macOS 使用 AX,需要系统辅助功能权限。Windows 使用 UI Automation,窗口激活遵守系统前台窗口及权限约束。Linux 构建时若检测到 `atspi-2` 开发包,会启用 AT-SPI 元素访问,否则提供 X11 窗口查询和激活。Wayland 的元素访问依赖桌面和应用提供 AT-SPI。应用启动参数接受 macOS bundle ID 或绝对 `.app` 路径、Windows 绝对 `.exe` 路径、Linux 绝对可执行文件路径。Linux 接口的所有权和超时处理依据官方文档。[AT-SPI 接口文档](https://docs.gtk.org/atspi2/)
|
||||
|
||||
## 实时视频与按需截图
|
||||
|
||||
实时视频提供两条路径,默认 `browser`,两条路径均由原生 CLI 的本机 WebSocket 直接发送给浏览器。Node 只处理会话、授权票据和 MCP 工具,不读取或转发实时视频字节;不再创建视频管道,不经过 MCP stdio 或模型上下文,不逐帧转 PNG、不重新编码视频。
|
||||
|
||||
| `video.decode_path` | 画面路径 |
|
||||
| --- | --- |
|
||||
| `browser` | MiniRTC 输出原始 H.264 Annex B → 浏览器 WebCodecs 解码 → canvas |
|
||||
| `minirtc` | MiniRTC 解码 → 紧凑 NV12 原始帧 → 浏览器 VideoFrame / canvas;浏览器不再解码 H.264 |
|
||||
|
||||
`video.decoder` 默认 `auto`:优先使用可用硬件解码器,不支持时使用软件。`software` 禁用本程序的硬件选择与硬件回退。浏览器路径使用 WebCodecs 的 `prefer-hardware` / `prefer-software`;这些值是浏览器 API 的偏好,浏览器可以忽略,不能据此宣称已确认物理硬件或软件后端。原生路径由 MiniRTC 的解码器工厂选择,本机 macOS 使用 VideoToolbox 或 OpenH264;其他平台使用 MiniRTC 已支持的后端。状态中的 `native_video_decoder` 返回 MiniRTC 报告的后端名称(统计可用前为空),不代表浏览器后端。
|
||||
|
||||
在 `~/.config/crossdesk/mcp/devices.json` 的根对象中加入:
|
||||
|
||||
```json
|
||||
"video": {
|
||||
"decode_path": "minirtc",
|
||||
"decoder": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
也可在 Agent 宿主的 MCP 启动参数中配置,命令行优先于文件:
|
||||
**DSH:** 在插件页面安装新版平台包,或重新执行 `dsh plugin ... add` 安装命令,然后重启 Harness。卸载可在插件页面操作,也可运行:
|
||||
|
||||
```sh
|
||||
crossdesk-mcp serve --stdio --video-path browser --video-decoder auto
|
||||
crossdesk-mcp serve --stdio --video-path minirtc --video-decoder software
|
||||
dsh plugin --profile desktop remove @chunkdash/crossdesk-mcp
|
||||
```
|
||||
|
||||
修改后重启 MCP,再建立连接。选项适用于整个 MCP 进程,两条路径均将解码选项传给原生端;浏览器路径中的原生解码用于截图缓存。PNG 编码按截图请求触发,包括单屏截图、多屏采集及动作或等待后的截图,WebSocket 实时视频不触发 PNG 编码;Apps 的兼容预览按需触发 PNG 编码。这里配置的是控制端解码器,不更改远端编码器。原有配置文件无需新增字段即可继续使用。
|
||||
**Codex:** 在原来的 `crossdesk-mcp` 安装目录执行 `npm install "/absolute/path/新版安装包.tgz"`,然后重启 Codex。停用时将配置中的 `enabled` 改为 `false`;卸载时移除对应配置段,并在安装目录执行 `npm uninstall @chunkdash/crossdesk-mcp`。
|
||||
|
||||
Agent 模式初始请求高帧率档位,初始分辨率上限为 1920 × 1080。连接后可通过 `crossdesk_video_settings` 调整帧率、分辨率、质量和码率;工具接受的分辨率最大为 3840 × 2160,实际分辨率和帧率由远端能力、网络和本机解码性能决定。面板显示实际尺寸与呈现 FPS。浏览器路径需要 WebCodecs H.264 解码能力;MiniRTC 路径仅需浏览器支持从 NV12 创建 VideoFrame 并绘制。浏览器面板能力不足时明确报错;Apps 面板无法连接实时通道或不支持解码时切换到下述有界 PNG 预览。NV12 不压缩,1080p / 30 FPS 约需 93 MB/s 本机传输,4K / 30 FPS 约需 373 MB/s;它不增加远端到控制端的 H.264 网络码率。移除 Node 转发没有消除 NV12 自身的本机传输与浏览器像素处理开销;这不是 GPU 零拷贝。
|
||||
卸载不会清除保存在本机的设备配置和系统凭据。
|
||||
|
||||
视频 WebSocket 在原生 CLI 的独立 I/O 线程中运行,只监听随机 `127.0.0.1` 端口,与 Node 面板 HTTP 端口不同。浏览器面板通过 cookie/CSRF 授权获取视频票据,Apps 通过 app-only `crossdesk_video_open` 和视图令牌获取。票据 15 秒内一次性有效,绑定视图、会话和代际,通过 WebSocket 第一条消息提交,不写入 URL、日志或模型可见工具结果。WebSocket 提供画面、已启用的 PCM 音频、收帧 ACK 和关键帧恢复请求,不能执行远端输入操作。最多 8 个活动观看者。H.264 丢帧后等待关键帧恢复;NV12 每帧独立,每个观看者最多一帧未确认,原生发送队列只保留最新画面;H.264 最多 8 帧未确认并受字节阈值限制。10 秒未确认最早一帧则关闭该视频通道,不阻塞其他观看者或 MCP 控制。视图撤销、显示器/连接代际变化、断开和进程退出会关闭对应视频连接;暂停 Agent 控制仍继续播放。页面隐藏或卸载时释放视频订阅,恢复可见时重新订阅。
|
||||
<a id="install-troubleshooting"></a>
|
||||
|
||||
浏览器页面和 MCP Apps 资源的 CSP 允许原生服务使用的 `ws://127.0.0.1:*`(仅动态端口),HTTP 控制仍限制到 Node 面板的确切地址。原生服务另外校验确切 Host、Origin 和一次性票据。宿主仍可能限制 loopback、WebSocket、WebCodecs 或 iframe 权限。已观察到 Codex 内嵌宿主过滤 HTTP/WS 白名单,最终以 `connect-src 'self'` 拦截原生视频连接;添加同义 CSP 字段无法解决协议限制。播放器区分 CSP 拦截,并在 Apps 实时视频失败时通过已授权的 MCP 通道提供兼容预览。
|
||||
## 常见问题
|
||||
|
||||
兼容预览显示“预览”和实际 FPS,最高每秒 1 帧、最长边 960 像素、PNG 每帧不超过 256 KiB,不含声音。`crossdesk_ui_preview` 仅供面板使用,验证视图令牌、代际及绑定会话,每个视图最多一个进行中的请求,并在响应前重新检查会话是否改变。图像只放在工具结果 `_meta` 中,不进入模型可见内容,也不占用 Agent 的操作截图历史。前端直接解码 PNG 字节,无需新增网络白名单;隐藏或卸载面板时停止刷新,迟到响应不再绘制。需要流畅视频及声音时使用 `--panel=browser`;`--panel=apps` 仍禁止自动打开外部浏览器。
|
||||
| 遇到的问题 | 如何处理 |
|
||||
| --- | --- |
|
||||
| 找不到下载文件或下载按钮 | 登录 GitHub,确认所选运行的 Artifacts 中包含对应平台的 `crossdesk-mcp-...` 文件;产物失效时选择其他可用运行 |
|
||||
| DSH 提示找不到 `dsh` | 使用插件页面安装;macOS 也可使用上文的应用内完整命令 |
|
||||
| DSH 安装后没有 CrossDesk 工具 | 确认插件已启用并重启 Harness;在插件页面查看错误。若报告原生程序缺失,重新安装对应平台的安装包 |
|
||||
| Codex 没有 CrossDesk 工具 | 检查 Node 版本和配置中的两处完整路径,先运行 `--help`,再重启 Codex。安装了 Codex CLI 时也可用 `codex mcp list` 查看配置 |
|
||||
| 工具可用,但没有右侧画面 | 确认使用桌面版;Codex 保留上文的两项显示参数。让 Agent“重新打开当前 CrossDesk 连接的画面” |
|
||||
| 页面打开后无法连接设备 | 确认远端在线、设备 ID 和访问密码正确;使用自建服务器时核对面板的“高级设置” |
|
||||
| 已连接,但黑屏或无法操作 | 检查远端 CrossDesk 的屏幕录制、辅助功能等系统权限;只有查看权限时,重新连接并授权操作 |
|
||||
| 能连接,但某项功能提示不支持 | 检查远端客户端版本,按提示更新兼容版本;窗口和应用操作还取决于远端系统及应用支持 |
|
||||
| 无法保存密码 | 检查本机系统凭据库是否可用;也可以取消“记住密码”,每次在面板手动输入后连接 |
|
||||
| 画面卡顿 | 让 Agent 降低预览分辨率或画质,并检查两端网络。画面帧率与 Agent 的决策速度是不同的指标 |
|
||||
| 没有声音 | 在面板点击“播放声音”,并确认远端允许录制桌面声音 |
|
||||
| 远端锁屏了 | 按面板的解锁提示输入系统锁屏密码;它与 CrossDesk 访问密码分别填写 |
|
||||
| 提示连接或页面入口过期 | 让 Agent 重新打开当前连接的画面,无需重新建立远端连接 |
|
||||
| 提示 `DEVICE_BUSY` | 先断开其他 Agent 或 MCP 进程对同一设备的连接 |
|
||||
| 提示 `serverName ... already in use` | 检查是否重复安装或配置了 CrossDesk 服务,只启用一个实例 |
|
||||
| 提示 `NATIVE_PACKAGE_MISSING` 或 `NATIVE_PROTOCOL_MISMATCH` | 重新安装表中对应平台的完整包;源码运行请检查 [原生程序配置](DEVELOPMENT.md#从源码运行) |
|
||||
| 为什么没有自动重连 | 在 **会话详情** 中按需启用“断线自动恢复”;此功能需要已保存的自动连接授权和可用凭据,主动断开不会触发恢复 |
|
||||
|
||||
2026-11-11 在本机 Codex MCP Apps 中以仅查看模式连接 macOS 远端,确认 WebSocket 被 CSP 拦截后自动显示兼容预览,画布帧计数持续递增,实测约 0.5–1 FPS。该画面在 256 KiB 上限内自适应缩小为 540 × 303;此结果验证了内嵌预览,不代表宿主已支持本机 WebSocket 实时视频。
|
||||
如果仍有问题,可 [提交反馈](https://github.com/kunkundi/crossdesk/issues),说明本机和远端系统、Codex / DSH 版本、安装包来源、报错内容及复现步骤。不要附上设备密码或带配对凭据的页面地址。
|
||||
|
||||
## 打包
|
||||
<a id="从源码运行"></a>
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
npm run pack:npm -- --out-dir /absolute/path/artifacts
|
||||
```
|
||||
## 更多文档
|
||||
|
||||
脚本生成可发布 tarball,包含面板、运行代码、源代码、许可和固定版本的五个平台可选依赖。CI 的 `MCP package` 工作流仅构建并上传 `mcp-package`,不发布 npm。原生包继续由 CLI 工作流构建,`doctor` 必须同时声明自动化协议和 `automation_video_protocols: ["h264-annexb/1", "nv12/1"]` 和 `automation_video_decoders: ["auto", "software"]`,以及 `automation_video_transport: "loopback-websocket/1"`、`automation_terminal_protocol: "ansi/1"`;先发布对应 0.2.0 平台包,再发布 MCP tarball。尚未进行真实跨平台发布/远端验收时,不应把构建成功等同于各宿主和设备上的联调完成。
|
||||
|
||||
从发布 tarball 重建 TypeScript/面板时,将包中的 `build-package.json` 复制为 `package.json`,运行 `npm ci && npm run build`。原生源码和依赖来源随平台包提供,详见 [源码来源](../cli/docs/source-provenance.md)。
|
||||
|
||||
## 本地审阅验证(2026-11-03)
|
||||
|
||||
动作参数校验和输入步骤生成集中在 `apps/cli/automation_protocol.*`,主循环负责调度、取消和权限检查。截图按色度网格对齐后仅转换所需区域,PNG 在容量受限的缓冲区中单次压缩;原生帧仅在没有其他读取者时复用内存。Node 按行累计响应字节,在完整响应到达时解码,避免反复扫描和复制大截图。
|
||||
|
||||
本机 macOS arm64 完成 TypeScript/面板构建、原生 CLI 构建、现有 wire/keyboard-state 测试,以及工程外的 MCP、会话和原生动作回归检查。ASan/UBSan 检查覆盖动作调度和输入清理;140 组随机图像覆盖完整帧、奇偶裁剪边界、缩放及容量回退,优化前后 PNG 字节一致。
|
||||
|
||||
使用 1920 × 1080 合成 NV12、最长边 1280、每项 6 次的 `-O2` 本地基准:完整画面编码约 22.5 → 11.5 ms,321 × 241 裁剪约 2.1 → 0.9 ms。数字仅衡量图像转换与编码,不包含网络、解码、远端执行或宿主渲染;未连接真实远端或完成 Windows/Linux 验收。额外测试代码、脚本、数据和产物均存放于工程外系统临时目录。
|
||||
- [源码构建与开发配置](DEVELOPMENT.md#从源码运行)
|
||||
- [工具接口、显示机制与验证范围](DEVELOPMENT.md)
|
||||
- [DSH 安装简明指南](integrations/deepseek-harness/README.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
- insert:
|
||||
- id: crossdesk
|
||||
name: '@chunkdash/crossdesk-mcp'
|
||||
@@ -1,44 +1,42 @@
|
||||
# CrossDesk Sidebar for DeepSeek Harness
|
||||
# 在 DeepSeek Harness 中使用 CrossDesk
|
||||
|
||||
在 DeepSeek Harness **桌面版**中,自动将 CrossDesk MCP 返回的页面打开到发起调用的会话侧栏。适配 Harness 0.2.2-rc.1 的 Sidebar Browser 和 Connection 接口。浏览器版的 iframe 不在支持范围内。
|
||||
安装 CrossDesk 插件后,你可以让 Agent 操作远程电脑,并在当前对话的右侧浏览器监看画面。
|
||||
|
||||
## 安装
|
||||
|
||||
先按 [DSH 完整安装指南](../../README.md#install-dsh) 安装 CrossDesk MCP 和兼容的原生 CLI,并注册名为 `crossdesk` 的 MCP 服务。**本插件不会自动注册 MCP 服务**;服务配置和插件安装缺一不可。
|
||||
1. 按 [下载安装包](../../README.md#install-package) 选择与你的本机系统和芯片匹配的平台包,解压 ZIP 并取出 `.tgz` 文件。
|
||||
2. 在 Harness 桌面版的插件页面安装该文件,或执行以下命令。将示例中的路径和版本号替换为实际文件:
|
||||
|
||||
使用 MCP 本地安装包时,插件及构建产物已包含在包内:
|
||||
```sh
|
||||
dsh plugin --profile desktop add "/absolute/path/chunkdash-crossdesk-mcp-0.1.0.tgz"
|
||||
```
|
||||
|
||||
3. 重启 Harness,确认 CrossDesk 插件已启用。
|
||||
|
||||
平台包已经包含所需的原生程序,并自动提供 MCP 工具和侧栏画面。安装时需要联网下载依赖。终端找不到 `dsh` 时,可直接使用插件页面,或按 [完整安装指南](../../README.md#install-dsh) 使用应用自带的命令。
|
||||
|
||||
## 连接并使用
|
||||
|
||||
在对话中发送:
|
||||
|
||||
> 打开 CrossDesk,让我选择远程电脑。
|
||||
|
||||
在右侧面板输入远端设备 ID 和访问密码。需要 Agent 操作远端时选择 **允许 Agent 操作**,然后点击 **授权并连接**。看到远端桌面后,即可下达任务,例如:
|
||||
|
||||
> 在远程电脑的浏览器中搜索天气。
|
||||
|
||||
密码只在面板中输入。需要停止操作时点击 **暂停 Agent**;结束使用时,在 **会话选项** 中选择 **断开连接**。关闭侧栏本身不会断开远端连接。
|
||||
|
||||
保存设备、自动连接、人工接管和声音的用法见 [用户指南](../../README.md#用户连接流程)。如果没有工具或画面,请查看 [常见问题](../../README.md#install-troubleshooting)。
|
||||
|
||||
## 更新与卸载
|
||||
|
||||
通过插件页面安装新版平台包后重启 Harness。卸载也可在插件页面完成,或执行:
|
||||
|
||||
```sh
|
||||
dsh plugin --profile desktop add /absolute/path/crossdesk-mcp/node_modules/@chunkdash/crossdesk-mcp/integrations/deepseek-harness
|
||||
dsh plugin --profile desktop remove @chunkdash/crossdesk-mcp
|
||||
```
|
||||
|
||||
从源码运行时,先构建 MCP,再安装工程中的插件目录:
|
||||
卸载后,保存在本机的设备配置和系统凭据仍会保留。
|
||||
|
||||
```sh
|
||||
npm --prefix /absolute/path/crossdesk/apps/mcp run build
|
||||
dsh plugin --profile desktop add /absolute/path/crossdesk/apps/mcp/integrations/deepseek-harness
|
||||
```
|
||||
|
||||
也可在 Harness 的插件页面通过本地路径安装。重启 Harness 和 CrossDesk MCP 后,确认 `crossdesk-sidebar` 组件启用并运行。MCP 的 YAML 配置、源码运行所需的 `--native-binary`、首次连接及排查步骤均见上述完整指南。该包只增加侧栏适配,不连接设备、不修改设备授权。
|
||||
|
||||
不要强制使用 `--panel=apps` 或 `--browser-launch=system`;保留默认 `auto`,或显式使用 `--panel=browser --browser-launch=host`。若服务名称不同,在 Harness profile 的 `cordis.patch.yml` 中添加:
|
||||
|
||||
```yaml
|
||||
- id: crossdesk-sidebar
|
||||
config:
|
||||
serverName: your_crossdesk_server_name
|
||||
```
|
||||
|
||||
## 行为与边界
|
||||
|
||||
- 宿主插件只接收指定 MCP 名称的 `crossdesk_open`、`crossdesk_connect`、`crossdesk_open_browser`、`crossdesk_unlock` 成功结果,验证页面类型及 `127.0.0.1` 启动地址。
|
||||
- 通过 Harness 已认证的 Connection 接口交接页面请求;客户端约每 750 毫秒检查当前会话,只在发起调用的会话中打开侧栏,不读取聊天记录或重新执行工具。
|
||||
- 页面请求在内存中最多保存 60 秒,具有多窗口领取租约及确认机制。相同视图重新签发时替换插件打开的旧标签;切换会话、卸载、过期后不重放历史启动地址。
|
||||
- 地址仅用于导航,不抓取启动页、复制配对令牌或代理视频。CrossDesk 继续使用本机 WebSocket / WebCodecs 播放和现有设备授权;插件不声明页面已经配对或视频已就绪,Agent 仍通过 `crossdesk_get_session` 确认。
|
||||
- Harness 网页版未启用此桥接,因为其 iframe 不能嵌入受保护的 CrossDesk 页面。不会放宽 CrossDesk 的 Origin、CSP、Cookie 或 `X-Frame-Options` 策略。
|
||||
|
||||
该插件与 MCP 一起构建,产物位于本目录的 `dist/`。卸载使用 `dsh plugin --profile desktop remove @chunkdash/dsh-crossdesk-sidebar`。
|
||||
|
||||
2026-11-12 已在 macOS / DeepSeek Harness 0.2.2-rc.1 验证自动打开、配对及 1920 × 1080、约 27–29 FPS 的实时播放;远端为已授权的 Mac mini,仅查看模式。
|
||||
|
||||
宿主接口参考:[Sidebar Browser](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-sidebar-browser)、[Right Sidebar](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-sidebar-right)、[Connection](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/connection)。
|
||||
源码构建和集成机制见 [开发参考](../../DEVELOPMENT.md#harness-development)。
|
||||
@@ -0,0 +1,39 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { isAbsolute } from "node:path";
|
||||
import z from "@deepseek-ai/schemastery";
|
||||
import { Config as McpConfig, apply as applyMcp } from "@deepseek-ai/dsh-mcp-client";
|
||||
import { nativeBinary } from "../../../dist/native.js";
|
||||
import { apply as applySidebar } from "./host.js";
|
||||
|
||||
export const name = "crossdesk";
|
||||
export const inject = ["tools", "connection"];
|
||||
export const Config = z.object({
|
||||
serverName: z.string().pattern(/^[a-zA-Z0-9_-]{1,24}$/).default("crossdesk"),
|
||||
nativeBinary: z.string(),
|
||||
configDir: z.string(),
|
||||
});
|
||||
|
||||
// Built into dist/harness.js beside the ordinary MCP entry. DSH supplies its
|
||||
// own MCP client and Node/Electron executable; no PATH or profile edits needed.
|
||||
export async function apply(ctx, config) {
|
||||
for (const field of ["nativeBinary", "configDir"])
|
||||
if (config[field] !== undefined && !isAbsolute(config[field]))
|
||||
throw new Error(`CrossDesk ${field} must be an absolute path`);
|
||||
let binary;
|
||||
try { binary = await nativeBinary(config.nativeBinary); }
|
||||
catch (error) {
|
||||
throw new Error("CrossDesk native CLI is unavailable or incompatible. Install the platform bundle with the native CLI included, or set nativeBinary for a source build.", { cause: error });
|
||||
}
|
||||
const args = [fileURLToPath(new URL("./main.js", import.meta.url)), "serve", "--stdio",
|
||||
"--panel=browser", "--browser-launch=host", "--native-binary", binary];
|
||||
if (config.configDir) args.push("--config-dir", config.configDir);
|
||||
// Reuse the host's client implementation so namespace reservation, tool
|
||||
// permissions, images, reconnect and awaited disposal have a single owner.
|
||||
// Duplicate legacy MCP entries are rejected before another server is spawned.
|
||||
await applyMcp(ctx, McpConfig({
|
||||
serverName: config.serverName, transport: "stdio", command: process.execPath, args,
|
||||
env: process.versions.electron ? { ELECTRON_RUN_AS_NODE: "1" } : {},
|
||||
toolCallTimeoutMs: 90000, failOnStartupError: true,
|
||||
}));
|
||||
applySidebar(ctx, { serverName: config.serverName });
|
||||
}
|
||||
Generated
+13
@@ -25,7 +25,20 @@
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"dsh": ">=0.2.2-rc.1 <0.3.0",
|
||||
"node": ">=22"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-mcp-client": ">=0.2.2-rc.1 <0.3.0",
|
||||
"@deepseek-ai/schemastery": "~3.19.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@deepseek-ai/dsh-mcp-client": {
|
||||
"optional": true
|
||||
},
|
||||
"@deepseek-ai/schemastery": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
|
||||
+24
-1
@@ -12,7 +12,22 @@
|
||||
"directory": "apps/mcp"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
"node": ">=22",
|
||||
"dsh": ">=0.2.2-rc.1 <0.3.0"
|
||||
},
|
||||
"main": "dist/harness.js",
|
||||
"exports": {
|
||||
".": "./dist/harness.js",
|
||||
"./client": "./dist/harness-client.js",
|
||||
"./package.json": "./package.json",
|
||||
"./dist/*": "./dist/*"
|
||||
},
|
||||
"dsh": {
|
||||
"bundle": { "patch": "cordis.patch.yml" },
|
||||
"client": {
|
||||
"inject": ["@deepseek-ai/dsh-client-ui-session", "@deepseek-ai/dsh-client-ui-sidebar-right", "@deepseek-ai/dsh-client-ui-sidebar-browser"],
|
||||
"platform": "web"
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
"crossdesk-mcp": "dist/main.js"
|
||||
@@ -30,6 +45,14 @@
|
||||
"@napi-rs/keyring": "2.1.2",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-mcp-client": ">=0.2.2-rc.1 <0.3.0",
|
||||
"@deepseek-ai/schemastery": "~3.19.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@deepseek-ai/dsh-mcp-client": { "optional": true },
|
||||
"@deepseek-ai/schemastery": { "optional": true }
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.19.15",
|
||||
"esbuild": "0.28.2",
|
||||
|
||||
@@ -3,9 +3,16 @@ import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "../integrations/deepseek-harness");
|
||||
const dist = resolve(root, "../../dist");
|
||||
await build({ entryPoints: [resolve(root, "src/plugin.js")], outfile: resolve(dist, "harness.js"),
|
||||
bundle: true, format: "esm", platform: "node", target: "node22",
|
||||
packages: "external" });
|
||||
await build({ entryPoints: [resolve(root, "src/host.js")], outfile: resolve(root, "dist/host.js"),
|
||||
bundle: true, format: "esm", platform: "node", target: "node22" });
|
||||
await build({ entryPoints: [resolve(root, "src/client.js")], outfile: resolve(root, "dist/client.js"),
|
||||
bundle: true, format: "cjs", platform: "browser", target: "es2022",
|
||||
banner: { js: 'window.__ModuleLoader__.load({ id: "@chunkdash/dsh-crossdesk-sidebar", factory: (require) => { var module = { exports: {} }; var exports = module.exports;' },
|
||||
footer: { js: "return module.exports; } });" } });
|
||||
for (const [id, outfile] of [
|
||||
["@chunkdash/crossdesk-mcp", resolve(dist, "harness-client.js")],
|
||||
["@chunkdash/dsh-crossdesk-sidebar", resolve(root, "dist/client.js")],
|
||||
]) await build({ entryPoints: [resolve(root, "src/client.js")], outfile,
|
||||
bundle: true, format: "cjs", platform: "browser", target: "es2022",
|
||||
banner: { js: `window.__ModuleLoader__.load({ id: ${JSON.stringify(id)}, factory: (require) => { var module = { exports: {} }; var exports = module.exports;` },
|
||||
footer: { js: "return module.exports; } });" } });
|
||||
@@ -16,19 +16,20 @@ import { parseArgs } from "node:util";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repository = resolve(root, "../..");
|
||||
const { values } = parseArgs({ options: { "out-dir": { type: "string" } } });
|
||||
const { values } = parseArgs({ options: {
|
||||
"out-dir": { type: "string" },
|
||||
"native-package-dir": { type: "string" },
|
||||
} });
|
||||
const output = resolve(values["out-dir"] ?? join(repository, "build/npm-mcp"));
|
||||
const project = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
||||
const cli = JSON.parse(
|
||||
await readFile(join(root, "../cli/package.json"), "utf8"),
|
||||
);
|
||||
const cli = JSON.parse(await readFile(join(root, "../cli/package.json"), "utf8"));
|
||||
const platforms = JSON.parse(
|
||||
await readFile(join(root, "../cli/npm/platforms.json"), "utf8"),
|
||||
);
|
||||
const native = await import(new URL("../dist/native.js", import.meta.url));
|
||||
if (native.NATIVE_VERSION !== cli.version)
|
||||
throw new Error("MCP and CLI native versions must match.");
|
||||
for (const file of ["main.js", "viewer-browser.html", "viewer-apps.html"])
|
||||
for (const file of ["main.js", "viewer-browser.html", "viewer-apps.html", "harness.js", "harness-client.js"])
|
||||
await access(join(root, "dist", file));
|
||||
const staging = await mkdtemp(join(tmpdir(), "crossdesk-mcp-pack-"));
|
||||
try {
|
||||
@@ -38,10 +39,12 @@ try {
|
||||
"ui",
|
||||
"scripts",
|
||||
"integrations",
|
||||
"cordis.patch.yml",
|
||||
"tsconfig.json",
|
||||
"tsconfig.ui.json",
|
||||
"package-lock.json",
|
||||
"README.md",
|
||||
"DEVELOPMENT.md",
|
||||
])
|
||||
await cp(join(root, entry), join(staging, entry), { recursive: true });
|
||||
await cp(join(repository, "LICENSE"), join(staging, "LICENSE"));
|
||||
@@ -59,11 +62,13 @@ try {
|
||||
"scripts/build-ui.mjs",
|
||||
"scripts/build-harness.mjs",
|
||||
"integrations",
|
||||
"cordis.patch.yml",
|
||||
"tsconfig.json",
|
||||
"tsconfig.ui.json",
|
||||
"build-package.json",
|
||||
"package-lock.json",
|
||||
"README.md",
|
||||
"DEVELOPMENT.md",
|
||||
"LICENSE",
|
||||
],
|
||||
optionalDependencies: Object.fromEntries(
|
||||
@@ -73,6 +78,31 @@ try {
|
||||
};
|
||||
delete manifest.scripts;
|
||||
delete manifest.devDependencies;
|
||||
// Platform bundles are installable before the native version reaches npm.
|
||||
// Preserve the native package's source, notices and build provenance too.
|
||||
if (values["native-package-dir"]) {
|
||||
const directory = resolve(values["native-package-dir"]);
|
||||
const bundled = JSON.parse(await readFile(join(directory, "package.json"), "utf8"));
|
||||
const platform = Object.values(platforms).find(p => p.packageName === bundled.name);
|
||||
if (!platform || bundled.version !== native.NATIVE_VERSION ||
|
||||
JSON.stringify(bundled.os) !== JSON.stringify([platform.os]) ||
|
||||
JSON.stringify(bundled.cpu) !== JSON.stringify([platform.cpu]) ||
|
||||
(platform.libc && JSON.stringify(bundled.libc) !== JSON.stringify([platform.libc])))
|
||||
throw new Error("Expected a compatible CrossDesk native platform package.");
|
||||
for (const entry of ["bin", "source", "LICENSE", "build-info.json"])
|
||||
await access(join(directory, entry));
|
||||
const binary = join(directory, "bin", platform.os === "win32" ? "crossdesk-cli.exe" : "crossdesk-cli");
|
||||
await access(binary);
|
||||
if (platform.os === process.platform && platform.cpu === process.arch)
|
||||
await native.nativeBinary(binary);
|
||||
const target = `${platform.os === "win32" ? "windows" : platform.os}-${platform.cpu}`;
|
||||
await cp(directory, join(staging, "native", target), { recursive: true });
|
||||
manifest.files.push("native");
|
||||
manifest.os = [platform.os];
|
||||
manifest.cpu = [platform.cpu];
|
||||
if (platform.libc) manifest.libc = [platform.libc];
|
||||
delete manifest.optionalDependencies;
|
||||
}
|
||||
await writeFile(
|
||||
join(staging, "package.json"),
|
||||
JSON.stringify(manifest, null, 2) + "\n",
|
||||
|
||||
@@ -9,7 +9,7 @@ export function browserHost(info: { name?: string } | undefined): BrowserHost |
|
||||
|
||||
export function browserNextStep(host?: BrowserHost): string {
|
||||
const open = host === "deepseek-harness"
|
||||
? "The CrossDesk Sidebar plugin for DeepSeek Harness Desktop automatically opens browser_launch_url in this session's sidebar. Immediately check crossdesk_get_session with view_id and wait_ms=25000; do not inspect or reinstall plugins before checking panel_status. A waiting panel is still starting. Only if panel_status remains not_confirmed, diagnose the companion plugin described in the CrossDesk MCP README; a Markdown link can also be opened with the host's Open in sidebar action. Do not use file preview or claim the sidebar is open before the panel reports ready."
|
||||
? "The CrossDesk MCP plugin for DeepSeek Harness Desktop automatically opens browser_launch_url in this session's sidebar. Immediately check crossdesk_get_session with view_id and wait_ms=25000; do not inspect or reinstall plugins before checking panel_status. A waiting panel is still starting. Only if panel_status remains not_confirmed, diagnose the CrossDesk plugin described in the CrossDesk MCP README; a Markdown link can also be opened with the host's Open in sidebar action. Do not use file preview or claim the sidebar is open before the panel reports ready."
|
||||
: "Open browser_launch_url in the host's built-in browser/sidebar using its browser-open tool (Codex: open_in_codex, target.type=browser, target.url=browser_launch_url, placement=right). If no host browser is available, show pairing_command for the user to run locally.";
|
||||
return `${open} Do not fetch, read, quote, or upload the launcher contents: they contain a one-time pairing credential. Then call crossdesk_get_session with view_id and wait_ms=25000. If the launcher expired, call crossdesk_open_browser with this view_id to issue another; do not reconnect the remote device.`;
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import { spawn, execFile, type ChildProcess } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { existsSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { Writable } from "node:stream";
|
||||
import { Failure, type Data, type VideoOptions } from "./common.js";
|
||||
|
||||
@@ -49,9 +51,12 @@ export async function nativeBinary(override?: string): Promise<string> {
|
||||
)
|
||||
throw new Failure("GLIBC_REQUIRED");
|
||||
try {
|
||||
const manifest = require.resolve(
|
||||
const bundled = fileURLToPath(new URL(`../native/${target}/package.json`, import.meta.url));
|
||||
const manifest = existsSync(bundled) ? bundled : require.resolve(
|
||||
`@chunkdash/crossdesk-cli-${target}/package.json`,
|
||||
);
|
||||
if (require(manifest).name !== `@chunkdash/crossdesk-cli-${target}`)
|
||||
throw new Failure("NATIVE_VERSION_MISMATCH");
|
||||
if (require(manifest).version !== NATIVE_VERSION)
|
||||
throw new Failure("NATIVE_VERSION_MISMATCH");
|
||||
binary = join(
|
||||
|
||||
@@ -84,7 +84,7 @@ export function createServer(
|
||||
{
|
||||
capabilities: { tools: {}, resources: {} },
|
||||
instructions:
|
||||
"Remote computer control. Open a panel for the user to select and authorize a device. Never request passwords in chat. In Codex, the default panel is the host built-in browser/sidebar for every user; no personal agent instructions are required. DeepSeek Harness Desktop also defaults to a host sidebar, opened automatically by the CrossDesk Sidebar companion plugin. Its HTTP launcher replaces the file launcher rejected by Harness. If that plugin is unavailable, explain how to install it or provide a Markdown link for Open in sidebar. Other clients keep their local environment and system browser defaults. Explicit panel and launch settings override these defaults. Whenever browser_launch_url is returned, immediately open that local launcher using the host browser-open tool (or let the DeepSeek Harness companion plugin handle it) before waiting for authorization or continuing the remote task, even if the connection is already ready. Do not wait for the user to ask to open the sidebar. Do not read or quote its contents; it contains a one-time pairing credential. In Codex, use open_in_codex with target.type=browser, target.url=browser_launch_url, placement=right. If the host has no browser-open tool, explain the limitation and show pairing_command for the user to run locally; do not claim the sidebar is open. Reissue expired launchers with crossdesk_open_browser using the existing view_id, without reconnecting. When a remote task is pending, immediately follow crossdesk_open with crossdesk_get_session(view_id, wait_ms: 25000) in the same turn. Repeat bounded waits on wait_timed_out until ready or the user cancels; do not ask the user to type a connection confirmation or click Continue. On ready, continue the requested task within current authorization. When state is reconnecting, continue bounded waits for the explicitly enabled recovery policy. Use the returned session_id after recovery and inspect a fresh screenshot. On closed/failed, stop waiting and do not automatically reconnect. Reuse the active session and its advertised automation capabilities. Prefer desktop_action for supported URL/app/accessibility operations. Use a recent screenshot observation for coordinate input; reuse action results instead of taking duplicate screenshots. Group fixed inputs that need no intermediate visual decision into a short action sequence, and wait for a relevant visual region or UI condition in that same call. Coordinates refer to the observation image. If the remote system is locked, focus its visible password field, take a fresh screenshot and call crossdesk_unlock. A saved system password is used automatically; otherwise the local panel asks the user. Wait with crossdesk_get_session(unlock_request_id, wait_ms: 25000), then verify a fresh screenshot before continuing the original task. Submitted actions are not remote execution acknowledgements. Never replay actions after uncertain outcomes. Human pause can only be resumed in the user panel.",
|
||||
"Remote computer control. Open a panel for the user to select and authorize a device. Never request passwords in chat. In Codex, the default panel is the host built-in browser/sidebar for every user; no personal agent instructions are required. DeepSeek Harness Desktop also defaults to a host sidebar, opened automatically by the CrossDesk MCP plugin’s built-in sidebar integration. Its HTTP launcher replaces the file launcher rejected by Harness. If that plugin is unavailable, explain how to install it or provide a Markdown link for Open in sidebar. Other clients keep their local environment and system browser defaults. Explicit panel and launch settings override these defaults. Whenever browser_launch_url is returned, immediately open that local launcher using the host browser-open tool (or let the DeepSeek Harness CrossDesk plugin handle it) before waiting for authorization or continuing the remote task, even if the connection is already ready. Do not wait for the user to ask to open the sidebar. Do not read or quote its contents; it contains a one-time pairing credential. In Codex, use open_in_codex with target.type=browser, target.url=browser_launch_url, placement=right. If the host has no browser-open tool, explain the limitation and show pairing_command for the user to run locally; do not claim the sidebar is open. Reissue expired launchers with crossdesk_open_browser using the existing view_id, without reconnecting. When a remote task is pending, immediately follow crossdesk_open with crossdesk_get_session(view_id, wait_ms: 25000) in the same turn. Repeat bounded waits on wait_timed_out until ready or the user cancels; do not ask the user to type a connection confirmation or click Continue. On ready, continue the requested task within current authorization. When state is reconnecting, continue bounded waits for the explicitly enabled recovery policy. Use the returned session_id after recovery and inspect a fresh screenshot. On closed/failed, stop waiting and do not automatically reconnect. Reuse the active session and its advertised automation capabilities. Prefer desktop_action for supported URL/app/accessibility operations. Use a recent screenshot observation for coordinate input; reuse action results instead of taking duplicate screenshots. Group fixed inputs that need no intermediate visual decision into a short action sequence, and wait for a relevant visual region or UI condition in that same call. Coordinates refer to the observation image. If the remote system is locked, focus its visible password field, take a fresh screenshot and call crossdesk_unlock. A saved system password is used automatically; otherwise the local panel asks the user. Wait with crossdesk_get_session(unlock_request_id, wait_ms: 25000), then verify a fresh screenshot before continuing the original task. Submitted actions are not remote execution acknowledgements. Never replay actions after uncertain outcomes. Human pause can only be resumed in the user panel.",
|
||||
},
|
||||
);
|
||||
type Definition = {
|
||||
@@ -176,7 +176,7 @@ export function createServer(
|
||||
add(
|
||||
"crossdesk_open",
|
||||
z.strictObject({ device_alias: alias.optional() }),
|
||||
"Open CrossDesk so the user can choose a device, enter its password, and authorize this session. In Codex, defaults to browser_launch_url: immediately open it in the host sidebar without reading the launcher file or asking the user to request the sidebar. DeepSeek Harness Desktop returns an HTTP browser_launch_url handled automatically by the CrossDesk Sidebar companion plugin; without that plugin, provide its installation instructions or a Markdown link for Open in sidebar. Other clients default to the local environment and system browser; explicit settings take precedence. Headless terminal environments return terminal_command, which you must show to the user to run in a separate terminal. Never run the terminal viewer in a captured tool-output channel. Returns immediately. To continue a pending remote task, call crossdesk_get_session with the returned view_id and wait_ms: 25000; no chat confirmation is needed.",
|
||||
"Open CrossDesk so the user can choose a device, enter its password, and authorize this session. In Codex, defaults to browser_launch_url: immediately open it in the host sidebar without reading the launcher file or asking the user to request the sidebar. DeepSeek Harness Desktop returns an HTTP browser_launch_url handled automatically by the CrossDesk MCP plugin’s built-in sidebar integration; without that plugin, provide its installation instructions or a Markdown link for Open in sidebar. Other clients default to the local environment and system browser; explicit settings take precedence. Headless terminal environments return terminal_command, which you must show to the user to run in a separate terminal. Never run the terminal viewer in a captured tool-output channel. Returns immediately. To continue a pending remote task, call crossdesk_get_session with the returned view_id and wait_ms: 25000; no chat confirmation is needed.",
|
||||
async (p, ctx) => {
|
||||
if (p.device_alias) await sessions.store.device(p.device_alias);
|
||||
const v = sessions.create(
|
||||
@@ -193,7 +193,7 @@ export function createServer(
|
||||
add(
|
||||
"crossdesk_open_browser",
|
||||
z.strictObject({ view_id: id.optional() }),
|
||||
"Open or reissue the local browser panel using the configured launch mode. In host mode (the Codex and DeepSeek Harness default), open browser_launch_url in the host sidebar without fetching the launcher contents. The CrossDesk Sidebar companion plugin opens it automatically in DeepSeek Harness Desktop. Use the existing view_id to renew an expired launcher without creating another remote connection. Strict apps/off policies deny browser fallback.",
|
||||
"Open or reissue the local browser panel using the configured launch mode. In host mode (the Codex and DeepSeek Harness default), open browser_launch_url in the host sidebar without fetching the launcher contents. The CrossDesk MCP plugin’s built-in sidebar integration opens it automatically in DeepSeek Harness Desktop. Use the existing view_id to renew an expired launcher without creating another remote connection. Strict apps/off policies deny browser fallback.",
|
||||
async (p, ctx) => {
|
||||
if (!["auto", "browser"].includes(sessions.policy))
|
||||
throw new Failure("PANEL_POLICY_DENIED");
|
||||
@@ -225,7 +225,7 @@ export function createServer(
|
||||
mode: mode.default("observe"),
|
||||
display_index: z.int().min(0).max(63).default(0),
|
||||
}),
|
||||
"Connect only a saved, previously authorized device with available credentials. In Codex, defaults to browser_launch_url: immediately open it in the host sidebar even when the remote connection is already ready. DeepSeek Harness Desktop returns an HTTP launcher opened by the CrossDesk Sidebar companion plugin. Do not fetch or read the launcher contents. AUTH_REQUIRED means the user must authorize through the panel or setup.",
|
||||
"Connect only a saved, previously authorized device with available credentials. In Codex, defaults to browser_launch_url: immediately open it in the host sidebar even when the remote connection is already ready. DeepSeek Harness Desktop returns an HTTP launcher opened by the CrossDesk MCP plugin’s built-in sidebar integration. Do not fetch or read the launcher contents. AUTH_REQUIRED means the user must authorize through the panel or setup.",
|
||||
async (p, ctx) => {
|
||||
const selected = sessions.choose(supported(ctx), !!host(ctx));
|
||||
// Reserve panel capacity before starting a connection. Once connected,
|
||||
|
||||
Reference in new issue
Block a user