接入 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 能力
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 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🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
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预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
fengyu-12/dsh-second-engine
接入 Codex 作为第二智能体,异步派单并双向互审
相关插件 · 模型接入
API 中继安全审计toby-bridges/api-relay-audit
面向 AI API 中转与 LLM 代理的本地安全审计。
★ 897已汉化DSH
69.4
免费搜索插件DDDMUC/dsh-free-search
DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。 一个给…
★ 761已汉化DSH
68.7
免费联网搜索liustack/modsearch
在官方 App 里模型能联网,切到 API 就不能了。ModSearch 把联网补回来:网页搜索、X…
★ 863已汉化DSH
68.5
OpenCode 免费模型FishBottle7/opencode2dsh
在 DSHDeepSeek Harness里原生使用 OpenCode Zen 的免费匿名模型。
★ 379已汉化DSH
67.8
AI 模型网关tingly-dev/tingly-box
Tingly Box Web UI 演示
★ 373已汉化DSH
67.4
WorkBuddy 账号池XDTrees/dsh-xdpool
把你能白嫖的模型额度接进 DSH 同一个池:WorkBuddy 多账号自动容错、十三家扫码登录、76…
★ 359已汉化DSH
67.1