# dsh-workbuddy-stack [English](README.md) | **简体中文** 一个 DeepSeek Harness 插件,负责把本地 WorkBuddy 反代链路维持在线 —— 模型选择器的 provider、它背后的网关,**以及 OpenAI Codex CLI 需要的那条 Responses 桥**。 在 WorkBuddy 模型能回答之前,有三样东西必须同时对上,而它们都不会把自己装成 Windows 服务: | 端口 | 服务 | 作用 | | --- | --- | --- | | `127.0.0.1:7863` | `wb2api` 网关 | OpenAI 兼容的 `/v1/chat/completions` | | `127.0.0.1:7864` | Responses 桥(**内置**) | Responses API → chat/completions,供 Codex CLI 使用 | | — | `workbuddy` provider | 模型选择器里的那条路由,**从 [`dsh-llm-workbuddy`](https://www.npmjs.com/package/dsh-llm-workbuddy) 挂载而来** | 这两个进程过去都要手动启动,于是重启一次电脑之后,模型选择器会悄无声息地指向一个死掉的端口 —— provider 插件加载正常、模型列表正常,直到第一次请求才失败。本插件接管它们:加载时探测各自的健康端点,缺什么就拉起什么,并按定时器复查,进程崩了会自动回来。 它刻意**既不重写网关,也不 fork 适配器**。网关只做进程编排;provider 路由则是用 `ctx.plugin()` 委派给 `dsh-llm-workbuddy` 挂载出来的 —— 那个包自己拥有约 2500 行的 WorkBuddy 适配器、设置面板和每日签到调度器,再抄一份只会让两边一更新就分叉。所以装一次就同时得到 provider + 网关 + 桥。 这种委派是叠加式的,不是排他的:如果你同时保留独立的 `dsh-llm-workbuddy`,谁先注册这条路由谁就拥有它,另一个会在日志里说明后退让,两种装法都安全。 ## 配置项 全部位于 profile 的 `cordis.yml` 中 `workbuddy-stack` 条目的 `config` 下;随包发布的 `cordis.patch.yml` 里是完整的默认值。 | 键 | 默认值 | 作用 | | --- | --- | --- | | `gatewayDir` | `~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel` | 网关存放 `config.json` 的目录。其中的 `api_key` 会同时用于**桥和 provider**,所以端口与密钥只需配一次。 | | `provider.enabled` | `true` | 是否注册 `workbuddy` 路由。 | | `provider.baseURL` | *(留空 → 用 `gateway.baseURL`)* | 适配器要访问的网关地址。 | | `provider.discovery` | `true` | 向网关的 `/v1/models` 拉取实时模型目录。 | | `provider.autoCheckin` | `true` | 领取 WorkBuddy 每日签到。 | | `gateway.acceptStatus` | `[200, 503]` | 网关在无可用账号时仍会跑着并返回 `503`,那是活着,不是挂了。 | | `bridge.conversationId` | `auto` | 对着 WorkBuddy 网关不是可选项:缺了它上游会回 `400 / 11-128`,网关随即停用该账号。 | 如果根本没装 `dsh-llm-workbuddy`,插件照旧监护网关与桥,并打印出补装它的那一条命令(`dsh plugin add dsh-llm-workbuddy`)。任何情况下都不会抛错。 ## 为什么桥是内置的 `codex-cli` 0.1xx 之后删掉了 `wire_api = "chat"`,只讲 OpenAI 的 **Responses** 协议(`POST /v1/responses`,SSE)。而 WorkBuddy 网关只实现 **Chat Completions**(`POST /v1/chat/completions`)。让 Codex 直连网关,每个请求都会死在这里: ``` unexpected status 404 Not Found, url: http://127.0.0.1:7863/v1/responses ``` 这不是配置项能绕过的问题 —— Codex 在加载阶段就直接拒绝 `wire_api = "chat"`。转换层是必需品,所以本包自带一个: ``` Codex CLI --(Responses)--> 桥 :7864 --(ChatCompletions)--> 网关 :7863 --> WorkBuddy ``` 它和 [codex-chat-bridge](https://github.com/kangjiazhen/codex-chat-bridge) 是同一份代码,以 `bridge/codex-chat-bridge.mjs` 内置(零依赖,跑在 Harness 自带的 Node 上),所以装这一个插件就够了 —— 不需要再 clone 第二个仓库,也没有任何路径要你填。 ## 安装 ```bash dsh plugin add git+https://github.com/kangjiazhen/dsh-workbuddy-stack.git ``` 或者从本地检出安装: ```bash dsh plugin add file:/path/to/dsh-workbuddy-stack ``` 插件自带 `cordis.patch.yml`,所以只要它成为 profile 依赖,profile 就会自动拿到 `workbuddy-stack` 这一条。 ### git 安装失败的话 除非另有指定,`pnpm` 会把 `git+https://…` 改写成 `git+ssh://git@…`,因此在没有配置 GitHub SSH 密钥的机器上,安装会失败并报 `Permission denied (publickey)`: ```bash npm config set git-protocol https ``` 如果走代理,还得单独告诉 git —— `pnpm` 是通过调用 git 二进制实现的,不会把自己的代理设置传下去: ```bash git config --global http.proxy http://127.0.0.1:7890 ``` ## 配合 Codex 使用 桥监听 `7864`,对外提供 Responses API。把 Codex 指过去: ```toml # ~/.codex/config.toml model = "global:gpt-5.3-codex" model_provider = "wb" [model_providers.wb] name = "workbuddy" base_url = "http://127.0.0.1:7864/v1" wire_api = "responses" experimental_bearer_token = "anything" request_max_retries = 2 stream_max_retries = 2 ``` 桥本身不校验这个 bearer —— 它用自己的方式(从网关 `config.json` 读出的 key)向上游认证。填任意非空字符串即可,因为 Codex 没有它就不启动。 之后正常用 Codex 就行: ```bash codex exec "列出当前目录的文件" ``` 如果选到了你的账号档位用不了的模型,网关会返回 `11158 model not allowed for user type [...]`。那是账号属性,不是桥的故障 —— 见[模型可用性](#模型可用性)。 ### 两个绝对不能跳过的设置 **1. `conversation_id` —— 能把账号搞封的那个。** WorkBuddy 上游会校验三个头(`X-Conversation-ID`、`X-Conversation-Request-ID`、`X-Conversation-Message-ID`),缺任意一个就返回 `400 {"code":"11-128","msg":"request illegal"}`。网关把这种情况归类为*账号故障*,处理方式是**禁用账号** —— 而且是一轮里把碰到的账号全部禁用。它给出的原因是 `re-login required`,这句话有误导性:凭证通常仍然有效,解冻就能立刻恢复服务。 所以桥默认 `conversationId: auto`:按 Codex 会话(取自 `prompt_cache_key`)推导出一个稳定 id,作为 `metadata.conversation_id` 发送。不要把它置空,插件会在启动时告警。 如果不幸已经踩了,解冻账号即可,不需要重新登录: ```bash # 网关面板:http://127.0.0.1:7863/panel/ curl -X POST http://127.0.0.1:7863/panel/api/accounts//revive ``` **2. bearer 必须和网关的 `api_key` 一致。** 没有它,网关对每个请求都回 `401`。为了不让你手工抄密钥,插件直接从网关的 `config.json` 里读 `api_key`(`gatewayDir` 就是干这个的)并交给桥。只有要覆盖时才设 `bridge.apiKey`。 ## 配置 默认值假设了常规目录结构,可在 profile 的 `cordis.yml` 中覆盖: ```yaml - id: workbuddy-stack name: dsh-workbuddy-stack config: intervalMs: 15000 # 复查间隔 probeTimeoutMs: 2000 # 单次探测超时 gatewayDir: ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel gateway: enabled: true autostart: true baseURL: http://127.0.0.1:7863 acceptStatus: [200, 503] command: ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel/wb2api.exe args: [-config, config.json] cwd: ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel bridge: enabled: true autostart: true baseURL: http://127.0.0.1:7864 acceptStatus: [200] command: '' # 留空 = 运行内置桥 upstream: '' # 留空 = 用网关的 baseURL apiKey: '' # 留空 = 读 gatewayDir/config.json conversationId: auto # 必填 —— 见上文 modelAllowlist: [] # 留空 = 放行所有模型 ``` 路径支持 `~`、`$VAR` / `${VAR}` 以及 Windows 的 `%VAR%`,所以同一份配置可以在机器之间直接拷。 `bridge.command` 留空是刻意的。一旦填了,插件就从「运行内置桥」切换成「监管这条命令」,用于指向另一个桥构建;`CCB_PORT`、`CCB_UPSTREAM` 等环境变量依然由插件推导。 ### `acceptStatus` 很关键 网关在**进程活着但上游账号暂时不可用**时(没登录,或所有账号都在冷却),`/healthz` 返回 **`503`**。那是*活着*,不是*挂了*。 只认 `2xx` 的监管器会把它读成「没启动」,于是拉起第二份、抢端口失败。所以网关那条默认接受 `[200, 503]`,而桥只接受 `[200]`。 ### `autostart: false` 只观察、永不拉起。当生命周期由别的东西掌管时用这个 —— 计划任务、容器、包装脚本 —— 你只想拿到健康状态上报。 ## 状态工具 当 `tools` 缝挂载后,插件会注册 `workbuddy_stack_status`: ``` gateway: up (http://127.0.0.1:7863/healthz, pid 14592) bridge: up (http://127.0.0.1:7864/healthz) ``` 每行都带实时探测结果、被监管子进程的 pid,以及最近一次拉起失败的错误 —— 这样「模型列表是空的」和「端口死了」不用翻日志也能分清。 如果 `@deepseek-ai/dsh-tools` 无法导入,工具仍会以原始描述符注册;如果 `tools` 缝根本不存在,监管照常运行。两者互不依赖。 ## 生命周期 - 第一次监管在 `apply()` 之后异步立刻执行,冷启动不会阻塞 Harness。 - 定时器是 `unref` 过的 —— 空转的监管器不会把进程吊住。 - dispose 时停掉定时器,并终止**本插件自己拉起**的子进程。加载插件之前就在跑的服务只探测上报、绝不接管,所以卸载插件不会杀掉别人启动的进程。 ## 模型可用性 哪些模型能应答,取决于你的 **WorkBuddy 账号档位**,与本插件和桥无关。一个 `personal_free_user` 账号在 2026-10-21 实测能触达: | 域 | 可用 | | --- | --- | | `global:` | `gpt-5.3-codex`、`glm-5.3`、`glm-5.2`、`kimi-k3`、`kimi-k2.6`、`hy4-preview`、`step-5-preview`、`deepseek-v4.1-flash`、`minimax-m3.1-flash` | | `cn:` | `deepseek-v4.1-flash`、`deepseek-v4-pro`、`glm-5.3` | 其余 GPT 模型(`gpt-5.4`、`gpt-5.5`、`gpt-6-*` 全系……)一律返回 `11158 model not allowed for user type [personal_free_user]`。这份名单随你的订阅变动,请当作快照而非承诺。 `modelAllowlist` 的用途是让已知不可达的模型**快速失败**:桥直接本地回 `400 model_not_allowed`,而不是让网关把拒绝归类成账号故障、进而禁用整个池子。留空是诚实的默认。 ## 范围与限制 - 只做进程监管加请求翻译。它不启动、停止或配置*上游* provider,也不对你账号能用哪些模型发表意见。 - 没有重启退避:瞬间死掉的子进程每个 `intervalMs` 会被拉起一次。如果二进制缺失或端口被别人占着,日志每轮都会说明,不会空转。 - spawn 时设了 `windowsHide: true`,被监管的控制台子进程不会在 Windows 上闪窗。 - Linux 和 macOS 用同一份代码即可;只有 `cordis.patch.yml` 里的默认路径是 Windows 专用的。 - 上游 WorkBuddy 项目已停止维护,并正在收紧反代检测。本插件能让链路活着,但保不住上游通道本身。 ## 相关 - [codex-chat-bridge](https://github.com/kangjiazhen/codex-chat-bridge) —— 本插件内置的那座桥,想单独用可以去这里。 - [LiteLLM](https://github.com/BerriAI/litellm)、[one-api](https://github.com/songquanpeng/one-api)、[new-api](https://github.com/Calcium-Ion/new-api) —— 完整网关,如果你需要路由与密钥管理,而不是本地进程监管。 ## 许可 MIT