开源 Agent 套件 ECC 的插件形态与第三方集成:两个方向的边界怎么划

2026-07-29

本文基于 ECC 仓库 commit 591ab5c(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/affaan-m/ECC 最新代码与文档为准。

在 ECC 这套 harness 增强件里,「插件」和「集成」是两个方向相反的接口,责任分配也完全不对称:插件方向上,ECC 是被安装的一方,它要迁就宿主 harness 的清单格式与缓存行为,出问题时你调的是宿主;集成方向上,ECC 是调用外部服务的一方,它把闸门、策略和最终决定全部留在调用者的代码里,出问题时账算在你头上。 这两条链路在仓库里分别落在 plugins/integrations/ 两个目录,看懂它们的差别,比记住安装命令有用得多。

站内已有的 MCP 协议是什么MCP 安全边界MCP 选型标准 讲的是通用方法论——扩展机制该怎么理解、边界该往哪里划、选型该看什么;本篇换个角度,盯住一个你现在就能 clone 下来对照的具体项目,看这些原则落到清单文件、缓存校验脚本和一份威胁模型文档上,实际长什么样。

一、被装进去的那一侧:一个仓库同时供三套清单

ECC 采用 MIT 许可证,仓库根目录同时躺着几套面向不同 harness 的插件清单。它们不是复制粘贴的产物,路径写法差异恰恰暴露了各家 harness 的约束。

Claude Code 这一侧最直白。.claude-plugin/plugin.json 里,skills 写成 ["./skills/"]commands 写成 ["./commands/"]mcpServers 是一个空对象;同目录的 marketplace.json 把这个仓库自己登记成一个 marketplace,插件条目的 source 直接写 "./"——仓库根就是插件根。安装动作对应 README 里的两行:先 /plugin marketplace add https://github.com/affaan-m/ECC,再 /plugin install ecc@ecc。不想敲命令的话,README 给了等价的 settings.json 写法,用 extraKnownMarketplaces 声明来源、enabledPlugins 打开 ecc@ecc

这里有个容易被跳过的限制,README 写得很直:Claude Code 插件无法分发 rules。所以规则包得你自己 clone 仓库、手动把 rules/common 加上一个你真正在用的语言包复制到 ~/.claude/rules/ecc/。这不是 ECC 偷懒,是宿主的能力边界——插件机制能带什么、不能带什么,永远是宿主说了算,这一点在你评估任何 harness 扩展时都成立,也是 Claude Code 技能机制 那类话题绕不开的前提。

顺带记住三个不能互换的标识符,README 专门辟了一段解释:GitHub 源仓库是 affaan-m/ECC,Claude 侧的 marketplace/插件标识是 ecc@ecc,npm 包名则是 ecc-universal。三个名字长得不像是有意为之,不是笔误。

二、Codex 那一侧:清单只放路径,内容一份都不复制

Codex 这条路的写法明显别扭,别扭的原因写在 plugins/ecc/README.md 第一段:Codex 不会去发现那些本地 marketplace source.path 指向 marketplace 根目录(./)的插件,条目必须指向一个具体的插件子目录。文档特意标了这是对着某个具体的 Codex CLI 版本和官方插件文档实测出来的结论,而不是照着规范推断的——这种把「结论有效期」写清楚的习惯,比结论本身更值得学,因为宿主行为随时可能变,一条没标验证条件的结论过几个月就成了误导。于是 .agents/plugins/marketplace.json 里的 source.path 写的是 ./plugins/ecc,而这个目录下除了一份说明文档,只有 .codex-plugin/plugin.json 一个清单。

清单里没有任何技能或 MCP 内容,全靠父级相对路径回指仓库根:skills../../skills/mcpServers../../.mcp.json,图标和 logo 同样用 ../../assets/ 回指。文档把这条叫作仓库的「无重复策略」——单一事实来源,不在插件目录里复刻一份。同时,仓库根还保留着一份 .codex-plugin/plugin.json,字段一模一样但路径是 ./skills/./.mcp.json,那是给仓库根 bundle 形态用的。两份清单的 nameversion 必须一致,tests/plugin-manifest.test.js 会卡这件事,scripts/release.sh 发版时两处一起 bump。

代价紧跟着来了。ECC 自己的文档承认,仓库 marketplace 的运行时技能加载在上游仍不可靠:Codex 只把插件文件夹复制进安装缓存,本地或个人 marketplace 的插件不一定在运行时被暴露出来。翻译成人话——清单里那些 ../../ 指向的东西,可能压根没跟着进缓存。文档同时列了上游与项目自身的两个跟踪 issue,没有假装问题不存在。

正因如此,plugins/ecc/README.md 明确说 codex plugin list 不足以证明运行时能加载到引用的技能和资产,要从 ECC 检出目录里跑:

node scripts/codex/check-plugin-cache.js

这个脚本会去 CODEX_HOME(没设就是 ~/.codex)下检查安装缓存,如果 .codex-plugin/plugin.json 指向的文件没被复制进那个缓存条目,它直接判失败。这是一个值得抄走的做法:当你的分发形态依赖宿主的复制行为时,写一个校验脚本把「装上了」和「能用」这两件事分开断言,别让「列表里有」冒充「运行时能加载」。

在上游问题落定之前,仓库给出的受支持路径是手动同步:

npm install && bash scripts/sync-ecc-to-codex.sh

README 还配了一条硬约束:一个 harness 只选一种安装方式,不要把 Codex 同步流程和 Codex marketplace 插件叠着装,也不要在装了 Claude Code 插件之后再跑完整手动安装。叠装的后果是技能、命令、钩子或配置被复制两份。

三、组成部分对照表

组成部分它负责什么仓库位置你什么时候会碰到它
Claude 插件清单声明 skills 与 commands 目录,用 ./ 相对仓库根.claude-plugin/plugin.json执行 /plugin install ecc@ecc
自托管 marketplace 目录把仓库自身登记成 marketplace,插件 source./.claude-plugin/marketplace.json执行 /plugin marketplace add 指向本仓库时
Codex 仓库级 marketplace 条目source.path 指向具体子目录而非根.agents/plugins/marketplace.json执行 codex plugin marketplace add affaan-m/ECC
插件目标目录清单只放清单不放内容,用 ../../ 回指根内容plugins/ecc/.codex-plugin/plugin.json排查 Codex 装完为什么加载不到技能时
仓库根 Codex 清单根 bundle 形态的同名清单,路径写 ./.codex-plugin/plugin.json按仓库根形态分发或改版本号时
缓存自检脚本断言清单引用的文件真被复制进安装缓存scripts/codex/check-plugin-cache.js插件列表正常但技能不生效时
清单一致性测试卡住两处清单的 name 与 version 不许漂移tests/plugin-manifest.test.js发版或手改清单后
Codex 同步脚本合并 AGENTS.md、prompts、agents 与 MCP 配置进 ~/.codex,带时间戳备份scripts/sync-ecc-to-codex.sh走受支持的 Codex 路径时
信任检查适配器一次 HTTP GET 换回一个 verdict,纯标准库无依赖integrations/aura/adapter.py在委派或结算前想加一道闸时
适配器威胁模型写清 verdict 证明什么、不证明什么、失败模式归谁integrations/aura/THREAT_MODEL.md评审要不要引入这个集成时

顺带一提,plugins/README.md 是一份与 ECC 自身无关的通用说明,讲的是怎么在 Claude Code 里添加 marketplace、怎么装 typescript-lspmgrep 这类第三方插件,以及插件文件落在 ~/.claude/plugins/ 下的 cache/installed_plugins.jsonknown_marketplaces.jsonmarketplaces/。你排查「插件到底装到哪去了」时,这几个位置就是第一现场。

四、往里挂的那一侧:AURA 适配器把哪些东西写死了

integrations/ 目录目前只有一个 aura,一个对手方信誉检查的适配器。它的 README 开头四条自我约束值得逐条看,因为每一条都是在主动缩小自己的权限面:零依赖(纯 Python 标准库,直接把 aura/ 文件夹 vendor 进去,不用 pip install);只读(唯一的网络调用是 GET /check?did=...,无鉴权无 API key);无耦合(不签名、不持有密钥、不动资金、不碰你的钱包);默认关闭(你不调它就什么都不发生,禁用等于删掉那行 import)。

用法上它没有全局钩子、没有猴子补丁、没有后台调用,就是一个你在信任边界上显式调用的闸:

from aura import before_settle, AuraUntrusted

def settle(counterparty_did: str, amount: float) -> None:
    try:
        before_settle(counterparty_did)        # rejects high_risk + unknown
    except AuraUntrusted as e:
        log.warning("blocked: %s", e)
        return                                  # your policy decides what to do
    pay(counterparty_did, amount)               # your existing logic, untouched

不想让它抛异常,可以自己读 aura_verdict() 返回的对象,里面有 verdict(五个取值:trustedcautionhigh_risknewunknown)、reasonscoreok。README 特意插了一条提醒,防止你误用:v.ok 反映的是 verdict 的类别(trustedcaution 为真),不等于 require_trust() 的结果,因为闸的默认放行集合还包含 new——决策看闸的返回或异常,v.ok 只用来展示。这种主动指出自家两个近似语义容易混淆的写法,比多写十行文档更有诚意。

策略旋钮也很克制:allow 收紧放行集合,base_url 指向自建或预发网关,timeout 自己定,fail_open 决定服务不可达时算放行还是拦截。默认是失败关闭,README 给的理由是一句可以直接抄进你团队规范的话:缺少证据不等于有信任的证据。而 aura_verdict() 在网络或解析出错时永不抛异常,只返回一个 unknown 并附带 reason,把「取不到信号」和「信号说不行」这两件事在类型层面就分开了。整个集成因此保持纯增量:你把适配器删掉,或者对方服务挂了,你原有的放行/拒绝逻辑跟以前一模一样地跑。测试也是离线的,用录制好的 /check 响应体回放,覆盖五种 verdict、放行列表、失败开放和不可达路径。

五、集成方自己写威胁模型,写的是什么

THREAT_MODEL.md 是这个集成里最有参考价值的文件,因为它花了大半篇幅说自己能干什么。

它先把 verdict 的性质框死:这是一个向后看的信号,是对已记录行为的陈述,不是预测,更不是对当前这个动作的授权。接着三条否定写得毫不含糊——不证明动作安全(一个 trusted 的对手方照样能提出恶意或有 bug 的交易,得另配一个向前看的动作风险检查,而且两个信号要分开保存,好让策略决定可审计);不证明执行质量;不证明活体调用方的身份(它查的是某个 DID 的信誉,不是你正在对话的实体控制着那个 DID)。

文档随后用一张表列了六类失败模式,每行三列:威胁、适配器里做了什么缓解、剩下的残余风险归谁。端点不可达返回 unknown 且默认失败关闭,但 fail_open 怎么选、超时设多少归你;DID 冒用直接标为超出范围,你得在信任 verdict 之前自己验证 DID 控制权;分数滞后于最近的坏行为,适配器不做缓存,你要是自己缓存就得约束 TTL 且不要跨会话复用;中间人与响应篡改这一侧,走 HTTPS 到固定主机,verdict 字符串对着固定白名单校验、不认识的值一律塌缩成 unknown,但你别把 base_url 指向不可信的镜像;刷分与女巫攻击承认适配器解决不了,建议高价值动作去看各维度而不是只信聚合值;过度信任这一条,则直接建议配合动作风险检查、托管和人工复核。

数据处理写得同样具体:只发送对手方 DID 一个查询参数,不含 PII、载荷、密钥;不存储任何东西,适配器无状态;收到的公开 JSON 原样挂在 .raw 上。文档末尾画了一张信任边界示意图,签名、资金流动和最终放行决定全在你的代码里,适配器只坐在只读的信誉那条边上。

这套写法对你有两个直接用处。一是引入任何外部依赖时,可以拿这几个问题当模板:它证明什么、不证明什么、失败时默认往哪边倒、数据流出去了什么、剩下的风险归谁。二是它示范了权限面收敛的具体手法——只读、无状态、显式调用、白名单校验、默认失败关闭,跟 最小权限设计 那一套是同一个思路,只是这里能看到落到代码接口上的样子。

六、边界与代价:这个设计放弃了什么

放弃了「一个清单打天下」。 同一份内容摊在好几处清单里(两处 marketplace 描述、两处 Codex 清单、一处 Claude 清单),路径写法各不相同(./skills/../../skills/),靠一致性测试和发版脚本兜底。好处是每个 harness 都拿到它认识的形态,代价是任何一次改名或改版本都要多处同步,仓库里那份根级清单的 interface 描述文案就还停留在更早的技能数上,跟 skills 目录里现在的数量已经对不上——这类文案漂移正是多清单形态的典型副作用。

放弃了 Codex 插件路径的开箱可用。 「不复制内容」换来了单一事实来源,也换来了对宿主复制行为的强依赖。文档没有回避,把这条路标成实验,把同步流程标成受支持路径,还配了校验脚本。你要是需要全部技能稳定可用,现在就该走同步。

同步流程本身也不是零成本。 它会往你的 ~/.codex 里合并 AGENTS.md、写入 prompts 与 agents、合并 MCP 配置,并且脚本头部注释里明确写着会安装全局 git 安全钩子(pre-commit 与 pre-push)。它做了时间戳备份、支持 --dry-run、合并采用只增不改的方式,但往你机器的全局位置写文件、挂 git 钩子这件事本身就是一次权限让渡,值不值得由你判断,别因为它有备份就当作无风险操作。

信任集成明确不管的事 已经在上一节列全了:不管身份控制权、不管动作是否安全、不管刷分。它只回答一个向后看的问题,剩下的事你得自己接。

七、上手与避坑清单

一个 harness 只选一条安装路径。 会踩是因为文档里安装方式并列摆着,看着像可以叠加,而插件安装和手动/同步安装往往落在不同目录,互相看不见对方。避法是装之前先确认这台机器上此前有没有装过——README 给的自查顺序是 node scripts/ecc.js list-installed,再跑 doctorrepair,别急着重装一遍。

别把 codex plugin list 当验证。 会踩是因为列表显示成功符合直觉,而这里失败发生在更下游的缓存复制环节,症状是「装是装上了,技能就是不出现」。避法是跑 node scripts/codex/check-plugin-cache.js,让它去比对清单引用的文件是否真在缓存里。

别把 Claude Code 插件当成 rules 也带过来了。 会踩是因为插件确实带了技能、agent、命令和插件托管的钩子,很容易默认规则也在里面。避法是按文档手动复制,从 rules/common 加一个你真正在用的语言包起步,别整目录全拷。

改版本号时记得两份 Codex 清单一起改。 会踩是因为 plugins/ecc/.codex-plugin/plugin.json 和根目录的 .codex-plugin/plugin.json 名字完全一样,只有路径前缀不同,肉眼扫过去以为是同一个文件。避法是跑 tests/plugin-manifest.test.js,或者干脆用 scripts/release.sh

别用 v.ok 做放行决策。 会踩是因为字段名太像一个最终答案,而它只表达 verdict 的类别,跟闸的默认放行集合并不重合。避法是决策一律看闸的返回或抛出的异常,v.ok 只拿来渲染界面。

先验 DID 控制权,再看信誉。 会踩是因为拿到一个 trusted 会本能地松一口气,而威胁模型把冒用明确划在范围之外。避法是在调用这道闸之前完成签名挑战之类的身份验证,顺序反了整条链路就白搭。

fail_open 要当成一次显式的风险取舍来做。 会踩是因为线上一旦因为外部服务不可达被拦住,第一反应就是把它打开止血,然后忘了关。避法是打开前先想清楚:这条路径是可逆的还是要动钱的,以及把这个开关的状态写进你的日志,别让它成为一个没人记得的隐式配置。

收束

把这篇的判断压成一句:ECC 在被集成的方向上迁就宿主、把不确定性用校验脚本显式暴露出来,在集成别人的方向上主动缩小权限面、把决定权和残余风险原封不动地退还给调用者。两个方向的共同点是不粉饰——上游有问题就写清哪个 issue,自己证明不了的事就写进威胁模型。

给自己一份最小自检:这台机器上此前有没有装过、装的哪一种;缓存校验脚本跑过没有;规则包是不是漏了;如果你打算引入那个信任集成,fail_open 的取值是不是一次有意识的决定,DID 控制权的验证挂在闸的前面还是后面。

要继续读的话,顺序建议是 plugins/ecc/README.md(看清多清单形态和它的代价),scripts/codex/check-plugin-cache.js(看一个校验脚本该断言什么),然后是 integrations/aura/THREAT_MODEL.md——最后这份文档你就算不用这个集成也值得读一遍,它是一个可以直接套用到自己项目上的模板。

本文属于 ECC 开源 Agent 套件专题(共 40 篇,从选择性安装一直拆到内部机制)。同一批还写了另一种取向的项目——不给你资产、只给你纪律的 superpowers 方法论专题

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。