🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

安卓端 Harness Chiyang001/DSHAN

DeepSeek Harness
Chiyang00116 天前有提交已提供中文文档扫描:含敏感能力

在手机上运行 DeepSeek Harness,让 AI 调用 Android 设备工具。

38.7/100
综合分
14
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/18 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装;安装清单取自 runtime/dsh-peer/package.json。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Chiyang001/DSHAN
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · other
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 16 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/10/22) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装;安装清单取自 runtime/dsh-peer/package.json

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-peer(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 09:41:05

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 116 处,展示前 107 条)
  • 执行外部命令来自配置/变量
    runtime/android-bootstrap.test.cjs:27
    const result = spawnSync(process.execPath, ['-e', code], {
  • 删除本地文件递归
    runtime/android-bootstrap.test.cjs:31
    } finally { rmSync(temp, { recursive: true, force: true }) }
  • 写入本地文件
    runtime/android-bootstrap.test.cjs:38
    mkdirSync(require('node:path').dirname(native), { recursive: true })
  • 写入本地文件
    runtime/android-bootstrap.test.cjs:39
    writeFileSync(join(require('node:path').dirname(native), 'package.json'), '{"type":"module"}')
  • 写入本地文件
    runtime/android-bootstrap.test.cjs:40
    writeFileSync(native, `function canOpenNativePath() { return false }
  • 写入本地文件
    runtime/android-bootstrap.test.cjs:45
    writeFileSync(bridge, `exports.canOpenNativePath = () => true
  • 写入本地文件
    runtime/android-bootstrap.test.cjs:48
    writeFileSync(shim, `import { createRequire } from 'node:module'
  • 写入本地文件
    runtime/android-bootstrap.test.cjs:58
    writeFileSync(join(temp, 'mobile-bootstrap.cjs'), `const Module = require('node:module')
  • 执行外部命令来自配置/变量
    runtime/android-bootstrap.test.cjs:76
    const result = spawnSync(process.execPath, ['-e', code], { encoding: 'utf8', cwd: temp })
  • 删除本地文件递归
    runtime/android-bootstrap.test.cjs:78
    } finally { rmSync(temp, { recursive: true, force: true }) }
  • 执行外部命令来自配置/变量
    runtime/android-bootstrap.test.cjs:102
    const result = spawnSync(process.execPath, ['-e', code], { encoding: 'utf8', env: { ...process.env, DSH_HOME: join(temp, 'home') } })
  • 删除本地文件递归
    runtime/android-bootstrap.test.cjs:104
    } finally { rmSync(temp, { recursive: true, force: true }) }
  • 执行外部命令来自配置/变量
    runtime/android-bootstrap.test.cjs:129
    const result = spawnSync(process.execPath, ['-e', code], { encoding: 'utf8', env: { ...process.env, DSH_HOME: join(temp, 'home') } })
  • 删除本地文件递归
    runtime/android-bootstrap.test.cjs:131
    } finally { rmSync(temp, { recursive: true, force: true }) }
  • 执行外部命令来自配置/变量
    runtime/android-bootstrap.test.cjs:163
    const result = spawnSync(process.execPath, ['-e', code], { encoding: 'utf8', env: { ...process.env, DSH_HOME: join(temp, 'home') } })
  • 删除本地文件递归
    runtime/android-bootstrap.test.cjs:165
    } finally { rmSync(temp, { recursive: true, force: true }) }
  • 读取密钥环境变量
    runtime/android-directory-picker-backend.js:9
    const token = () => process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 发起网络请求
    runtime/android-directory-picker-backend.js:73
    response = await fetch(BRIDGE, {
  • 读取本地文件
    runtime/android-directory-picker-backend.test.js:10
    const patches = parse(readFileSync(new URL('./android.patch.template.yml', import.meta.url), 'utf8'))
  • 读取密钥环境变量
    runtime/android-directory-picker-backend.test.js:25
    const originalToken = process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-directory-picker-backend.test.js:27
    process.env.DSH_ANDROID_BRIDGE_TOKEN = 'test-token'
  • 读取密钥环境变量
    runtime/android-directory-picker-backend.test.js:56
    if (originalToken === undefined) delete process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-directory-picker-backend.test.js:57
    else process.env.DSH_ANDROID_BRIDGE_TOKEN = originalToken
  • 读取密钥环境变量
    runtime/android-display.test.js:5
    const savedFetch = globalThis.fetch, savedToken = process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-display.test.js:6
    process.env.DSH_ANDROID_BRIDGE_TOKEN = 'display-test'
  • 读取密钥环境变量
    runtime/android-display.test.js:58
    if (savedToken === undefined) delete process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-display.test.js:59
    else process.env.DSH_ANDROID_BRIDGE_TOKEN = savedToken
  • 写入本地文件
    runtime/android-flock.cjs:30
    await fs.copyFile(source, temp, constants.COPYFILE_EXCL)
  • 写入本地文件
    runtime/android-flock.cjs:31
    await fs.chmod(temp, 0o600)
  • 删除本地文件
    runtime/android-flock.cjs:35
    } finally { await fs.rm(temp, { force: true }) }
  • 读取本地文件
    runtime/android-flock.cjs:42
    const appRoot = await realpath(dataRoot ?? dirname(process.env.HOME))
  • 读取本地文件
    runtime/android-flock.cjs:45
    if (await realpath(ancestor).catch(() => undefined) === appRoot) return ancestor
  • 写入本地文件
    runtime/android-host.js:321
    if (process.env.DSH_ANDROID_URL_FILE) writeFileSync(process.env.DSH_ANDROID_URL_FILE, url)
  • 发起网络请求
    runtime/android-native-command.cjs:6
    const request = http.request({
  • 读取密钥环境变量
    runtime/android-native-command.cjs:14
    'x-bridge-token': process.env.DSH_ANDROID_BRIDGE_TOKEN,
  • 读取密钥环境变量
    runtime/android-native-command.test.cjs:6
    process.env.DSH_ANDROID_BRIDGE_TOKEN = 'test-only'
  • 读取密钥环境变量
    runtime/android-native-command.test.cjs:41
    delete process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 发起网络请求
    runtime/android-network.cjs:8
    const request = http.request({
  • 读取密钥环境变量
    runtime/android-network.cjs:11
    'x-bridge-token': process.env.DSH_ANDROID_BRIDGE_TOKEN },
  • 读取本地文件
    runtime/android-preset-default.test.cjs:14
    assert.equal(YAML.parse(fs.readFileSync(patch, 'utf8'))[0].config.default, 'android')
  • 写入本地文件
    runtime/android-preset-default.test.cjs:16
    fs.writeFileSync(patch, user)
  • 读取本地文件
    runtime/android-preset-default.test.cjs:18
    assert.equal(fs.readFileSync(patch, 'utf8'), user)
  • 写入本地文件
    runtime/android-preset-default.test.cjs:20
    fs.writeFileSync(patch, legacy)
  • 读取本地文件
    runtime/android-preset-default.test.cjs:22
    const migrated = YAML.parse(fs.readFileSync(patch, 'utf8'))
  • 读取本地文件
    runtime/android-preset-default.test.cjs:26
    const once = fs.readFileSync(patch, 'utf8')
  • 读取本地文件
    runtime/android-preset-default.test.cjs:28
    assert.equal(fs.readFileSync(patch, 'utf8'), once)
  • 读取本地文件
    runtime/android-preset-default.test.cjs:29
    const overlay = YAML.parse(fs.readFileSync(path.join(__dirname, 'android.patch.template.yml'), 'utf8'))
  • 删除本地文件
    runtime/android-preset-default.test.cjs:32
    fs.rmSync(home, { recursive: true, force: true })
  • 读取密钥环境变量
    runtime/android-progress.test.js:5
    process.env.DSH_ANDROID_BRIDGE_TOKEN = 'test-only'
  • 读取密钥环境变量
    runtime/android-progress.test.js:23
    } finally { globalThis.fetch=original; delete process.env.DSH_ANDROID_BRIDGE_TOKEN }
  • 发起网络请求
    runtime/android-settings/index.js:63
    const response = await fetch('http://127.0.0.1:3981/rpc', {
  • 读取密钥环境变量
    runtime/android-settings/index.js:65
    headers: { 'content-type': 'application/json', 'x-bridge-token': process.env.DSH_ANDROID_BRIDGE_TOKEN },
  • 读取本地文件
    runtime/android-settings/kernel-updater.js:24
    const core = JSON.parse(await readFile(join(this.runtime, 'node_modules/@deepseek-ai/dsh/package.json'), 'utf8'))
  • 读取本地文件
    runtime/android-settings/kernel-updater.js:25
    try { this.state = { ...this.state, ...JSON.parse(await readFile(this.file, 'utf8')) } } catch (error) { if (error.code !== 'ENOENT') this.state.logs.
  • 写入本地文件
    runtime/android-settings/kernel-updater.js:49
    await mkdir(this.home, { recursive: true })
  • 写入本地文件
    runtime/android-settings/kernel-updater.js:50
    await writeFile(`${this.file}.tmp`, snapshot)
  • 写入本地文件
    runtime/android-settings/kernel-updater.js:51
    await rename(`${this.file}.tmp`, this.file)
  • 删除本地文件递归
    runtime/android-settings/kernel-updater.js:107
    await rm(stage, { recursive: true, force: true })
  • 读取密钥环境变量
    runtime/android-settings/kernel-updater.js:116
    method: 'POST', headers: { 'content-type': 'application/json', 'x-bridge-token': process.env.DSH_ANDROID_BRIDGE_TOKEN },
  • 删除本地文件递归
    runtime/android-settings/kernel-updater.test.js:29
    } finally { await updater.flush(); await rm(home, { recursive: true, force: true }) }
  • 读取本地文件
    runtime/android-settings/kernel-updater.test.js:53
    const persisted = JSON.parse(await readFile(join(home, 'kernel-update.json'), 'utf8'))
  • 删除本地文件递归
    runtime/android-settings/kernel-updater.test.js:59
    } finally { await updater.flush(); await rm(home, { recursive: true, force: true }) }
  • 写入本地文件
    runtime/android-settings/kernel-worker.js:38
    await mkdir(workerData.stage, { recursive: true })
  • 写入本地文件
    runtime/android-settings/kernel-worker.js:39
    await writeFile(join(workerData.stage, 'package.json'), JSON.stringify({
  • 读取本地文件
    runtime/android-settings/kernel-worker.js:67
    const core = JSON.parse(await readFile(join(workerData.stage, 'node_modules/@deepseek-ai/dsh/package.json'), 'utf8'))
  • 读取本地文件
    runtime/android-settings/kernel-worker.js:69
    const sharp = JSON.parse(await readFile(join(workerData.stage, 'node_modules/sharp/package.json'), 'utf8'))
  • 读取本地文件
    runtime/android-settings/kernel-worker.js:70
    const wasm = JSON.parse(await readFile(join(workerData.stage, 'node_modules/@img/sharp-wasm32/package.json'), 'utf8'))
  • 读取本地文件
    runtime/android-settings/kernel-worker.js:73
    await stat(join(workerData.stage, 'node_modules/@deepseek-ai', name, 'package.json'))
  • 读取本地文件
    runtime/android-settings/kernel-worker.js:75
    await stat(join(workerData.stage, 'node_modules/@deepseek-ai/dsh/lib/bin.js'))
  • 读取本地文件
    runtime/android-settings/kernel-worker.js:76
    await stat(join(workerData.stage, 'node_modules/npm/node_modules/@npmcli/arborist/lib/index.js'))
  • 写入本地文件
    runtime/android-settings/kernel-worker.js:77
    await writeFile(join(workerData.stage, '.kernel-ready.json'), JSON.stringify({ version: core.version, verifiedAt: new Date().toISOString() }))
  • 读取本地文件
    runtime/android-storage.test.cjs:15
    if (sync.existsSync(b)) return -[...require('node:util').getSystemErrorMap()].find(([,value]) => value[0] === 'EEXIST')[0]
  • 写入本地文件
    runtime/android-storage.test.cjs:16
    sync.renameSync(a, b); return 0
  • 写入本地文件
    runtime/android-storage.test.cjs:20
    await fs.writeFile(source, '图片数据')
  • 读取本地文件
    runtime/android-storage.test.cjs:22
    assert.equal(await fs.readFile(source, 'utf8'), '图片数据')
  • 读取本地文件
    runtime/android-storage.test.cjs:23
    assert.equal(await fs.readFile(dest, 'utf8'), '图片数据')
  • 写入本地文件
    runtime/android-storage.test.cjs:24
    await fs.writeFile(source, 'new')
  • 读取本地文件
    runtime/android-storage.test.cjs:26
    assert.equal(await fs.readFile(dest, 'utf8'), '图片数据')
  • 读取本地文件
    runtime/android-storage.test.cjs:27
    assert.deepEqual((await fs.readdir(dir)).sort(), ['dest', 'source'])
  • 删除本地文件
    runtime/android-storage.test.cjs:34
    await fs.rm(dir, { recursive: true, force: true })
  • 读取密钥环境变量
    runtime/android-tools-loop.test.js:10
    const originalToken = process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-tools-loop.test.js:11
    process.env.DSH_ANDROID_BRIDGE_TOKEN = 'test-only'
  • 写入本地文件
    runtime/android-tools-loop.test.js:15
    await writeFile(viewPath, 'mock-image-bytes')
  • 删除本地文件
    runtime/android-tools-loop.test.js:82
    await unlink(viewPath)
  • 删除本地文件
    runtime/android-tools-loop.test.js:83
    await rmdir(folder)
  • 读取密钥环境变量
    runtime/android-tools-loop.test.js:85
    if (originalToken === undefined) delete process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-tools-loop.test.js:86
    else process.env.DSH_ANDROID_BRIDGE_TOKEN = originalToken
  • 读取密钥环境变量
    runtime/android-tools.js:22
    const token = process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 发起网络请求
    runtime/android-tools.js:28
    const response = await fetch(endpoint, {
  • 读取本地文件
    runtime/android-tools.js:183
    image = await attachments.saveImage({ data: await readFile(result.viewPath), mediaType: 'image/png', name: 'phone-screen.png' })
  • 读取密钥环境变量
    runtime/android-transport.test.js:7
    const originalToken = process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-transport.test.js:8
    process.env.DSH_ANDROID_BRIDGE_TOKEN = 'transport-test'
  • 读取密钥环境变量
    runtime/android-transport.test.js:36
    if (originalToken === undefined) delete process.env.DSH_ANDROID_BRIDGE_TOKEN
  • 读取密钥环境变量
    runtime/android-transport.test.js:37
    else process.env.DSH_ANDROID_BRIDGE_TOKEN = originalToken
  • 发起网络请求
    runtime/dsh-peer/client.js:99
    const response = await fetch('/peer/settings')
  • 发起网络请求
    runtime/dsh-peer/client.js:127
    const response = await fetch('/peer/settings', {
  • 发起网络请求
    runtime/dsh-peer/index.js:192
    register('peer_tools', 'List tools explicitly shared by the connected Harness, including parameter schemas.', {}, async (_args, exec) => JSON.stringif
  • 发起网络请求
    runtime/dsh-peer/index.js:198
    const result = await requirePeer(exec).request('call', { name: args.name, arguments: argumentsValue }, exec.signal);
  • 读取密钥环境变量
    runtime/dsh-peer/network.js:3
    export async function androidHostAddresses(signal, token = process.env.DSH_ANDROID_BRIDGE_TOKEN, fetcher = fetch) {
  • 发起网络请求
    runtime/dsh-peer/protocol.js:65
    if (!address) return await fetch(url, options);
  • 发起网络请求
    runtime/dsh-peer/protocol.js:69
    const request = (target.protocol === 'https:' ? https : http).request(target, {
  • 发起网络请求
    runtime/dsh-peer/settings-client.js:99
    const response = await fetch('/peer/settings')
  • 发起网络请求
    runtime/dsh-peer/settings-client.js:127
    const response = await fetch('/peer/settings', {
  • 读取本地文件
    runtime/mobile-bootstrap.cjs:13
    const before = fs.existsSync(patch) ? fs.readFileSync(patch, 'utf8') : '[]\n'
  • 写入本地文件
    runtime/mobile-bootstrap.cjs:22
    fs.mkdirSync(path.dirname(patch), { recursive: true })
  • 写入本地文件
    runtime/mobile-bootstrap.cjs:24
    fs.writeFileSync(temporary, String(document), { mode: 0o600 })
  • 写入本地文件
    runtime/mobile-bootstrap.cjs:25
    fs.renameSync(temporary, patch)
扫描于 2026/11/3(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 运行AI调用工具

README

由 DeepSeek 最新模型翻译生成
DSHAN Deepseek Harness for Android Native 在手机上运行 DeepSeek Harness,让 AI 调用 Android 设备工具。 快速开始 ·内核更新 ·从源码构建 ·验证记录 项目简介 DSHAN 是一个单 APK 原型:在 Android 应用进程内嵌 Node.js,启动官方 @deepseek-ai/dsh 的 Web profile,并把手机设备操作能力注册为 Harness 工具。APK 不需要电脑在旁运行。 | 能力 | 说明 | | 手机端对话 | 在应用内使用官方 Harness Web UI,配置自己的 DeepSeek API Key | | 设备操作 | 通过 Shizuku、无线 ADB 配对或 Root,向 Harness 提供 android_ 手机工具 | | 屏幕与文件 | 截图、读取 UI 层级、文件读写、文件名搜索与文字搜索 | | 内核更新 | 在设置中下载并切换 DSH 内核,保留会话与设置 | | 手机与电脑互联 | 内置 DSH Peer 插件,经配对后双向调用已共享的 Harness 工具,支持配置跨网络 P2P/TURN | 手机与电脑互联的安装、配对说明见 DSH Peer 使用说明。
展开完整文档(README 剩余 1 节)
DSHAN
Deepseek Harness for Android Native
在手机上运行 DeepSeek Harness,让 AI 调用 Android 设备工具。

快速开始 ·内核更新 ·从源码构建 ·验证记录

项目简介

DSHAN 是一个单 APK 原型:在 Android 应用进程内嵌 Node.js,启动官方 @deepseek-ai/dsh 的 Web profile,并把手机设备操作能力注册为 Harness 工具。APK 不需要电脑在旁运行。

| 能力 | 说明 |
| 手机端对话 | 在应用内使用官方 Harness Web UI,配置自己的 DeepSeek API Key |
| 设备操作 | 通过 Shizuku、无线 ADB 配对或 Root,向 Harness 提供 android_ 手机工具 |
| 屏幕与文件 | 截图、读取 UI 层级、文件读写、文件名搜索与文字搜索 |
| 内核更新 | 在设置中下载并切换 DSH 内核,保留会话与设置 |
| 手机与电脑互联 | 内置 DSH Peer 插件,经配对后双向调用已共享的 Harness 工具,支持配置跨网络 P2P/TURN |

手机与电脑互联的安装、配对说明见 DSH Peer 使用说明。手机在 设置 → 连接(Android 设置右侧)扫描电脑插件生成的二维码,或输入完整配对码,选择会话和允许共享的工具后连接。独立公网服务在 Server 文件夹,使用 Python/Flask 和服务器上的 FRP,支持加密中继。普通用户电脑只运行连接插件,不运行 FRP 或配对服务器;STUN/TURN 可用于提高直连可用性。

[!NOTE]
本仓库仅提供源码,不提供 Release 或预构建 APK。自行构建支持 Android 11 及以上 / arm64-v8a。首次运行会解包官方 npm 包,请预留数百 MB 可用存储。

快速开始

1. 安装与授权

1. 按从源码构建生成并安装 APK,打开应用,按引导向导完成初次配置。
2. 在手机安装并启动 Shizuku。首次启动按 Shizuku 应用内指引开启系统无线调试并启动服务。
3. 回到本应用,在引导向导中点击 使用 Shizuku 一键授权,接受授权弹窗。
4. 在向导中开启 允许 Harness 控制这台手机。任意 shell 命令另有独立开关,默认关闭。

已 Root 的手机可在向导的“连接设备权限”页点击 检测并授权 Root,在 Root 管理器中允许本应用,检测取得 UID 0 后开启 Root 模式。也可以在 设置 → Android 设置 中检测、开启或关闭。Root 模式默认关闭,开启状态会保存;应用重启时重新检查授权。未 Root 或授权被拒绝时不能开启。

也可选择 通过通知栏输入 ADB 配对码,无需 Shizuku。后续入口在 设置 → Android 设置 → 无线 ADB 配对。允许通知后,在开发者选项开启 无线调试 → 使用配对码配对设备,保持系统配对窗口打开,下拉通知栏,在 DSHAN 配对通知中输入六位配对码并发送。配对完成后自动发现并连接本机 ADB;连接后仍需开启 允许 Harness 控制这台手机。

如果无法自动发现配对端口,在同一个通知输入框输入 配对端口 六位配对码(用空格分隔)。如果配对成功但连接失败,通知会改为接收无线调试主页的 连接端口;该端口与配对窗口的端口不同。配对通知可取消,等待五分钟后自动结束。无线调试关闭或应用进程重启后,可从上述入口重新连接。

首次配置向导包含欢迎、连接设备、操作权限、后台悬浮窗和完成配置五步。虚拟副屏只在 设置 → Android 设置 中按需配置,不在向导中提示。

Root 模式下,主屏点击、滑动、按键、截图和设备 shell 命令使用 su,无需连接 Shizuku;仍需开启设备控制,任意 shell 命令还需单独授权。Root 失效时不会自动切换权限方式,请重新授权或关闭 Root 模式。副屏枚举及副屏截图仍使用现有 Shizuku 服务。此模式不会为手机执行 Root,也不会扩大 Node 文件工具的访问权限。

2. 配置与对话

完成向导后进入 DeepSeek Harness 页面,在官方 Web UI 的模型设置中配置自己的 DeepSeek API Key,即可开始对话。例如:

先检查 android_status,再截屏,帮我打开设置中的蓝牙页面。

后续可在 设置 → Android 设置 中重新授权 Shizuku、管理存储访问权限或修改 Harness 控制开关。主界面按系统返回键会将应用退到后台。

[!IMPORTANT]
模型执行操作时,应用进程必须保持运行。首版尚无后台常驻服务,部分厂商系统会在离开应用后回收进程。关闭 Shizuku 服务或在系统应用设置中强制停止本应用,即可中断设备操作。

内核更新

在设置面板顶部点击 检查更新(位于“打开配置文件”左侧),通过 https://registry.npmmirror.com 查询 @deepseek-ai/dsh 的 latest 版本。

1. 点击 下载并安装更新,查看依赖解析、镜像请求、下载校验、解包安装和最终验证的实时日志,以及包处理数量与耗时。
2. 等待安装完成;关闭窗口不会取消安装。
3. 点击 重启并启用,切换到新内核。重启会中断当前任务。

| 更新行为 | 说明 |
| 数据保留 | 更新安装在独立目录,不改动会话与设置 |
| 失败回退 | 旧内核保留;新内核启动后 120 秒内未加载界面则自动回退 |
| 更新范围 | 仅更新 DSH 内核及依赖,不升级 APK 内置 Node.js |
| 兼容性检查 | Node 不兼容时显示原因并禁止更新 |

从源码构建

环境要求

| 依赖 | 要求 |
| Java | JDK 17+ |
| Android | SDK API 34、NDK / CMake |
| Gradle | 8.13 |
| JavaScript | Node / npm |

构建脚本使用官方 npm 版本 @deepseek-ai/dsh@0.1.7-rc.2,以及 nodejs-mobile 的 Node 24 Android libnode.so。首次构建先运行下载脚本,它会校验运行时的 SHA-256。

构建命令

在项目根目录运行:

npx --yes node@24 --version
./scripts/fetch-node.ps1
npm ci --prefix runtime --omit=dev --ignore-scripts --os=android --cpu=arm64 --libc=bionic
./scripts/prepare-runtime.ps1
./gradlew assembleDebug

生成的 APK 位于 app/build/outputs/apk/debug/。

适配与当前边界

这是开发原型。2026-10-03 已在 OPPO PKQ110 / Android 16 / Shizuku 上验证更新内核 0.2.0-rc.2 的对话、重启恢复会话、原生锁、文件读写与搜索、截图和 UI 层级读取。具体覆盖范围见 Android 验证记录。

| 项目 | 当前说明 |
| 桌面插件 | 官方 Harness 的桌面终端、PowerShell、浏览器启动等插件不是针对 Android 开发的,部分能力在手机上可能不可用;android_ 工具用于手机操作 |
| 文件打开插件 | 使用官方 dsh-native-command 的 ESM / CommonJS 插件可通过 Android 打开文件、查询打开方式和指定应用;按扩展名识别 MIME,文本编辑接口保持文本方式打开 |
| 文字输入 | android_text 支持中文、Emoji、换行和混合文本;Unicode 输入通过内置输入法提交,需要已授权 Shizuku / Root,并在输入后恢复原键盘 |
| 截图查看 | android_screenshot 返回图片的本地路径;请让模型接着调用官方 read_image 工具查看 |
| 文件搜索 | glob / grep 已使用无需 Android 二进制的实现;android_find_files / android_search_text 保留为字面量搜索工具 |
| 搜索上限 | 搜索有数量、时间和文件大小上限,会明确报告跳过和截断 |

运行时适配与本地通信

- 官方 Harness 依赖的 node-addon-require-builtin 没有 Android 原生包;本项目在固定的 Node 24 移动构建上通过 --expose-internals 适配,升级 Node / Harness 时需要重新验证。
- APK 提供真正的 Android flock,并将会话 / 新文件发布适配为 renameat2(RENAME_NOREPLACE),保留原子提交与并发保护。
- 图片使用 sharp 的 WASM 后端;附件通过临时副本原子发布,并将目录同步限制在应用私有数据目录。已有下载内核也会在启动时获得适配。
- Web UI 仅绑定 127.0.0.1:3080;原生桥接仅绑定 127.0.0.1:3981,并以每次进程启动随机产生的 token 鉴权。

插件兼容范围

DSHAN 保留 DSH 的 Cordis 插件接口,通过启动适配层接入 Android;内置和已下载内核均使用 APK 中的适配文件。

| 插件依赖 | Android 行为与限制 |
| openNativePath / openNativeAssociatedPath | 使用 Android 文件查看器打开图片、PDF 和其他文件;需要手机安装相应查看应用 |
| openNativeTextFile | 使用文本查看器打开配置或源码;共享的是只读副本,外部编辑不会写回原文件 |
| nativeFileApplications / openNativeFileApplication | 查询系统注册的处理应用,指定应用时重新验证文件关联;图标暂返回 null |
| 文件访问 | 仅允许应用私有目录,以及已取得所有文件访问权限的共享存储;共享副本按请求隔离,避免同名文件覆盖 |
| 终端、文件锁、图片处理 | 继续使用已有 Android PTY、原生 flock 和 sharp WASM 适配 |
| pnpm 插件安装器 | 在有效配置和安装器 UI 中禁用:APK 的内嵌 Node 不能直接作为外部 Node / pnpm 可执行程序使用 |
| 桌面原生模块、PowerShell、桌面应用枚举和文件管理器定位 | 仍需各自的 Android 实现,不能保证兼容;第三方原生模块需提供 Android 构建 |

文件打开兼容层已通过自动化桥接及 ESM / CommonJS 加载测试;Android 打开方式的实际展示和启动仍需真机验证。

Android 工作区工具适配

所有预设(Android、标准、PTC、精简及自定义预设)在插件加载和配置更新时统一应用 Android 策略:保留文件读写工具,提供无需外部二进制的 glob / grep,并补齐手机工具。适配同样用于已下载内核;不需要逐个预设手动改配置。

- glob 支持 **、通配符、花括号和隐藏文件;没有路径分隔符的模式按文件名匹配任意深度。
- grep 使用 JavaScript Unicode 正则表达式,支持 include 文件过滤、中文、行号和单文件搜索。与 ripgrep 的正则方言存在差异,不支持的表达式会报错。
- 相对搜索路径以会话工作目录为基准,目录搜索不跟随指向搜索根之外的链接。搜索使用同一文件系统服务,权限错误不会通过 shell 绕过。
- 每次最多检查 5000 个条目、读取 8 MiB、返回 100 项,单文件上限 256 KiB;跳过二进制和无效 UTF-8。返回 skipped / truncated,正则在独立 Worker 中执行并限制时间。
- 无 Android 实现的桌面 Bash、PowerShell、Office 转换、依赖运行环境工具、tmux、SenseVoice,以及需要外部 Node / pnpm 的 PTC 工作流和插件安装器,从有效配置中禁用或移除。安装器 UI 同步禁用。文件系统、对话、网络工具和应用内的内核更新保留。

手机 shell 操作使用 android_shell,需要设备连接、设备控制和单独的任意 shell 授权;应用内普通终端不保证具备 Linux 桌面开发环境。

虚拟副屏

欢迎向导的“虚拟副屏”步骤可以跳过;设置 → Android 设置提供创建、关闭、AI 管理授权和开发者选项入口。创建采用系统“模拟辅助显示设备”,默认 720×1280 / 240 dpi,使用独立内容模式。需要连接 Shizuku 或已授权的 Root;已有其他模拟副屏时不会覆盖,仅关闭配置匹配 DSHAN 的副屏。

创建后允许 DeepSeek 使用 android_virtual_display 管理副屏;可随时关闭“允许 DeepSeek 管理虚拟副屏”。AI 仍需设备控制授权,并调用 android_displays、android_select_display 选择真实 ID。副屏截图需要 Android 14+ 和 Shizuku,部分应用不能在副屏启动;副屏不可用时不会自动操作主屏。关闭副屏会中断其中的应用任务。

android_launch_app 通过系统包管理器解析当前用户的 Launcher Activity,再使用显式组件和指定显示 ID 启动,兼容微信、B 站等无法通过隐式 Launcher Intent 启动的应用。副屏启动后核对前台任务所在显示器;无法确认时报告 APP_DISPLAY_NOT_VERIFIED,不会转到主屏重试。2026-10-06 已在 OPPO / Shizuku 的临时副屏验证设置、B 站、微信的显式启动及显示 ID 核对。

副屏文字输入统一使用 Unicode 输入法。Android 可能将键盘窗口显示在主屏,但输入连接仍属于副屏;DSHAN 核对系统输入客户端的 UID、PID、显示 ID,以及提交前的输入连接和焦点版本,不能用键盘窗口所在屏幕判断输入目标。焦点不属于指定副屏时拒绝输入,不转到主屏。2026-10-06 已在副屏 22 的独立测试输入框验证中文、混合文本、Emoji、换行和原键盘恢复。

权限预设依赖底层 dsh-bash-sandbox 服务,因此保留该服务,只移除桌面 Bash 工具入口。权限列表继续提供只读、工作区写入和完全访问;选择预设不会自动授予 Android、Shizuku 或 Root 权限。

上游与参考

- DeepSeek Harness — 官方 Harness 项目
- Shizuku — 手机设备操作授权
- nodejs-mobile — Android Node.js 运行时
- Android ADB 文档 — Android 调试与设备操作参考
预览分享卡片长什么样 ↓Chiyang001/DSHAN
分享 · DeepSeek Harness Hub
Chiyang001/DSHAN

在手机上运行 DeepSeek Harness,让 AI 调用 Android 设备工具。

回到顶部