@coda-console/plugin-check 0.4.4 → 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,29 +14,45 @@ 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 接入协议正文的公开副本
23
- .coda/workflows/ 本仓库自己的流程:sync-coda 把内置插件的副本同步进 Coda
24
+ .coda/workflows/ 本仓库自己的流程:sync-coda 等新版本发到 npm,再给 Coda 开升级依赖的 PR
25
+ .claude-plugin/ 官方市场的索引 marketplace.json,见「官方市场」
24
26
  ```
25
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
+
26
41
  ## Coda 自带的插件
27
42
 
28
43
  `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
- 副本没有变化就什么都不做。也可以单独发起,同步默认分支的最新提交。
44
+ 合进 main 后它们发布为 npm 包 `@coda-console/<id>`([npm 发布](#npm-发布)),Coda 后端的 `package.json` 把它们写成钉住版本号的依赖,
45
+ 随后端的 `npm ci` 装好,新机器首次启动就带着它们。
46
+
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
+ 也可以单独发起,同步默认分支的最新提交。
36
52
  - **改了哪个插件就升哪个插件的 `version`**(`plugins/<id>/` 下任何文件,含 `package-lock.json`;只改 `test/` 不用):Coda 的插件页与遥测按版本认插件。
37
53
  CI 在 PR 上运行 `node scripts/check-versions.mjs origin/<目标分支>` 核对;Dependabot 升 CLI、SDK 的 PR 也要补一个升版本的提交。
38
- - Coda 的「检查更新」可以从这里装同一插件的新提交,盖过内置副本;只有本仓库的 `plugins/<id>` 这个来源能用这些名字。
39
- - 测的是插件自己代码的用例在 `test/plugins/<id>/`;把 Coda 与入口合在一起测的(启动 Coda 后端、写进 Coda 的存储)留在 Coda,对副本跑。
54
+ - Coda 的「检查更新」可以从 npm 装同一个包的新版本,盖过 Coda 带的版本;只有 `@coda-console/<id>` 这个包能用这些名字。
55
+ - 测的是插件自己代码的用例在 `test/plugins/<id>/`;把 Coda 与入口合在一起测的(启动 Coda 后端、写进 Coda 的存储)留在 Coda,对它装的 npm 包跑。
40
56
  `review` 是一个流程,要 Coda 的流程引擎才跑得起来,它的用例都在 Coda。
41
57
 
42
58
  | 插件 | npm 依赖(与 Coda 后端同版本) | 运行机上另要 |
@@ -66,6 +82,33 @@ CLI 不在或运行不了就用它自己的安装程序安装,低于插件要
66
82
  Grok 1.0.52(审批模式通知核对过的版本)、OpenCode 1.0.151(`opencode acp` 第一个报当前模型、恢复的会话能接着跑的版本:之前 `session/load` 不登记会话,下一轮报 Session not found);Cursor 没有已知的下限,能运行就行(它自己会更新)。
67
83
  Codex 与 Claude 的 CLI、SDK 是插件的 npm 依赖,不需要设置动作。
68
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
+
69
112
  ### Grok 审批模式
70
113
 
71
114
  Grok 插件通过 ACP 提供原来的「自动审批」「全部放行」两个选项;新会话未选择时沿用运行机配置。
@@ -88,7 +131,7 @@ Codex 用普通 ACP 选择型配置项 `service-tier`(「速度」)报告速
88
131
 
89
132
  `codex-accounts` 2.0.0 自己管理账号,不需要预装账号管理器或 Python。安装时 Coda 安装它的 npm 依赖和
90
133
  `codex` 插件;本版接管额度、登录入口和续期通知的配套入口是 `codex` 1.1.0。已有安装升级时两个插件一起更新,
91
- Coda 内置副本同步时也一起带上这两个目录。
134
+ Coda 升级依赖时也一起升这两个包。
92
135
 
93
136
  - 首次使用会把运行机 `$CODEX_HOME/auth.json`(默认 `~/.codex/auth.json`)里的 ChatGPT 登录导入插件,作为第一个
94
137
  普通受控账号并默认选用。原文件保留;之后的凭据、续期与选择都由插件维护,原登录文件后续变化不会替换受控账号。
@@ -139,15 +182,15 @@ Coda 按这个通用类别显示「额度不足」,不触发服务故障重试
139
182
  Codex 会话、面板与登录用的 Codex CLI 就是 `codex` 插件依赖的 `@openai/codex`;Claude 的是 `claude` 插件依赖的
140
183
  `@anthropic-ai/claude-agent-sdk`。插件按包名从自己的依赖里找它们,Coda 不另给路径。升级就是改这两个版本:
141
184
  Dependabot(`.github/dependabot.yml`)每周为它们各开一个 PR,连同 `package-lock.json` 一起改;合并后在 Coda「设置 › 插件」
142
- 对该插件点「检查更新」,内置副本随 Coda 同步到新提交。升 `@openai/codex` 时 codex-acp 经 `overrides` 跟着用新版本,
143
- 合并前用真实会话跑一轮。
185
+ 对该插件点「检查更新」;合并时勾选「部署」,`sync-coda` 给 Coda 开升级依赖的 PR。升 `@openai/codex` 时 codex-acp 经 `overrides` 跟着用新版本,
186
+ Coda 根上的同一条 overrides 由 `sync-coda` 一起改(npm 只认根的,Coda 的测试会核对);合并前用真实会话跑一轮。
144
187
 
145
188
  ## 在 Coda 里安装
146
189
 
147
190
  「设置 › 插件 › 从仓库添加…」:
148
191
 
149
192
  1. **仓库**填 `coda-console/coda-plugins`(或 `https://github.com/coda-console/coda-plugins`);**版本**留空是默认分支,也可以填分支、标签或完整的提交号。
150
- 2. **读取**:Coda 拉取那个提交,把仓库根与两层以内 `package.json` 带 `contributes` 的目录各列成一个插件——这个仓库里是 `plugins/` 下的七个与两个模板。内置插件可以从自身来源单独更新;卸载后,也可以从其他来源安装同名替代插件。
193
+ 2. **读取**:Coda 拉取那个提交,把仓库根与两层以内 `package.json` 带 `contributes` 的目录各列成一个插件——这个仓库里是 `plugins/` 下的七个与两个模板。Coda 内置的那几个只能从 npm 上自己的包单独更新;在 Coda 里先卸载它,才能从这里装同名的。
151
194
  3. 选一个,确认里逐条列出它会运行的命令(Agent 的 `command`、面板的命令与动作),确认就是批准:这些命令以你的账户在空间里每台机器上运行,Coda 不做隔离,只装你信任的来源。
152
195
 
153
196
  **Coda 只认提交**:安装时钉在读取到的那个提交上,每台机器各自取同一个提交。「检查更新」用记下的仓库与版本再读一次,提交变了才有更新;
@@ -309,7 +352,7 @@ CI 先装各插件的依赖再跑全部测试,`plugins/*` 只查清单:真
309
352
 
310
353
  MIT,见 [LICENSE](LICENSE)。各插件的品牌图形归各自权利人所有,来源写在插件的 `icons/README.md`。
311
354
 
312
- `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` 在加载时给锁定版本的
313
356
  [codex-acp](https://github.com/agentclientprotocol/codex-acp)(Apache-2.0,Copyright 2025 JetBrains s.r.o.)打补丁,补丁里含 codex-acp 构建产物的原文片段。
314
357
  这些片段按 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 使用,许可全文也随 npm 包装在 `node_modules/@agentclientprotocol/codex-acp/LICENSE`;
315
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',
@@ -136,11 +141,11 @@ export const DEPTH = 2;
136
141
  // `npm ci` in the plugin's version directory.
137
142
  export const NPM = ['dependencies', 'optionalDependencies'];
138
143
  export const NPM_LOCK = 'package-lock.json';
139
- // Those whose copies in Coda are taken from `plugins/<id>` of this repository (backend/src/resources/plugins.lock.json): a
140
- // version from the same repository and directory may be installed over a copy. Other sources may use the same ID once
141
- // the built-in plugin is uninstalled; only the target backend can check its installed state (builtinProblem).
142
- export const BUILTIN_HERE = ['claude', 'codex', 'codex-accounts', 'grok', 'kimi', 'opencode'];
143
- export const BUILTIN_REPOSITORY = /(^|[/:])coda-console\/coda-plugins(\.git)?\/?$/i;
144
+ // Those kept in `plugins/<id>` here that Coda ships as npm dependencies, `@coda-console/<id>` (docs/plugins.md「内置插件的来源」):
145
+ // a version of the same package may be installed over Coda's. Other sources may use the same ID once the built-in plugin is
146
+ // uninstalled; only the target backend can check its installed state (builtinProblem).
147
+ export const BUILTIN_HERE = ['claude', 'codex', 'codex-accounts', 'grok', 'kimi', 'opencode', 'review'];
148
+ export const BUILTIN_SCOPE = '@coda-console/';
144
149
  // An agent, a concurrency group and a command are one plugin's (catalogProblem, claims). Those the built-in plugins declare, by
145
150
  // plugin; and `claude`, the agent ID sessions from before #1069 still store (docs/plugins.md「Agent」).
146
151
  export const BUILTIN_AGENTS = { 'claude-sdk': 'claude', codex: 'codex', grok: 'grok', kimi: 'kimi', opencode: 'opencode', claude: 'claude' };
@@ -7,9 +7,9 @@ import { execFileSync } from 'node:child_process';
7
7
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
8
8
  import { basename, isAbsolute, join, normalize, relative, sep } from 'node:path';
9
9
  import { inflateSync } from 'node:zlib';
10
- import { AGENT_ID, AGENT_LIMITS, BUILTIN_AGENTS, BUILTIN_COMMANDS, BUILTIN_GROUPS, BUILTIN_HERE, BUILTIN_REPOSITORY, CAPABILITY,
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)) {
@@ -227,11 +230,6 @@ function files(dir) {
227
230
  walk('');
228
231
  return { top: null, entries };
229
232
  }
230
- // The addresses the repository at `top` is fetched from.
231
- function remotes(top) {
232
- try { return execFileSync('git', ['-C', top, 'remote', '-v'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).split('\n').map(l => l.split(/\s+/)[1]).filter(Boolean); }
233
- catch { return []; }
234
- }
235
233
  // Why npm could not install from the lock file next to the manifest (`npm ci` refuses a lock that does not match; Coda's
236
234
  // manifestProblem refuses it first), or null.
237
235
  function lockProblem(dir, manifest) {
@@ -266,13 +264,13 @@ export function checkManifest(dir, say) {
266
264
  const { top, entries } = files(dir);
267
265
  // Git names the top by its real path; so is the directory measured.
268
266
  const path = top ? relative(top, realpathSync(dir)).split(sep).filter(Boolean) : null;
269
- // A built-in plugin kept here, in its own place: Coda's copy of it comes from there, and so may a newer version.
267
+ // A built-in plugin as its package is named: Coda ships a version of that package, and a newer one may stand in for it.
270
268
  const declaredId = manifest.id !== undefined ? manifest.id : manifest.name;
271
- const own = BUILTIN_HERE.includes(declaredId) && path?.join('/') === `plugins/${declaredId}` && remotes(top).some(url => BUILTIN_REPOSITORY.test(url));
269
+ const own = BUILTIN_HERE.includes(declaredId) && manifest.name === `${BUILTIN_SCOPE}${declaredId}`;
272
270
  const id = part('manifest.name', () => {
273
271
  check(typeof declaredId === 'string' && PLUGIN_ID.test(declaredId), `package.json 的 ${manifest.id !== undefined ? 'id' : 'name'} 只能用小写字母、数字和「-」,以字母开头,至多 40 个字符`);
274
272
  return declaredId;
275
- }, name => `package.json 能读,插件 ID ${name}${own ? `(Coda 内置插件 ${name} 自己的来源:从这里装的新版本盖过 Coda 的内置副本)` : ''}`);
273
+ }, name => `package.json 能读,插件 ID ${name}${own ? `(Coda 内置插件 ${name}:Coda 带的是 npm 上 ${manifest.name} 的一个版本,这个包的新版本可以盖过它)` : ''}`);
276
274
  const contributes = part('manifest.contributes', () => {
277
275
  check(manifest.contributes && typeof manifest.contributes === 'object' && !Array.isArray(manifest.contributes),
278
276
  'contributes 须为对象(Coda 只把带 contributes 的目录当插件)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coda-console/plugin-check",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Coda 插件的契约检查:扮演 Coda 读清单、启动 Agent 讲 ACP 与 _coda/wait、读面板。",
5
5
  "license": "MIT",
6
6
  "repository": {