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

第二引擎 fengyu-12/dsh-second-engine

DeepSeek 客户端
fengyu-1231 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

接入 Codex 作为第二智能体,异步派单并双向互审

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

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

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/fengyu-12/dsh-second-engine.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
生态插件
可安装,未声明 dsh 能力
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 31 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

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

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

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

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

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 209 处,展示前 96 条)
  • 读取本地文件
    src/index.js:90
    accessSync(candidate, constants.X_OK)
  • 读取本地文件
    src/index.js:109
    const fromFile = readFileSync(join(homedir(), '.dsh', 'second-engine-mode.txt'), 'utf8').trim()
  • 读取密钥环境变量
    src/index.js:146
    const accounting = budgetGate(TOKEN_LEDGER_PATH, process.env.SECOND_ENGINE_TOKEN_BUDGET)
  • 读取本地文件
    src/index.js:213
    for (const group of readdirSync(root)) {
  • 读取本地文件
    src/index.js:215
    if (existsSync(candidate)) return candidate
  • 读取本地文件
    src/index.js:229
    const saved = JSON.parse(readFileSync(POLICY_WATCH_STATE_PATH, 'utf8'))
  • 写入本地文件
    src/index.js:246
    mkdirSync(dir, { recursive: true, mode: 0o700 })
  • 执行外部命令来自配置/变量
    src/index.js:268
    child = execFile(POLICY_WATCH_PYTHON, [POLICY_WATCH_PATH, '--json', '--session', String(sessionId)], { timeout: POLICY_WATCH_TIMEOUT_MS, maxBuffer: 25
  • 读取本地文件
    src/index.js:285
    const stat = statSync(path)
  • 执行外部命令来自配置/变量
    src/index.js:297
    const probe = spawnSync(POLICY_WATCH_PYTHON, ['--version'], { encoding: 'utf8', timeout: 3000 })
  • 读取密钥环境变量
    src/index.js:356
    const NOTIFY_TOKEN_PATH = process.env.DSH_BRIDGE_TOKEN_PATH || join(process.env.DSH_HOME || join(process.env.HOME || '/root', '.dsh'), '.bridge_token'
  • 写入本地文件
    src/index.js:578
    writeFileSync(tmp, text, { encoding: 'utf8', mode })
  • 写入本地文件
    src/index.js:579
    chmodSync(tmp, mode)
  • 写入本地文件
    src/index.js:581
    renameSync(tmp, path)
  • 删除本地文件
    src/index.js:583
    try { unlinkSync(tmp) } catch { }
  • 写入本地文件
    src/index.js:589
    mkdirSync(CODEX_DIR, { recursive: true })
  • 写入本地文件
    src/index.js:747
    mkdirSync(dirname(path), { recursive: true })
  • 删除本地文件
    src/index.js:954
    try { unlinkSync(join(CODEX_DIR, name)) } catch { }
  • 执行外部命令固定命令
    src/index.js:1199
    execFile('bash', [BOARD_SCRIPT, ...args], { env: process.env, timeout: 15000 }, (error, stdout, stderr) => {
  • 读取密钥环境变量
    src/index.js:1493
    const headers = process.env.PI_BRIDGE_TOKEN ? { 'x-bridge-token': process.env.PI_BRIDGE_TOKEN } : undefined
  • 发起网络请求
    src/index.js:1496
    const r = await fetch(`http://127.0.0.1:${PI_BRIDGE_PORT}/api/runtime-settings`, { headers, signal: ctl.signal })
  • 删除本地文件
    src/index.js:1888
    unlinkSync(full)
  • 删除本地文件
    src/index.js:2039
    try { unlinkSync(tmp) } catch { }
  • 读取密钥环境变量
    src/index.js:2091
    configured: (kr !== null && kr.id === 'deepseek') || builtinKey !== '' || Boolean(process.env.DEEPSEEK_API_KEY),
  • 删除本地文件
    src/index.js:2190
    try { unlinkSync(join(dir, name)); removed.push(name) } catch { }
  • 发起网络请求
    src/index.js:2425
    const r = await fetch(urls[k], {
  • 发起网络请求
    src/index.js:2821
    const r = await fetch(url, {
  • 删除本地文件
    src/index.js:2897
    unlinkSync(full)
  • 执行外部命令固定命令
    src/index.js:2971
    const out = spawnSync('df', ['-B1', dir], { timeout: 3000 })
  • 发起网络请求
    src/index.js:3332
    const req = http.get(url, (resp) => { resp.on('error', () => { }); resp.resume() })
  • 执行外部命令固定命令
    src/index.js:3378
    execFile('git', ['status', '--porcelain'], { cwd: workdir, timeout: 20000 }, (err, stdout) => {
  • 执行外部命令固定命令
    src/index.js:3570
    execFile('git', args, { timeout: 60000, ...opts }, (err) => { resolve(err === null) })
  • 发起网络请求
    src/index.js:5521
    const resp = await fetch(CODEX_RELEASES_URL, {
  • 发起网络请求
    src/index.js:5537
    const resp = await fetch(PI_NPM_LATEST_URL, {
  • 发起网络请求
    lib/client.js:273
    const r = await fetch(path)
  • 发起网络请求
    lib/client.js:281
    const r = await fetch(path, {
  • 发起网络请求
    lib/client.js:294
    const r = await fetch(path, { method: 'DELETE' })
  • 写入本地文件
    src/process-safety.js:37
    mkdirSync(dir, { recursive: true, mode: 0o700 })
  • 执行外部命令固定命令
    src/process-safety.js:58
    const child = spawn('sh', ['-c', script, 'second-engine', command, ...args], { ...options, cwd: workdir, shell: false })
  • 读取本地文件
    src/token-ledger.js:17
    try { raw = readFileSync(path, 'utf8') } catch (e) { if (e.code === 'ENOENT') return { knownTokens: 0, unknown: 0, jobs: 0 }; throw e }
  • 写入本地文件
    src/token-ledger.js:31
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    src/token-ledger.js:32
    appendFileSync(path, JSON.stringify({ id, source, usage: unknown ? 'unknown' : 'known', tokens, at: new Date().toISOString() }) + '\n', { mode: 0o600
  • 读取本地文件
    src/bridge-routes.js:53
    accessSync(script, constants.X_OK)
  • 读取本地文件
    src/engine/registry.js:86
    const v = readFileSync(ENGINE_FILE, 'utf8').trim()
  • 写入本地文件
    src/engine/registry.js:94
    mkdirSync(dirname(ENGINE_FILE), { recursive: true })
  • 写入本地文件
    src/engine/registry.js:95
    writeFileSync(ENGINE_FILE, `${id}\n`, 'utf8')
  • 读取密钥环境变量
    src/engine/pi-probe.js:20
    const KEYRING = process.env.CODEX_KEYRING || join(homedir(), '.codex', 'keyring.json')
  • 读取本地文件
    src/engine/pi-probe.js:31
    try { kr = JSON.parse(readFileSync(KEYRING, 'utf8')) } catch { }
  • 读取本地文件
    src/engine/pi-probe.js:34
    try { settings = JSON.parse(readFileSync(join(homedir(), '.pi', 'agent', 'settings.json'), 'utf8')) } catch { }
  • 读取本地文件
    src/engine/pi-probe.js:36
    try { models = JSON.parse(readFileSync(PI_MODELS_PATH, 'utf8')) } catch { }
  • 执行外部命令固定命令
    src/engine/pi-probe.js:62
    child = spawn('sh', ['-lc', 'command -v pi'], { stdio: ['ignore', 'pipe', 'ignore'] })
  • 执行外部命令来自配置/变量
    src/engine/pi-probe.js:75
    try { child = spawn(bin, ['--version'], { stdio: ['ignore', 'pipe', 'pipe'] }) } catch { return resolve('') }
  • 执行外部命令来自配置/变量
    src/engine/pi-probe.js:108
    child = spawn(bin, args, { stdio: ['pipe', 'pipe', 'pipe'], env })
  • 读取本地文件
    src/engine/pi-probe.js:151
    modelsJsonExists = existsSync(PI_MODELS_PATH)
  • 读取本地文件
    src/engine/pi-probe.js:153
    const j = JSON.parse(readFileSync(PI_MODELS_PATH, 'utf8'))
  • 发起网络请求
    src/engine/pi-bridge.js:15
    const response = await fetch(`http://127.0.0.1:${PI_BRIDGE_PORT}/api/health`, { signal: controller.signal })
  • 读取本地文件
    src/engine/pi-bridge.js:32
    if (!existsSync(script)) return { ok: false, error: `找不到聊天桥脚本:${script}` }
  • 写入本地文件
    src/engine/pi-bridge.js:37
    try { mkdirSync(bridgeCwd, { recursive: true }) } catch { }
  • 执行外部命令来自配置/变量
    src/engine/pi-bridge.js:38
    const proc = spawn(process.execPath, [script], {
  • 读取密钥环境变量
    src/engine/pi-bridge.js:57
    const headers = process.env.PI_BRIDGE_TOKEN ? { 'x-bridge-token': process.env.PI_BRIDGE_TOKEN } : undefined
  • 发起网络请求
    src/engine/pi-bridge.js:58
    const response = await fetch(`http://127.0.0.1:${PI_BRIDGE_PORT}/api/shutdown`, { method: 'POST', headers, signal: controller.signal })
  • 发起网络请求
    src/bridge.js:314
    const upstream = await fetch(`${base}/chat/completions`, {
  • 发起网络请求
    src/bridge.js:534
    const resp = await fetch(`http://127.0.0.1:${bridge.address().port}/responses`, {
  • 发起网络请求
    src/bridge.js:566
    const nf = await fetch(`http://127.0.0.1:${bridge.address().port}/other`, { method: 'POST' })
  • 发起网络请求
    src/bridge.js:572
    const bad = await fetch(`http://127.0.0.1:${badBridge.address().port}/responses`, {
  • 写入本地文件
    src/app-server-client.js:151
    try { writeFileSync(outPath, parts.join('')) } catch { }
  • 读取本地文件
    src/collab.js:22
    try { text = readFileSync(COLLAB_PATH, 'utf8') } catch { return {} }
  • 写入本地文件
    src/collab.js:65
    mkdirSync(dirname(COLLAB_PATH), { recursive: true })
  • 写入本地文件
    src/collab.js:67
    writeFileSync(tmp, text, { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/collab.js:68
    chmodSync(tmp, 0o600)
  • 写入本地文件
    src/collab.js:70
    renameSync(tmp, COLLAB_PATH)
  • 删除本地文件
    src/collab.js:72
    try { unlinkSync(tmp) } catch { }
  • 读取本地文件
    src/engine/runtime.js:24
    const m = statSync(p).mtimeMs
  • 读取本地文件
    src/engine/pi-rpc-runtime.js:138
    const m = statSync(p).mtimeMs
  • 写入本地文件
    src/engine/pi-rpc-runtime.js:276
    try { writeFileSync(runOutPath, lastAssistantText, 'utf8') } catch { }
  • 读取密钥环境变量
    bridge/pi/server.mjs:26
    const TOKEN = process.env.PI_BRIDGE_TOKEN || ''
  • 执行外部命令来自配置/变量
    bridge/pi/server.mjs:44
    this.child = spawn(PI_BIN, args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
  • 读取本地文件
    bridge/pi/server.mjs:153
    const html = readFileSync(page, 'utf8')
  • 执行外部命令来自配置/变量
    bridge/web/selftest.mjs:25
    const v = execFileSync(process.env.CODEX_BIN || 'codex', ['--version'], { encoding: 'utf8' }).trim();
  • 发起网络请求
    bridge/web/selftest.mjs:34
    const r = await fetch(`${AS_HTTP}/readyz`, { signal: AbortSignal.timeout(3000) });
  • 发起网络请求
    bridge/web/selftest.mjs:96
    const r = await fetch(`http://127.0.0.1:${PORT}/api/health`, { signal: AbortSignal.timeout(3000) });
  • 读取本地文件
    bridge/web/server.mjs:60
    const existing = (await readFile(TOKEN_FILE, 'utf8')).trim();
  • 写入本地文件
    bridge/web/server.mjs:66
    await writeFile(TOKEN_FILE, `${fresh}\n`, { mode: 0o600 });
  • 写入本地文件
    bridge/web/server.mjs:70
    await mkdir(STATE_DIR, { recursive: true });
  • 写入本地文件
    bridge/web/server.mjs:71
    await mkdir(WORKDIR, { recursive: true });
  • 读取本地文件
    bridge/web/server.mjs:79
    for (const [k, v] of Object.entries(JSON.parse(await readFile(OWN_FILE, 'utf8')))) owned.set(k, v);
  • 写入本地文件
    bridge/web/server.mjs:92
    writeFile(OWN_FILE, JSON.stringify(obj), { mode: 0o600 }).catch(() => {});
  • 读取本地文件
    bridge/web/server.mjs:235
    pids = await readdir('/proc');
  • 读取本地文件
    bridge/web/server.mjs:243
    fds = await readdir(`/proc/${pid}/fd`);
  • 读取本地文件
    bridge/web/server.mjs:250
    target = await readlink(`/proc/${pid}/fd/${fd}`);
  • 读取本地文件
    bridge/web/server.mjs:265
    const fromFile = (await readFile(AS_PID_FILE, 'utf8')).trim();
  • 执行外部命令固定命令
    bridge/web/server.mjs:448
    execFileSync('python3', ['-c', 'import sys,tomllib; tomllib.loads(sys.stdin.read())'], { input: text, stdio: ['pipe', 'ignore', 'pipe'] });
  • 写入本地文件
    bridge/web/server.mjs:589
    copyFileSync(CODEX_CONFIG, backup);
  • 写入本地文件
    bridge/web/server.mjs:590
    writeFileSync(tmp, updated, 'utf8');
  • 删除本地文件
    bridge/web/server.mjs:595
    try { unlinkSync(tmp); } catch { }
  • 发起网络请求
    bridge/web/server.mjs:752
    const response = await fetch(new URL('/second-engine/api/models', HOST_API_URL), {
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 异步派单
  • 双向互审

把 OpenAI Codex CLI 接入 DeepSeek Harness 当第二引擎:异步工单、双向互审、终端↔网页共享会话桥、文件任务板 · 把 Codex CLI 作为第二个智能体接入 DeepSeek Harness。

README

由 DeepSeek 最新模型翻译生成
dsh-second-engine 英文:将 OpenAI Codex CLI 作为第二智能体接入 DeepSeek Harness——异步工单、对抗式审查、共享的终端↔Web 会话桥接,以及基于文件的任务看板。设计说明位于 docs/。 🚀 你的 AI 也能拥有自己的 AI:装一个插件、再装个 223MB 的 Codex CLI,你的 DeepSeek Harness 里就多了一位 7×24 待命的第二引擎——主 AI 干累了可以「让 Codex 试试」,写完代码自动互审挑刺,卡住还会主动喊救命。一个手机,两颗大脑,你只管裁决。 | 平台 | 状态 | | Android(DSHA) | ✅ 实测全功能(免 ROOT 免 Termux:DSHA 下载 · 项目主页)| | Windows / macOS(DSH 桌面壳) | ⚠️ 理论兼容未实测(插件为纯 Node;需换装 x86_64 版 Codex 二进制;App 通知能力视宿主而定)。桌面壳参考:desk-harness / deepseekharness-desktop | 占用账单(安卓实测):Codex 本体 223MB + 数据 33MB + 插件依赖 0.3MB ≈ 256MB,换一个独立执行、互审、救场的第二 Agent。
展开完整文档(README 剩余 1 节)
dsh-second-engine

英文:将 OpenAI Codex CLI 作为第二智能体接入 DeepSeek Harness——异步工单、对抗式审查、共享的终端↔Web 会话桥接,以及基于文件的任务看板。设计说明位于 docs/。

🚀 你的 AI 也能拥有自己的 AI:装一个插件、再装个 223MB 的 Codex CLI,你的 DeepSeek Harness 里就多了一位 7×24 待命的第二引擎——主 AI 干累了可以「让 Codex 试试」,写完代码自动互审挑刺,卡住还会主动喊救命。一个手机,两颗大脑,你只管裁决。

| 平台 | 状态 |
| Android(DSHA) | ✅ 实测全功能(免 ROOT 免 Termux:DSHA 下载 · 项目主页)|
| Windows / macOS(DSH 桌面壳) | ⚠️ 理论兼容未实测(插件为纯 Node;需换装 x86_64 版 Codex 二进制;App 通知能力视宿主而定)。桌面壳参考:desk-harness / deepseekharness-desktop |

占用账单(安卓实测):Codex 本体 223MB + 数据 33MB + 插件依赖 0.3MB ≈ 256MB,换一个独立执行、互审、救场的第二 Agent。

把 OpenAI Codex CLI 接入 DeepSeek Harness(DSH),作为与主 AI 并行的第二引擎。

在同一台 Android 设备的 DSHA 容器(Ubuntu / proroot)里,主 AI(DSH)与 Codex 双引擎并存:主 AI 编排与审核,Codex 独立复核、并行下探、提供异构第二意见——三者各司其职:Codex 生产 · 主 AI 编排审核 · 用户裁决。

功能

- 设置面板(DSH 设置 → 第二引擎)
- 引擎状态灯:Codex 二进制 / config.toml / API Key(纯本地探测,不上报)
- 模型提供方管理:预设 DeepSeek / 智谱(端点均经实测),支持添加任意自定义提供方(Provider ID / 名称 / API 地址 / 协议 / 模型),可编辑、填 Key、切换激活
- 模型获取与选择:一键从提供方拉取可用模型列表,下拉切换
- 会话清理:按保留天数清理 Codex 会话文件
- 浮动球:会话界面常驻半露小球,点开即达状态摘要与工单入口
- 异步工单:面板直接下发任务(后台 codex exec),完成经 App 通知提醒,支持取消
- 双向互审:把产出交给 Codex 做多轮批判性复核(轮数上限 / 零新增提前收敛 / 分歧打包上交用户 / 任意时刻熔断),轮数在面板设置
- 卡点求助协议:插件自动向 ~/.codex/AGENTS.md 写入协作协议——Codex 连续失败或绕圈时主动调 /api/consult 提交卡点并通知,主 AI 带建议接手
- 本地翻译桥(实验性):responses → chat completions 协议转换,让仅有 chat 协议的提供方(云知声 / 部分中转)也能接入 Codex
- 知识层:随包分发 SKILL.md,主 AI 自动获得唤起与协作流程(工单下发、diff 审查、异常速查)

环境单例 —— 安卓上的 PC 式双引擎

Android 的沙箱模型把环境切碎了:每个 App 各自下载依赖、各部署一遍。本插件逆着这个模型,把 DSHA 容器变成双引擎共用的环境单例:

- Codex 不自带依赖:工单里跑的每条命令直接使用容器现成的 Python / Node / 工具链 / 词库(55 个沉淀脚本即取即用),插件本体仅 0.3MB——它只是环境的调度器,不是又一轮部署;
- 双引擎互为备份:主 AI 装插件/写配置出问题时,Codex 有完整 shell 可以接手排查修复;反过来 Codex 卡住走 consult 协议喊救命,主 AI 带建议回来——谁都能救谁。

双引擎分工

| 角色 | 职责 |
| Codex(第二引擎) | 生产:exec 小粒度工单、独立复核、并行下探 |
| DSH 主 AI | 编排:拆任务、布置环境、审 diff、落主区 |
| 用户 | 裁决:分歧点上交,最终取舍由人 |

协作看板(dual-board,v1.3 · 2026-09-27)

长任务协作走文件看板。脚本与协议随插件:tools/dual-board/(board.sh / board_tool.py / dispatch.sh / PROTOCOL.md,2026-09-30 收编、路径自定位);运行数据默认仍在 ~/proj/dual-board/,可用 DUAL_BOARD_DIR 覆盖(Codex 侧由 ~/proj/AGENTS.md 自动注入,无需人工提醒):

- 写板唯一入口 board.sh(v1.2 起):CAS + O_EXCL 锁 + 原子写 + 状态机 + JSONL 归档 + doctor 体检都在工具里,手改 board.json 视为破坏协议。退出码即语义(3 CAS 冲突 / 4 依赖未完成 / 5 状态非法 / 6 锁超时 / 7 scope 越界 / 8 无 git 降级)。
- 派单一律走 dispatch.sh:自动注入收件箱未读段、附派单纪律、在派单前记基线 commit 与工作区脏快照到 .dispatch/.base;交付后用 board.sh scope-check 机械校验写范围越界。
- 看板:board.json —— 状态 pending / in_progress / blocked / completed / abandoned(无 reopen,completed 不可复改);工单三字段:task(用户原话,不可变)/ context(主 AI 解读)/ frame(质疑前提是分内事,由工具自动写入)。
- 收件箱:inbox-codex.md / inbox-lead.md 双向留言(经 board.sh inbox-append),已读标注 + 按月 JSONL 转档(archive/);交付必含四段:交付 / 异议位 / 下一步建议 / 自验。
- 双通道:短任务走 /api/task(ephemeral 零记忆,保互审独立性);长任务 codex exec resume 成员会话(一条链绑一个任务,看板 session 字段登记,收工归档)。
- 可视化:浏览器聊天桥(3095)的会话列表抽屉 = 成员会话切换,终端/网页/主 AI 三方同链。
- ⚠️ 已知限制:脏工作区下 scope 校验会漏报(协议 §十一);给 Codex 的 workdir 尽量干净。

前置要求

- DeepSeek Harness(新版 App + Web)
- OpenAI Codex CLI ≥ 0.155.1

安装 Codex CLI(Android proot / aarch64 Linux)

到 Releases 页 下载 aarch64-unknown-linux-musl 资产(musl 静态二进制在 proot 下可直接运行),解压后放进 PATH:

curl -LO https://github.com/openai/codex/releases/latest/download/
tar -xzf && install -m755 codex /usr/local/bin/codex
codex --version # 验证

GitHub 直连慢/失败:在下载 URL 前加镜像前缀(如 https://ghproxy.net/https://github.com/...)。资产文件名以 Releases 页实际列表为准。

安装

1. 放置插件实体
cp -a dsh-second-engine
cd && npm install --omit=dev

2. 注册进 profile(link: 方式,指向插件实体目录)
cd ~/.dsh/profiles/web
package.json: dependencies 加 "dsh-second-engine": "link:" dsh.profile.bundles 数组加 "dsh-second-engine"
pnpm install

3. 重启 Web,设置页出现「第二引擎」分区即成功

配置

1. 设置 → 第二引擎 → 模型提供方 → 选择预设(DeepSeek / 智谱)或添加自定义;
2. 填入 API Key(保存于 ~/.codex/keyring.json,0600 权限,界面仅脱敏回显);
3. 「获取模型列表」→ 下拉选择模型 → 启用;
4. 终端快速启动:codex-env(自动注入激活提供方的 Key 并进入 Codex TUI);只想取浏览器聊天桥网址用 codex-env -u。

终端使用(TUI)

| 动作 | 方式 |
| 启动 | 终端页敲 codex-env(自动注入激活提供方的 Key → 进入 ~/proj → 拉起 TUI)|
| 退出 | Ctrl+D 或输入 /quit |
| 取桥网址 | codex-env -u(只打印网址、不进 TUI、不注入 Key);或 cat /.url(桥目录=运行实例目录,默认 bridge/web/) |
| 网页聊天桥 | 浏览器打开取到的网址(127.0.0.1:3095,自带 token 鉴权,等于密码别外传);终端 TUI 与网页共享同一批会话 |
| 换模型 | TUI 内 /model(或回设置面板「获取模型列表」选)|
| 看改动 | /diff;会话恢复 codex resume |
| 中文输入 | App 终端输入法受限时,在 Web 输入框写好复制粘贴进去 |

预设提供方(端点实测)

| 提供方 | Base URL | 协议 | 说明 |
| DeepSeek | https://api.deepseek.com/v1 | responses | 直连 |
| 智谱 | https://open.bigmodel.cn/api/v1 | responses | GLM Coding 端点 |
| 云知声 | https://maas-api.unisound.com/v1 | chat(经本地桥) | 多模型聚合 MaaS |

会话与磁盘(谁落盘,落哪里)

| 来源 | 会话记录 |
| 面板/浮动球下发工单 | 默认不落盘(ephemeral,审计靠 git diff);勾选「保留会话」才落盘 |
| 终端 TUI / codex exec(命令行) | 落盘 ~/.codex/sessions/年/月/日/(约 33KB/次,可 codex resume)|
| 共同 | ~/.codex/.sqlite 状态库(官方管理,插件不清理)|

服务层每 6 小时自动兜底清理(保留 7 天 / 总量超 200MB 从最旧删);面板「会话清理」可手动按天清理。

API 入口(唤起 Codex 的正确地址)

插件路由挂在 Web 服务上,地址取环境变量 $DSH_WEB_URL(DSH 启动时注入,随 Web 端口变化,不要写死端口):

W="$DSH_WEB_URL" # 例: http://127.0.0.1:
curl -s "$W/second-engine/api/status" # 自检: {"ok":true,"codexBinary":true,...}

curl -s -X POST "$W/second-engine/api/task" \
-H 'Content-Type: application/json' \
-d '{"prompt":"","workdir":"~/proj","ephemeral":true}' # → {"id":"..."} 立即返回

- ⚠️ 不要用 App 设备桥的端口(/app/:读屏/点按/截屏/通知那套)去拼插件路由:那不是插件路由,未知路径会被当 shell 命令执行,返回
{"result":"[NO_CMD]"} —— 看着像「插件 API 不存在」,实际是敲错门(2026-09-27 实测)。
- 工单是异步的:POST /api/task 拿到 {id} 就可以走人,完成经 App 通知提醒;等结果用
tools/wait-task.sh [轮询间隔=5] [总超时=600] 放进后台 job(完成通知自动唤醒等待者,退出码 0=done / 2=失败 / 3=不存在 / 4=接口不可用 / 124=超时),
不要自己写 sleep+curl 轮询。

支持能力一览(v1.1.0)

| 能力 | 状态 | 说明 |
| 提供方切换 / 工单 / 双向互审 | ✅ | DeepSeek / 智谱直连,云知声经翻译桥;切换不丢用户配置(sandbox、MCP) |
| AGENTS.md 联网搜索三档降级 | ✅ v2 | 原生 web_search 优先 → Exa MCP 备胎 → 全无则凭已有知识完成任务并标注「未经联网核实」;v1 用户自动原位升级 |
| tools/t-tool.sh | ✅ 实测 | tmux 交互窗口:屏幕快照、哨兵式等待、run 退出码回传、owner 防串台;依赖 tmux(apt install tmux) |
| tools/wait-task.sh | ✅ 实测 | 工单完成阻塞等待,退出码语义完整;配后台 job 使用,完成通知自动唤醒等待者 |
| Exa MCP(可选外挂) | ⚠️ 限额 | https://mcp.exa.ai/mcp 匿名可用但有隐性限额(偶发 rate limit,重试即可);正式用去 exa.ai 注册免费 key |
| 面板「联网搜索与 MCP」分区 | ✅ v0.3.4 | web_search 开关(live↔disabled)与 MCP 增删直接落 config.toml;八项验收 + 切提供方持久性 + 智谱端到端原生搜索实测通过 |
| 工单审批策略 | ✅ v0.3.4 修复 | approval_policy="never" 固定写入 config;此前字段丢失曾致写工作副本外目录的工单无限挂起 |
| 面板交互 | ✅ v1.0 | 分区折叠(右上角「展开/收回」带框文字按钮 + 标题行整行可点)、抽屉限高 78vh 内滚、工单列表固定最近 5 条(面板高度恒定,头部 × 不被挤掉)——手机实测确认 |

已知限制

- 沙箱:proot 环境无 Landlock / bubblewrap,Codex 需 danger-full-access 运行——安全防线在流程层(工作副本隔离 + diff 审查 + git 仲裁),不要把主工作区直接交给 Codex;
- 翻译桥:实验性;内部以非流式请求上游、以流式事件回放,长任务首包延迟等于整段生成时间;
- 工单记录:内存态,重启 Web 即清空;
- 完成提醒:经 App 通知提醒用户回 Web 唤起主 AI 验收(DSH 暂无会话主动注入 API);
- t-tool run 前提:目标会话须停在 shell 提示符;pane 正跑前台程序时哨兵等不到,表现为 TIMEOUT(会话不损,等程序结束即可);命令须写单行;
- t-tool wait 基线语义:匹配的是全屏(含旧输出),"等命令跑完"一律用 run;需要"只匹配新增输出"时传基线行数(第 5 参);
- DeepSeek 无联网搜索:官方 Responses API 不执行 web_search(文档明示禁用)——搜索走 Exa 备胎或主 AI 代查;
- 完成通知节流:App 通知 30 秒节流,连续快速完成的任务可能合并提醒;
- 插件 API 无鉴权:/second-engine/api/* 仅绑定 127.0.0.1(外网不可及),但本机任意 App 均可无认证调用——个人设备低风险,接入外部 agent 前将统一加 token(bridge_token 同款机制)。

执行模式(2026-09-27 起)

工单与复核支持两种执行模式,切换不需要重启:

| 模式 | 机制 | 能力 |
| exec(默认) | codex exec 一次性子进程 + -o 文件 | 与旧版完全一致,零风险 |
| appserver | codex app-server JSON-RPC 长连接 | 流式进度(GET /api/task?id= 的 progress,最近 20 条归一化事件)+ 真打断(/api/task/cancel 先发 turn/interrupt,65ms 级结束);输出仍落 -o 文件,看门狗判据不变 |

echo appserver > ~/.dsh/second-engine-mode.txt # 切 app-server 模式
echo exec > ~/.dsh/second-engine-mode.txt # 切回 exec

优先级:环境变量 SECOND_ENGINE_EXEC_MODE > 上述模式文件 > 默认 exec。
验收脚本:bash $DSH_HOME/scripts/dsh/se-appserver-verify.sh(流式进度 / 打断 / exec 回归三条判据)。

实时可见性(怎么判断"到底行动了没"):
- 模型清单:POST /api/models {id} 对智谱会并集两个端点(/api/v1(Codex 专用,只列 3 个)+ /api/coding/paas/v4(全量)),响应里的 sources 字段回显实际来源。实测两端点共用同一把 key,且 /api/v1/responses 支持全部 glm 模型(2026-09-28)。
- exec 模式:GET /api/task?id= 看 liveBytes(过程流字节数,持续增长即在动);过程流保留在 .live(最终结果仍以 -o 文件为准)。
- appserver 模式:除 liveBytes 外还有 progress 数组(归一化事件,最近 20 条,可见 reasoning / 文本增量 / 工具调用 / 用量)。
协议依据:~/proj/appserver-spike/MAPPING.md(事件 → 归一化字段映射,源自 app-server 生成的 JSON Schema)。

架构

dsh-second-engine/
├── skills/second-engine/SKILL.md # 知识层:主 AI 的操作流程 ├── src/index.js # 服务层:路由 / 提供方 / 工单 / 桥 ├── src/bridge.js # 翻译桥:responses ↔ chat ├── lib/client.js # 界面层:设置面板 + 浮动球 └── cordis.patch.yml # bundle 收录声明

License

MIT
预览分享卡片长什么样 ↓fengyu-12/dsh-second-engine
分享 · DeepSeek Harness Hub
fengyu-12/dsh-second-engine

接入 Codex 作为第二智能体,异步派单并双向互审

回到顶部