专为 DeepSeek Harness 打造的 iPhone 应用。
37.8/100
综合分
3
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add 0x5446/rowel该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · ui
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 15 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包rowel-workspace(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/4 08:25:07
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 85 处,展示前 85 条)
- 写入本地文件bridle/src/cli.ts:563
writeFileSync(target, exportIdentity(state, passphrase, deviceId), { mode: 0o600 }) - 读取本地文件bridle/src/cli.ts:590
archive = readFileSync(source, 'utf8') - 读取本地文件bridle/src/cli.ts:650
for (const entry of readdirSync(dir, { withFileTypes: true })) { - 读取本地文件bridle/src/cli.ts:653
else if (entry.name.endsWith('.js')) newest = Math.max(newest, statSync(full).mtimeMs) - 读取本地文件bridle/src/cli.ts:775
statSync(modulePath) - 删除本地文件递归bridle/src/cli.ts:880
rmSync(join(home, entry), { recursive: true, force: true }) - 发起网络请求bridle/src/cli.ts:898
const response = await fetch(new URL('/healthz', toHttpUrl(state.relayUrl)), { signal: AbortSignal.timeout(5_000) }) - 写入本地文件bridle/src/identity.ts:172
mkdirSync(home, { recursive: true, mode: 0o700 }) - 读取本地文件bridle/src/identity.ts:176
raw = readFileSync(path, 'utf8') - 读取本地文件bridle/src/identity.ts:322
return JSON.parse(readFileSync(statePath(), 'utf8')) as BridleState - 写入本地文件bridle/src/identity.ts:348
mkdirSync(rowelHome(), { recursive: true, mode: 0o700 }) - 写入本地文件bridle/src/identity.ts:353
writeFileSync(lock, String(process.pid), { flag: 'wx', mode: 0o600 }) - 读取本地文件bridle/src/identity.ts:368
if (readFileSync(lock, 'utf8') === String(process.pid)) unlinkSync(lock) - 删除本地文件bridle/src/identity.ts:368
if (readFileSync(lock, 'utf8') === String(process.pid)) unlinkSync(lock) - 读取本地文件bridle/src/identity.ts:395
holder = readFileSync(lock, 'utf8') - 读取本地文件bridle/src/identity.ts:396
age = Date.now() - statSync(lock).mtimeMs - 读取本地文件bridle/src/identity.ts:402
if (readFileSync(lock, 'utf8') === holder) unlinkSync(lock) - 删除本地文件bridle/src/identity.ts:402
if (readFileSync(lock, 'utf8') === holder) unlinkSync(lock) - 写入本地文件bridle/src/identity.ts:412
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 }) - 写入本地文件bridle/src/identity.ts:413
renameSync(temporary, path) - 写入本地文件bridle/src/identity.ts:414
chmodSync(path, 0o600) - 写入本地文件bridle/src/instances.ts:70
mkdirSync(join(path, '..'), { recursive: true, mode: 0o700 }) - 写入本地文件bridle/src/instances.ts:72
writeFileSync(temporary, `${JSON.stringify(homes, null, 2)}\n`, { mode: 0o600 }) - 写入本地文件bridle/src/instances.ts:73
renameSync(temporary, path) - 读取本地文件bridle/src/instances.ts:103
const parsed: unknown = JSON.parse(readFileSync(indexPath(), 'utf8')) - 读取本地文件bridle/src/instances.ts:114
state = JSON.parse(readFileSync(join(home, 'bridle.json'), 'utf8')) as typeof state - 读取本地文件bridle/src/instances.ts:147
info = JSON.parse(readFileSync(join(home, 'runtime.json'), 'utf8')) as RuntimeInfo - 读取本地文件bridle/src/instances.ts:161
return existsSync(join(home, 'bridle.json')) - 发起网络请求bridle/src/pair.ts:75
const response = await fetch(new URL('/v1/pair/offer', toHttpUrl(state.relayUrl)), { - 写入本地文件bridle/src/runtime.ts:48
mkdirSync(rowelHome(), { recursive: true, mode: 0o700 }) - 写入本地文件bridle/src/runtime.ts:51
writeFileSync(temporary, `${JSON.stringify(info, null, 2)}\n`, { mode: 0o600 }) - 写入本地文件bridle/src/runtime.ts:52
renameSync(temporary, path) - 删除本地文件bridle/src/runtime.ts:58
rmSync(runtimePath()) - 读取本地文件bridle/src/runtime.ts:96
info = JSON.parse(readFileSync(runtimePath(), 'utf8')) as RuntimeInfo - 写入本地文件bridle/src/service.ts:57
mkdirSync(rowelHome(), { recursive: true, mode: 0o700 }) - 写入本地文件bridle/src/service.ts:60
mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true }) - 写入本地文件bridle/src/service.ts:61
writeFileSync(path, launchdPlist(command, args, log), { mode: 0o644 }) - 执行外部命令固定命令bridle/src/service.ts:63
execFileSync('launchctl', ['load', '-w', path], { stdio: 'ignore' }) - 写入本地文件bridle/src/service.ts:68
mkdirSync(join(homedir(), '.config', 'systemd', 'user'), { recursive: true }) - 写入本地文件bridle/src/service.ts:69
writeFileSync(path, systemdUnit(command, args), { mode: 0o644 }) - 执行外部命令固定命令bridle/src/service.ts:70
execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'ignore' }) - 执行外部命令固定命令bridle/src/service.ts:71
execFileSync('systemctl', ['--user', 'enable', '--now', `${SERVICE_LABEL}.service`], { stdio: 'ignore' }) - 删除本地文件bridle/src/service.ts:86
rmSync(path, { force: true }) - 删除本地文件bridle/src/service.ts:92
rmSync(path, { force: true }) - 执行外部命令来自配置/变量bridle/src/service.ts:101
execFileSync(command, args, { stdio: 'ignore' }) - 读取本地文件bridle/src/version.ts:18
const manifest = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')) as { version?: string } - 读取本地文件dsh-plugin/src/index.ts:73
const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as { name?: unknown; version?: unknown } - 写入本地文件e2e/src/dsh.ts:81
mkdirSync(userHome, { recursive: true }) - 读取本地文件e2e/src/dsh.ts:84
? modelRows(readFileSync(settings, 'utf8')) - 写入本地文件e2e/src/dsh.ts:91
mkdirSync(join(home, 'profiles', 'web'), { recursive: true }) - 写入本地文件e2e/src/dsh.ts:92
writeFileSync(join(home, 'profiles', 'web', 'cordis.patch.yml'), patch) - 执行外部命令来自配置/变量e2e/src/dsh.ts:99
const child = spawn(bin, ['web', '--host', '127.0.0.1', '--port', String(port), '--no-open'], { - 删除本地文件递归e2e/src/dsh.ts:171
rmSync(home, { recursive: true, force: true }) - 删除本地文件递归e2e/src/stack.ts:153
rmSync(home, { recursive: true, force: true }) - 读取本地文件ios/asc.mjs:59
{ key: readFileSync(KEY_PATH, 'utf8'), dsaEncoding: 'ieee-p1363' }) - 请求外部主机api.appstoreconnect.apple.comios/asc.mjs:71
const response = await fetch(`https://api.appstoreconnect.apple.com${path}`, { - 发起网络请求ios/asc.mjs:71
const response = await fetch(`https://api.appstoreconnect.apple.com${path}`, { - 读取本地文件ios/asc.mjs:263
bytes = readFileSync(path) - 发起网络请求ios/asc.mjs:301
const put = await fetch(operation.url, { - 读取本地文件marketing/video/render.mjs:28
const beats = JSON.parse(readFileSync(join(raw, 'beats.json'), 'utf8')) - 执行外部命令来自配置/变量marketing/video/render.mjs:48
const run = (file, args) => execFileSync(file, args, { stdio: 'inherit', cwd: here }) - 写入本地文件marketing/video/render.mjs:64
mkdirSync(join(here, 'public'), { recursive: true }) - 写入本地文件marketing/video/render.mjs:101
writeFileSync(list, parts.map(p => `file '${p}'`).join('\n') + '\n') - 删除本地文件marketing/video/render.mjs:102
rmSync(join(here, 'public/phone.mp4'), { force: true }) - 删除本地文件marketing/video/render.mjs:107
for (const part of [...parts, list, source]) rmSync(part, { force: true }) - 写入本地文件marketing/video/render.mjs:110
writeFileSync(join(here, 'public/beats.json'), `${JSON.stringify(timeline, null, 2)}\n`) - 写入本地文件marketing/video/render.mjs:112
mkdirSync(join(here, 'out'), { recursive: true }) - 读取本地文件marketing/video/review.mjs:21
const beats = JSON.parse(readFileSync(join(here, 'raw/review-beats.json'), 'utf8')) - 执行外部命令来自配置/变量marketing/video/review.mjs:22
const run = (file, args) => execFileSync(file, args, { stdio: 'inherit', cwd: here }) - 写入本地文件marketing/video/review.mjs:31
mkdirSync(join(here, 'public'), { recursive: true }) - 删除本地文件marketing/video/review.mjs:44
rmSync(source, { force: true }) - 写入本地文件marketing/video/review.mjs:50
writeFileSync(join(here, 'public/review-beats.json'), `${JSON.stringify(timeline, null, 2)}\n`) - 写入本地文件marketing/video/review.mjs:52
mkdirSync(join(here, 'out'), { recursive: true }) - 发起网络请求marketing/video/src/timeline.js:38
const timeline = await (await fetch(staticFile(file))).json() - 读取本地文件relay/src/server.ts:444
return readFileSync(configured, 'utf8') - 发起网络请求relay-worker/src/apns.ts:167
response = await fetch(`${host}/3/device/${target.token}`, { method: 'POST', headers, body }) - 发起网络请求relay-worker/src/index.ts:43
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { - 请求外部主机switchboardrelay-worker/src/index.ts:176
return await env.SWITCHBOARD.get(id).fetch(new Request('https://switchboard/bridle', request)) - 发起网络请求relay-worker/src/index.ts:176
return await env.SWITCHBOARD.get(id).fetch(new Request('https://switchboard/bridle', request)) - 请求外部主机switchboardrelay-worker/src/index.ts:194
return await target.fetch(new Request(`https://switchboard/app?device=${encodeURIComponent(deviceId)}`, request)) - 发起网络请求relay-worker/src/index.ts:194
return await target.fetch(new Request(`https://switchboard/app?device=${encodeURIComponent(deviceId)}`, request)) - 发起网络请求relay-worker/src/switchboard.ts:63
override async fetch(request: Request): Promise<Response> { - 发起网络请求site/worker.js:57
async fetch(request, env) { - 发起网络请求site/worker.js:59
if (!url.pathname.startsWith('/ingest/')) return env.ASSETS.fetch(request) - 发起网络请求site/worker.js:78
const response = await fetch(new URL(`https://${host}${path}${url.search}`), init)
扫描于 2026/11/4(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库
涉及外部主机:api.appstoreconnect.apple.com、switchboard
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
界面预览
3 张 · 点击查看大图功能亮点
- 原生iOS客户端
- 手机回复智能体
- 中继仅见密文
README
由 DeepSeek 最新模型翻译生成Rowel
专为 DeepSeek Harness 打造的 iPhone 应用。
你的智能体正等着你——随时随地回应。
dsh 停下来请求权限时,你的手机会震动,你能看到它想运行的命令或 diff,轻点一下就能让 Mac 继续。查看智能体做了什么,回答它卡住的那个问题,在火车上启动下一件事。端到端加密;中继读不到你的流量。
rowel.novabox.ai ·加入 TestFlight · MIT,全部开源:应用、Bridle、中继、协议。
只为 dsh 打造。 Rowel 说的是 DeepSeek Harness API——会话、审批、模型、技能。它不是 Claude Code、Codex、Cursor 或其他智能体的遥控器。Harness 本身能跑很多模型(DeepSeek、GLM、Qwen 等等),Rowel 让你在它们之间挑选。
展开完整文档(README 剩余 1 节)
Rowel 专为 DeepSeek Harness 打造的 iPhone 应用。 你的智能体正等着你——随时随地回应。 dsh 停下来请求权限时,你的手机会震动,你能看到它想运行的命令或 diff,轻点一下就能让 Mac 继续。查看智能体做了什么,回答它卡住的那个问题,在火车上启动下一件事。端到端加密;中继读不到你的流量。 rowel.novabox.ai ·加入 TestFlight · MIT,全部开源:应用、Bridle、中继、协议。 只为 dsh 打造。 Rowel 说的是 DeepSeek Harness API——会话、审批、模型、技能。它不是 Claude Code、Codex、Cursor 或其他智能体的遥控器。Harness 本身能跑很多模型(DeepSeek、GLM、Qwen 等等),Rowel 让你在它们之间挑选。 | 组件 | 是什么 | 运行在哪 | | Rowel | iOS 应用 | 你的 iPhone | | Bridle | 一个伴随进程,替 Harness 说话 | 运行 dsh 的 Mac | | Relay | 一个看不到内容的交换机 | 公网——我们的,或你自己的 | iPhone internet your Mac ┌────────┐ ┌──────────┐ ┌──────────────────┐ │ Rowel │◄──wss───►│ Relay │◄──wss───►│ Bridle ──► dsh │ └────────┘ sealed └──────────┘ sealed └──────────────────┘ └────────── Noise_IK_25519_ChaChaPoly_SHA256 ─────────┘ Bridle 主动拨出。不用端口转发,不用公网 IP,路由器上什么都不用改。 手机和 Mac 在同一网络时,应用会先尝试直连 Mac,四分之一秒后再拨中继;本地路径通常胜出,之后中继就不再承载这个会话。 没有账号。配对就是终端里的一个二维码。 目前进展 Mac 端已完成,日常在用。中继已部署。Rowel 1.1 需要 dsh 0.2 或更新版本——dsh 0.2 换掉了整套 API,应用和 Bridle 现在直接说新 API。应用在 TestFlight 上,直到通过 App Store 审核。你也可以用 Xcode 自己构建,免费,但有个七天的坑。 还在用 dsh 0.1?Bridle 0.1.x 和应用 1.0 继续可用;用 ROWEL_REF=v0.1.7 安装。升到 dsh 0.2 是单向的——先读从 dsh 0.1 升级。 跑起来 1. 在 Mac 上安装 Bridle curl -fsSL https://rowel.novabox.ai/install | sh 需要 Node 22+ 和 git —— 脚本不会安装这两样;缺什么它就停下来告诉你 —— 还需要 dsh 0.2,脚本会检查并给出警告。它安装的所有东西都在 ~/.rowel 里,另外在 PATH 上放一个符号链接(以及 npm 的常规缓存)。它一共 144 行,其中超过三分之一是注释,如果你想先读一遍再运行,可以看看。 2. 配对 bridle pair 如果 Bridle 没在运行,它会启动 Bridle,打印一个二维码,然后保持运行。在 App 里点 Connect a Mac,再点 I've run it — scan the code。如果 Bridle 已经在运行(作为服务,或作为 dsh 的插件),它只会打印一个新码。--code 作用相同,用于手动输入而不是扫码。 如果 dsh 已经在运行 —— 手动启动的,或者由登录项保持运行 —— Bridle 无法从外部登录它:dsh 0.2 只把登录 token 交给自己的插件。改为在 dsh 内部运行 Bridle,然后重启 dsh: bridle plugin install bridle status 会告诉你当前是哪种情况,bridle doctor 会在两者都不是时告诉你该怎么做。如果放任不管,且没有 dsh 在运行,Bridle 会自己启动一个,并用它打印的 token 登录。 通过 SSH 连接时,终端可能画不出二维码,加上 --link 可以打印原始配对链接。当无法使用摄像头时,bridle pair --code 会打印一个 8 字符的码供你输入,然后等待:Mac 随后会显示手机的公钥并请你确认,你拿它和手机上显示的公钥核对。SECURITY.md 说明了这一步为什么存在。 3. 保持运行 bridle service install 登录后自动启动,关闭终端也不受影响。bridle service uninstall 可以把它卸掉。 bridle 命令 bridle start (and pair, on the first run) bridle pair a pairing QR, starting bridle if none runs (--code: a code to type) bridle status machine, relay, harness, paired devices bridle devices list paired devices bridle revoke <prefix> remove one bridle backup <file> save this machine's identity, encrypted bridle restore <file> put a saved identity back bridle service install keep it running after login bridle plugin install run bridle inside dsh instead (how it signs in to a dsh already running) bridle doctor check this machine's setup bridle 本身可用的参数: --relay <url> a different relay (default: the public one) --dsh <url> the harness, if it is not on a usual port --advertise <url> an extra address to put in the pairing code — a tunnel hostname the machine cannot discover for itself. LAN and Tailscale addresses are found automatically. --direct-port <n> fix the local-network port --no-direct do not listen on the local network at all --no-auto-start never launch the harness --link also print the raw pairing link 状态保存在 ~/.rowel/bridle.json,权限 0600,里面存着这台机器的私钥。ROWEL_HOME 可以把它挪走。 一台 Mac 上跑两个 harness 一个 Bridle 只服务一个 dsh。放任不管时,它会探测常见端口(3080–3083、8080、8791),并沿用上次应答的那个 dsh——只有一个 harness 时没问题,有两个就是掷硬币。--dsh 终结这种猜测: bridle --dsh http://127.0.0.1:3081 如果那里没有应答,Bridle 会在该端口上启动一个 harness——启动行会显示 (started by bridle),表示是它启动的。--no-auto-start 让它改为拒绝启动。 同一个身份只能运行一个 Bridle;第二次启动会停下来,并说出第一个的名字。所以要把现有配对挪到别的端口,先停掉正在运行的 Bridle,再用 --dsh 启动。要让第二个 harness 与第一个并存,给它一个自己的身份: ROWEL_HOME=~/.rowel-3081 bridle --dsh http://127.0.0.1:3081 它首次运行会打印自己的二维码,应用里会显示第二台机器——名字相同,靠指纹后缀区分。bridle pair 不需要这些:它什么都不启动,所以能愉快地跑在已经启动的 Bridle 旁边。 从 dsh 0.1 升级 dsh 0.2 首次启动时会重写 ~/.dsh——settings.yaml 变成 settings.yaml.imported,其中的各个 section 移入 profile——而 dsh 0.1 读不了 0.2 写入的对话。所以: 1. 升级前完整备份 ~/.dsh。想回退,就要恢复那份副本,并重新安装 @deepseek-ai/dsh@0.1.1-rc.2,而不只是降级。 2. 把 dsh、Bridle(curl -fsSL https://rowel.novabox.ai/install | sh)和应用一起升到 1.1。应用 1.0 无法与 Bridle 0.2 通信,Bridle 0.2 也无法与 dsh 0.1 通信。 3. 如果你的模型来自网关——Command Code、OpenRouter,任何配置在 llm-pi-ai 下的东西——升级后就没了:dsh 0.2 的默认 profile 不加载那个 adapter,导入时也会丢掉这个 section。把它放回 ~/.dsh/profiles/web/cordis.patch.yml,从 settings.yaml.imported 复制 providers 块(以及你的默认模型): - insert: - id: llm-pi-ai name: '@deepseek-ai/dsh-llm-pi-ai' config: providers: commandcode: # as it was under llm-pi-ai.providers apiKeyEnv: COMMANDCODE_API_KEY api: openai-completions baseURL: https://api.commandcode.ai/provider/v1 … its models list, unchanged - id: agent-default-model config: provider: commandcode model: deepseek/deepseek-v4.1-flash 然后重启 dsh。密钥留在原处:路由通过环境变量来引用它们。 4. 如果 Bridle 之前作为 dsh 的插件运行,它现在仍然是;否则运行 bridle plugin install 并重启 dsh(见 Pair)。 它保护什么,不保护什么 中继按电路编号在两个 socket 之间转发密封帧。它没有密钥材料,也打不开这些帧,这是形态本身的属性,而不是不承诺任何人的行为——端到端测试会通过中继抓取手机流量,只要其中出现方法名、会话 id、机器名或对话标题,测试就失败。(中继确实会从 Bridle 自己的注册信息里获知机器的显示名称;SECURITY.md 列出了它获知的全部内容。)隧道的两套实现都只用各自平台自带的原语,Node 的 node:crypto 和 Swift 的 CryptoKit。代码树里没有任何第三方密码学依赖。 人们低估的那部分: dsh 向浏览器索取的,不过是它打印出来的那个 token,而 Bridle 持有这次登录,所以配对后的手机对那台 Mac 拥有的权限,和它自己的终端一样——可以运行命令,读写文件。bridle revoke 是收回权限的唯一办法。应用里没有任何一项权限比这更小;应用只是少画了几个按钮。 SECURITY.md 是完整的威胁模型,包括中继确实会获知什么、手机解锁意味着什么,以及已知的弱点。在配对任何你在意的东西之前,值得先读一读。 从源码构建 npm install npm test # build, docs check, unit tests. Seconds. npm run test:e2e # the whole stack; ROWEL_E2E_DSH_BIN=<dsh 0.2> for the parts that need dsh, ROWEL_E2E_MODEL=1 for the few that spend model turns npm run test:ios # needs Xcode and brew install xcodegen npm run vectors # regenerate the cross-language test vectors protocol/ Noise, frames, pairing, relay wire format (TypeScript) bridle/ the companion process and its CLI dsh-plugin/ the same core, mounted inside the harness instead of beside it relay/ the Node relay relay-worker/ the same relay on Cloudflare Workers and Durable Objects e2e/ tests that span all three ios/ the app (Swift, SwiftUI, XcodeGen) 两套实现如何保持同一套协议 Noise 握手和帧编码写了两遍,一遍 TypeScript,一遍 Swift。「我的服务端和我的客户端能通话」在两端都是我自己写的时候证明不了什么,所以 npm run vectors 用固定的密钥和固定的临时密钥跑一遍握手,写出确定性的测试夹具;Swift 那边逐字节比对——握手消息、握手哈希、确认号、传输密文、配对链接、帧编码。 这里失败意味着协议分叉,不是测试不稳定。它已经抓到过一次:Foundation 的 JSONEncoder 不保证键的顺序,两端都能正常解析,自己跟自己对话再怎么聊也发现不了。 运行你自己的中继 bridle --relay wss://your.host 就是客户端侧的全部。relay/ 是单个 Node 进程,没有数据库,也没有配置文件;relay-worker/ 是同一个交换机跑在 Cloudflare Workers 上,公开的那个就跑在这上面。docs/deployment.md 里有 DNS 和 TLS 的细节。 文档 docs/ 是中文写的——它是给维护这个项目的人看的,翻译它比让它保持正确还费劲。英文读者并不是看不了关键部分:SECURITY.md 讲了威胁模型,CONTRIBUTING.md 讲了构建方式和规则,代码注释和提交信息也全程是英文。 | docs/architecture.md | 为什么是这个形态,新功能该放哪 | | docs/protocol.md | 线上传输的确切字节,够你写第三个客户端 | | docs/fold.md | 事件日志怎么变成屏幕画面,一条条规则讲 | | docs/dsh-api-inventory.md | 应用用到的 dsh 0.2 端点和流,以及用在哪 | | docs/deployment.md | 怎么跑 relay、DNS,以及上线还差什么 | docs/README.md 说明了哪些章节是规范级、哪些是设计级。想照着设计级的章节重新实现,先来问一声。 许可 MIT。见 LICENSE。
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
0x5446/rowel
专为 DeepSeek Harness 打造的 iPhone 应用。
相关插件 · UI / 主题
破甲增强版JeffKeeping/dsh-purge-plus
dsh-purge 的非官方分支fork:适配 DeepSeek Harness 0.2.1-rc.2、破甲内核…
★ 387已汉化DeepSeek 客户端
71.9
无代码业务系统平台nocobase/nocobase
NocoBase 是什么
★ 24,804已汉化DSH
71.8
1Panel 服务器面板1Panel-dev/1Panel
全球 250 万+ 自托管用户共同喜爱。
★ 37,350已汉化DSH
71.5
工作状态行动态ccch1mneyyy/working-activity
作者:chimney@ccch1mneyyy。社区项目,非 DeepSeek 官方项目。
★ 664已汉化DSH
71.2
多模型配置管家farion1231/cc-switch
跨平台桌面一体化助手,支持 Claude Code、Codex、OpenCode、DeepSeek Harness 等工具,唯一官网 ccswitch.io
★ 151,506已汉化DSH
71.1
AI 矢量设计工具ZSeven-W/openpencil
全球首个 AI 原生开源矢量设计工具。
★ 7,027已汉化DSH
71.1
这是你的仓库?认领条目