@coda-console/plugin-check 0.4.5 → 0.4.6

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/README.md CHANGED
@@ -14,21 +14,41 @@ plugins/grok/ Grok:转发运行机上 Grok CLI 的 ACP,并适配
14
14
  plugins/opencode/ OpenCode:在运行机的 opencode acp 与 Coda 之间转发,送入会话指令
15
15
  plugins/codex-accounts/ Codex 多账号:管理登录、自动续期与账号选择,接管 Codex 额度面板
16
16
  plugins/cursor/ Cursor:在运行机的 cursor-agent acp 与 Coda 之间转发,送入会话指令
17
+ plugins/magpie/ Magpie:把本机的 Magpie 网关作为模型服务提供给 Pi 等插件,安装时装好 Magpie 并让网关常驻
17
18
  plugins/review/ PR 生命流程:会话开的 PR 先跑项目的检查、再交叉评审,你批准后合并、部署与发布;也是写 PR 流程的样例
18
19
  templates/agent/ 最小的 Agent 插件:手写 ACP 的回声 Agent,演示会话指令、插话、Agent 活动与自主轮次
19
20
  templates/panel/ 最小的面板插件:一张卡片、一条进度条、一个按钮
20
21
  bin/、check/ 契约检查(仓库根的 package.json 就是它,@coda-console/plugin-check)
21
22
  test/ 检查包的测试;test/plugins/ 是各插件的测试(插件目录里只放运行要用的文件)
22
23
  docs/agent-protocol.md Coda 接入协议正文的公开副本
24
+ .coda/workflows/ 本仓库自己的流程:sync-coda 等新版本发到 npm,再给 Coda 开升级依赖的 PR
25
+ .claude-plugin/ 官方市场的索引 marketplace.json,见「官方市场」
23
26
  ```
24
27
 
28
+ ## 官方市场
29
+
30
+ `.claude-plugin/marketplace.json` 是 Coda 的官方市场(Coda 的 docs/plugins.md「市场」):格式照搬 Claude Code 的
31
+ [marketplace.json](https://code.claude.com/docs/en/plugins/marketplace-reference),Coda 新装时就登记了它,插件页的「商店」从这里列出可以装的插件。
32
+ 它列不随 Coda 发布、维护者推荐的插件:本仓库里的(`./plugins/<id>`),以及别的公开仓库或 npm 包。随 Coda 发布的那几个在 Coda 自带的市场里,这里不重复列。
33
+
34
+ - **加一个插件**就是在 `plugins` 里加一个条目:`name` 是插件 ID,写 `displayName`、`description`、`category`、`tags` 与 `source`。
35
+ 来源可以是 `./plugins/<id>`、`{ "source": "github", "repo": "owner/repo" }`、`{ "source": "git-subdir", "url": "owner/repo", "path": "<目录>" }`
36
+ 或 `{ "source": "npm", "package": "<包名>" }`;Coda 不装 `archive`、`command` 来源,也不认版本范围。
37
+ - **分类**只用这几个,Coda 的插件页按它分组:`Agent`、`模型与账号`、`工作流程`、`机器与资源`、`Skills 与工具`。
38
+ - **本仓库里的插件**条目要写 `version`,与它清单里的一样:Coda 按条目的版本标「可更新」。升插件版本时一起改。
39
+ - `npm test` 里的 `test/check-marketplace.test.mjs` 用 `scripts/check-marketplace.mjs` 核对上面几条(CI 跑 `npm test`),本地也可以直接运行这个脚本。
40
+
25
41
  ## Coda 自带的插件
26
42
 
27
43
  `codex`、`claude`、`kimi`、`grok`、`opencode`、`codex-accounts`、`review` 是 Coda 内置的插件,源码从此在这里维护。
28
44
  合进 main 后它们发布为 npm 包 `@coda-console/<id>`([npm 发布](#npm-发布)),Coda 后端的 `package.json` 把它们写成钉住版本号的依赖,
29
45
  随后端的 `npm ci` 装好,新机器首次启动就带着它们。
30
46
 
31
- - **改它们先在这里提 PR、合并**,等发布流程发出新版本,再在 Coda 里把依赖升到那个版本(Coda 的 docs/development.md「coda-plugins 维护的内置插件」)。
47
+ - **改它们先在这里提 PR、合并**,发布流程发出新版本后,再让 Coda 把依赖升到那个版本(Coda 的 docs/development.md「coda-plugins 维护的内置插件」)。
48
+ 升依赖由本仓库的部署流程 `sync-coda`(`.coda/workflows/sync-coda.bpmn`、`scripts/sync-coda.mjs`)做:合并时勾选「部署」,它在合并提交上先等
49
+ `publish.yml` 跑完、npm 读得到这个提交的版本(发布失败就带着那次运行的地址失败),再把 Coda 用到的包升到这些版本(只升不降)、照抄插件的 `overrides`,
50
+ 推到 Coda 的分支 `sync/coda-plugins` 并开 PR(开着的就只更新),PR 由开这边 PR 的会话负责、走 Coda 的 PR 流程;Coda 已是这些版本就什么都不做。
51
+ 也可以单独发起,同步默认分支的最新提交。
32
52
  - **改了哪个插件就升哪个插件的 `version`**(`plugins/<id>/` 下任何文件,含 `package-lock.json`;只改 `test/` 不用):Coda 的插件页与遥测按版本认插件。
33
53
  CI 在 PR 上运行 `node scripts/check-versions.mjs origin/<目标分支>` 核对;Dependabot 升 CLI、SDK 的 PR 也要补一个升版本的提交。
34
54
  - Coda 的「检查更新」可以从 npm 装同一个包的新版本,盖过 Coda 带的版本;只有 `@coda-console/<id>` 这个包能用这些名字。
@@ -62,6 +82,33 @@ CLI 不在或运行不了就用它自己的安装程序安装,低于插件要
62
82
  Grok 1.0.52(审批模式通知核对过的版本)、OpenCode 1.0.151(`opencode acp` 第一个报当前模型、恢复的会话能接着跑的版本:之前 `session/load` 不登记会话,下一轮报 Session not found);Cursor 没有已知的下限,能运行就行(它自己会更新)。
63
83
  Codex 与 Claude 的 CLI、SDK 是插件的 npm 依赖,不需要设置动作。
64
84
 
85
+ ### 模型服务
86
+
87
+ Claude 与 Codex 各有一项设置「模型服务」(`claude.modelProvider`、`codex.modelProvider`,`format: coda-model-provider`),
88
+ 选一个别的插件提供的[模型服务](https://github.com/coda-console/coda/blob/main/docs/plugins.md#模型服务),如 Magpie。
89
+ 只经会话进程的启动参数与按会话的覆盖生效,不改运行机上的 `~/.claude/settings.json`、`~/.codex/config.toml`;不选、或提供它的插件卸载了,与原来一样。
90
+
91
+ - **接口**:Claude 只接 `anthropic-messages` 的服务,Codex 只接 `openai-responses` 的(Codex 0.162 已不接受 `wire_api = "chat"`)。
92
+ 选了别的接口的服务,入口只在标准错误记一行,不列它的模型。
93
+ - **模型**:会话打开时从服务的「地址/models」列出模型,问法同 Coda 的「检查可用模型」(Claude 带 `x-api-key`,Codex 带 `Authorization: Bearer`),
94
+ 排在 Agent 自己的模型后面:ID 是 `<服务>/<模型>`(如 `magpie/anthropic/deepseek/deepseek-v4-pro`),名字是「<模型> (<服务标题>)」,没有推理强度可选。
95
+ 列不出来时只有 Agent 自己的模型。
96
+ - **何时换**:选模型只是记下(Coda 读模型目录时会把每个模型都选一遍,不该因此启动什么);下一轮开始前,所选模型的服务与会话正在用的不同,才把会话换过去。
97
+ - **Claude**:以 `resume` 新起一个 SDK 会话(还没说过话的会话用同一个会话 ID 新起),flag settings 的 `env` 设 `ANTHROPIC_BASE_URL`、
98
+ `ANTHROPIC_AUTH_TOKEN`(服务的 Key,没有 Key 写 `none`)与 `ANTHROPIC_MODEL`、`ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL`、
99
+ `CLAUDE_CODE_SUBAGENT_MODEL`(子 Agent、标题等后台调用也用这个模型)。运行机自己通往 API 的办法一样都不带过去:`ANTHROPIC_API_KEY`、
100
+ `CLAUDE_CODE_OAUTH_TOKEN`、`ANTHROPIC_CUSTOM_HEADERS`、云服务商开关等置空,`apiKeyHelper` 置空(不然 API Key、helper 给的 Key 会作为 `x-api-key` 发给服务)。
101
+ flag settings 高于运行机与项目的 settings,它们 `env` 里的同名变量盖不过它;换回 Claude 自己的模型时新起的会话不带这些,运行机的配置照旧。
102
+ 换回 Claude 自己的模型同样新起一个,不带这些;审批模式与推理强度沿用。还有后台任务、子 Agent 或定时唤醒没结束时,换到另一边的模型被拒绝(-32602),因为它们随原来的进程结束。
103
+ 新起的那个起不来(SDK 当场报错、初始化失败或超时),这一轮失败,会话随即结束(同 SDK 会话自己结束),Coda 下次再打开它。
104
+ - **Codex**:Codex 一个线程只绑一个模型 provider,所以照 codex-acp 处理 ACP `providers/set`、`providers/disable` 的办法:重启 app-server,
105
+ 把服务作为它 OpenAI 槽位的 gateway(`model_providers.custom-gateway`,`wire_api = "responses"`,Key 放在 `Authorization` 头),再 resume 会话的线程;
106
+ 换回 Codex 自己的模型时撤掉 gateway 再 resume。resume 带着会话打开时的 Coda 会话指令与 MCP 服务器,app-server 里的后台命令按 codex-acp 原有的做法报为停止。
107
+ 换的过程中失败(重启的 app-server 起不来、resume 失败)时这一轮失败,下一轮从重启起重新换(会话在哪个 provider 上由入口自己记:
108
+ codex-acp 在 resume 之前就记下了 gateway,新 app-server 起不来时还留着已关闭的旧客户端,都不能拿来当换成了)。
109
+ 一句话都还没说的线程 Codex 没有写下来,resume 不了:先以第一条消息(前 80 字)为名 `thread/name/set` 把它写下,这也就成了会话标题(不再由 Codex 生成)。
110
+ Codex 没有服务的模型的元数据,它为此发的「Model metadata … not found」与恢复时的「recorded with model …」提示不进会话。
111
+
65
112
  ### Grok 审批模式
66
113
 
67
114
  Grok 插件通过 ACP 提供原来的「自动审批」「全部放行」两个选项;新会话未选择时沿用运行机配置。
@@ -135,8 +182,8 @@ Coda 按这个通用类别显示「额度不足」,不触发服务故障重试
135
182
  Codex 会话、面板与登录用的 Codex CLI 就是 `codex` 插件依赖的 `@openai/codex`;Claude 的是 `claude` 插件依赖的
136
183
  `@anthropic-ai/claude-agent-sdk`。插件按包名从自己的依赖里找它们,Coda 不另给路径。升级就是改这两个版本:
137
184
  Dependabot(`.github/dependabot.yml`)每周为它们各开一个 PR,连同 `package-lock.json` 一起改;合并后在 Coda「设置 › 插件」
138
- 对该插件点「检查更新」,Coda 随后把依赖升到发布的新版本。升 `@openai/codex` 时 codex-acp 经 `overrides` 跟着用新版本,
139
- Coda 升 `@coda-console/codex` 时也要改它根上的同一条 overrides(npm 只认根的,Coda 的测试会核对);合并前用真实会话跑一轮。
185
+ 对该插件点「检查更新」;合并时勾选「部署」,`sync-coda` 给 Coda 开升级依赖的 PR。升 `@openai/codex` 时 codex-acp 经 `overrides` 跟着用新版本,
186
+ Coda 根上的同一条 overrides 由 `sync-coda` 一起改(npm 只认根的,Coda 的测试会核对);合并前用真实会话跑一轮。
140
187
 
141
188
  ## 在 Coda 里安装
142
189
 
@@ -305,7 +352,7 @@ CI 先装各插件的依赖再跑全部测试,`plugins/*` 只查清单:真
305
352
 
306
353
  MIT,见 [LICENSE](LICENSE)。各插件的品牌图形归各自权利人所有,来源写在插件的 `icons/README.md`。
307
354
 
308
- `plugins/codex/agent/` 里的 `relay.mjs`、`titles.mjs`、`network.mjs`、`process-exit.mjs`、`options.mjs`、`subagents.mjs`、`instructions.mjs`、`steering.mjs` 在加载时给锁定版本的
355
+ `plugins/codex/agent/` 里的 `relay.mjs`、`titles.mjs`、`network.mjs`、`process-exit.mjs`、`options.mjs`、`subagents.mjs`、`instructions.mjs`、`steering.mjs`、`providers.mjs` 在加载时给锁定版本的
309
356
  [codex-acp](https://github.com/agentclientprotocol/codex-acp)(Apache-2.0,Copyright 2025 JetBrains s.r.o.)打补丁,补丁里含 codex-acp 构建产物的原文片段。
310
357
  这些片段按 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 使用,许可全文也随 npm 包装在 `node_modules/@agentclientprotocol/codex-acp/LICENSE`;
311
358
  codex-acp 本身作为 npm 依赖安装,不在本仓库里。
package/check/coda.mjs CHANGED
@@ -100,6 +100,11 @@ export const AGENT_ID = /^[a-z][a-z0-9-]{0,39}$/;
100
100
  export const PANEL_ID = /^[a-z][a-z0-9-]{0,63}$/;
101
101
  export const POINT = /^[a-z][a-z0-9-]{0,39}$/;
102
102
  export const SETTING_KEY = /^[a-z][a-z0-9-]*(\.[a-z][A-Za-z0-9]*)+$/;
103
+ // The string formats a setting may have, beside `coda-agent` and `coda-agent-match` (backend/src/components/plugins/plugin-rules.mjs
104
+ // STRING_FORMATS): a duration, a repository, a directory of the machine, a model service another plugin provides
105
+ // (`<插件>/<服务>`, MODEL_PROVIDER), and the address of one.
106
+ export const STRING_FORMATS = ['duration', 'coda-repo', 'coda-directory', 'coda-model-provider', 'coda-model-url'];
107
+ export const MODEL_PROVIDER = /^[a-z][a-z0-9-]{0,39}\/[a-z][a-z0-9-]{0,39}$/;
103
108
  export const CAPABILITY = /^[a-z][a-z0-9-]{0,63}$/;
104
109
  // What a plugin may contribute (backend/src/components/plugins/plugin-rules.mjs CONTRIBUTES); anything else is refused.
105
110
  export const CONTRIBUTES = ['configuration', 'capabilities', 'instructions', 'panels', 'agents', 'extensionPoints', 'extensions', 'groups', 'events', 'commands', 'gitCredentials',
@@ -9,7 +9,7 @@ import { basename, isAbsolute, join, normalize, relative, sep } from 'node:path'
9
9
  import { inflateSync } from 'node:zlib';
10
10
  import { AGENT_ID, AGENT_LIMITS, BUILTIN_AGENTS, BUILTIN_COMMANDS, BUILTIN_GROUPS, BUILTIN_HERE, BUILTIN_SCOPE, CAPABILITY,
11
11
  CODA_COMMANDS, CODA_EVENT_DOMAINS, COMMAND, COMMAND_LIMIT, BUILTIN_ONLY, CONTRIBUTES, EVENT, EVENT_LIMIT, DEPTH, ICON_LIMIT, INSTRUCTIONS_LIMIT, NPM, NPM_LOCK, PANEL_ID, PANEL_LIMITS, PLUGIN_ID, POINT,
12
- SEGMENT, SERVICE, SETTING_KEY, SETUP_KEYS, SIZE_LIMIT } from './coda.mjs';
12
+ MODEL_PROVIDER, SEGMENT, SERVICE, SETTING_KEY, SETUP_KEYS, SIZE_LIMIT, STRING_FORMATS } from './coda.mjs';
13
13
 
14
14
  class Invalid extends Error {}
15
15
  const check = (condition, message) => { if (!condition) throw new Invalid(message); };
@@ -52,9 +52,9 @@ function schema(value, where, entry = false) {
52
52
  const agent = value.format === 'coda-agent';
53
53
  check(['boolean', 'integer', 'number', 'string', 'object', 'array'].includes(value.type), `${where} 的 type 不受支持:${value.type}`);
54
54
  check((value.items === undefined && value.uniqueItems === undefined) || value.type === 'array', `${where}:只有多选(type: array)才写 items 与 uniqueItems`);
55
- check(value.format === undefined || agent || value.format === 'coda-agent-match' || value.format === 'duration', `${where} 的 format 不受支持:${value.format}`);
55
+ check(value.format === undefined || agent || value.format === 'coda-agent-match' || STRING_FORMATS.includes(value.format), `${where} 的 format 不受支持:${value.format}`);
56
56
  check(!agent || value.type === 'object', `${where}:coda-agent 是对象`);
57
- check(value.format !== 'duration' || value.type === 'string', `${where}:duration 是文本`);
57
+ check(!STRING_FORMATS.includes(value.format) || value.type === 'string', `${where}:${value.format} 是文本`);
58
58
  check(value.enum === undefined || (Array.isArray(value.enum) && value.enum.length && ['string', 'integer', 'number'].includes(value.type)), `${where} 的 enum 无效`);
59
59
  const out = { type: value.type, format: value.format, enum: value.enum, minimum: value.minimum, maximum: value.maximum };
60
60
  if (value.type === 'array') {
@@ -106,6 +106,9 @@ function checkValue(spec, value, where) {
106
106
  } else if (spec.type === 'string') {
107
107
  check(typeof value === 'string' && value.length <= 20000 && !value.includes('\0'), `${where}须为文本`);
108
108
  check(spec.format !== 'duration' || isDuration(value), `${where}须为 ISO 8601 时长,如 P30D、PT6H`);
109
+ // A repository's address Coda reads as it installs from one; here only the directory and the model service.
110
+ check(spec.format !== 'coda-directory' || value === '' || value.startsWith('/'), `${where}须为绝对目录`);
111
+ check(spec.format !== 'coda-model-provider' || value === '' || MODEL_PROVIDER.test(value), `${where}须为「插件/服务」,如 cliproxy/cliproxy`);
109
112
  } else if (spec.type === 'object') {
110
113
  object(value, where);
111
114
  for (const [k, v] of Object.entries(value)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coda-console/plugin-check",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Coda 插件的契约检查:扮演 Coda 读清单、启动 Agent 讲 ACP 与 _coda/wait、读面板。",
5
5
  "license": "MIT",
6
6
  "repository": {