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

多节点控制平面 lixiaoshuang79/dsh-helm

MCP
lixiaoshuang7970 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

把单机连接器扩展成多节点统一入口,按策略路由到任意机器

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 70 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 66 处,展示前 66 条)
  • 执行外部命令来自配置/变量
    packages/cli/src/cli.ts:256
    const res = spawnSync(name, args, { stdio: 'inherit', shell: true })
  • 读取本地文件
    packages/cli/src/doctor.ts:107
    if (!existsSync(path)) return { found: false, display_name: '', node_id: '', hub_url: '', hasToken: false }
  • 读取本地文件
    packages/cli/src/doctor.ts:109
    const raw = JSON.parse(readFileSync(path, 'utf8')) as Record<string, unknown>
  • 执行外部命令来自配置/变量
    packages/cli/src/doctor.ts:133
    const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 5000, shell })
  • 读取本地文件
    packages/cli/src/doctor.ts:329
    } else if (!existsSync(plistPath)) {
  • 读取本地文件
    packages/cli/src/doctor.ts:333
    const id = parseTunnelIdFromPlist(readFileSync(plistPath, 'utf8'))
  • 发起网络请求
    packages/cli/src/ha-proxy.ts:62
    return fetch(url, { signal: AbortSignal.timeout(timeoutMs) })
  • 读取本地文件
    packages/cli/src/install.ts:41
    if (existsSync(configPath)) {
  • 发起网络请求
    packages/cli/src/pair.ts:194
    .request(HUB_METHODS.ENROLLMENT_CONSUME, params, { timeoutMs: 10_000 })
  • 读取本地文件
    packages/cli/src/pair.ts:241
    if (existsSync(target) && !opts.force) {
  • 写入本地文件
    packages/cli/src/pair.ts:269
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    packages/cli/src/pair.ts:276
    writeFileSync(target, JSON.stringify(nodeConfig, null, 2), { mode: 0o600 })
  • 写入本地文件
    packages/cli/src/pair.ts:277
    chmodSync(target, 0o600)
  • 发起网络请求
    packages/dashboard/src/hub.ts:69
    const res = await fetch(url, {
  • 发起网络请求
    packages/dashboard/src/hub.ts:95
    const healthzRes = await fetch(healthzUrl, { signal: AbortSignal.timeout(timeoutMs) })
  • 发起网络请求
    packages/dashboard/src/metrics.ts:56
    const res = await fetch(new URL('/metrics', baseUrl), { signal: AbortSignal.timeout(timeoutMs) })
  • 发起网络请求
    packages/dashboard/src/pair.ts:26
    const res = await fetch(new URL(path, hubUrl), { signal: AbortSignal.timeout(timeoutMs) })
  • 读取本地文件
    packages/dashboard/src/server.ts:87
    let html = await readFile(path.join(PUBLIC_DIR, 'index.html'), 'utf8')
  • 执行外部命令固定命令
    packages/dashboard/src/services.ts:64
    execFileSync('pgrep', ['-f', escapePgrepPattern(pattern)], { stdio: 'ignore', timeout: 3000 })
  • 执行外部命令固定命令
    packages/dashboard/src/services.ts:120
    const output = execFileSync('launchctl', ['list'], { encoding: 'utf8', timeout: 5000 })
  • 执行外部命令固定命令
    packages/dashboard/src/services.ts:129
    const output = execFileSync('systemctl', ['--no-pager', 'list-units', '--all'], { encoding: 'utf8', timeout: 5000 })
  • 发起网络请求
    packages/dashboard/src/status.ts:104
    const res = await fetch(new URL('/cp-status', hubUrl), { signal: AbortSignal.timeout(timeoutMs) })
  • 发起网络请求
    packages/dashboard/src/tunnel.ts:108
    const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) })
  • 发起网络请求
    packages/hub/src/connection.ts:83
    return this.peer.request(method, params, { timeoutMs })
  • 发起网络请求
    packages/hub/src/control-plane.ts:242
    const res = await conn.request(NODE_METHODS.MCP_CALL, { tool: op, args: params }, 60_000)
  • 发起网络请求
    packages/hub/src/control-plane.ts:258
    const ws = (await conn.request(NODE_METHODS.LIST_WORKSPACES, {}, 15_000)) as WorkspaceInfo[]
  • 发起网络请求
    packages/hub/src/control-plane.ts:274
    const ss = (await conn.request(NODE_METHODS.LIST_SESSIONS, {}, 15_000)) as SessionInfo[]
  • 发起网络请求
    packages/hub/src/ha.ts:846
    const resp = (await this.rpcPeerFor(peer).request(CP_METHODS.ELECT, req, { timeoutMs: 10_000 })) as ElectResponse | undefined
  • 发起网络请求
    packages/hub/src/ha.ts:892
    const resp = (await this.rpcPeerFor(peer).request(CP_METHODS.LEASE_RENEW, req, { timeoutMs: 10_000 })) as { ok?: boolean } | undefined
  • 发起网络请求
    packages/hub/src/ha.ts:914
    const resp = (await this.rpcPeerFor(peer).request(CP_METHODS.LEASE_RENEW, req, { timeoutMs: 10_000 })) as { ok?: boolean } | undefined
  • 发起网络请求
    packages/hub/src/ha.ts:1097
    const resp = (await out.peer.request(CP_METHODS.SYNC, full, { timeoutMs: 10_000 })) as CpPeerState | undefined
  • 发起网络请求
    packages/hub/src/ha.ts:1131
    await this.outbound.peer.request(CP_METHODS.HEARTBEAT, state, { timeoutMs: 10_000 })
  • 发起网络请求
    packages/hub/src/ha.ts:1141
    await st.inbound.request(CP_METHODS.HEARTBEAT, state, { timeoutMs: 10_000 })
  • 读取本地文件
    packages/hub/src/hub-cli.ts:54
    const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version?: string }
  • 读取本地文件
    packages/hub/src/hub-cli.ts:86
    const raw = JSON.parse(readFileSync(`${process.env.HOME ?? '.'}/.dsh/helm/node.json`, 'utf8')) as { node_id?: string }
  • 读取密钥环境变量
    packages/hub/src/hub-cli.ts:166
    const envTokenLookup = tokenLookupFromEnv(process.env.DSH_HELM_TOKEN)
  • 读取密钥环境变量
    packages/node-agent/src/agent-cli.ts:45
    if (process.env.DSH_HELM_MCP_TOKEN) cfg.local_mcp_token = process.env.DSH_HELM_MCP_TOKEN
  • 发起网络请求
    packages/node-agent/src/agent.ts:110
    void this.peer.request(HUB_METHODS.PRESENCE_REPORT, { node_id: this.cfg.node_id, claim }).catch(() => {
  • 发起网络请求
    packages/node-agent/src/agent.ts:308
    await this.peer?.request(HUB_METHODS.NODE_REGISTER, { node: this.nodeInfo() })
  • 发起网络请求
    packages/node-agent/src/agent.ts:316
    await this.peer?.request('catalog.reconcile', { node_id: this.cfg.node_id, sessions, workspaces })
  • 发起网络请求
    packages/node-agent/src/agent.ts:379
    await this.peer?.request('catalog.reconcile', { node_id: this.cfg.node_id, sessions, workspaces })
  • 发起网络请求
    packages/node-agent/src/agent.ts:488
    await this.peer?.request('node.heartbeat', { node_id: this.cfg.node_id, status }, { timeoutMs: 10_000 })
  • 读取本地文件
    packages/node-agent/src/bridge.ts:60
    if (readFileSync(c, 'utf8').trim().length > 0) return c
  • 发起网络请求
    packages/node-agent/src/bridge.ts:99
    this.fetchImpl = opts.fetchImpl ?? ((...args: Parameters<typeof fetch>) => fetch(...args))
  • 读取本地文件
    packages/node-agent/src/bridge.ts:263
    return readFileSync(path, 'utf8').trim()
  • 读取本地文件
    packages/node-agent/src/config.ts:66
    disk = JSON.parse(readFileSync(path, 'utf8')) as NodeConfigFile
  • 写入本地文件
    packages/node-agent/src/config.ts:86
    mkdirSync(cfgDir, { recursive: true })
  • 写入本地文件
    packages/node-agent/src/config.ts:87
    writeFileSync(path, JSON.stringify({ ...cfg, token }, null, 2), { mode: 0o600 })
  • 写入本地文件
    packages/node-agent/src/config.ts:88
    chmodSync(path, 0o600)
  • 发起网络请求
    packages/node-agent/src/steer.ts:60
    const fetchImpl = opts.fetchImpl ?? ((...args: Parameters<typeof fetch>) => fetch(...args))
  • 删除本地文件
    packages/node-agent/src/summary.ts:214
    rmSync(this.cachePath(sessionId), { force: true })
  • 读取本地文件
    packages/node-agent/src/summary.ts:227
    const parsed = JSON.parse(readFileSync(this.cachePath(sessionId), 'utf8')) as SessionSummary
  • 写入本地文件
    packages/node-agent/src/summary.ts:239
    mkdirSync(this.cacheDir, { recursive: true })
  • 写入本地文件
    packages/node-agent/src/summary.ts:240
    writeFileSync(this.cachePath(sessionId), JSON.stringify(data, null, 2), { mode: 0o600 })
  • 执行外部命令来自配置/变量
    packages/platform/src/tailscale.ts:21
    const defaultExec: TailscaleExec = (file, args, opts) => execFileSync(file, args, opts)
  • 写入本地文件
    packages/store/src/db.ts:81
    mkdirSync(dirname(opts.file), { recursive: true })
  • 写入本地文件
    patches/goal-guard.mjs:64
    writeFileSync(tmp, content)
  • 执行外部命令来自配置/变量
    patches/goal-guard.mjs:66
    execFileSync(process.execPath, ['--check', tmp], { stdio: 'pipe' })
  • 读取本地文件
    patches/goal-guard.mjs:75
    if (!existsSync(target)) {
  • 读取本地文件
    patches/goal-guard.mjs:79
    let src = readFileSync(target, 'utf8')
  • 写入本地文件
    patches/goal-guard.mjs:94
    copyFileSync(target, bak)
  • 写入本地文件
    patches/goal-guard.mjs:95
    writeFileSync(target, src)
  • 读取本地文件
    patches/goal-guard.mjs:102
    let pluginSrc = existsSync(pluginTarget) ? readFileSync(pluginTarget, 'utf8') : ''
  • 读取本地文件
    patches/goal-guard.mjs:102
    let pluginSrc = existsSync(pluginTarget) ? readFileSync(pluginTarget, 'utf8') : ''
  • 读取本地文件
    patches/goal-guard.mjs:126
    let tgSrc = existsSync(toolGoalTarget) ? readFileSync(toolGoalTarget, 'utf8') : ''
  • 读取本地文件
    patches/goal-guard.mjs:126
    let tgSrc = existsSync(toolGoalTarget) ? readFileSync(toolGoalTarget, 'utf8') : ''
扫描于 2026/11/3(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库

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

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 多节点统一入口
  • 按策略路由到任意机器

README

由 DeepSeek 最新模型翻译生成
dsh-helm DSH 多节点控制平面:把单机「ChatGPT ↔ DSH」连接器扩展成多节点控制平面。多台机器上的 DeepSeek Harness(DSH)通过节点代理(node-agent)注册到统一 Hub,ChatGPT 经一个入口即可路由到任意节点——读写代码、管理会话、查看健康,且不暴露任何节点给公网。 ChatGPT Web(连接器/插件) │ OpenAI Secure MCP Tunnel(tunnel-client,TLS) ▼ Hub 控制平面 MCP 127.0.0.1:3471(ChatGPT 入口) mesh :3470(节点接入) │ 路由:显式 target → session owner → workspace owner → presence → default ├──────────────┬──────────────┬──────────────┐ ▼ ▼ ▼ ▼ node-agent node-agent node-agent node-agent (每台机器:出站 WS + HMAC 握手) │ │ │ │ ▼ ▼ ▼ ▼ daemon 3457 → DSH daemon 3457 → DSH ……
展开完整文档(README 剩余 1 节)
dsh-helm

DSH 多节点控制平面:把单机「ChatGPT ↔ DSH」连接器扩展成多节点控制平面。多台机器上的 DeepSeek Harness(DSH)通过节点代理(node-agent)注册到统一 Hub,ChatGPT 经一个入口即可路由到任意节点——读写代码、管理会话、查看健康,且不暴露任何节点给公网。

ChatGPT Web(连接器/插件) │ OpenAI Secure MCP Tunnel(tunnel-client,TLS) ▼ Hub 控制平面 MCP 127.0.0.1:3471(ChatGPT 入口) mesh :3470(节点接入) │ 路由:显式 target → session owner → workspace owner → presence → default ├──────────────┬──────────────┬──────────────┐ ▼ ▼ ▼ ▼ node-agent node-agent node-agent node-agent (每台机器:出站 WS + HMAC 握手) │ │ │ │ ▼ ▼ ▼ ▼ daemon 3457 → DSH daemon 3457 → DSH …… (各节点本地 helm daemon,Bearer 鉴权)

- 每个节点跑 dsh-helm agent:只出站连 hub(mesh WS),向内桥接本机 helm daemon 的 MCP(127.0.0.1:3457/mcp)。
- hub 是唯一入口:ChatGPT 经 hub MCP(3471)调用工具,hub 按路由策略转发到正确节点;节点数对 ChatGPT 透明。
- 单机兼容:单节点且 node_id == hub defaultNodeId 时,路由与工具调用行为等价于单机 daemon(摘要/guard/steer 为上层增强,不影响既有调用语义)。

功能特性

- 多节点注册与心跳:节点身份 node_id(UUID)+ HMAC 挑战握手;15s 心跳、45s 租约;新版 agent 心跳超时自动重连(半开连接检测重连)。
- 五级路由:显式 target_node → session owner → workspace owner → 无歧义 presence → defaultNodeId 兜底;destructive/write 操作目标不清晰时 fail-closed 拒绝(route_confirmation_required),绝不猜。
- 转发可溯源:每次转发结果附带 _route.node_name(display_name)标注执行节点;route_explain 预演不执行。
- MCP 工具面 19+5:单机 daemon 的 19 个工具(code_/sessions_/projects_list/supervisor_health 等,snake_case 参数不变)原样保留,新增 nodes_list/node_get/route_explain/presence_claim/presence_release;所有可路由工具带可选 target_node。
- presence:手动声明(10 分钟 pin)+ macOS 前台应用自动探测(桌面 sidecar);15s 歧义窗口内双节点高置信 → 判 ambiguous,不自动选。
- 分层健康:control / channel / adapter / datapath / serena / tunnel 各层独立上报,绝不折叠成单一 status: ok。
- 跨节点聚合:workspaces_list/sessions_list/agents_list/projects_list 返回多节点扁平结果(每条带 node_id)。
- 审计与路由日志:节点注册、心跳、路由决策、presence 变更全部落库(audit/route_log)。
- 元数据红线:hub 存储只含元数据(节点/租约/会话与工作区目录/审计),从不存储 DSH 会话正文。

目录结构

dsh-helm/
├── packages/
│ ├── protocol/ # wire 协议:envelope、JSON-RPC、HMAC 握手、常量 │ ├── store/ # SQLite:节点注册表、presence、目录、审计 │ ├── hub/ # 控制面:Router、WS mesh 3470、MCP 3471 │ ├── node-agent/ # 节点代理:出站 WS、重连、本地 DSH 桥 │ ├── presence/ # presence providers(手动/macOS/浏览器) │ ├── platform/ # 跨平台适配(launchd/systemd/Windows 模板) │ └── cli/ # dsh-helm CLI(init/agent/hub/status/nodes/…)
├── tests/integration/ # 双 fake node 端到端测试 └── scripts/ # ops 脚本(bash,macOS 优先)

快速开始

前置:Node.js >= 22.5、pnpm、curl;每台节点机先装好 DSH 与 helm daemon(127.0.0.1:3457/mcp,Bearer token 在 ~/.agent-chatgpt-helm/token)。

1. 安装 CLI(构建 + 写 ~/.local/bin/{dsh-helm,dsh-helm-agent,dsh-helm-hub},幂等) ./scripts/install.sh

2. 初始化节点身份(生成 ~/.dsh/helm/node.json,权限 0600) dsh-helm init

3. 编辑 ~/.dsh/helm/node.json:设置 hub_url 与 local_mcp_token hub_url:内网/Tailscale 用 ws://:3470,生产用 wss://
4. hub 机器:启动控制面(mesh 3470 + MCP 3471;默认只绑 127.0.0.1) dsh-helm hub 多机场景:dsh-helm hub --bind --mcp-bind 127.0.0.1

5. 节点机器:启动 agent(先前台验证,再装自启服务) dsh-helm agent ./scripts/install-service.sh # macOS:launchd 服务(com.dsh-helm.node-agent)

6. 自检与状态 ./scripts/verify.sh # 0 全绿 / 1 警告 / 2 严重 ./scripts/health.sh # 节点状态表(走 hub MCP supervisor_health) dsh-helm status # 本地配置与连接状态

加入更多节点:新节点机 dsh-helm init 后,把 node.json 的 node_id 与 token 经安全渠道交给 hub 管理员,在 hub 机器上执行(幂等:追加/更新 token 表,自动重载 launchd 服务): bash ./scripts/register-node.sh

详细流程见 docs/onboarding.md。

接入 ChatGPT

两条路径,按部署阶段选择:

- A. 单机直连(起步):本机已有 helm daemon 时,hub 把本机节点当 local node,行为与单机连接器一致,无需隧道。
- B. 多节点(控制平面,推荐):OpenAI Secure MCP Tunnel 接入 hub MCP(3471),ChatGPT 一个入口管所有节点。

OpenAI Platform 侧完整教程(建 tunnel / 绑定 workspace / 建 API key / tunnel-client 参数 / 代理)见 docs/chatgpt-tunnel-setup.md;ChatGPT Web 侧(开发者模式 / 创建连接器 / 测试)见 docs/chatgpt-connector.md。

已有单机 connector 的机器升级:旧形态(tunnel 直连 daemon 3457)没有会话瘦身/插队/响应守卫等升级能力;按 docs/single-connector-upgrade.md 迁移到单机兼容模式(本地 hub + agent,tunnel 指向 hub MCP 3471),ChatGPT 侧零改动即获得全部升级能力(内容瘦身 / steer 插队 / 响应守卫,见下节)。

两种拓扑取舍:每台 daemon 各配一个 tunnel+连接器(多入口、各管各),或一个 hub tunnel + 一个连接器管 N 台节点(单入口,推荐——hub 路由 target_node/路由规则,回复带 node_name)。

控制面 HA(双 Control Plane)

两台 hub 组成一个 quorum(2/2)控制面,任一台故障时另一台仍可服务读路由与节点入口。

- 角色与租约:--cp-priority 小者胜出为 leader(唯一写者);leader 每 10s 向 peer 续写租约,peer 失联超过租约 TTL(--cp-failover-ms,默认 45s)→ 双方进入 read-only-no-quorum,写操作返回 QUORUM_LOST。follower 永不单方提升——失去 quorum 时只读不写(CAP 优先安全)。
- 恢复:peer 重连 → 注册表全量同步 → 强制重选(term+1)→ 租约双方确认 → 写恢复。整个恢复窗口内双方保持只读。
- agent 多 endpoint:node.json 配 hub_url + fallback_urls,重连时轮询尝试、成功后 pin;故障时自动切到第二 CP。
- 观测:GET /cp-status 返回 role/phase/writeMode/quorum/term/leaderId/peers/syncOk/leaseEpoch/failoverCount;dsh-helm doctor 与 Dashboard「控制面 HA」卡直接展示。
- ChatGPT 入口 HA:OpenAI tunnel-client 的 --mcp.server-url 是 channel 限定、无同连接器多后端 failover。本地起 dsh-helm ha-proxy(默认 127.0.0.1:3481,--primary http://127.0.0.1:3471 --secondary http://:3471),tunnel 仍指向一个连接器(3481);主 CP 失联自动切副 CP、恢复后切回。双 tunnel + 双连接器是备选拓扑。
- 第二 CP 部署:dsh-helm hub --cp-peer ws://:3470 --cp-priority 1 --cp-id --cp-token-env DSH_HELM_CP_TOKEN;两侧 DSH_HELM_TOKEN 都含双方节点 token 表(任一 agent 故障切换时对方 CP 都能认证)。MCP 需跨机可达时用 --mcp-bind (Tailscale ACL 围栏,单机场景保持 loopback)。

设备配对(新增 DSH 设备)

Dashboard「新增 DSH 设备」→ 生成一次性配对码(10 分钟有效、单次消费、仅存哈希);新机器执行 dsh-helm join --control-plane ws://:3470 --code 完成入网(生成长期 node token 写入 ~/.dsh/helm/node.json,hub 只存 hash/状态)。配对 API 仅 loopback + 防 CSRF 头;日志只记哈希前缀。详见 docs/security.md §5。

MCP Context Isolation(大上下文稳定性) ChatGPT ↔ DSH 连接器在长时间运行、大上下文 session 下的响应瘦身与监控(兼容层,链路不变):

- sessions_get 默认摘要:默认只返回结构化摘要(id/title/status/workspace/created_at/updated_at/last_message_summary/last_assistant_summary/current_goal/current_goal_seq/last_user_message/recent_evidence{commits,paths,errors,tests}/history_ref/safety_sanitized/token_estimate/continuation_available,无 messages)。摘要由 node agent 生成:只向 DSH 要最后 20 条消息(SUMMARY_WINDOW),current_goal 取窗口内行动性最高的用户指令(附来源 seq),recent_evidence 为正则启发式提取,疑似凭据行在进入任何摘要字段前剔除(safety_sanitized 标记)。实测基线:早期大 session 响应 75KB → 1.2KB;信息保真验收 fixture(1000 条消息)约 107KB → 0.7KB,默认响应 .json(60s TTL,写操作后失效)。
- 完整历史按需取:include_messages=true(可配 max_messages,默认 20)返回完整消息;before_seq 参数透传但 DSH 0.1.1 未实现真实翻页(探测实测:max_messages ≤100 且 beforeSeq 无效)——最近 100 条之外的历史当前不可达,history_ref 显式标注可达范围(reachable_max_messages:100);旧调用(不带参数)自动走摘要,调用方无需改动参数,但注意返回内容从完整消息变为摘要(需要原文时显式 include_messages=true)。
- Response Size Guard:hub 所有 MCP 响应统一 middleware,MAX_RESPONSE_BYTES=50000;超限自动 smart-truncate(保证仍是合法 JSON,挂 truncated 元数据),日志 [mcp-guard] original=.. returned=.. truncated。
- 健康监控:hub 新增 GET /metrics(请求数/平均与最大响应字节/截断与错误计数/活跃连接/perTool 明细)、GET /readyz(HA quorum 就绪)、GET /version;Dashboard 新增「MCP 控制面」页签展示。
- 纠错插队/立即干预:sessions_prompt 支持 mode=queue|steer(默认 queue 排队语义不变);steer 绕过队列经 DSH 宿主 API 注入运行中回合(结构化返回 steered/queued/rejected/unavailable),DSH 历史事件 agent/inbox/spliced 证实注入。设计评审与实施细节见 docs/priority-queue.md。

模型门禁(ChatGPT 指令须声明 gpt-5-6-thinking)

隧道/协议层不带调用方模型(实测确认),因此采用声明式门禁:ChatGPT 总导演
每次下发指令时在消息文本里声明当前模型([model-check] 当前模型是 ),
hub 路由前校验 sessions_prompt / sessions_create(initial_message):

- 声明 gpt-5-6-thinking → 放行;
- 声明 5.5-mini → 拒绝 model_rejected(附 received 原文);
- 无声明 → 拒绝 model_declaration_required。

被拒返回 MCP isError + 结构化 JSON,ChatGPT 可读原因并切模型重试。
实现见 packages/hub/src/mcp/model-gate.ts,ChatGPT 侧指令模板见 docs/model-gate.md。

Goal 守卫(ChatGPT 指令禁止在 DSH 开启 goal)

问题(2026-08-25 代码级定位):链路中 @beforewave/dsh-chatgpt-helm 插件(DSH web profile)的 DshAdapter.prompt() 把 ChatGPT 发来的消息以 source:{kind:"user"} 注入 DSH 会话——伪装成「直接人类输入」。而 DSH 的 create_goal 权威校验(dsh-tool-goal 的 requireDirectHuman)只认 source.kind === "user" 的回合事件,于是 ChatGPT 一条长任务指令就能让 DSH 开 goal;回合结束 goal 自动注入下一段(source.kind="goal")自主续跑,根本不听 ChatGPT 的指挥。

修复(patches/goal-guard.mjs v2,幂等补丁):两处——对插件 lib/index.js 三处修改,另补丁 DSH 核心 dsh-tool-goal 权威校验:

1. createSession/prompt() 的消息注入 source:{kind:"user"} → source:{kind:"user", relayedBy:"dsh-chatgpt-helm"}:消息在 DSH GUI 正常显示原文(与本人消息一致),但带转发标记;
2. prompt() 注入前:会话存在 active goal 时自动 pause(disarm 自动续跑)——存量 goal 也不会在 ChatGPT 指挥期间继续「一段结束自动注入下一段」;
3. inject 数组补充 "goals" 服务;
4. dsh-tool-goal 的 hasDirectHumanInput 增加 && relayedBy === void 0:带转发标记的消息不算「直接人类输入」,ChatGPT 指令从此无法通过 create_goal/update_goal 的人类权威校验(GOAL_TOOL_DRIVER_REQUIRED 拒绝),系统提示中「直接人类请求」的 goal 推断前提也不成立。

本机 GUI 管理 goal 不受影响(web 消息无 relayedBy 标记)。应用后需重启 DSH web 生效;DSH 升级或插件重装/升级后重跑一次即可(node patches/goal-guard.mjs,自动备份 .bak-goalguard-)。端到端验证方法见 docs/goal-guard.md。

平台支持

| 平台 | hub | node agent | presence | 服务自启 |
| macOS | ✅ 已验证 | ✅ 已验证 | ✅ 桌面 sidecar 自动 + 手动 | ✅ launchd(install-service.sh) |
| Linux | ✅ 部分支持 | ✅ 部分支持 | ✅ 手动 | ✅ systemd 模板(@dsh-helm/platform) |
| Windows | ⚠️ 需 Node ≥22.5 | ⚠️ 脚手架 | 🚧 待真机验证 | 🚧 Task Scheduler 模板 |

核心代码零平台特定逻辑(launchd/osascript/PowerShell 全部隔离在 packages/platform 与 packages/presence);macOS 双机(Tailscale)已真机验证,Linux/Windows 待真机验证。

文档

| 文档 | 内容 |
| docs/architecture.md | 架构、协议、路由决策、数据模型、工具面 |
| docs/chatgpt-tunnel-setup.md | OpenAI Platform 隧道创建与 tunnel-client 配置 |
| docs/chatgpt-connector.md | ChatGPT Web 连接器创建与使用 |
| docs/onboarding.md | 新机器加入控制平面 |
| docs/security.md | 凭据、网络边界、Tailscale ACL、威胁模型摘要 |
| docs/troubleshooting.md | 症状 → 排查 → 解决 |
| docs/threat-model.md | 完整威胁模型(15 条威胁) |
| docs/upstream-compat.md | 上游 beforewave helm 兼容基线 |

安全要点

- 凭据:~/.dsh/helm/node.json(节点 token)与 daemon token 文件均 0600;hub token 表经 DSH_HELM_TOKEN 环境注入(不落盘);token 不出现在 argv/git/日志;隧道凭据用 env: 语法注入。
- 绑定:hub 默认只绑 127.0.0.1;跨机建议 Tailscale + --bind ,--mcp-bind 127.0.0.1 保持 MCP 仅 loopback。hub MCP(3471)v1 无鉴权——严禁直接暴露公网;生产 mesh 走 wss://(TLS 由反代/外部 https server 负责)。
- fail-closed:破坏性操作(sessions_prompt/sessions_resume)无明确目标即拒绝;presence 歧义窗口内不猜测。
- 无正文存储:store 只存元数据与审计,不落 DSH 会话内容。
- 详细安全模型见 docs/security.md 与 docs/threat-model.md。

状态与事实分层

版本 v0.1.0。自动化验证全绿(单元 + 双 fake node 全协议端到端集成测试 + 信息保真验收:399/399(48 文件),build/lint 干净);macOS 双机 Tailscale 真机冒烟完成。doctor/dashboard/install 已实现;CLI 在线 RPC 命令(nodes/node/route-explain/presence/rotate-token)仍需 live hub 连接(当前提示 requires live hub connection,计划下一里程碑),同一能力可经 hub MCP 工具(nodes_list 等)使用;session handoff v1 诚实返回 unsupported。

能力状态按证据强度分层(不混淆):

| 层次 | 内容 | 证据 |
| 已实现并测试 | 五级路由 + fail-closed、HMAC 握手、presence(手动 + macOS 桌面探测)、分层健康、HA 双 CP(quorum/租约/failover + ha-proxy)、设备配对(pair/join)、MCP Context Isolation(默认摘要/Response Guard/steer 插队)、CLI 15 子命令 | 单元 + 集成测试全绿;acceptance 报告见 docs/fidelity-acceptance.md 与 docs/priority-queue.md |
| 依赖上游但已实测 | DSH 0.1.1 sessions_prompt mode=queue/steer(宿主 API 注入;agent/inbox/spliced 验证)、max_messages 生效、beforeSeq 翻页无效(协议限制) | 真实链路 smoke + 探测记录(docs/priority-queue.md §2/§5) |
| 官方未说明 / 实验性 | 同一 OpenAI tunnel 多 tunnel-client 双实例语义(容灾阶梯 2,需实测);Linux/Windows 平台支持 | OpenAI 官方文档零表述(docs/chatgpt-disaster-recovery.md);平台表见前文 |
| 已知限制与未闭环风险 | ①最近 100 条之外的历史不可达(DSH 0.1.1 beforeSeq 无效;修复路径=agent 历史归档,见 fidelity §7);②hub MCP(3471)v1 无鉴权——严禁公网暴露;③CLI 在线 RPC 命令未接通 live hub;④审计无防篡改/哈希链、token 静态明文存储(详见 threat-model §4/§5) | 验收/冒烟实测;威胁模型逐条 docs/threat-model.md |

明确不承诺:非 production-ready 保证;HA 为自管控制面冗余,无 SLA / zero-downtime 承诺;OpenAI 官方能力边界(tunnel 多实例 HA、密钥自动轮换)未获得前不承诺。验收判定为 CONDITIONAL PASS(保真与安全闭环,完备性受 DSH 0.1.1 协议边界限制)。

ops 脚本

| 脚本 | 作用 |
| scripts/install.sh | 安装 CLI(node 检查 / 构建 / 三个 wrapper),幂等 |
| scripts/uninstall.sh | 卸载(--purge 全删) |
| scripts/verify.sh | 自检(node / wrapper / node.json 0600 / 本地 daemon / hub 端口),退出码 0/1/2 |
| scripts/health.sh | 节点状态表(hub MCP 优先,本地 store 退化) |
| scripts/install-service.sh | 装 node agent 为 launchd 服务(macOS),--stop 卸载 |
| scripts/register-node.sh | hub 机注册/更新节点 token(幂等,自动重载 launchd) |
| scripts/dsh-helm-watchdog.sh | 15s 自愈 watchdog(进程级拉起,单实例锁) |

所有脚本 bash 3.2 兼容、[dsh-helm] 输出前缀、幂等、只探测不修改生产端口(3080/3457/3458)上的现有服务。
预览分享卡片长什么样 ↓lixiaoshuang79/dsh-helm
分享 · DeepSeek Harness Hub
lixiaoshuang79/dsh-helm

把单机连接器扩展成多节点统一入口,按策略路由到任意机器

回到顶部