从codex-plugin-cc学5个设计模式:薄转发、verbatim输出与单一事实源
【免费下载链接】codex-plugin-ccUse Codex from Claude Code to review code or delegate tasks.项目地址: https://gitcode.com/GitHub_Trending/co/codex-plugin-cc
codex-plugin-cc是 OpenAI 推出的 Claude Code 插件:让你在 Claude Code 里直接调用 Codex 做代码审查(/codex:review)或委派任务(/codex:rescue)。它代码量不大,却处处体现工程取舍。本文带你读懂它的 5 个可复用设计模式:薄转发、verbatim 输出、单一事实源、共享代理(连接复用)、结构化输出兜底,并给出源码位置方便对照阅读。
项目一览:3 层结构 🗂️
整个插件只有三层,理解结构后再看模式会非常清晰:
| 层 | 目录 | 职责 |
|---|---|---|
| 命令层 | plugins/codex/commands/ | 每个斜杠命令一份 Markdown,告诉 Claude「怎么调脚本、怎么回话」 |
| 脚本层 | plugins/codex/scripts/ | Node.js 脚本,真正干活:起任务、管状态、渲染结果 |
| 数据层 | 本地状态文件 | state.json+ 每个任务一份 JSON,所有事实的落盘处 |
入口是 codex-companion.mjs,setup / review / task / status / result / cancel等子命令全部在这里分派。
模式一:薄转发 —— 命令文件只“传话”,不干活 💡
新手常见做法:把逻辑写进命令定义里。这个插件反着来——命令文件是薄转发层,几乎不含业务逻辑。
以 rescue.md 为例,它对子代理的约束极其克制:
The subagent is a thin forwarder only. It should use one
Bashcall to invokenode .../codex-companion.mjs task ...and return that command's stdout as-is.
(子代理只做一件事:一次Bash调用脚本,然后把 stdout 原样返回。)
转发链条是:/codex:rescue命令 →codex-rescue子代理 → 一次脚本调用 → codex-companion.mjs 分派到handleTask。整条链路上没有任何一层“顺手”做额外的事。
可迁移的经验:把“决策逻辑”收敛到唯一的脚本入口,上层全部变薄。以后改行为只改一处,上层 Markdown 基本不用动。
模式二:verbatim 输出 —— AI 只搬运,不改写 📋
这是全文最值得抄的一条规则。review.md 对 Claude 的要求是三连否定:
- Return the command stdout verbatim, exactly as-is.(原样返回)
- Do not paraphrase, summarize, or add commentary.(不许转述、总结、加评论)
- Do not fix any issues mentioned in the review output.(不许顺手修代码)
脚本侧也配合了:render.mjs 的renderTaskResult拿到 Codex 的原始输出后,只是补一个换行就原样输出,不做任何加工。
更狠的约束写在 codex-result-handling/SKILL.md:
CRITICAL: After presenting review findings, STOP. ... Auto-applying fixes from a review is strictly forbidden, even if the fix is obvious.
为什么重要:LLM 转述 AI 输出时最容易“好心办坏事”——丢细节、改语气、甚至擅自执行。让模型当“传声筒”、让脚本控制最终文本,结果才可预期、可复现。
模式三:单一事实源 —— 一份 state.json 定乾坤 🗄️
状态管理是背景任务插件的难点。本项目的做法见 state.mjs:
- 一个仓库一套状态:目录名 = 仓库名 + 根路径 SHA256 前 16 位(state.mjs#L29-L44),不同仓库互不串扰;
state.json是唯一权威:任务列表、配置都只存这一份,读取时自动合并默认值并容错损坏文件;jobs/目录存全量记录:每个任务一份完整 JSON(含请求参数、原始输出),state.json里只留索引和摘要;- 自动瘦身:
pruneJobs只保留最近MAX_JOBS = 50条,被裁剪的任务连同日志一起删除(state.mjs#L80-L84)。
写入路径也收敛为单一入口updateState→saveState,任何地方改状态都走这里。
可迁移的经验:把“权威数据”和“全量明细”分两份,但只允许一个写入函数。多入口写入是状态类 bug 的头号来源。
模式四:共享代理 —— 多个客户端复用一条连接 🔌
Codex 的 app-server 启动成本高。如果每个任务各起一条连接,资源浪费还容易互相打架。插件的方案是 app-server-broker.mjs:一个常驻的 JSON-RPC 代理进程,所有客户端通过本地 socket 连到它,由它独占并转发到 Codex。
关键设计都在 250 行内:
- 忙则拒绝:已有活跃请求时,新请求直接收到
broker is busy错误,而不是排队死等(app-server-broker.mjs#L173-L182); - 流式事件精确路由:
turn/completed只发回发起对应线程的那个客户端; - 特例白名单:
turn/interrupt允许在流式进行中插队,保证“取消”永远可用(app-server-broker.mjs#L170-L195); - 干净关停:SIGTERM/SIGINT 下断开所有 socket、删掉 socket 文件与 pid 文件。
“拒绝 + 路由 + 特例”三件套,就是一个最小可用的连接复用网关。
模式五:结构化输出 + 优雅降级 🧯
对抗性审查(/codex:adversarial-review)要求 Codex 按 JSON Schema 输出,见 review-output.schema.json:verdict/summary/findings/next_steps四个必填块。
渲染层 render.mjs 体现了成熟系统的姿态:
- 解析失败→ 不报错崩溃,直接展示 parse error + 原始输出;
- 结构不合法→ 展示 validation error + 原始输出;
- 合法→ 逐字段
normalize(缺 severity 补low、缺 title 补Finding N),按严重度排序后渲染成表格。
“先校验、再清洗、兜底展示原文”三步走,让 LLM 输出这种不确定性输入始终有确定性的呈现结果。
一张表总结:5 个模式的“一句话心法” 📌
| # | 模式 | 一句话心法 | 代表文件 |
|---|---|---|---|
| 1 | 薄转发 | 上层只传话,逻辑收口在唯一入口 | rescue.md |
| 2 | verbatim 输出 | AI 当传声筒,脚本控制最终文本 | review.md |
| 3 | 单一事实源 | 一份权威数据 + 一个写入函数 | state.mjs |
| 4 | 共享代理 | 复用连接,忙则拒绝,事件精确路由 | app-server-broker.mjs |
| 5 | 结构化输出兜底 | 校验 → 清洗 → 原文兜底 | render.mjs |
新手阅读路线:按这个顺序翻源码 🚀
- README.md —— 10 分钟看懂 7 个斜杠命令与典型流程;
- commands/ 下 8 个 Markdown —— 看懂“薄转发层”怎么写;
- codex-companion.mjs —— 命令分派与任务生命周期(可配合 tracked-jobs.mjs 看
queued → running → completed/failed/cancelled); - lib/ —— state、render、git、process 各司其职的小模块;
- tests/ —— 每个 lib 模块都有对应测试,是理解行为契约的最快路径。
这 5 个模式不挑语言也不挑场景:写 CLI、做代理层、管后台任务时都能直接搬。建议 clone 下来,边跑/codex:review边对照源码,一遍即可。
【免费下载链接】codex-plugin-ccUse Codex from Claude Code to review code or delegate tasks.项目地址: https://gitcode.com/GitHub_Trending/co/codex-plugin-cc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考