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

Pi 插件兼容桥 weijiafu14/pi2dsh

DeepSeek Harnessv0.27.1
weijiafu147 天前有提交已提供中文文档扫描:含敏感能力

打通 Pi 与 DSH 生态:一套 Pi Host ABI 就能让未经修改的 Pi 扩展作为原生 DSH 插件运行。

64.7/100
综合分
213
GitHub 星标
1,989
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add pi2dsh
npm 源 · pi2dsh 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · ui
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

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

✓npm 包pi2dsh @ 0.27.1
✓Node 引擎要求 >=22.19.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 09:40:12

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

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 259 处,展示前 93 条)
  • 读取本地文件
    dist/index.mjs:18
    if (existsSync(join(dir, "cordis.yml")) && existsSync(join(dir, "package.json"))) return dir;
  • 读取本地文件
    dist/index.mjs:25
    return JSON.parse(await readFile(join(profileRoot, "package.json"), "utf8"));
  • 读取本地文件
    dist/index.mjs:29
    return existsSync(join(dir, "package.json")) ? dir : void 0;
  • 读取本地文件
    dist/index.mjs:51
    packageJson = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
  • 写入本地文件
    dist/cli.mjs:71
    writeFileSync(parsed.values.out, output);
  • 读取本地文件
    dist/host.mjs:46
    const { attributes, body } = parseFrontmatter(await readFile(source, "utf8"));
  • 读取本地文件
    dist/host.mjs:73
    if (existsSync(join(dir, "package.json"))) return dir;
  • 读取本地文件
    dist/runtime-BFJsumJX.mjs:70
    else if (arg === "-e" || arg === "--extension") parsed.extensions.push(await realpath(resolve(cwd, value(i++))));
  • 读取本地文件
    dist/runtime-BFJsumJX.mjs:82
    prompts.push(`<file name=${JSON.stringify(path)}>\n${await readFile(path, "utf8")}\n</file>`);
  • 写入本地文件
    dist/runtime-BFJsumJX.mjs:93
    await chmod(directory, 448);
  • 写入本地文件
    dist/runtime-BFJsumJX.mjs:153
    await chmod(socketPath, 384);
  • 写入本地文件
    dist/runtime-BFJsumJX.mjs:156
    await writeFile(join(directory, "pi"), `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(client)} "$@"\n`, { mode: 448 });
  • 删除本地文件
    dist/runtime-BFJsumJX.mjs:167
    await rm(directory, {
  • 读取本地文件
    dist/runtime-BFJsumJX.mjs:318
    if (existsSync(root)) for (const directory of readdirSync(root, { withFileTypes: true })) {
  • 读取本地文件
    dist/runtime-BFJsumJX.mjs:318
    if (existsSync(root)) for (const directory of readdirSync(root, { withFileTypes: true })) {
  • 读取本地文件
    dist/runtime-BFJsumJX.mjs:320
    for (const entry of readdirSync(join(root, directory.name), { withFileTypes: true })) {
  • 读取本地文件
    dist/runtime-BFJsumJX.mjs:364
    for (const exported of priorExports) if ((!present.has(exported.id) || hidden.has(exported.id)) && existsSync(exported.path)) unlinkSync(exported.path
  • 删除本地文件
    dist/runtime-BFJsumJX.mjs:364
    for (const exported of priorExports) if ((!present.has(exported.id) || hidden.has(exported.id)) && existsSync(exported.path)) unlinkSync(exported.path
  • 写入本地文件
    dist/runtime-BFJsumJX.mjs:385
    writeFileSync(temporary, text, { mode: 384 });
  • 写入本地文件
    dist/runtime-BFJsumJX.mjs:386
    renameSync(temporary, path);
  • 删除本地文件
    dist/runtime-BFJsumJX.mjs:388
    if (existsSync(temporary)) unlinkSync(temporary);
  • 写入本地文件
    dist/runtime-BFJsumJX.mjs:409
    mkdirSync(sidecarDir(), { recursive: true });
  • 删除本地文件
    dist/runtime-BFJsumJX.mjs:449
    unlinkSync(this.sidecarPath(sessionId));
  • 删除本地文件
    dist/runtime-BFJsumJX.mjs:458
    if (this.sessionIdOfArchiveFile(file) === sessionId) unlinkSync(file);
  • 执行外部命令来自配置/变量
    dist/runtime-BFJsumJX.mjs:1493
    spawn(cmd, args, {
  • 读取本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:556
    mtime: statSync(path).mtime
  • 读取本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:589
    input: createReadStream(filePath, { encoding: "utf8" }),
  • 读取本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:716
    if (statSync(explicitPath).size > 0) throw new Error(`Session file is not a valid pi session: ${explicitPath}`);
  • 读取本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:1606
    return await realpath(resolvedPath);
  • 执行外部命令固定命令
    dist/pi-coding-agent-lcNs9Wos.mjs:1861
    const result = spawnSync("where", ["bash.exe"], {
  • 读取本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:1868
    if (firstMatch && existsSync(firstMatch)) return firstMatch;
  • 执行外部命令固定命令
    dist/pi-coding-agent-lcNs9Wos.mjs:1874
    const result = spawnSync("which", ["bash"], {
  • 读取本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:1894
    if (existsSync(customShellPath)) return getBashShellConfig$1(customShellPath);
  • 写入本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:2314
    this.tempFileStream = createWriteStream(this.tempFilePath);
  • 写入本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:5667
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:5668
    writeFileSync(path, `${JSON.stringify(sorted, null, 2)}\n`, "utf-8");
  • 写入本地文件
    dist/pi-coding-agent-lcNs9Wos.mjs:5672
    mkdirSync(trustDir, { recursive: true });
  • 执行外部命令固定命令
    dist/pi-coding-agent-lcNs9Wos.mjs:6122
    const result = spawnSync("where", ["bash.exe"], {
  • 执行外部命令固定命令
    dist/pi-coding-agent-lcNs9Wos.mjs:6135
    const result = spawnSync("which", ["bash"], {
  • 执行外部命令来自配置/变量
    dist/pi-coding-agent-lcNs9Wos.mjs:6370
    const child = execFile(command, args, (error) => resolve(error === null));
  • 读取本地文件
    dist/mcp-config-C1K-IzzB.mjs:51
    if (!existsSync(path)) continue;
  • 读取本地文件
    dist/mcp-config-C1K-IzzB.mjs:54
    parsed = JSON.parse(readFileSync(path, "utf8"));
  • 读取本地文件
    dist/source-l77aIDwD.mjs:9
    await access(path);
  • 读取本地文件
    dist/source-l77aIDwD.mjs:66
    const info = await stat(absolute);
  • 读取本地文件
    dist/source-l77aIDwD.mjs:77
    const manifest = piManifest(JSON.parse(await readFile(packageJsonPath, "utf8")));
  • 读取本地文件
    dist/source-l77aIDwD.mjs:98
    for (const entry of await readdir(dir, { withFileTypes: true })) {
  • 读取本地文件
    dist/source-l77aIDwD.mjs:104
    const info = await stat(fullPath);
  • 读取本地文件
    dist/source-l77aIDwD.mjs:131
    if ((await stat(absolute)).isDirectory()) for (const entry of await collectAutoExtensionEntries(absolute)) output.add(entry);
  • 删除本地文件
    dist/source-l77aIDwD.mjs:205
    await rm(rootDir, {
  • 删除本地文件
    dist/source-l77aIDwD.mjs:221
    if (temporary) await rm(rootDir, {
  • 执行外部命令固定命令
    dist/pi-tui-DruSeKmd.mjs:3354
    return execSync("tmux display-message -p '#{client_termfeatures}'", {
  • 读取本地文件
    dist/pi-tui-DruSeKmd.mjs:3985
    if (!statSync(baseDir).isDirectory()) return null;
  • 读取本地文件
    dist/pi-tui-DruSeKmd.mjs:4022
    const entries = readdirSync(searchDir, { withFileTypes: true });
  • 读取本地文件
    dist/pi-tui-DruSeKmd.mjs:4029
    isDirectory = statSync(fullPath).isDirectory();
  • 发起网络请求
    dist/pi-ai-Cy_BRMmd.mjs:1173
    return await fetch(input, init);
  • 发起网络请求
    dist/pi-ai-Cy_BRMmd.mjs:1209
    response = await fetch(TOKEN_URL$1, {
  • 发起网络请求
    dist/pi-ai-Cy_BRMmd.mjs:1620
    const response = await fetch(url, {
  • 发起网络请求
    dist/pi-ai-Cy_BRMmd.mjs:2805
    const response = await fetch(url, init);
  • 发起网络请求
    dist/pi-ai-Cy_BRMmd.mjs:2931
    return (await fetch(url, {
  • 发起网络请求
    dist/pi-ai-Cy_BRMmd.mjs:3050
    const response = await fetch(`${oauthHost}/api/oauth/device_authorization`, {
  • 读取本地文件
    dist/oauth-bridge-C_MAPMGA.mjs:286
    const data = JSON.parse(readFileSync(path, "utf8"));
  • 写入本地文件
    dist/oauth-bridge-C_MAPMGA.mjs:296
    await mkdir(dirname(this.#path), { recursive: true });
  • 写入本地文件
    dist/oauth-bridge-C_MAPMGA.mjs:298
    await writeFile(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 384 });
  • 写入本地文件
    dist/oauth-bridge-C_MAPMGA.mjs:299
    await rename(temp, this.#path);
  • 读取本地文件
    dist/oauth-bridge-C_MAPMGA.mjs:458
    fileExists: async (path) => existsSync(path)
  • 发起网络请求
    dist/openai-DkzRh5_G.mjs:1289
    const FetchResponse = "Response" in fetch ? fetch.Response : (await fetch("data:,")).constructor;
  • 发起网络请求
    dist/openai-DkzRh5_G.mjs:6362
    return this.request(Promise.resolve(opts).then((opts) => {
  • 读取本地文件
    dist/analyzer-BCHAvJ70.mjs:157
    return (await stat(path)).isFile();
  • 读取本地文件
    dist/analyzer-BCHAvJ70.mjs:192
    const parsed = JSON.parse(await readFile(join(rootDir, "package.json"), "utf8"));
  • 读取本地文件
    dist/analyzer-BCHAvJ70.mjs:221
    info = await lstat(path);
  • 读取本地文件
    dist/analyzer-BCHAvJ70.mjs:231
    for (const entry of await readdir(path)) output.push(...await expandAsset(join(path, entry), rootDir));
  • 读取本地文件
    dist/analyzer-BCHAvJ70.mjs:243
    const info = await lstat(source);
  • 读取本地文件
    dist/analyzer-BCHAvJ70.mjs:249
    const text = await readFile(source, "utf8");
  • 发起网络请求
    dist/anthropic-messages-CaYTgvLF.mjs:1008
    const FetchResponse = "Response" in fetch ? fetch.Response : (await fetch("data:,")).constructor;
  • 发起网络请求
    dist/anthropic-messages-CaYTgvLF.mjs:5298
    return this.request(Promise.resolve(opts).then((opts) => {
  • 发起网络请求
    dist/dist-QBBA-dL_.mjs:876
    const req = ((typeof url === "string" ? url : url.href).startsWith("https:") ? https : http$1).request(url, opts);
  • 发起网络请求
    dist/google-shared-LEpwok5n.mjs:798
    fetch(...args) {
  • 发起网络请求
    dist/google-shared-LEpwok5n.mjs:808
    if (typeof input === "object" && !(input instanceof URL)) return this.request({
  • 发起网络请求
    dist/google-shared-LEpwok5n.mjs:814
    else return this.request({
  • 发起网络请求
    dist/google-shared-LEpwok5n.mjs:1174
    return exports.instance.request(opts);
  • 发起网络请求
    dist/google-shared-LEpwok5n.mjs:4346
    fetch(...args) {
  • 发起网络请求
    dist/google-shared-LEpwok5n.mjs:4356
    if (typeof input === "object" && !(input instanceof URL)) return this.request({
  • 写入本地文件
    dist/google-shared-LEpwok5n.mjs:27471
    const writer = createWriteStream(params.downloadPath);
  • 写入本地文件
    dist/google-shared-LEpwok5n.mjs:27475
    await writeFile(params.downloadPath, response, { encoding: "base64" });
  • 读取本地文件
    dist/google-shared-LEpwok5n.mjs:28399
    fileStat.size = (await fs.stat(file)).size;
  • 发起网络请求
    dist/mistral-conversations-D-Kf38nj.mjs:126
    if (init == null) return fetch(input);
  • 发起网络请求
    dist/mistral-conversations-D-Kf38nj.mjs:127
    else return fetch(input, init);
  • 发起网络请求
    dist/mistral-conversations-D-Kf38nj.mjs:292
    if (!activeContext) return this.wrappedClient.request(request);
  • 发起网络请求
    dist/mistral-conversations-D-Kf38nj.mjs:296
    if (!observability) return await this.wrappedClient.request(request);
  • 发起网络请求
    dist/mistral-conversations-D-Kf38nj.mjs:297
    return await observability.runWithContext(activeContext, () => this.wrappedClient.request(request));
  • 发起网络请求
    dist/mistral-conversations-D-Kf38nj.mjs:5505
    let response = await this.#httpClient.request(req);
  • 发起网络请求
    dist/src-ChZlNrRV.mjs:4786
    async function fetch(url, options_) {
  • 发起网络请求
    dist/src-ChZlNrRV.mjs:4900
    resolve(fetch(new Request(locationURL, requestOptions)));
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-agent-loop@deepseek-ai/dsh-attachment@deepseek-ai/dsh-attachment-local@deepseek-ai/dsh-authorization@deepseek-ai/dsh-commands@deepseek-ai/dsh-credentials@deepseek-ai/dsh-credentials-local@deepseek-ai/dsh-agent-instructions@deepseek-ai/dsh-launch-environment@deepseek-ai/dsh-llm
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 运行未修改的Pi扩展
  • 作为原生DSH插件加载
  • 打通Pi与DSH生态
v0.1.22026-08-14
首个可对外安装的版本。 · 生成的包内嵌经过测试的 pi2dsh 运行时和 Pi 宿主 shim。 · 没有未发布的 npm 依赖,也没有奇怪的 URL 子依赖;默认输出符合 DSH 的 blockExoticSubdeps 策略。 · 默认生成的产物通过官方 dsh 插件的安装、激活、移除流程。 · 同一套内嵌架构通过了全部四个固定社区包的运行时检查,也跑通了一次真实的 DeepSeek 持久化工具调用与结果。 · 完整门禁:29 项测试;语句覆盖率 85.37%,分支覆盖率 76.18%,函数覆盖率 94.14%,行覆盖率 88.77%;publint 和打包后全新安装冒烟测试均通过。
v0.1.12026-08-14
已被 v0.1.2 取代。v0.1.1 演示了 URL 运行时方案,但 DSH 在 blockExoticSubdeps 下会正确拒绝异常子依赖。请用 v0.1.2,它内嵌了测试过的适配器,能通过默认的官方插件管理器流程。
v0.1.02026-08-14
首个通过审计的 Pi 到 DeepSeek Harness 桥接。 · 把 Pi 的工具、命令、提示词、技能、选定生命周期事件和宿主导入转换成可安装的 DSH 包。 · 遇到未知或不支持的能力直接失败,不放行。 · 审计了 Pi 官方目录前 50 项:4 个待复核,46 个被拦截,0 个审计错误。 · 运行时验证了 pi-lsp、rpiv-web-tools、pi-image-gen,以及 pi-ask-user 降级后的无头回退。 · 通过生成的包验证了真实 DeepSeek 模型的持久化工具调用与结果。 · CI、覆盖率阈值、publint、打包安装和官方 DSH 插件管理器测试全部通过。 附带的 npm tarball 与打包安装冒烟测试验证过的产物是同一份。npm 仓库发布还在等维护者认证。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
pi2dsh 把 Pi 上的一套工具链整体搬到 DeepSeek Harness,不用重建。 你在 Pi 上用的扩展、MCP 管理面、子代理、记忆、后台任务,在 DSH 上装的是同一个 npm 包,原样运行——web、终端、headless 都一样。 dsh plugin add pi2dsh # 装一次 dsh plugin add pi-mcp-adapter # 之后装你在 Pi 上已经在用的包,直接用 npm 原包 MCP 是验证得最深的一条:完整协议面——OAuth、resources、prompts、MCP Apps、 elicitation、sampling、cancellation——经 DSH 自己的审批和渲染跑通(证据矩阵)。端到端验证过的清单在现在到底哪些真能用,表外的不作声明。 版本支持明确对应: | pi2dsh | DSH 宿主 | 维护状态 | | 0.27.1 | 0.2.1-rc.2 / 0.2.1-rc.3 | 验收与发布状态 | | 0.27.0 | 0.2.1-rc.2 | 验收与发布状态 | | 0.26.0 | 0.2.0-rc.2 | 发布验收与缺凭证项目 | | 0.25.2 | 0.1.5-rc.2 | 保留检查点:发布验收 | Pi 扩展语义仍固定在 0.84.1。
展开完整文档(README 剩余 1 节)
pi2dsh

把 Pi 上的一套工具链整体搬到 DeepSeek Harness,不用重建。 你在 Pi 上用的扩展、MCP 管理面、子代理、记忆、后台任务,在 DSH 上装的是同一个 npm 包,原样运行——web、终端、headless 都一样。

dsh plugin add pi2dsh # 装一次
dsh plugin add pi-mcp-adapter # 之后装你在 Pi 上已经在用的包,直接用 npm 原包

MCP 是验证得最深的一条:完整协议面——OAuth、resources、prompts、MCP Apps、 elicitation、sampling、cancellation——经 DSH 自己的审批和渲染跑通(证据矩阵)。端到端验证过的清单在现在到底哪些真能用,表外的不作声明。

版本支持明确对应:

| pi2dsh | DSH 宿主 | 维护状态 |
| 0.27.1 | 0.2.1-rc.2 / 0.2.1-rc.3 | 验收与发布状态 |
| 0.27.0 | 0.2.1-rc.2 | 验收与发布状态 |
| 0.26.0 | 0.2.0-rc.2 | 发布验收与缺凭证项目 |
| 0.25.2 | 0.1.5-rc.2 | 保留检查点:发布验收 |

Pi 扩展语义仍固定在 0.84.1。新版引擎只针对已验证的当前 DSH 版本维护;旧版收到具体故障后,从对应发布 tag 分支修复。存量会话格式迁移由 DSH 负责。
未经验证的新宿主版本不会自动写入兼容声明。

两版宿主验证使用的终端为 @deepseek-harness-tui/dsh-tui@0.17.0 和 @xmoon76/dsh-pi-tui@0.5.3。旧 dsh-pi-tui 0.5.1 仍引用已删除的宿主组件,请升级终端,不要用 allow-version 绕过。

为什么有这个项目

DeepSeek Harness(DSH)的理念值得押注——可重建的持久会话日志、干净的服务组合、一条真能讲清楚的 agent 循环。它的插件生态还年轻。Pi 的生态已经成熟:几百个已发布的包,很多都有真实用户。

pi2dsh 是一层兼容层,把 Pi 的公开扩展 ABI 实现在 DSH 的原生服务之上,让 Pi 包 以发布的原样跑在 DSH 上——不 fork、不打补丁、不为每个包写适配器。对 Pi 的用户或团队,这意味着换宿主不用重建工具链;对 Pi 插件作者,这意味着多一个宿主而不用维护移植。对用户,一切是 DSH;对插件,一切是 Pi。

同时,pi2dsh 也是对 DSH 架构的一次覆盖完整能力面的持续实战检验。我们不是为单个插件打补丁,而是在检验:Pi 插件依赖的模型、工具、会话、交互、资源和客户端能力,能否只通过 DSH 对外开放的服务与扩展点,保持原有逻辑和生命周期。如果这些能力都能在 DSH 上成立,就说明 DSH 为构建 Agent 及其插件生态所设定的架构目标,至少在这个方向上经受住了真实生态的验证;任何必须旁路、降级或无法表达的地方,也会准确暴露它仍缺少的架构能力。

安装

一次引擎,之后想装谁装谁:

dsh plugin --profile web add pi2dsh
dsh plugin --profile web add pi-mcp-adapter

然后重启 dsh——插件在启动时挂载。

profile 必须带一个界面 bundle。 DSH 内置模板是 web 与 headless;产品自己装了界面的自定义 profile 也完全有效,例如 dsh-tui profile 里的 @deepseek-harness-tui/dsh-tui。只有随手新建、没装任何界面层的空 profile 才会启动后没人驱动;这种情况先把目标界面加进 dsh.profile.bundles。

就这一种方式。没有转换步骤,没有生成产物,不用构建。引擎会读出你 profile 里的 Pi 包(每一个都是你显式装的),用同一个桥实例挂载它们:一个模型目录、一个登录、一个凭证存储、一个升级单元。

日常操作:

| 要做的事 | 命令 |
| 装插件 | dsh plugin add <包名>(然后重启 dsh) |
| 卸插件 | dsh plugin remove <包名>——先卸插件,再卸引擎 |
| 升级插件 | dsh plugin add <包名>@latest,引擎不动 |
| 升级引擎 | dsh plugin add pi2dsh@latest,插件不动 |
| 升级前先体检 | npx pi2dsh inspect <包名>@<版本> |

两条安装期提示值得提前知道:

- ERR_PNPM_IGNORED_BUILDS:pnpm 默认拦截依赖的构建脚本。在 $DSH_HOME/profiles/web 里跑 pnpm approve-builds,或者把提示里的包在该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下设成 true,然后重跑 add。(这是你的决定权,桥不会绕过它。)
- 刚发版后 add 装到了旧版本:pnpm 的 minimumReleaseAge 会跳过刚发布不久的版本。显式钉版本即可:dsh plugin add pi2dsh@<版本>。

需要 Node.js 22.19+ 和 DeepSeek Harness。

引擎配置

引擎从自己的插件行读一个 config 块。目前只有一个 opt-in:

$DSH_HOME/profiles/<profile>/cordis.patch.yml
- id: pi2dsh
config:
serveNativeSubagents: true

serveNativeSubagents(默认关):让 DSH 原生子代理——DSH 自己的 subagent 委派所创建、会话带 subagent 血统的孩子——拿到 profile 里全部已发现的 Pi 包,挂在孩子自己的 agent scope 上:包的 tools/commands/ prompt 段只对这个孩子可见,孩子结束全部随之卸载。关掉时,这类孩子就是普通 DSH agent,与引入前完全一致。

挂载对孩子的首轮有保证:prompt 装配和工具执行都会等挂载落地,只跑一轮的孩子也能看到完整工具集。每个孩子恰好被伺候一次,不管 DSH 从哪条路径创建它——agent registry 报为 runtime root 的孩子由引擎的根挂载路径负责,其余由子代理路径负责,两边永不重叠。Pi 子代理桥(@tintinweb/pi-subagents 一类包经 subagent bridge 创建的孩子)不受这个开关影响:它们本来就走创建方包自己的 per-spawn loader 挂载,桥用 pi2dsh-sub- 会话 id 前缀认出它们(前缀在持久化 resume 后依然稳定),所以任何孩子都不会被挂两次。

走一遍:终端里的进阶 MCP

这个例子最能说明这座桥值什么。dsh-TUI 自带原生的 /mcp 命令(DSH 官方 MCP 客户端)——它能用,也原封不动。而 Pi 生态里有一个功能强得多的 MCP 利器:全屏服务器管理器、工具懒加载、用一个代理工具代替把几十个工具塞满模型上下文、用 JavaScript 编排多次 MCP 调用、OAuth 登录、资源与提示词。装上桥,这个包原样就能跑。

1. 安装

dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui # profile 已存在则跳过
dsh plugin --profile dsh-tui add pi2dsh
dsh plugin --profile dsh-tui add pi-mcp-adapter

然后重启 dsh——插件在启动时挂载。

2. 配置你的 MCP 服务器

在 dsh-TUI 里运行:

/pi-mcp setup

setup 流程可以把你已有宿主配置里的 MCP 服务器定义收编进 adapter 自己的标准 mcp.json。不存在任何桥专属的配置——你接触到的一切都是这个包自己的界面。

3. 用起来

/pi-mcp

打开全屏交互式服务器管理器——底栏写着启用/停用、重连、OAuth 登录的按键。模型经 DSH 的正常工具注册表拿到 adapter 的 mcp 与 mcpScript 工具;每个 Agent (包括 /new 开的)都有自己独立、完整连接的一份实例。

dsh-TUI 的原生命令保持独立,两者共存:

/mcp # 原生 DSH MCP 客户端状态 /pi-mcp # 装入的 Pi adapter 管理器

这条走查背后验证过什么:16 项宿主可影响的能力在 stock npm 全家桶上端到端跑通——三种真实传输、发现、代理与热加载直连工具、mcpScript、资源、提示词、MCP 图片变成真实 DSH 附件、MCP Apps、经 DSH 官方问答的工具审批、elicitation、对着真实 DSH 模型运行时的 sampling、取消与会话重启。完整证据矩阵: docs/mcp-compatibility.md。

完整可跑版本:examples/tui-mcp。

现在到底哪些真能用

分两级,这两件事不是一回事。

第一级——端到端实测过,配可跑示例

有人真坐下来,在真实 DSH loop 上用了这个插件的真功能,亲眼看到它工作。
要信就信这张表。

| 插件 | 验证了什么 | 在哪验的 | 示例 |
| @kassing/pi-vision | 图片委托给视觉模型;贴图伴生路由;分析结果注入纯文本模型的这一轮 | CLI + Web | vision-bridge |
| @crazygit/pi-codex-image-gen | ChatGPT/Codex OAuth 调 gpt-image-2 生图;本地参考图走 DSH 审批后上传;编辑图片;原生附件存储并在 Web 内直接显示 | CLI + Web | codex-image-gen |
| pi-btw | /btw <问题> 跑成 DSH 子代理界面里的真子会话;/btw-inject;/btw --save;主会话保持干净 | CLI + Web | side-conversation |
| pi-powerline-footer | 终端状态条(模型、思考档位、项目、上下文用量)以浮动状态胶囊画进 DSH web,带颜色 | Web | presentation-surfaces |
| pi-mcp-adapter | 完整管理界面画进 dsh-TUI;stdio/Streamable HTTP/SSE;发现、直连/代理/脚本调用、resources、prompts、图片、结构化内容、MCP Apps、审批、elicitation、sampling、取消与重启全部穿过 DSH 运行时;原生 /mcp 与 /pi-mcp 共存 | CLI + Web + dsh-TUI | tui-mcp · mcp-at-scale · 证据矩阵 |
| @tintinweb/pi-subagents | 模型自己带团队:派子代理(真宿主原生工具)、后台跑+完成通知、运行中转向、等结果、带记忆续命、真停得住的打断、/pi-agents 管理面、跨重启按归档身份重开——每个子代理都是原生 DSH 会话 | CLI + dsh-TUI | subagents · 验收报告 |
| pi-provider-alibaba | 阿里云 Token Plan(中国区)专属 key:实时目录、冷启动动态模型、完整工具闭环与重启均已实测;包也声明 Coding/API 路由,但必须分别使用不可混用的专属凭证 | CLI + Web | alibaba-token-plan |
| @ff-labs/pi-fff + pi-lens | 给模型真正的代码导航工具:ffgrep 内容搜索找到只存在于一个文件里的标记串,lsp_diagnostics 经真实的本地 typescript-language-server 报出植入的 TS2322 —— 两条都从会话日志里工具自己的结果断言 | CLI + Web | code-navigation |
| pi-vision-tool | 工具注册,且带一个 DSH 需要转换的 JSON Schema 形状(anyOf → oneOf) | CLI + Web | — |
| pi-approval-guardian | 每次工具调用先由第二个模型审批;放行与拒绝两条路都看到了 | CLI(裸环境) | — |
| pi-code | 仓库里的 Claude Code 配置原样在 DSH 生效:settings.json 的 env 与 PreToolUse hook、CLAUDE.md 的 @import(文本进入请求的系统提示,不是模型自己读文件)、.claude/skills、.claude/rules(常驻与 paths: 限定)、output styles、.mcp.json 服务器(pi-code 自有 MCP 客户端)、.claude/commands 的 /name 与 slash_command 工具两条路;首个会话在 DSH 原生问答卡里回答 "Trust this project?"。.claude/agents 的 Task 子代理不可用(pi-code 拉起的是 Pi CLI) | CLI + Web | claude-code-config |
| pi-hermes-memory | 跨会话记忆端到端:会话一 memory_add 写入,全新会话召回(代号不存在于任何其它输入);五个工具、十条 /memory- 命令、系统提示记忆注入 | CLI | persistent-memory |
| pi-background-tasks | 后台任务:bg_run 起 60 秒长任务,bg_logs 在任务仍在运行时读到前几行输出(最终行可证明地不在结果里) | CLI | background-tasks |

前两个的示例还没写。按本项目自己的规矩,补示例前必须重新端到端验证一遍,所以这张表如实标出今天谁有示例。

第二级——能挂载,且注册面能被探针调起来

Pi 目录里月下载量前 50 的包,每个都在真实 DSH 运行时里挂载,然后用黑盒探针调用。状态截至 2026-08-14;逐包的机器可读证据在 community/。

50 个里 47 个探针调用成功 · 1 个没有可探测面 · 2 个待复跑。

这一级不能说明的事:不能说明这个插件的真功能按你的用法能跑通。探针是拿合成参数去调一个注册面,用户跑的是一整条工作流。pi-btw 就是最好的反例——它在这张表里挂着"working"挂了好几周,而真实会话里 /btw <问题> 是直接失败的:这个功能需要补两个 ABI 缺口(Pi 公开可写的 AgentState.messages,以及给桥接命令声明输入描述符),而任何探针都不会碰到它们。两个缺口都在 0.11.0 修好了,而且都是通用修复——同样用法的插件一起解锁。

所以下面这张表请读成"桥覆盖了这个插件用到的面",而不是"这个插件已知可用"。
你要是试了哪个,不管成没成,反馈回来都有用。

| 能力 | 插件 |
| MCP | pi-mcp-adapter · pi-mcp-extension |
| 联网搜索与抓取 | pi-web-access · pi-deepseek-search · pi-web-search · @ollama/pi-web-search · @juicesharp/rpiv-web-tools |
| 代码导航与编辑 | pi-lens(ast-grep)· @narumitw/pi-lsp · pi-readseek · @ff-labs/pi-fff · pi-landstrip · pi-hashline-edit-pro¹ |
| 子代理与后台任务 | @gotgenes/pi-subagents · @mjasnikovs/pi-task |
| 记忆 | pi-hermes-memory · pi-goosedump |
| 计划与目标 | @narumitw/pi-goal · pi-goal-list-loop-audit · @narumitw/pi-plan-mode · @juicesharp/rpiv-todo |
| 问你 / 审批 | @juicesharp/rpiv-ask-user-question · pi-ask-user · @gotgenes/pi-permission-system · @juicesharp/rpiv-advisor |
| 侧边对话 | pi-btw · @narumitw/pi-btw |
| 模型与 provider | pi-provider-litellm · pi-llama-cpp · pi-prompt-template-model · @vigolium/piolium |
| 图像 | @kassing/pi-vision(见上文)· @amaster.ai/pi-image-gen |
| 外部集成 | @llblab/pi-telegram · pi-cursor-sdk² · @howaboua/pi-codex-conversion · pi-agent-browser-native² · pi-harness-runtime |
| 提示词与工作流 | pi-simplify · pi-fabric² · mitsupi · pi-cc-extensions · pi-rtk-optimizer · pi-interview¹ |
| 终端装饰 | pi-powerline-footer · @narumitw/pi-statusline · pi-zentui |
| 语音 | @juicesharp/rpiv-voice |
| 用量统计 | @alexanderfortin/pi-deepseek-usage³ |

¹ 能挂载,调用验证待复跑(装置侧的失败,不是包或桥的问题)。
² 业务逻辑真跑到底了,然后拒绝了合成的探针参数——属于正常工作、参数校验正确。
³ 纯事件钩子包:四个订阅全都挂上了,但每个处理器都要求有活的 DeepSeek 计费会话(它拉账单用量并渲染 footer),黑盒探针没有可安全断言的调用面。

前 50 之外的包不是另一类情况——桥里没有任何逐包代码。哪个包撞上 ABI 缺口,补上那个缺口,撞同一处的包一起解锁。

第一级是靠一个一个啃第二级长出来的。完整的验证阶梯、以及每一级分别能证明什么、不能证明什么:support matrix。

技术架构

三层,谁也不跨谁:

┌─ Pi 插件 ───────────────────────────────────────────────────┐ │ 原样的 npm 包。它看到的是一个完整的 Pi 宿主:三个 Pi 运行时 │ │ 导入、registerX、ctx.、33 个生命周期事件。它永远不知道 │ │ DSH 的存在。 │ └──────────────────────────┬──────────────────────────────────┘ │ Pi 的公开 ABI ┌──────────────────────────▼──────────────────────────────────┐ │ pi2dsh——翻译官,也是唯一同时懂两边词汇的地方。目录投影、 │ │ 事件桥、会话与子代理桥、凭证、vendored 的 Pi 逻辑。 │ └──────────────────────────┬──────────────────────────────────┘ │ 一个普通 DSH 插件 + llm adapter ┌──────────────────────────▼──────────────────────────────────┐ │ DeepSeek Harness。它只看到一个普通插件,永远不知道 Pi 的存在。│ └─────────────────────────────────────────────────────────────┘

DSH 有两半,桥也有两半。上面那根柱子是服务端;浏览器壳有自己的插件面, 当一个 Pi 能力是"形态"而不是"行为"时,它落在那边:

┌──────────── DSH 服务端(cordis) ───────────┐ ┌──────── DSH 浏览器壳 ────────────┐ │ 服务 · waterfall · durable 事件 │ │ dsh.client + exports "./client" │ │ │ │ slot 注册表(ui-slots) │ │ pi2dsh 引擎 │ │ shell.overlay ← 浮层与 pill │ │ 工具 · 命令 · 模型 · 会话 │ │ header.utilities ← 头部文本 │ │ 子代理桥 ───────────────────┐ │ │ overlay ← widget 胶囊/卡片 │ │ browser-state 注册表 │ │ │ composer.dock ← working/底部 │ │ GET /pi2dsh/browser-state┼── 自有通路 ─┼──┼─▶ 四个座位,共用一个轮询 │ └───────────────────────────────┴─────────────┘ └───────────────────────────────────┘

浏览器半边用的数据走本包自己的路由,不走 DSH 的 typed Remote 体系——那是一等公民的代码生成契约,仓外插件跟自己的 UI 说话就该自带通道。一个会话一个 payload,喂给所有座位:侧边对话浮层,以及 Pi 的呈现面(status、widget、header、 footer、title、working/thinking 类),都画在宿主自己的 slot 座位里,而不是再造一套。另外两条宿主规则决定浏览器半边能不能被装载:包必须导出 ./package.json (宿主按子路径解析清单),./client 产物必须是闭包工厂格式而不是普通 ESM。

保证它靠谱的几条标准:

- DSH 已经有的东西,桥绝不再造一遍。 工具进 DSH 的工具注册表,模型进 DSH 的 llm 配置,只有 server 配置的 MCP 交给 dsh-mcp-client,skills 交给 dsh-skill-filesystem,提问交给 DSH 的 user questions。用户显式安装的 Pi 能力包可以保留它自己拥有的行为;桥只映射公开宿主面,不抄它的 transport。
- 不造桥私有的用户世界。 常规配置仍然全是 DSH 形状:DSH 设置、命令、凭证。
如果一个被安装的能力包明确自带管理面,就保留并在冲突时标出来源——dsh-TUI 的 /mcp 不动,Pi 管理面叫 /pi-mcp。
- 零逐包特判。 核心里没有任何 if (packageName === …)。修一个 ABI 缺口,撞上它的包一起解锁。
- 绝不伪装成功。 映射不了的能力会如实告诉你——同一个插件同一项能力只提示一次,讲人话。如果某个插件在启动期就需要这样一项能力,它会被整包标成不可用并给出卸载建议,而不是半死不活地跑着。
- 验证过才算数。 每项能力都有公开 API 契约测试,并且必须在它声称支持的每个 DSH 界面上端到端跑通才会发布。

这件事正在检验 DSH 什么

pi2dsh 同时也是一套会执行的 DSH 插件架构压力测试。Pi 提供的不是为了迎合 DSH 现编的几个 demo,而是一套已经被大量真实插件用过的公开 ABI,所以它很适合回答: DSH 所说的“能力由插件自由组合”,到底走到了哪一步。

目前的结论不是简单的“好”或“不好”,而是边界已经很清楚:

- DSH 对替换一整项能力的公开 seam 是成立的:工具、命令、模型 adapter、用户提问、原生子会话和浏览器 slot 都已经承接住真实 Pi 能力。
- 压力集中在从内部扩展已有能力:仓外插件新增一种持久会话事件、拦截真正发出的 provider 请求/响应、在压缩发生前改变决定、在项目资源加载前参与 trust。
- pi2dsh 用 sidecar 或另一条 adapter 把功能绕通,对用户有价值,但不能算 DSH 原生架构已经承接成功。我们会把“功能可用”和“宿主 seam 完整”分开记。

一个容易说错的例子:pi-btw 的回答已经是真正的 DSH child session,宿主能打开、续聊和恢复;sidecar 存的是 Pi 自定义 entry 等 DSH 仓外插件目前无法写进原生日志的事实,不是把整个子会话伪造了一遍。模型侧也一样:自带 transport 的 Pi provider 可以注册成原生 DSH route;从 DSH rc.8 开始,只声明目录的 provider 也有了完整的官方路径——pi2dsh 把端点、输入模态、推理档位和 DSH 已开放的协议 compat 翻译成 llm-pi-ai profile,真正请求仍由 DSH 发出。

项目采用一套统一的 Pi → DSH 架构映射标准:具体 Pi 接口 → Pi 能力契约 → DSH 承载机制 → 公开 seam → 真实插件验证 → 五级结论。
这是一条统一推理路线,不是额外的运行时层。可扩展的分支维护在手写的架构模型知识树,真实场景维护在逐插件验证记录,三类总体判断维护在架构结论。架构分类不再由 JSON 总账或生成器产生;曾经观察到的 111 条 Pi 规则和 45 个 DSH 子系统只是带版本的调查快照,不是固定总量或完整性证明。这样既能从总体看到已经适配的能力,也能从任一插件下钻到它用了哪些 Pi 能力、落到哪些 DSH 机制、实际达到哪一级。
当前仍有 4 个 DSH 缺口;第 5 个历史缺口 DSH-ARCH-002 已由 rc.8 上游修复。
这些是已经坐实的发现,不是已经完成全覆盖。
当前已向上游提交的实证包括 #2708:让仓外插件安全写持久事件和 #3076:llm-pi-ai 丢 provider compat 字段;后者已在 rc.8 的 profile schema 中解决。

Pi 的开放能力在 DSH 上怎么落

Pi 包能碰到的每一个面,以及它落到 DSH 的什么位置。下面这些表是从桥在运行时真正查的那份规则生成的,所以不会和代码脱节。

针对固定的 Pi 0.84.1,当前生成目录有 111 条上游形状的规则行。桥另外保留了一个兼容扩展 unregisterTool;它会出现在工具细表中,但明确不计入这个总数。
sessionManager 等嵌套对象仍可能用一行代表多个方法,架构审计会明确写出这层边界。

| 能力域 | Pi 面数 | 状态 |
| 工具 | 11 | 2 语义一致 · 9 已映射并写明差异 |
| 命令、flag、编辑器输入 | 13 | 13 已映射并写明差异 |
| 消息、上下文、agent 循环 | 20 | 9 语义一致 · 11 已映射并写明差异 |
| 会话与侧边对话 | 24 | 6 语义一致 · 18 已映射并写明差异 |
| 模型、provider、凭证 | 15 | 1 语义一致 · 12 已映射并写明差异 · 2 不提供 |
| 向用户提问与渲染 | 24 | 5 语义一致 · 19 已映射并写明差异 |
| 项目环境与资源 | 4 | 2 语义一致 · 1 已映射并写明差异 · 1 不提供 |
| 合计 | 111 | 25 语义一致 · 83 已映射并写明差异 · 3 不提供 |

另外还有 Pi 三个运行时包(pi-coding-agent、pi-tui、pi-ai)的 203 个导入符号,由 vendored 或 headless shim 提供——所以插件自己钉的 Pi 版本永远不会被加载,清单见导入的 Pi 运行时符号。

每个能力域页面对每一个面都写清它属于哪个 Pi 能力契约、理论上应落到哪个 DSH 承载机制与公开 seam、当前代码实际上如何实现。新增接口如果没有能力归属或理论映射,文档检查会直接失败。

从能力索引开始看。机器可读版:pi2dsh matrix --json。

用订阅登录也能用:DSH 本身只提供静态 HTTP header,桥补上了 Pi 生态的交互式 OAuth 层。任何声明了 oauth 块的 Pi provider 包都会得到一条可用的 /login <provider>,跑的是这个包自己的协议代码——Pi 官方的四条流程(OpenAI Codex、Anthropic、GitHub Copilot、Kimi Code)内置;只装好引擎、还没装第一个社区 Pi 包时也能直接使用。凭证按 Pi 的 auth.json 语义持久化,并通过标准的 dsh-credentials provider 按请求解析,所以你的订阅能驱动 DSH 原生 llm 路径上的真实调用。细节见模型。

哪些是刻意不提供的,以及为什么:运行时装包和独立模型运行时属于宿主及其安全门;provider 的 payload/header/response 拦截应该写成 DSH llm adapter;项目信任是宿主的决定。见模型与项目环境。

我们自己欠的那一块:插件自绘卡片。Pi 插件可以自带渲染器,目前这类注册我们接下来但不调用,所以这种笔记会显示成原生的上下文注入行——内容你和模型都拿得到,只是没有插件自己的样式。客户端半边已经在并占着四个座位(侧边对话浮层、头部、 widget 区、working 区)——卡片渲染器正是它还没画的那部分。

示例

每一项验证过的能力都配一个完整可跑的 example。example 里的每条命令都在真实 DSH loop 上实际跑过才会进来。

| 示例 | 你能得到什么 |
| vision-bridge | 纯文本模型回答图片问题——CLI 与 Web 双端,附探针图 |
| codex-image-gen | 用 ChatGPT/Codex 订阅生图和改图,包含 DSH 上传审批与 Web 内直接显示结果 |
| side-conversation | /btw <问题> 在 DSH 原生子代理界面里开一条侧边线程,主会话保持干净 |
| presentation-surfaces | 真插件(pi-powerline-footer)的终端界面画进 DSH Web 座位,附 top50 里哪些 Pi 插件会画界面 |
| subscription-login | 用 ChatGPT / Claude / Copilot / Kimi 订阅账号当 DSH 的模型:/login、登录后自动建路由与凭证 |
| gateway-compat | 私有 / 国内 / 代理网关拒收 developer 角色:rc.8 官方 profile 如何把 Pi compat 声明送到真实请求(附透传录制代理) |
| alibaba-token-plan | 通过原版 Pi provider 使用阿里云百炼 Plan;包含动态 DeepSeek 模型、工具闭环与重启验证 |
| custom-gateways | 按 DSH 官方方式接任何 OpenAI 兼容网关,每个 Pi 插件都能看到它 |
| code-navigation | 给模型真正的代码导航工具——在自带样例工程上跑模糊内容搜索与语言服务器诊断,CLI 与 Web 双端 |
| tui-mcp | 保留 dsh-TUI 原生 /mcp,把 Pi 生态管理面作为 /pi-mcp 加进来,并让完整的宿主相关 MCP 功能面穿过 DSH 运行时 |
| mcp-at-scale | 真实 51 工具 MCP 服务器藏在 adapter 两个元工具后面:工具面有界、规模化懒发现、超时预算把 ~120 秒工具砍到秒级——CLI 与 Web 双端 |
| claude-code-config | pi-code 在 DSH 上读取项目的 .claude/:信任问答、settings env、PreToolUse hook、CLAUDE.md @import、skills——headless 与 web,两条 DSH 线 |
| persistent-memory | pi-hermes-memory 跨两个真会话:一处存事实、全新会话召回——含安装真正需要的构建审批步骤(better-sqlite3) |
| background-tasks | pi-background-tasks:后台起长任务、任务仍在跑时读它的输出——"实时跟踪"性质从工具自己的结果断言 |
| subagents | 模型自己带小团队:派单、后台、中途转向、收结果、带记忆续命、真停得住、/pi-agents 管理、跨重启重开 |

其它工具

除了引擎,CLI 还有几个辅助命令:

npx pi2dsh inspect <包名>@<版本> # 升级前的兼容性报告
npx pi2dsh matrix --json # 完整能力矩阵
npx pi2dsh mcp-config # Pi 的 mcpServers 配置 → DSH 官方 MCP 条目

开发与验证

pnpm verify # 类型检查 + 契约测试 + 打包检查
pnpm audit:community # 前 50 静态筛查
pnpm test:community # 深度运行时 + 官方插件管理器 + e2e
DEEPSEEK_API_KEY=… pnpm test:live # 真实模型验收(key 只从环境读)
CODEX_AUTH_FILE=… pnpm test:codex-image # 真 OAuth 生图 + 参考图编辑 + Web 像素显示

逐项能力的验收证据:docs/acceptance.md。
工作标准:CLAUDE.md 与 docs/STANDARDS.md。

许可

MIT。vendored 的 Pi 源码保留其上游 MIT 许可(src/compat/vendor/PI-LICENSE);生成的 bundle 保留拷贝过来的上游许可与声明文件。
预览分享卡片长什么样 ↓weijiafu14/pi2dsh
分享 · DeepSeek Harness Hub
weijiafu14/pi2dsh

打通 Pi 与 DSH 生态:一套 Pi Host ABI 就能让未经修改的 Pi 扩展作为原生 DSH 插件运行。

回到顶部