Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Manual install requires Go 1.25+. Windows target hosts need no manual download
- Quality matrix (live E2E status): [QUALITY_MATRIX.md](docs/development/QUALITY_MATRIX.md)
- Host update runbook: [UPDATE_RUNBOOK.md](docs/operations/UPDATE_RUNBOOK.md)
- Quality gate: `./scripts/check.sh`
- DeepSeek Harness integration: [integrations/deepseek-harness](integrations/deepseek-harness/README.md)

## License

Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ curl -fsSL https://raw.githubusercontent.com/EitanWong/remote-dev-skillkit/main/
- 质量矩阵(live E2E 状态):[QUALITY_MATRIX.md](docs/development/QUALITY_MATRIX.md)
- 主机更新手册:[UPDATE_RUNBOOK.md](docs/operations/UPDATE_RUNBOOK.md)
- 质量门禁:`./scripts/check.sh`
- DeepSeek Harness 集成:[integrations/deepseek-harness](integrations/deepseek-harness/README.zh-CN.md)

## 许可证

Expand Down
105 changes: 105 additions & 0 deletions integrations/deepseek-harness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Remote Dev in DeepSeek Harness

[中文](README.zh-CN.md)

This experimental Bundle connects DeepSeek Harness to Remote Dev Skillkit
through Harness's shipped `@deepseek-ai/dsh-mcp-client`. It adds one stdio MCP
server named `rdev`; it does not copy rdev's tools or create another control
plane.

## Prerequisites

- DeepSeek Harness developer preview `0.1.0-rc.8`, using its supported Node
range `^22.19.0 || >=24.0.0`, with `dsh` and pnpm 11 on `PATH`.
- `rdev` installed. The Bundle checks `RDEV_BIN`, then
`~/.local/bin/rdev`, then `PATH`.

Pin the verified Harness preview before installing the Bundle:

```sh
pnpm add --global @deepseek-ai/dsh@0.1.0-rc.8
dsh --version # must print 0.1.0-rc.8
```

If pnpm reports that its global bin directory is missing, run `pnpm setup`,
restart the shell, and repeat the two commands. Do not use an unversioned
`@deepseek-ai/dsh`: npm `latest` is still rc.7 while this Bundle is verified
against rc.8.

From the Remote Dev Skillkit checkout, install both pieces:

```sh
./scripts/install.sh
dsh plugin --profile web add ./integrations/deepseek-harness
```

The Bundle is intentionally private and is not published to npm yet. Restart a
running Profile after adding or removing it.

## Connect a gateway

For an existing managed host, point Harness at the operator-managed HTTPS
gateway. Store the bearer token in an owner-readable file; set only its path:

```sh
export RDEV_GATEWAY_URL=https://gateway.example
export RDEV_GATEWAY_OPERATOR_TOKEN_FILE=/protected/rdev-operator.token
dsh --profile web --dump-config
dsh web
```

For a loopback development journey, start the gateway separately, then launch
Harness from another terminal:

```sh
rdev gateway serve --dev
RDEV_GATEWAY_URL=http://127.0.0.1:8787 dsh web
```

With no `RDEV_GATEWAY_URL`, rdev uses an isolated in-process control plane. That
is useful for MCP contract checks, but a remote host cannot join it.

The config dump must contain exactly one `id: rdev` row. Successful Profile
startup proves the MCP process connected and synchronized its tool list because
the Bundle enables `failOnStartupError`. Harness exposes the tools under the
`mcp__rdev__` prefix; rdev's dotted raw names receive Harness's deterministic
hash suffix.

Headless use is opt-in:

```sh
dsh plugin --profile headless add ./integrations/deepseek-harness
dsh --profile headless "List the online remote hosts and summarize their state."
```

## Remove

```sh
dsh plugin --profile web remove rdev-deepseek-harness
```

## Troubleshooting

| Symptom | Action |
| --- | --- |
| `spawn rdev ENOENT` | Set `RDEV_BIN` to the absolute `rdev` path. |
| `read operator token file` | Check the path and owner-only read permission. |
| Profile startup fails after a Harness update | Re-test against the pinned rc.8 contract; preview APIs may break. |
| Session waits with no host | Confirm a reachable gateway is configured, then inspect host heartbeat freshness. |

The Bundle contract is part of `go test ./...` and the repository's normal
`./scripts/check.sh` gate. Maintainers with `dsh` installed can run
`integrations/deepseek-harness/live-smoke.sh` for an isolated loopback-gateway
and Profile boot; it never writes to the normal `~/.dsh` directory.

## Security and compatibility

The Bundle starts `rdev mcp serve` only. Session policy, operator authorization,
audit, interruption, and target-host controls stay in rdev. No token value is
embedded in the package, no inbound target port is opened, and MCP stdout stays
protocol-only.

This Bundle is pinned and tested against DeepSeek Harness `0.1.0-rc.8`, which
upstream labels a developer preview with breaking changes expected. Custom
native tool names and purpose-built host/session/task cards are intentionally
deferred until that plugin UI contract stabilizes.
98 changes: 98 additions & 0 deletions integrations/deepseek-harness/README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# 在 DeepSeek Harness 中使用 Remote Dev

[English](README.md)

这是一个实验性 Bundle。它复用 DeepSeek Harness 自带的
`@deepseek-ai/dsh-mcp-client`,通过 stdio 接入 `rdev`,不会复制工具定义,
也不会创建第二套控制面。

## 前置条件

- DeepSeek Harness developer preview `0.1.0-rc.8`,使用其支持的 Node 范围
`^22.19.0 || >=24.0.0`,并且 `dsh`、pnpm 11 已在 `PATH` 中。
- 已安装 `rdev`。Bundle 会依次查找 `RDEV_BIN`、`~/.local/bin/rdev` 和
`PATH`。

先固定到已经验证过的 Harness 版本,再安装 Bundle:

```sh
pnpm add --global @deepseek-ai/dsh@0.1.0-rc.8
dsh --version # 必须输出 0.1.0-rc.8
```

如果 pnpm 提示 global bin 目录未配置,先执行 `pnpm setup`,重启终端后再
重复上面两条命令。不要使用不带版本的 `@deepseek-ai/dsh`:npm 的
`latest` 目前仍是 rc.7,而本 Bundle 按 rc.8 验证。

在 Remote Dev Skillkit 仓库根目录执行:

```sh
./scripts/install.sh
dsh plugin --profile web add ./integrations/deepseek-harness
```

当前 Bundle 刻意保持 private,尚未发布到 npm。新增或移除 Bundle 后,
需要重启正在运行的 Profile。

## 连接网关

要操作已托管主机,请连接 operator 管理的 HTTPS gateway。Bearer token
保存在仅 owner 可读的文件中,只传文件路径:

```sh
export RDEV_GATEWAY_URL=https://gateway.example
export RDEV_GATEWAY_OPERATOR_TOKEN_FILE=/protected/rdev-operator.token
dsh --profile web --dump-config
dsh web
```

本机开发时,先单独启动 loopback gateway,再从另一个终端启动 Harness:

```sh
rdev gateway serve --dev
RDEV_GATEWAY_URL=http://127.0.0.1:8787 dsh web
```

不设置 `RDEV_GATEWAY_URL` 时,`rdev` 使用 MCP 进程内的隔离控制面,只适合
协议检查,远程主机无法加入。

配置输出中应恰好出现一个 `id: rdev`。Bundle 设置了
`failOnStartupError`,所以 Profile 成功启动即代表 MCP 子进程已连接并同步
工具列表。工具名以 `mcp__rdev__` 开头;原始名称中的点号会触发 Harness
添加确定性的 hash 后缀。

Headless 用法:

```sh
dsh plugin --profile headless add ./integrations/deepseek-harness
dsh --profile headless "列出当前在线的远程主机,并概括状态。"
```

## 移除

```sh
dsh plugin --profile web remove rdev-deepseek-harness
```

## 常见问题

| 现象 | 处理 |
| --- | --- |
| `spawn rdev ENOENT` | 将 `RDEV_BIN` 设置为 `rdev` 的绝对路径。 |
| `read operator token file` | 检查路径和 owner-only 读取权限。 |
| Harness 更新后 Profile 启动失败 | 按 rc.8 契约重新验证;preview API 可能破坏兼容。 |
| 会话一直等待主机 | 确认配置了可达网关,再核对主机心跳是否新鲜。 |

Bundle 契约已纳入 `go test ./...` 和仓库标准 `./scripts/check.sh` 门禁。维护者
安装 `dsh` 后可运行 `integrations/deepseek-harness/live-smoke.sh`,它使用临时
loopback gateway 和 Profile,不会写入正常的 `~/.dsh`。

## 安全与兼容边界

Bundle 只启动 `rdev mcp serve`。会话策略、operator 授权、审计、中断和目标
主机控制仍由 rdev 负责。包内不写入 token 值,不开放目标主机入站端口,
MCP stdout 始终只承载协议。

当前版本锁定并验证 DeepSeek Harness `0.1.0-rc.8`。上游仍将其标记为
developer preview,并明确可能发生破坏性变更。专属 clean tool names 以及
主机、会话、任务原生卡片等 UI,等上游插件 UI 契约稳定后再接入。
17 changes: 17 additions & 0 deletions integrations/deepseek-harness/cordis.patch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
- insert:
- id: rdev
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: rdev
transport: stdio
command: !!js >-
process.env.RDEV_BIN?.trim() || ((candidate) => process.getBuiltinModule('node:fs').existsSync(candidate) ? candidate : 'rdev')(process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:os').homedir(), '.local', 'bin', process.platform === 'win32' ? 'rdev.exe' : 'rdev'))
args: !!js >-
['mcp', 'serve'].concat(process.env.RDEV_GATEWAY_URL?.trim() ? ['--gateway-url', process.env.RDEV_GATEWAY_URL.trim()].concat(process.env.RDEV_GATEWAY_OPERATOR_TOKEN_FILE?.trim() ? ['--operator-token-file', process.env.RDEV_GATEWAY_OPERATOR_TOKEN_FILE.trim()] : []) : [])
failOnStartupError: true
toolCallTimeoutMs: 60000
reconnect:
enabled: true
initialDelayMs: 500
maxDelayMs: 10000
maxAttempts: 8
89 changes: 89 additions & 0 deletions integrations/deepseek-harness/live-smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BUNDLE="${ROOT}/integrations/deepseek-harness"
TMP="$(mktemp -d)"
DSH_BIN="${DSH_BIN:-dsh}"
DSH_PID=""
GWPID=""

cleanup() {
if [[ -n "${DSH_PID}" ]]; then
kill "${DSH_PID}" 2>/dev/null || true
wait "${DSH_PID}" 2>/dev/null || true
fi
if [[ -n "${GWPID}" ]]; then
kill "${GWPID}" 2>/dev/null || true
wait "${GWPID}" 2>/dev/null || true
fi
rm -rf "${TMP}"
}
trap cleanup EXIT

command -v "${DSH_BIN}" >/dev/null 2>&1 || { printf 'live smoke: dsh not found; set DSH_BIN\n' >&2; exit 1; }
command -v pnpm >/dev/null 2>&1 || { printf 'live smoke: pnpm not found on PATH\n' >&2; exit 1; }
command -v npm >/dev/null 2>&1 || { printf 'live smoke: npm not found on PATH\n' >&2; exit 1; }
command -v node >/dev/null 2>&1 || { printf 'live smoke: node not found on PATH\n' >&2; exit 1; }
command -v curl >/dev/null 2>&1 || { printf 'live smoke: curl not found on PATH\n' >&2; exit 1; }

go build -o "${TMP}/rdev" ./cmd/rdev
(cd "${BUNDLE}" && npm pack --dry-run --json >"${TMP}/pack.json")

GW_PORT="$(node -e "const net=require('node:net');const s=net.createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})")"
GW="http://127.0.0.1:${GW_PORT}"
"${TMP}/rdev" gateway serve --dev --addr "127.0.0.1:${GW_PORT}" >"${TMP}/gateway.json" 2>"${TMP}/gateway.err" &
GWPID="$!"
for _ in $(seq 1 40); do
if curl -fsS "${GW}/v1/hosts" >/dev/null 2>&1; then break; fi
sleep 0.25
done
curl -fsS "${GW}/v1/hosts" >/dev/null || { printf 'live smoke: gateway API did not become ready\n' >&2; sed -n '1,120p' "${TMP}/gateway.err" >&2; exit 1; }

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"dsh-live-smoke","version":"1"}}}' '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| "${TMP}/rdev" mcp serve >"${TMP}/mcp.jsonl"
node - "${TMP}/mcp.jsonl" <<'NODE'
const fs = require('node:fs')
const lines = fs.readFileSync(process.argv[2], 'utf8').trim().split('\n').map(JSON.parse)
const tools = lines.find(line => line.id === 2)?.result?.tools
if (!Array.isArray(tools) || tools.length !== 11) throw new Error(`expected 11 rdev MCP tools, got ${tools?.length}`)
NODE

printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"dsh-live-smoke-remote","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdev.hosts.list","arguments":{}}}' \
| "${TMP}/rdev" mcp serve --gateway-url "${GW}" >"${TMP}/mcp-remote.jsonl"
node - "${TMP}/mcp-remote.jsonl" <<'NODE'
const fs = require('node:fs')
const lines = fs.readFileSync(process.argv[2], 'utf8').trim().split('\n').map(JSON.parse)
const hosts = lines.find(line => line.id === 3)?.result?.structuredContent?.hosts
if (!Array.isArray(hosts)) throw new Error('remote rdev.hosts.list did not return a hosts array')
NODE

export DSH_HOME="${TMP}/dsh-home"
"${DSH_BIN}" plugin --profile web add "${BUNDLE}"
RDEV_BIN="${TMP}/rdev" RDEV_GATEWAY_URL="${GW}" "${DSH_BIN}" --profile web --dump-config >"${TMP}/config.yml"
[[ "$(grep -c 'id: rdev' "${TMP}/config.yml")" == 1 ]] || { printf 'live smoke: composed config does not contain exactly one rdev row\n' >&2; exit 1; }

PORT="$(node -e "const net=require('node:net');const s=net.createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})")"
RDEV_BIN="${TMP}/rdev" RDEV_GATEWAY_URL="${GW}" "${DSH_BIN}" web --no-open --port "${PORT}" >"${TMP}/dsh.log" 2>&1 &
DSH_PID="$!"

for _ in $(seq 1 120); do
if curl -fsS "http://127.0.0.1:${PORT}/" >/dev/null 2>&1; then
printf 'deepseek-harness live smoke: ok (loopback gateway, remote MCP call, 11 tools, Profile ready)\n'
exit 0
fi
if ! kill -0 "${DSH_PID}" 2>/dev/null; then
printf 'live smoke: dsh exited before readiness\n' >&2
sed -n '1,200p' "${TMP}/dsh.log" >&2
exit 1
fi
sleep 0.25
done

printf 'live smoke: timed out waiting for dsh web\n' >&2
sed -n '1,200p' "${TMP}/dsh.log" >&2
exit 1
10 changes: 10 additions & 0 deletions integrations/deepseek-harness/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "rdev-deepseek-harness",
"version": "0.1.0-rc.1",
"description": "Remote Dev Skillkit MCP tools for DeepSeek Harness",
"license": "MIT",
"private": true,
"files": ["cordis.patch.yml", "README.md", "README.zh-CN.md"],
"engines": { "node": "^22.19.0 || >=24.0.0" },
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
Loading
Loading