@coda-console/plugin-check 0.0.0-stage → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 coda-console
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,315 @@
1
- # Temporary Holding Version
1
+ # Coda 插件
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Coda 的公开插件仓库,和给插件作者用的契约检查。Coda 自带的 Agent 插件(Codex、Claude、Kimi、Grok、OpenCode 与 Codex 多账号)和 PR 生命流程也在这里维护。
4
+
5
+ Coda 是一个多 Agent 客户端(Mac、iPhone 与运行在你机器上的后端)。Agent 与资源页的面板都以插件接入:
6
+ 插件是一个目录,清单就是它的 `package.json`(`contributes.agents`、`contributes.panels` 等,照 VS Code 扩展的写法);
7
+ Agent 讲 [ACP](https://agentclientprotocol.com),外加 Coda 的 Agent 活动报告 `_coda/wait`,见 [接入协议](docs/agent-protocol.md)。
8
+
9
+ ```
10
+ plugins/codex/ Codex:入口在同一进程里运行锁定版本的 codex-acp(npm 依赖),面板「Codex」
11
+ plugins/claude/ Claude SDK:基于 Claude Agent SDK 的 ACP 入口(npm 依赖),面板「Claude」
12
+ plugins/kimi/ Kimi Code:在运行机的 kimi acp 与 Coda 之间转发,面板「Kimi」
13
+ plugins/grok/ Grok:转发运行机上 Grok CLI 的 ACP,并适配审批模式,面板「Grok」
14
+ plugins/opencode/ OpenCode:在运行机的 opencode acp 与 Coda 之间转发,送入会话指令
15
+ plugins/codex-accounts/ Codex 多账号:管理登录、自动续期与账号选择,接管 Codex 额度面板
16
+ plugins/cursor/ Cursor:在运行机的 cursor-agent acp 与 Coda 之间转发,送入会话指令
17
+ plugins/review/ PR 生命流程:会话开的 PR 先跑项目的检查、再交叉评审,你批准后合并、部署与发布;也是写 PR 流程的样例
18
+ templates/agent/ 最小的 Agent 插件:手写 ACP 的回声 Agent,演示会话指令、插话、Agent 活动与自主轮次
19
+ templates/panel/ 最小的面板插件:一张卡片、一条进度条、一个按钮
20
+ bin/、check/ 契约检查(仓库根的 package.json 就是它,@coda-console/plugin-check)
21
+ test/ 检查包的测试;test/plugins/ 是各插件的测试(插件目录里只放运行要用的文件)
22
+ docs/agent-protocol.md Coda 接入协议正文的公开副本
23
+ .coda/workflows/ 本仓库自己的流程:sync-coda 把内置插件的副本同步进 Coda
24
+ ```
25
+
26
+ ## Coda 自带的插件
27
+
28
+ `codex`、`claude`、`kimi`、`grok`、`opencode`、`codex-accounts`、`review` 是 Coda 内置的插件,源码从此在这里维护。
29
+ Coda 仓库里的 `backend/src/resources/plugins/<id>/` 是本仓库**某个提交的只读副本**:Coda 的同步脚本按它的锁文件
30
+ (记着本仓库的提交与每个目录的树哈希)把那个提交里的 `plugins/<id>` 原样复制过去,新机器、离线首启都带着它们。
31
+
32
+ - **改它们先在这里提 PR、合并**,再在 Coda 里同步到那个提交;不要直接改 Coda 里的副本(Coda 的测试会核对副本与锁文件一致)。
33
+ 同步由本仓库的部署流程 `sync-coda`(`.coda/workflows/sync-coda.bpmn`、`scripts/sync-coda.mjs`)做:合并时勾选「部署」,
34
+ 它在合并提交上运行 Coda 的同步脚本,推到 Coda 的分支 `sync/coda-plugins` 并开 PR(开着的就只更新),PR 由开这边 PR 的会话负责、走 Coda 的 PR 流程;
35
+ 副本没有变化就什么都不做。也可以单独发起,同步默认分支的最新提交。
36
+ - **改了哪个插件就升哪个插件的 `version`**(`plugins/<id>/` 下任何文件,含 `package-lock.json`;只改 `test/` 不用):Coda 的插件页与遥测按版本认插件。
37
+ CI 在 PR 上运行 `node scripts/check-versions.mjs origin/<目标分支>` 核对;Dependabot 升 CLI、SDK 的 PR 也要补一个升版本的提交。
38
+ - Coda 的「检查更新」可以从这里装同一插件的新提交,盖过内置副本;只有本仓库的 `plugins/<id>` 这个来源能用这些名字。
39
+ - 测的是插件自己代码的用例在 `test/plugins/<id>/`;把 Coda 与入口合在一起测的(启动 Coda 后端、写进 Coda 的存储)留在 Coda,对副本跑。
40
+ `review` 是一个流程,要 Coda 的流程引擎才跑得起来,它的用例都在 Coda。
41
+
42
+ | 插件 | npm 依赖(与 Coda 后端同版本) | 运行机上另要 |
43
+ | --- | --- | --- |
44
+ | `codex` | `@agentclientprotocol/codex-acp` 1.12.0、`@openai/codex` 0.162.1;`overrides` 让 codex-acp 用同一个 `@openai/codex` | — |
45
+ | `claude` | `@agentclientprotocol/sdk` 1.4.0、`@anthropic-ai/claude-agent-sdk` 0.3.296(平台二进制是 SDK 自己的可选依赖) | — |
46
+ | `codex-accounts` | `proper-lockfile` 4.1.2;用依赖插件 `codex` 的 Codex CLI | 无;依赖 `codex` 插件 |
47
+ | `kimi` | 无 | Kimi CLI 2.1.1 及以上(设置动作安装或升级;面板可从 Coda 安装、登录) |
48
+ | `grok` | 无 | Grok CLI 1.0.52 及以上(设置动作安装或升级;面板可从 Coda 安装、登录) |
49
+ | `opencode` | 无 | 已登录的 OpenCode CLI 1.0.151 及以上(设置动作安装或升级) |
50
+
51
+ 带依赖的插件各有自己的 `package-lock.json`(`node_modules` 不提交);Coda 在插件的版本目录里 `npm ci` 装它们,
52
+ 这几个插件不是本仓库根的 npm workspaces(根包是契约检查,装它不该带上 Codex、Claude 的平台二进制)。
53
+
54
+ ### 会话指令与设置动作
55
+
56
+ Coda 的会话指令(`systemPrompt`)各插件都声明并接受,只接受 `append` 模式的文本块,其他回 -32602、不建立会话:
57
+ Claude 追加在 `claude_code` 预设的系统提示词后面;Codex 经 `thread/start`、`thread/resume` 作为 `developerInstructions` 交给 Codex,
58
+ 接在会话目录下 Codex 自己配置的 `developer_instructions`(`config/read`)后面
59
+ (codex-acp 按 ACP 的 schema 读请求会丢掉 `systemPrompt`,入口改放在请求的 `_meta` 里交给打过补丁的 codex-acp)。
60
+ Kimi、Grok、OpenCode、Cursor 的 CLI 不接受它:入口不转给 CLI,在 `session/new` 建立的原生会话第一次 `session/prompt` 时,
61
+ 把指令包在 `<coda-session-instructions>` 里作为单独的一段文本放在用户输入前面,之后与 `session/load` 恢复的会话不再送;
62
+ 第一条是斜杠命令时留到下一条。Codex 另声明插话:一轮已经结束时收到的 `_session/steering` 回 `promptRequired`,不另开一轮。
63
+
64
+ Kimi、Grok、OpenCode、Cursor 带设置动作(`contributes.setup`,`scripts/setup.mjs`),用户安装或更新插件时 Coda 在每台机器上运行:
65
+ CLI 不在或运行不了就用它自己的安装程序安装,低于插件要求的版本就升级,较新的版本保留。要求的版本:Kimi 2.1.1(入口读得懂的会话文件布局)、
66
+ Grok 1.0.52(审批模式通知核对过的版本)、OpenCode 1.0.151(`opencode acp` 第一个报当前模型、恢复的会话能接着跑的版本:之前 `session/load` 不登记会话,下一轮报 Session not found);Cursor 没有已知的下限,能运行就行(它自己会更新)。
67
+ Codex 与 Claude 的 CLI、SDK 是插件的 npm 依赖,不需要设置动作。
68
+
69
+ ### Grok 审批模式
70
+
71
+ Grok 插件通过 ACP 提供原来的「自动审批」「全部放行」两个选项;新会话未选择时沿用运行机配置。
72
+ `session/set_mode` 转为 Grok 自己的界面使用的 `_x.ai/yolo_mode_changed` 通知,在原进程中切换,
73
+ 不修改运行机配置。自动审批的判断、禁止规则与 hooks 都由 Grok 执行。
74
+ 该通知已核对 Grok CLI 1.0.52,升级 CLI 时需复核。
75
+
76
+ ### Codex 速度
77
+
78
+ Codex 用普通 ACP 选择型配置项 `service-tier`(「速度」)报告速度,Coda 按通用 `configOptions` 展示和设置。
79
+ 选项包含「标准」以及当前模型的 `serviceTiers`:`priority` 显示为「加速」,`ultrafast` 显示为「超高速」,其余档位保留原名与说明。
80
+ 没有加速档位的模型不显示速度选项;可用档位取决于 Codex 返回的模型和账号能力,不按模型名字硬编码。
81
+ 打开或恢复会话时沿用 Codex 报的实际档位;换模型时使用新模型的默认档位,调整推理强度保留速度。
82
+ 下一轮将选中的档位传给 Codex,选择「标准」明确清除加速。
83
+
84
+ ### Codex 账号与额度
85
+
86
+ `codex` 与 `codex-accounts` 是两个独立插件:Codex Agent 由 Coda 默认安装;多账号插件需用户在「设置 › 插件」主动安装。
87
+ 仅随 Coda 提供插件目录不代表启用。未安装多账号插件时保留 Codex 原生登录与额度看板,安装后由多账号插件接管,卸载后恢复。
88
+
89
+ `codex-accounts` 2.0.0 自己管理账号,不需要预装账号管理器或 Python。安装时 Coda 安装它的 npm 依赖和
90
+ `codex` 插件;本版接管额度、登录入口和续期通知的配套入口是 `codex` 1.1.0。已有安装升级时两个插件一起更新,
91
+ Coda 内置副本同步时也一起带上这两个目录。
92
+
93
+ - 首次使用会把运行机 `$CODEX_HOME/auth.json`(默认 `~/.codex/auth.json`)里的 ChatGPT 登录导入插件,作为第一个
94
+ 普通受控账号并默认选用。原文件保留;之后的凭据、续期与选择都由插件维护,原登录文件后续变化不会替换受控账号。
95
+ 只有系统钥匙串里的凭据无法从文件导入,需要在插件中登录一次。
96
+ - 没有已有登录时,从「资源 › 添加资源 › Codex 多账号」完成设备码登录。已有账号后,通过面板菜单的「重新登录」
97
+ 添加其他账号;登录同一身份会更新其凭据。添加第二个账号不会改变已选账号。
98
+ - 接入后「Codex」自带的额度看板隐藏,只显示「Codex 多账号」里的账号额度。原 Codex 登录入口也委托给账号插件,
99
+ 避免建立另一套登录;面板中没有「使用本机账号」。卸载账号插件后,Codex 自带看板恢复。
100
+ - 每个账号支持选择、查看额度、使用完全重置和移除。选择在新会话和轮次交界处生效;移除已选账号后须重新选择或添加,
101
+ 会话不会回退到原登录。首次导入只发生一次,移除的账号不会再次被自动导入。
102
+
103
+ 账号存放于 `CODA_PLUGIN_STATE/accounts/<身份摘要>/auth.json`,目录权限 0700、文件权限 0600。
104
+ 每个账号有独立的 `CODEX_HOME`,由插件依赖中的 Codex 执行设备码授权及 refresh-token 续期。
105
+ 跨进程的每账号锁覆盖续期、额度读取、重新登录和移除,避免多个会话同时使用同一个轮换凭据。
106
+ 会话只通过 `codex/account-source` 的私有管道拿到 access token;refresh token 留在受控登录文件中。
107
+ 登录存储与续期采用 [Codex 官方认证机制](https://learn.chatgpt.com/docs/auth#credential-storage)。
108
+
109
+ 旧版的 `selection.json` 仍然识别:`native` 转为首次导入的账号;指向其他旧账号的选择保留,并提示在插件中重新登录该账号,
110
+ 不会悄悄切换。新版不再读取旧外部管理器的账号库,其他账号可直接从插件添加。
111
+
112
+ `codex/account-source` 的完整进程约定在 `plugins/codex/agent/account-source.mjs`:`selected` 返回身份和名称,
113
+ `tokens` 返回该身份的 access token,`refresh: true` 要求续期;无可用选择应返回错误。来源还通过 `--login` 接受
114
+ 原 Codex 登录入口的委托,并自行提供额度面板。Codex 把自己的依赖目录通过 `CODA_PLUGIN_DEPENDENCIES.codex`
115
+ 交给来源,来源与面板使用同一份 Codex CLI。
116
+
117
+ ### Agent 的错误
118
+
119
+ Codex 插件在 ACP 出口把 codex-acp 错误 `data.message`、`data.details` 或字符串 `data` 中的原因补进标准 `error.message`,
120
+ 详情裁掉两端空白并去重,错误码、请求 ID 与原始 `data` 保留。Coda 和其他 ACP 客户端直接显示 `message` 即可;
121
+ 这层适配由插件维护,客户端无需理解 codex-acp 的内部错误格式。
122
+
123
+ Codex 的原生失败统一以 ACP 错误结束,包括只有 `turn/completed.failed` 而没有单独错误通知的情况。
124
+ 插件将服务端内部错误、5xx、响应流中断、模型容量不足,以及已知的 `model '…' is not enabled in rustponsesapi` 报成
125
+ `error.data._meta.coda.failure: {kind: "service_unavailable", input: "accepted"}`。新轮请求明确被拒绝、只给
126
+ `Internal error` 时按临时故障报告 `input: "not_accepted"`;这表示允许有限重试,不表示已确定历史故障根因。
127
+ 明确的额度耗尽、认证、上下文限制、参数、配置或沙箱错误不带此标记。
128
+ 插件不做退避或重新发送;Coda 按通用协议统一退避,客户端只支持 ACP 时仍能读到完整错误。
129
+
130
+ Kimi 2.1.1 会把五小时额度耗尽(`403 You've reached your 5-hour usage limit.`)误报为 ACP `auth_required`。
131
+ Kimi 插件在 `session/prompt` 的回执中将这条已知错误改为普通执行错误(-32603),在标准 `error.message` 中保留服务端完整原因,
132
+ 同时按[执行失败协议](docs/agent-protocol.md#执行失败)报告 `error.data._meta.coda.failure: {kind: "quota_exhausted"}`。
133
+ Coda 按这个通用类别显示「额度不足」,不触发服务故障重试;其他 ACP 客户端仍能读取完整原因。
134
+ 插件不推算额度重置时间;其他认证失败、403、短时限流与普通错误原样保留。
135
+ `CODA_DISABLE_RUNTIME_PATCHES=kimi` 关闭这层适配。
136
+
137
+ ### 升级 Agent 的 CLI 与 SDK
138
+
139
+ Codex 会话、面板与登录用的 Codex CLI 就是 `codex` 插件依赖的 `@openai/codex`;Claude 的是 `claude` 插件依赖的
140
+ `@anthropic-ai/claude-agent-sdk`。插件按包名从自己的依赖里找它们,Coda 不另给路径。升级就是改这两个版本:
141
+ Dependabot(`.github/dependabot.yml`)每周为它们各开一个 PR,连同 `package-lock.json` 一起改;合并后在 Coda「设置 › 插件」
142
+ 对该插件点「检查更新」,内置副本随 Coda 同步到新提交。升 `@openai/codex` 时 codex-acp 经 `overrides` 跟着用新版本,
143
+ 合并前用真实会话跑一轮。
144
+
145
+ ## 在 Coda 里安装
146
+
147
+ 「设置 › 插件 › 从仓库添加…」:
148
+
149
+ 1. **仓库**填 `coda-console/coda-plugins`(或 `https://github.com/coda-console/coda-plugins`);**版本**留空是默认分支,也可以填分支、标签或完整的提交号。
150
+ 2. **读取**:Coda 拉取那个提交,把仓库根与两层以内 `package.json` 带 `contributes` 的目录各列成一个插件——这个仓库里是 `plugins/` 下的七个与两个模板。内置插件可以从自身来源单独更新;卸载后,也可以从其他来源安装同名替代插件。
151
+ 3. 选一个,确认里逐条列出它会运行的命令(Agent 的 `command`、面板的命令与动作),确认就是批准:这些命令以你的账户在空间里每台机器上运行,Coda 不做隔离,只装你信任的来源。
152
+
153
+ **Coda 只认提交**:安装时钉在读取到的那个提交上,每台机器各自取同一个提交。「检查更新」用记下的仓库与版本再读一次,提交变了才有更新;
154
+ 上一个提交留着,可以回退。清单里的 `version` 只作显示,不参与比较——改了行为就把它加一,方便人看。这个仓库不打标签、不发 npm、不发 release。
155
+
156
+ 自己的插件可以放在自己的仓库里(单插件仓库让仓库根就是插件目录),在自己的 Mac 上试的时候,仓库填本机仓库的绝对路径即可:Coda 读的是**提交**,没提交的改动它看不到。
157
+
158
+ ## 写一个插件
159
+
160
+ 从模板开始:把 `templates/agent` 或 `templates/panel` 复制成自己的目录,改掉 `name`(插件 ID)与 Agent 的 `id`。
161
+
162
+ **清单**(`package.json`)
163
+
164
+ - `name` 是包名;另写 `id` 时以它为 Coda 插件 ID,否则以 `name` 为 ID。ID 只能用小写字母、数字和 `-`,以字母开头,至多 40 个字符。
165
+ 本仓库的包名是 `@coda-console/<id>`,设置名与 Agent ID 仍使用原来的插件 ID。可以与 Coda 内置插件同名;从其他来源替换时,须先在目标机器卸载内置插件。检查器不读取机器的安装状态,同名占用由 Coda 在读取来源和安装时检查。
166
+ - `displayName`、`description`、`version` 显示在安装确认与插件页。
167
+ - `contributes` 只能有 Coda 认识的贡献:`agents`、`panels`、`configuration`、`capabilities`、`instructions`、`extensionPoints`、`extensions`、`groups`、`commands`、`services`、`setup`;读不懂的键一律报错,不会被悄悄忽略。
168
+ - `setup` 是插件的设置动作:`{ "command": "…" }`,一行、不超过 4000 字(与面板命令同样的规则),没有别的键。
169
+
170
+ **从仓库安装的限制**:带 npm 依赖(`dependencies`、`optionalDependencies`)就要把 `package-lock.json` 一起提交,Coda 在插件的版本目录里
171
+ `npm ci`(较早的 Coda 不收带 npm 依赖的插件);不能有符号链接或子模块;整个目录不超过 10 MiB(`node_modules` 不算,也不该提交);
172
+ 目录名只用字母、数字和 `._-`,在仓库根下两层以内。
173
+
174
+ **Agent**(`contributes.agents`,例子见 `templates/agent/package.json`、`plugins/cursor/package.json`)
175
+
176
+ | 字段 | 内容 |
177
+ | --- | --- |
178
+ | `id` | Agent ID:小写字母、数字和 `-`,以字母开头,至多 40 个字符;会话按它存,所有插件里不能重复(不能用 `codex`、`claude`、`claude-sdk`、`kimi`、`grok`、`opencode`) |
179
+ | `displayName` | 显示名,一行,不超过 40 字 |
180
+ | `icon` | `light`:插件目录里的 PDF,不超过 32 KiB;可选 `dark`;`template: true` 表示单色、随文字着色。画布按 256 px 出(iOS 按 96 pt 光栅化、只缩不放) |
181
+ | `command` | 启动命令,一行,`sh -c` 运行,以 `exec` 开头,进程就是 Agent 本身 |
182
+ | `modes` | 可省:`title`、`unset`、`note` 是模式选择的标题与说明;`auto`、`full` 是这个 Agent 的「自动审批」「全部放行」各是哪个 ACP 模式 |
183
+ | `skillsDir`、`configFiles`、`setupHint` | 可省:Agent 在工作副本里找 skills 的目录;它读模型列表的配置文件(变了 Coda 重读);用不了时给用户的一句话 |
184
+
185
+ Agent 进程的环境是会话的环境再加插件进程的那组变量(面板与面板的动作、放在会话 `PATH` 上的命令也一样;继承来的同名变量先去掉,一个插件的进程拿不到别的插件的):
186
+ `CODA_NODE`(Coda 后端自己的 Node,插件自带的入口用它运行,不在 Agent 的 `PATH` 上)、`CODA_PLUGIN_PATH`(插件目录,从仓库装的是它钉住的版本目录)、
187
+ `CODA_PLUGIN_STATE`(插件在这台机器上的状态目录,跨会话、跨版本保留)、`CODA_PLUGIN_SETTINGS`(插件设置的 JSON)、
188
+ `CODA_PLUGIN_EXTENSIONS`(别的插件接在它扩展点上的东西,没有就不设)、`CODA_PLUGIN_DEPENDENCIES`(它依赖的插件的目录,没有就不设)、
189
+ `CODA_ARBITER`(`1`:这是它所在实例自己的正式后端;动本机共享的东西前看它)。
190
+ 插件自带的入口只能用插件目录里的代码、Node 自带的模块与清单里声明的 npm 依赖。
191
+
192
+ 照 [接入协议](docs/agent-protocol.md) 实现,另外几条容易漏的:
193
+
194
+ - 标准输出只写 JSON-RPC,一行一条;日志写标准错误(Coda 不读它)。请求 ID 是字符串。
195
+ - 必须接受会话指令:`initialize` 结果声明 `agentCapabilities.sessionCapabilities.systemPrompt: {}`,`session/new`、`session/load` 收到的
196
+ `systemPrompt`(`append` 模式)在第一轮之前生效。没有原生系统提示词入口的,在原生会话第一次 `session/prompt` 时把指令作为单独、明确分隔的一段
197
+ 放在用户输入前面送入,之后与 `session/load` 恢复的会话不再送。
198
+ - 插话可选:在 `initialize` 结果顶层声明 `_meta.steering.supported: true`,就要处理 `_session/steering`:一轮进行中回 `injected`,
199
+ 这一轮已经结束回 `promptRequired`(`reason: "noRunningTurn"`),且不自己另开一轮。
200
+ - `session/new` 要报当前模型:`configOptions` 里 `category: "model"` 的那一项的 `currentValue`(或 `models.currentModelId`)。Coda 每轮开始前核对,没有就不发这一轮。
201
+ - 没登录时在 `initialize`、`session/new` 或 `session/load` 回 -32000,Coda 提示用户去登录。
202
+ - `session/cancel` 后 30 秒内结束这一轮(`stopReason: "cancelled"`),否则 Coda 关掉它的进程组。
203
+ - Coda 关会话是对 Agent 的进程组发 SIGTERM,0.8 秒后 SIGKILL:收到 SIGTERM 要尽快退出。
204
+ - 一轮之外的审批与提问 Coda 一律拒绝,除非正有活动在 `handling`(那是一轮自主轮次)。
205
+
206
+ **面板**(`contributes.panels`,例子见 `templates/panel`):`command` 用 `sh -c` 运行,标准输出是 `{ "items": [...] }`:
207
+
208
+ | 项 | 字段 |
209
+ | --- | --- |
210
+ | `card` | `title`、可选 `subtitle`、`items`(至多 20 项,不能再放卡片) |
211
+ | `meter` | `label`,可选 `value`、`fraction`(0–1,**还剩多少**,不知道就不写)、`note`、`time`(ISO 8601)与 `timeLabel` |
212
+ | `stat` | `label`、`value` |
213
+ | `markdown` | `text`(不超过 4000 字) |
214
+ | `button` | `label`、`action`(须是这个面板声明的动作)、可选 `args`、`disabled`、`confirm` |
215
+ | `error` | `text` |
216
+
217
+ 只在打开资源页或点「刷新」时读,30 秒没结束或输出超过 64 KiB 就停下;读不懂的键或类型算这次读取失败。`items` 为空的面板不显示。
218
+ 命令的环境是后端的环境(不带会话 ID)加上插件进程的那组变量(同上),`PATH` 最前面是 Coda 后端的 `node`;`CODA_GROUPS_STATE` 是这一刻这个后端各并发组的占用与排队
219
+ (`{now, groups}`);动作另有 `CODA_PANEL_ARGS`(按钮的 `args`)。
220
+ 动作至多跑 10 分钟,成功后 Coda 立即重读面板。
221
+
222
+ ## npm 发布
223
+
224
+ 仓库根的 `@coda-console/plugin-check` 与 `plugins/*` 各自发布为公开 npm 包;包名带 scope,插件 ID 单独写在清单的 `id`。
225
+ `repository.directory` 指向插件所在目录。Coda 必须先支持清单的 `id`,才能从改名后的仓库或 npm 读取这些插件。
226
+ 插件在仓库里的锁文件仍供 Git 来源安装与本仓库测试使用;npm 包不带 `package-lock.json`,从 npm 单独安装时按依赖声明解析,随 Coda 发的版本由 Coda 自己的锁文件固定。
227
+
228
+ `.github/workflows/publish.yml` 在合入 main 或手动发起时检查测试、清单与 npm 包内容,然后逐包发布 npm 上尚不存在的版本。
229
+ 只有发布步骤有 `id-token: write`,由 GitHub 托管运行机通过 npm Trusted Publishing 鉴权并生成 provenance,不配置 npm 写令牌。
230
+ 查询 registry 失败会停下,只有明确的 404 才按未发布处理;已发布的版本跳过,修复失败后可重新运行同一个工作流。
231
+
232
+ 首次接入(或新增包)由组织维护者执行,要求 npm 11.15.0 以上、账号已启用 2FA,并有对应包的写权限:
233
+
234
+ ```sh
235
+ npm login
236
+ node scripts/publish-npm.mjs --dry-run # 检查所有包实际会打入哪些文件,不发布
237
+ node scripts/publish-npm.mjs --bootstrap
238
+ gh workflow run publish.yml --repo coda-console/coda-plugins --ref main
239
+ ```
240
+
241
+ 先让 `publish.yml` 合入 main,再运行 bootstrap。它只给 npm 上还不存在的包发布 `0.0.0` 占位版(`bootstrap` 标签),
242
+ 逐包配置 GitHub 仓库 `coda-console/coda-plugins`、流程文件 `publish.yml`、允许 `npm publish`、不指定 environment。
243
+ 已有可信发布配置会显示出来供维护者核对,不自动覆盖。首次 2FA 验证可选择五分钟内不再询问,包与包之间停两秒以免限流。
244
+ 配置后立即发起上述 GitHub 工作流:npm 要求新配置在两天内完成第一次成功发布,由 CI 发布仓库里的实际版本并验证信任关系。
245
+ 确认 OIDC 发布成功后,在 npm 包设置的 Publishing access 中选择 Require two-factor authentication and disallow tokens。
246
+
247
+ ## 契约检查
248
+
249
+ 扮演 Coda 去读一个插件目录:清单照 Coda 的规则读,Agent 照 Coda 的样子启动、说 Coda 说的话,面板照 Coda 的样子读一次。
250
+ **权威校验仍是 Coda 的「读取来源」**(「从仓库添加…」里的读取):检查包的规则抄自 Coda,抄的是哪个提交写在 `check/coda.mjs`,两边不一致时以 Coda 为准。
251
+
252
+ ```sh
253
+ npm ci
254
+ node bin/check.mjs <插件目录> [--agent <id>] [--manifest] [--live] [--prompt "…"]
255
+
256
+ # 不克隆,直接从 GitHub 跑(钉一个提交)
257
+ npx github:coda-console/coda-plugins#<提交> <插件目录>
258
+ ```
259
+
260
+ | 级 | 要不要登录 | 查什么 |
261
+ | --- | --- | --- |
262
+ | 清单(总是) | 不要 | `package.json` 读得懂、ID 合法且不与内置的撞(本仓库 `plugins/<id>` 里的内置插件除外);每个 Agent 字段齐全、`command` 一行、图标是不超过 32 KiB 的 PDF;面板、组与命令的声明,Agent、组与命令不与内置插件的撞;从仓库安装的限制,带 npm 依赖要有对得上的 `package-lock.json` |
263
+ | 握手(默认) | 不要 | `sh -c` 启动 `command`(环境照 Coda 给插件进程的:`CODA_NODE`、`CODA_PLUGIN_PATH`、临时的 `CODA_PLUGIN_STATE`、设置默认值的 `CODA_PLUGIN_SETTINGS`,依赖的插件在旁边目录里时给 `CODA_PLUGIN_DEPENDENCIES`),发与 Coda 相同的 `initialize`,要求 `protocolVersion` 为 1、声明会话指令(`sessionCapabilities.systemPrompt`,没声明算失败);`session/new` 带会话指令,给出会话与当前模型,回 -32000 记为未登录、跳过后面的检查(不算失败);像 Coda 一样对进程组发 SIGTERM,5 秒内要退出(超过 0.8 秒提醒);另起一个进程,关掉标准输入后 5 秒内不退出只提醒。面板读一次,输出合格式 |
264
+ | 一轮(`--live`) | 要 | 第一轮问会话指令里的检查词(一个随机词),回复里要有它;再发一轮提示词(默认「只回复 ok」)收到 `stopReason`;每条 `session/update` 过 ACP SDK 的 schema;`_coda/wait` 按 Coda 的 `validWait` 与协议的规则核对(子 Agent 不带 `childSessionId`、定时不带 `at` 只提醒);审批与提问一律回拒绝,在一轮之外发来的算失败;再发一轮并 `session/cancel`,30 秒内要结束,读到取消后还在做事或拖过 5 秒才答复却不回 `cancelled` 算失败(见下);有自主轮次的也取消一次;声明插话的,一轮结束后插话要回 `promptRequired` 且不自己另开一轮,一轮进行中插话要回 `injected` 或 `promptRequired`(`injected` 的那一轮照常回 `stopReason`);声明 `loadSession` 的,新开进程 `session/load`(同样带会话指令)要成功,随后在新进程里再跑一轮也要收到 `stopReason` |
265
+
266
+ **取消后的 `stopReason` 怎么判**:检查发 `session/cancel` 时紧跟一个只会得到 -32601 的探测请求(`_codaCheck/barrier`)。Agent 按顺序读,
267
+ 探测的答复一定写在它读到取消之后,所以本轮的答复与 `session/update` 落在探测答复的前后,就说明它们写出时读没读到取消。
268
+ 本轮在读到取消后又发了干活的更新(消息、思考、工具、计划),或过了 5 秒才答复,却不回 `cancelled`,算失败——用户按的「停止」没让它停;
269
+ 答复先于读到取消写出(取消送到前这一轮已经结束),或读到取消时已经做完、随即答复的(如 Codex 做完后等标题时被取消),只记一条说明。
270
+
271
+ `--manifest` 只查清单(什么也不运行);`--agent <id>` 只查一个 Agent。退出码:0 通过,1 有失败,2 用法不对。
272
+ 面板的动作与插件的设置动作(`contributes.setup`)不运行(可能改动东西、装软件);Coda 自己的 MCP 服务器不给(`mcpServers` 为空)。
273
+ 检查给的会话指令不是 Coda 的全文,是一段替身加一句检查词。
274
+
275
+ 在自己插件仓库的 CI 里:
276
+
277
+ ```yaml
278
+ - uses: actions/setup-node@v4
279
+ with: { node-version: 24 }
280
+ - run: npx --yes github:coda-console/coda-plugins#<提交> . --live
281
+ ```
282
+
283
+ ## 协议正文的副本
284
+
285
+ Coda 主仓库不公开,所以 `docs/agent-protocol.md` 是它 `docs/agent-protocol.md` 的副本,由脚本从检查包规则所抄的那个提交同步,不要直接改:
286
+
287
+ ```sh
288
+ node scripts/sync-protocol.mjs --coda <Coda 仓库目录> # 同步(看着像私有信息的内容会让它停下)
289
+ node scripts/sync-protocol.mjs --check # CI:副本声明的 Coda 提交、ACP 协议版本、SDK 版本与检查包一致,正文没被手改
290
+ ```
291
+
292
+ Coda 改协议时,同一个改动也给这里开 PR:先改 `check/` 里抄来的规则和 `CODA_COMMIT`,再同步副本。
293
+
294
+ ## 开发
295
+
296
+ ```sh
297
+ npm ci
298
+ node scripts/install-plugins.mjs # 在每个带 package-lock.json 的插件目录里 npm ci(照 Coda 装版本目录的参数)
299
+ npm test # node --test 'test/**/*.test.mjs':检查包(两个模板跑全三级、故意讲错协议的假 Agent)与各插件的测试
300
+ node bin/check.mjs templates/agent --live --prompt "wait 看看自主轮次"
301
+ ```
302
+
303
+ 检查包唯一的依赖是 `@agentclientprotocol/sdk`,版本与 Coda 后端相同(1.4.0);`zod` 是它的 peer 依赖。
304
+ `test/plugins/` 的测试从插件目录自己的 `node_modules` 找依赖,没装就报错提示先跑 `install-plugins.mjs`;
305
+ `test/plugins/contract.test.mjs` 用假的对端(Claude 的假 SDK、Codex 的假 app-server、充当 `kimi acp` 的假 Agent)对真实入口跑检查包的三级,不用登录。
306
+ CI 先装各插件的依赖再跑全部测试,`plugins/*` 只查清单:真实的握手与一轮要各家的 CLI 与登录,不在 CI 跑。
307
+
308
+ ## 许可
309
+
310
+ MIT,见 [LICENSE](LICENSE)。各插件的品牌图形归各自权利人所有,来源写在插件的 `icons/README.md`。
311
+
312
+ `plugins/codex/agent/` 里的 `relay.mjs`、`titles.mjs`、`network.mjs`、`process-exit.mjs`、`options.mjs`、`subagents.mjs`、`instructions.mjs`、`steering.mjs` 在加载时给锁定版本的
313
+ [codex-acp](https://github.com/agentclientprotocol/codex-acp)(Apache-2.0,Copyright 2025 JetBrains s.r.o.)打补丁,补丁里含 codex-acp 构建产物的原文片段。
314
+ 这些片段按 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 使用,许可全文也随 npm 包装在 `node_modules/@agentclientprotocol/codex-acp/LICENSE`;
315
+ codex-acp 本身作为 npm 依赖安装,不在本仓库里。
package/bin/check.mjs ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ // coda-plugin-check <插件目录> [--agent <id>] [--manifest] [--live] [--prompt "…"]
3
+ import { resolve } from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import pkg from '../package.json' with { type: 'json' };
6
+ import { ACP_PROTOCOL_VERSION, CODA_COMMIT } from '../check/coda.mjs';
7
+ import { checkPlugin } from '../check/index.mjs';
8
+ import { format } from '../check/report.mjs';
9
+
10
+ const USAGE = `用法:coda-plugin-check <插件目录> [--agent <id>] [--manifest] [--live] [--prompt "…"]
11
+
12
+ 清单(总是查) package.json 能不能被 Coda 读懂,从仓库安装的限制(带 npm 依赖要有 package-lock.json)
13
+ 握手(默认查) 照 Coda 的样子启动每个 Agent,发 initialize、session/new(要声明会话指令 systemPrompt);SIGTERM 后 5 秒内要退出(关标准输入后不退出只提醒);读每个面板一次
14
+ 一轮(--live) 每个 Agent 先问会话指令里的检查词,再发一轮提示词(默认「只回复 ok」)、再发一轮并取消(读到取消后要回 cancelled)、核对 _coda/wait;
15
+ 声明插话的,一轮结束后与一轮进行中各插话一次;声明 loadSession 的再开进程 session/load,并在新进程里再跑一轮
16
+
17
+ --agent <id> 只查这一个 Agent(面板不读)
18
+ --manifest 只查清单,什么也不运行
19
+ --live 查一轮:会真的发提示词,可能要登录、花额度
20
+ --prompt "…" 一轮用的提示词`;
21
+
22
+ let options;
23
+ try {
24
+ options = parseArgs({ allowPositionals: true, options: {
25
+ agent: { type: 'string' }, manifest: { type: 'boolean' }, live: { type: 'boolean' }, prompt: { type: 'string' }, help: { type: 'boolean', short: 'h' } } });
26
+ } catch (e) { console.error(`${e.message}\n\n${USAGE}`); process.exit(2); }
27
+ const { values, positionals } = options;
28
+ if (values.help) { console.log(USAGE); process.exit(0); }
29
+ if (positionals.length !== 1 || (values.manifest && values.live)) { console.error(USAGE); process.exit(2); }
30
+
31
+ // Detached agents and panels, and temporary directories, go with the checker (check/leftovers.mjs).
32
+ for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => process.exit(130));
33
+ const dir = resolve(positionals[0]);
34
+ const { report, plugin } = await checkPlugin(dir, { agent: values.agent ?? null, manifestOnly: !!values.manifest, live: !!values.live, prompt: values.prompt });
35
+ const shown = positionals[0];
36
+ console.log(format(report, { title: `coda-plugin-check ${pkg.version} · 插件 ${plugin?.id ?? '?'}(${shown})· 规则抄自 Coda ${CODA_COMMIT.slice(0, 7)} · ACP 协议版本 ${ACP_PROTOCOL_VERSION}` }));
37
+ console.log('权威校验仍是 Coda 的「读取来源」:提交后在「设置 › 插件 › 从仓库添加…」填仓库地址(或本机仓库的绝对路径)读一次,以它为准。');
38
+ process.exit(report.ok ? 0 : 1);