feat: 以原生应用上架 DesireCore Control 1.4.0 (#6)

This commit is contained in:
2026-09-18 17:35:34 +08:00
committed by GitHub
parent 223be1f04b
commit 07442e03ca
18 changed files with 446 additions and 20 deletions

View File

@@ -9,7 +9,7 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户
```text
.
├── README.md # 本文件
├── SCHEMA_VERSION # 数据格式版本号(当前 4.0.0)
├── SCHEMA_VERSION # 数据格式版本号(当前 4.1.0)
├── manifest.json # 仓库元数据(版本、统计、维护者)
├── package.json # Registry 校验入口
├── schemas/ # legacy entry、仓库 manifest 与 catalog sidecar Schema
@@ -38,6 +38,16 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户
└── service-status.json
```
## 原生应用(4.1)
[DesireCore Control 1.4.0](entries/desirecore-control/) 是面向外部智能体的独立宿主机应用,包含 ChatGPT 隧道管理。它以 `type: native-app`、`install.method: native-node`、`identity.kind: app` 收录;MCP 只是对外协议,不注册到 DesireCore 内部工具列表,不伪装为 Docker。
原生应用必须声明宿主机 Node 版本、`docker: false`、精确管理端口、最低客户端版本、不可变 Release URL/ref/SHA-256 和审核记录。安装说明同时覆盖安装、升级、验证及卸载,不随 DesireCore 启停,不默认启用控制或隧道。条目禁止 `connection`、`exposes`、`endpoint`、工具数等内部服务配置;`allowAgentBinding` 为 false。兼容来源字段只写在 manifest.source 与 sidecar.provenance.content,二者必须一致;sidecar 不写运行时 governance.source。
安装要求支持 native-app 的客户端(Control 条目最低 10.0.169)及核心应用安装技能 >=1.4.0。目录同步不能升级桌面客户端或技能;旧版看不到条目或提示升级时,不得修改类型绕过兼容检查。新制品已发布不代表新版桌面客户端已发布,也不代表用户已完成 ChatGPT 联调。
English: Native applications are independently installed host processes. Their outward MCP protocol does not make them internal service packages. Pinned artifacts, explicit client/skill compatibility and App lifecycle receipts are required; catalog publication alone cannot upgrade an older client. The Control listing is the sole application entry, not a Docker or MCP duplicate.
## 条目格式
每个条目是 `entries/<id>/` 下的一个目录,目录名即条目 ID。
@@ -52,7 +62,7 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户
|------|------|------|------|
| `id` | string | ✅ | 唯一标识,与目录名一致 |
| `name` | string | ✅ | 显示名称 |
| `type` | string | ✅ | 条目类型:`docker-app` / `mcp` / `http-api` / `external-integration` |
| `type` | string | ✅ | 条目类型:`native-app` / `docker-app` / `mcp` / `http-api` / `external-integration` |
| `version` | string | ✅ | 上游原始版本字符串;可为 SemVer、CalVer 或不透明版本 |
| `description` | string | ✅ | 一行功能摘要 |
| `author` | string | | 作者或组织 |

View File

@@ -1 +1 @@
4.0.0
4.1.0

View File

@@ -0,0 +1,116 @@
{
"$schema": "../../schemas/catalog-metadata.v1.schema.json",
"schemaVersion": 1,
"identity": {
"kind": "app",
"id": "desirecore-control"
},
"presentation": {
"defaultLocale": "zh-CN",
"i18n": {
"zh-CN": {
"name": "DesireCore Control",
"summary": "供外部智能体控制本机多个 DesireCore 实例的独立应用,内置 ChatGPT 隧道管理。",
"description": "DesireCore Control 是独立开源的宿主机应用:自动识别本机实例、展示管理页面,并通过对外 MCP 提供窗口、截图与 CDP 操作。1.4.0 支持管理官方 ChatGPT tunnel-client。默认只读,不随 DesireCore 启停,不向内部智能体注册 MCP。应用市场安装需要支持 native-app 的客户端及应用安装管理技能 1.4.0;隧道客户端另行安装,真实账户凭据由用户在本机提供。"
},
"en-US": {
"name": "DesireCore Control",
"summary": "Independent application for external agents to control local DesireCore instances, with ChatGPT tunnel management.",
"description": "Runs natively on the host and discovers multiple local CDP instances. Version 1.4.0 manages the official ChatGPT tunnel client. Readonly by default, no internal MCP registration or DesireCore lifecycle coupling. Requires a native-app-aware client and installation skill 1.4.0; tunnel installation and credentials are separate."
}
},
"tags": [
"DesireCore",
"external-agents",
"ChatGPT",
"CDP",
"control"
]
},
"release": {
"state": "known",
"version": "1.4.0",
"versionScheme": "semver"
},
"timestamps": {
"catalogUpdatedAt": {
"state": "known",
"value": "2026-09-18",
"precision": "day"
},
"releasePublishedAt": {
"state": "known",
"value": "2026-09-18",
"precision": "day"
},
"reviewedAt": {
"state": "known",
"value": "2026-09-18",
"precision": "day"
},
"upstreamObservedAt": {
"state": "known",
"value": "2026-09-18",
"precision": "day"
}
},
"provenance": {
"content": {
"kind": "release",
"url": "https://github.com/desirecore-agent/desirecore-cdp-mcp/releases/download/v1.4.0/desirecore-cdp-mcp-1.4.0.tgz",
"ref": "38184fbb01f82d4dd850b67366cdac048a09474b",
"sha256": "ecfd6e770cd1854d7368ec89852b5e798cc04f6e19bbb59fedae52a56bacbee0"
}
},
"governance": {
"stewardship": "community",
"availability": "installable",
"redistribution": "allowed",
"listingMaintainer": {
"name": "DesireCore Registry maintainers",
"url": "https://github.com/desirecore/registry"
},
"upstreamMaintainer": {
"name": "desirecore-agent",
"url": "https://github.com/desirecore-agent/desirecore-cdp-mcp"
},
"branding": {
"relationship": "independent-listing",
"nameUsage": "nominative",
"logoStatus": "not-used"
},
"compliance": {
"licenseEvidencePath": "LICENSE",
"noticePath": "NOTICE",
"reviewedRef": "38184fbb01f82d4dd850b67366cdac048a09474b",
"reviewedAt": "2026-09-18",
"reviewedBy": "release-and-package-validation (see review.md)",
"upstreamEndorsed": false
},
"license": {
"state": "known",
"value": "MIT",
"evidencePath": "LICENSE"
}
},
"compatibility": {
"platforms": {
"state": "known",
"values": [
"windows",
"macos",
"linux"
]
},
"requiredClientVersion": "10.0.169"
},
"spec": {
"kind": "app",
"category": "tools",
"placementPolicy": {
"showInMyApps": true,
"allowNavPin": true,
"allowAgentBinding": false
}
}
}

View File

@@ -0,0 +1,72 @@
# 安装、升级与卸载 DesireCore Control
这是供外部智能体使用的**独立应用**,不是给 DesireCore 内部智能体安装的 MCP。只接受 resolver 已复核的 `kind=app`、`manifest.type=native-app` 和本指南。需要支持原生应用的客户端(目录最低版本 10.0.169)及 app-install-manager >=1.4.0;旧版拒绝时停止,不改成 Docker/MCP,不绕过收据与 operationId。
## 安装前确认
向用户确认目标设备与独立应用安装目录。Node.js >=22.22.2,npm 可用;512 MB 内存/磁盘为建议预留,不是实测峰值。安装阶段不需要 DesireCore 实例在线,不读取用户实例、截图或凭据。默认管理页面端口 9333;启动前被占用则说明并停止,不终止占用进程、不偷偷改端口。
固定制品:`https://github.com/desirecore-agent/desirecore-cdp-mcp/releases/download/v1.4.0/desirecore-cdp-mcp-1.4.0.tgz`
SHA-256:`ecfd6e770cd1854d7368ec89852b5e798cc04f6e19bbb59fedae52a56bacbee0`
源码提交:`38184fbb01f82d4dd850b67366cdac048a09474b`。下载后先比对 resolver manifest.source 与本指南的版本、URL、ref、摘要;不一致立即停止。只从该固定 Release 安装,依赖通过 npm 正常解析;摘要针对应用 tgz,不声称覆盖全部传递依赖。
## Windows PowerShell
默认独立安装根为当前用户 `%LOCALAPPDATA%\DesireCoreControl`,不得混用 DesireCore 数据 home。明确存在旧版本或自定义安装目录时先确认实际路径,不能覆盖未知目录。
```powershell
$ErrorActionPreference = 'Stop'
$Root = Join-Path $env:LOCALAPPDATA 'DesireCoreControl'
$Prefix = Join-Path $Root 'versions\1.4.0'
$Temp = Join-Path ([IO.Path]::GetTempPath()) ('dc-control-install-' + [guid]::NewGuid())
New-Item -ItemType Directory -Path $Temp | Out-Null
$Archive = Join-Path $Temp 'desirecore-cdp-mcp-1.4.0.tgz'
Invoke-WebRequest -Uri 'https://github.com/desirecore-agent/desirecore-cdp-mcp/releases/download/v1.4.0/desirecore-cdp-mcp-1.4.0.tgz' -OutFile $Archive
if ((Get-FileHash -LiteralPath $Archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne 'ecfd6e770cd1854d7368ec89852b5e798cc04f6e19bbb59fedae52a56bacbee0') { throw '制品校验失败,不安装' }
npm install --prefix "$Prefix" --omit=dev --ignore-scripts --no-audit --no-fund "$Archive"
if ($LASTEXITCODE -ne 0) { throw '安装失败' }
$Package = Join-Path $Prefix 'node_modules\desirecore-cdp-mcp'
$Meta = Get-Content -Raw -LiteralPath (Join-Path $Package 'package.json') | ConvertFrom-Json
if ($Meta.name -ne 'desirecore-cdp-mcp' -or $Meta.version -ne '1.4.0') { throw '已安装版本不符' }
node (Join-Path $Package 'bin\desirecore-control.cjs') --help
if ($LASTEXITCODE -ne 0) { throw '应用入口自检失败' }
$Empty = Join-Path $Temp 'empty-registry.json'
[IO.File]::WriteAllText($Empty, '{"version":1,"instances":[]}', [Text.UTF8Encoding]::new($false))
node (Join-Path $Package 'bin\desirecore-cdp-mcp.cjs') list --registry "$Empty"
if ($LASTEXITCODE -ne 0) { throw '隔离实例发现自检失败' }
# 仅移除本次随机创建的临时目录;应用包、用户数据和凭据保留。
Remove-Item -LiteralPath $Temp -Recurse
```
安装入口与空名录自检通过才能回写 installed。保持 resolver 的原生 App lifecycle receipt,不添加 runtimeServerId;使用同一 sourceId、entryId、deviceId、operationId,按 app-install-manager 的 build-receipt-patch 回写,不直接编辑 installed-entries.json。installed 只表示安装通过,不代表应用/隧道正在运行。
## macOS / Linux
使用用户确认的独立前缀(默认 `$HOME/.local/share/desirecore-control/versions/1.4.0`),无需 sudo。不支持时不要自动升级 Node 或更改系统配置。用 `mktemp -d` 创建本次下载目录,`curl --fail --location` 下载上述固定 URL,分别用 `shasum -a 256`(macOS)或 `sha256sum`(Linux)核对**上述完整摘要**后再执行:
```sh
npm install --prefix "$PREFIX" --omit=dev --ignore-scripts --no-audit --no-fund "$ARCHIVE"
node "$PREFIX/node_modules/desirecore-cdp-mcp/bin/desirecore-control.cjs" --help
# REGISTRY 是本次临时目录内写入 {"version":1,"instances":[]} 的 UTF-8 文件。
node "$PREFIX/node_modules/desirecore-cdp-mcp/bin/desirecore-cdp-mcp.cjs" list --registry "$REGISTRY"
```
同时读取该前缀的 package.json 核对 name/version。任何一步失败立即停止,不能仅凭下载成功或 npm 退出码 0 回写 installed。只清理本次创建的临时目录。
## 独立启动与 ChatGPT 隧道
安装完成后向用户提供准确的启动命令。用户另行批准启动时,在独立终端运行 `node <Prefix>/node_modules/desirecore-cdp-mcp/bin/desirecore-control.cjs`。访问 `http://127.0.0.1:9333/`;系统浏览器打开,不嵌入 DesireCore 窗口,不创建登录/开机自启动项,不把进程生命周期绑定到 DesireCore。
默认只读,不追加 `--allow-control` 或 `--chatgpt-tunnel`。官方 tunnel-client 是可选的独立依赖,安装需要另行确认;账户 key 由用户在本机管理页输入,不发到聊天、日志、Git 或安装收据。市场安装本身不创建 Platform Tunnel、不连接 ChatGPT、不开放公网 CDP。操作说明见 usage.md。
## 升级、重装和回滚
只按用户批准的精确安装身份操作。新版本安装到独立版本目录并校验成功后才切换启动入口和 catalogReceipt;不得覆盖运行中的包目录。旧版仍可用时升级失败恢复 installed 并保留原 receipt/version;首装失败或已不可用时按协议回 failed。不要删除用户凭据、旧实例数据或其它版本以“修复”安装。版本切换后要求用户重新扫描外部客户端工具定义。
## 卸载
先由 resolver 按原 installed ownership + lifecycle receipt 返回本指南,再获得用户确认。仅停止明确由此安装启动的 Control 进程及其自有隧道,不执行按名称批量 kill Node/tunnel-client,不停止 DesireCore。对准确安装前缀运行 `npm uninstall --prefix <Prefix> desirecore-cdp-mcp`,并确认该包入口已不存在,再回写 uninstalled。卸载失败但原应用仍可用时回 installed,不能遗留 uninstalling。
默认保留 `%LOCALAPPDATA%\DesireCoreMcp` 或 `$HOME/.desirecore-mcp` 的用户凭据,以及其它版本与用户配置;删除这些材料必须另行获得明确同意。不得清理 DesireCore 的任何 home、用户数据、实例名录或内部 MCP 配置。

View File

@@ -0,0 +1,32 @@
{
"id": "desirecore-control",
"name": "DesireCore Control",
"type": "native-app",
"version": "1.4.0",
"author": "desirecore-agent",
"description": "供外部智能体控制本机多个 DesireCore 实例的独立应用,内置 ChatGPT 隧道管理。",
"shortDesc": "供外部智能体控制本机多个 DesireCore 实例的独立应用,内置 ChatGPT 隧道管理。",
"fullDesc": "DesireCore Control 是独立开源的宿主机应用:自动识别本机实例、展示管理页面,并通过对外 MCP 提供窗口、截图与 CDP 操作。1.4.0 支持管理官方 ChatGPT tunnel-client。默认只读,不随 DesireCore 启停,不向内部智能体注册 MCP。应用市场安装需要支持 native-app 的客户端及应用安装管理技能 1.4.0;隧道客户端另行安装,真实账户凭据由用户在本机提供。",
"tags": ["DesireCore", "external-agents", "ChatGPT", "CDP", "control"],
"icon": "monitor",
"iconLetter": "DC",
"platformSupport": ["windows", "macos", "linux"],
"category": "tools",
"githubUrl": "https://github.com/desirecore-agent/desirecore-cdp-mcp",
"requiredClientVersion": "10.0.169",
"install": {
"method": "native-node",
"requirements": { "docker": false, "node": ">=22.22.2", "minMemory": "512MB(安装建议预留,非实测峰值)", "minDisk": "512MB(建议预留)", "ports": [9333] },
"configNeeded": ["宿主机 Node.js >=22.22.2 与 npm", "应用安装管理技能 >=1.4.0", "ChatGPT 隧道为可选功能,需独立安装官方 tunnel-client 并由用户提供凭据"]
},
"placementPolicy": { "showInMyApps": true, "allowNavPin": true, "allowAgentBinding": false },
"source": { "kind": "release", "url": "https://github.com/desirecore-agent/desirecore-cdp-mcp/releases/download/v1.4.0/desirecore-cdp-mcp-1.4.0.tgz", "ref": "38184fbb01f82d4dd850b67366cdac048a09474b", "sha256": "ecfd6e770cd1854d7368ec89852b5e798cc04f6e19bbb59fedae52a56bacbee0" },
"stewardship": "community",
"availability": "installable",
"license": "MIT",
"redistribution": "allowed",
"listingMaintainer": { "name": "DesireCore Registry maintainers", "url": "https://github.com/desirecore/registry" },
"upstreamMaintainer": { "name": "desirecore-agent", "url": "https://github.com/desirecore-agent/desirecore-cdp-mcp" },
"branding": { "relationship": "independent-listing", "nameUsage": "nominative", "logoStatus": "not-used" },
"compliance": { "licenseEvidencePath": "LICENSE", "noticePath": "NOTICE", "reviewedRef": "38184fbb01f82d4dd850b67366cdac048a09474b", "reviewedAt": "2026-09-18", "reviewedBy": "release-and-package-validation (see review.md)", "upstreamEndorsed": false }
}

View File

@@ -0,0 +1,11 @@
# 1.4.0 目录发布核验
日期:2026-09-18。产品是面向外部智能体的独立宿主机应用,不是内部 MCP。
上游:desirecore-agent/desirecore-cdp-mcp,固定源码 38184fbb01f82d4dd850b67366cdac048a09474b,GitHub Release v1.4.0。
制品:desirecore-cdp-mcp-1.4.0.tgz;SHA-256 ecfd6e770cd1854d7368ec89852b5e798cc04f6e19bbb59fedae52a56bacbee0。
MIT 仅涵盖独立应用仓库;制品保留 LICENSE、NOTICE;第三方依赖遵循各自许可。不转载 DesireCore 主工程,不使用其图标或声明上游背书。
上游 PR #2 的 Windows/Linux/macOS CI 均通过;Release workflow 35326965105 完成构建、测试和 package smoke。源码侧包验证覆盖隔离安装、零实例启动、管理页和对外 SDK 调用,不读取真实实例或运行账户密钥。未完成用户的实际 ChatGPT 工作区及真实 DesireCore 操作验收,不能把 ready 或 CI 提升为该验收。
目录要求支持 native-app 的客户端(最低 10.0.169)与安装技能 1.4.0。catalog 为 installable 只表示满足此兼容条件时可按审核后的指南获取,不表示旧版客户端能安装、已安装或正在运行。发布应用目录不等于发布新版桌面客户端。

View File

@@ -0,0 +1,27 @@
# 使用 DesireCore Control 1.4.0
这是一款独立应用。先启动已安装的 Control,再在系统浏览器打开终端显示的本机管理页(默认 http://127.0.0.1:9333/)。关闭 DesireCore 不应关闭 Control;退出 Control 也不关闭 DesireCore。安装完成不等于正在运行,必要时在应用资源中发起启动请求。
## 本机实例
在管理页输入 Control 自己的本机 token。应用自动列出各实例实际端口,不扫固定端口段,不替实例开启 CDP。先核对实例,再将 instanceId 与该实例的 targetId 提供给外部客户端。截图和 DOM 可能含隐私。实例重启后重新选择 ID,不重放超时操作。
## ChatGPT 安全隧道
1. 用户另行安装官方 native tunnel-client,在 OpenAI Platform 创建 Tunnel 并关联目标工作区,准备具有 Tunnels Read + Use 的运行 key。不要把 key 交给市场安装智能体或发到聊天。
2. 使用 `desirecore-control --tunnel-client <绝对路径>`(本地前缀安装则通过 Node 加载该前缀 bin 入口)。在管理页的“ChatGPT 安全隧道”输入本次启动终端所示 admin-token,再填 Tunnel ID 和运行 key。
3. 点击启动后,应用自动接线实际 MCP 端点、调用与初始化认证头。ChatGPT 开发者连接选择 Tunnel 和对应 ID;采用本机认证头注入时选择认证 None,不配置覆盖 Authorization 的同名头。
4. state=running 只表示子进程运行,ready=true 只表示客户端就绪。必须在 ChatGPT 实际调用 desirecore_list_instances,再核对实例并截图,才是端到端验收。
MCP token、隧道 admin-token、OpenAI key 用途不同,不可互换。普通安装和启动不会自动开放隧道;停止隧道只停止本应用拥有的客户端,不删除 Platform Tunnel。
## 控制权限
默认只读。用户明确授予控制权后,可在本机终端以 `--allow-control` 重启。此权限可执行主世界 JavaScript 并访问完整 IPC,不是沙箱。不能通过市场分类或内部 Agent 绕过 DesireCore 自身的受治理 GUI 工具。
## 更新与客户端要求
在市场“应用”而非“MCP 服务”中管理此产品。市场应用类型为 native-app,要求支持该类型的客户端(最低 10.0.169)和 app-install-manager >=1.4.0;旧版不支持时应显示升级要求或不显示该条目,不能改成 Docker/MCP 安装。同步市场目录不会自动升级桌面客户端或智能体技能。
上游固定版说明:https://github.com/desirecore-agent/desirecore-cdp-mcp/tree/v1.4.0
官方隧道指南:https://developers.openai.com/api/docs/guides/secure-mcp-tunnels

View File

@@ -1,20 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"id": "desirecore-registry-manifest",
"version": "4.0.0",
"version": "4.1.0",
"name": "DesireCore Registry",
"description": "DesireCore 官方注册表 — Docker 应用、MCP 服务、HTTP API 和第三方外部集成的统一目录",
"description": "DesireCore 官方注册表 — 原生应用、Docker 应用、MCP 服务、HTTP API 和第三方外部集成的统一目录",
"maintainer": "DesireCore Team",
"repository": "https://github.com/desirecore/registry",
"lastUpdated": "2026-08-31",
"lastUpdated": "2026-09-18",
"stats": {
"totalEntries": 22,
"totalEntries": 23,
"dockerApps": 8,
"nativeApps": 1,
"mcpServices": 8,
"httpApis": 5,
"externalIntegrations": 1
},
"dataVersion": "4.0.0",
"dataVersion": "4.1.0",
"catalogMetadata": {
"version": "1.0.0",
"schema": "schemas/catalog-metadata.v1.schema.json",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@desirecore/registry",
"version": "4.0.0",
"version": "4.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@desirecore/registry",
"version": "4.0.0",
"version": "4.1.0",
"devDependencies": {
"ajv": "8.20.0"
},

View File

@@ -1,12 +1,12 @@
{
"name": "@desirecore/registry",
"version": "4.0.0",
"version": "4.1.0",
"private": true,
"description": "Validated catalog data for the DesireCore Registry",
"type": "module",
"scripts": {
"validate": "node scripts/validate-registry.mjs",
"test": "node --test scripts/validate-registry.test.mjs && npm run validate"
"test": "node --test scripts/validate-registry.test.mjs scripts/native-app.test.mjs && npm run validate"
},
"devDependencies": {
"ajv": "8.20.0"

View File

@@ -5,11 +5,87 @@
"description": "A strictly discriminated DesireCore Registry catalog entry. External integrations are discovery-only pointers and cannot carry installation or connection semantics.",
"oneOf": [
{ "$ref": "#/definitions/dockerApp" },
{ "$ref": "#/definitions/nativeApp" },
{ "$ref": "#/definitions/mcp" },
{ "$ref": "#/definitions/httpApi" },
{ "$ref": "#/definitions/externalIntegration" }
],
"definitions": {
"nativeApp": {
"type": "object",
"description": "宿主机原生应用:独立安装和运行,不通过 MCP 服务注册或 Docker 容器部署。",
"required": ["id", "name", "type", "version", "author", "description", "tags", "icon", "iconLetter", "platformSupport", "category", "fullDesc", "shortDesc", "install", "source", "requiredClientVersion", "placementPolicy", "stewardship", "availability", "license", "redistribution", "listingMaintainer", "upstreamMaintainer", "branding", "compliance"],
"additionalProperties": false,
"properties": {
"id": { "$ref": "#/definitions/id" },
"name": { "$ref": "#/definitions/nonEmptyString" },
"type": { "const": "native-app" },
"version": { "$ref": "#/definitions/version" },
"author": { "$ref": "#/definitions/nonEmptyString" },
"description": { "$ref": "#/definitions/nonEmptyString" },
"tags": { "$ref": "#/definitions/tags" },
"icon": { "$ref": "#/definitions/nonEmptyString" },
"iconLetter": { "type": "string", "minLength": 1, "maxLength": 2 },
"platformSupport": { "$ref": "#/definitions/platformSupport" },
"category": { "const": "tools" },
"fullDesc": { "$ref": "#/definitions/nonEmptyString" },
"shortDesc": { "$ref": "#/definitions/nonEmptyString" },
"githubUrl": { "$ref": "#/definitions/httpsUrl" },
"requiredClientVersion": { "$ref": "#/definitions/version" },
"install": {
"type": "object", "required": ["method", "requirements", "configNeeded"], "additionalProperties": false,
"properties": {
"method": { "const": "native-node", "description": "按审核后的 install.md 在宿主机安装固定 Node 制品;不自动启动应用或隧道。" },
"requirements": {
"type": "object", "required": ["docker", "node", "minMemory", "minDisk", "ports"], "additionalProperties": false,
"properties": {
"docker": { "const": false },
"node": { "type": "string", "minLength": 1, "maxLength": 80 },
"minMemory": { "$ref": "#/definitions/nonEmptyString" },
"minDisk": { "$ref": "#/definitions/nonEmptyString" },
"ports": { "type": "array", "minItems": 1, "maxItems": 1, "uniqueItems": true, "items": { "type": "integer", "minimum": 1, "maximum": 65535 }, "description": "安装默认使用的本机管理页面端口,不是对外公网端口或内部服务。" }
}
},
"configNeeded": { "type": "array", "items": { "$ref": "#/definitions/nonEmptyString" } }
}
},
"placementPolicy": {
"type": "object", "required": ["showInMyApps", "allowNavPin", "allowAgentBinding"], "additionalProperties": false,
"properties": { "showInMyApps": { "type": "boolean" }, "allowNavPin": { "type": "boolean" }, "allowAgentBinding": { "const": false } }
},
"source": {
"type": "object", "required": ["kind", "url", "ref", "sha256"], "additionalProperties": false,
"properties": {
"kind": { "const": "release" }, "url": { "$ref": "#/definitions/httpsUrl" },
"ref": { "type": "string", "pattern": "^[a-f0-9]{40}(?:[a-f0-9]{24})?$" },
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"upstreamRepo": { "$ref": "#/definitions/httpsUrl" }
}
},
"stewardship": { "enum": ["community", "partner", "pointer"] },
"availability": { "enum": ["installable", "listing-only"] },
"license": { "$ref": "#/definitions/nonEmptyString" },
"redistribution": { "enum": ["allowed", "source-pointer-only", "verify-package-terms"] },
"listingMaintainer": { "$ref": "#/definitions/nativeMaintainer" },
"upstreamMaintainer": { "$ref": "#/definitions/nativeMaintainer" },
"branding": {
"type": "object", "required": ["relationship", "nameUsage", "logoStatus"], "additionalProperties": false,
"properties": { "relationship": { "const": "independent-listing" }, "nameUsage": { "const": "nominative" }, "logoStatus": { "const": "not-used" } }
},
"compliance": {
"type": "object", "required": ["licenseEvidencePath", "reviewedRef", "reviewedAt", "reviewedBy", "upstreamEndorsed"], "additionalProperties": false,
"properties": {
"licenseEvidencePath": { "$ref": "#/definitions/nonEmptyString" }, "noticePath": { "$ref": "#/definitions/nonEmptyString" },
"reviewedRef": { "type": "string", "pattern": "^[a-f0-9]{40}(?:[a-f0-9]{24})?$" },
"reviewedAt": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "reviewedBy": { "$ref": "#/definitions/nonEmptyString" }, "upstreamEndorsed": { "const": false }
}
}
}
},
"nativeMaintainer": {
"type": "object", "required": ["name", "url"], "additionalProperties": false,
"properties": { "name": { "$ref": "#/definitions/nonEmptyString" }, "url": { "$ref": "#/definitions/httpsUrl" }, "verified": { "type": "boolean" } }
},
"id": {
"type": "string",
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",

View File

@@ -21,6 +21,7 @@
"properties": {
"totalEntries": { "type": "integer", "minimum": 0 },
"dockerApps": { "type": "integer", "minimum": 0 },
"nativeApps": { "type": "integer", "minimum": 0 },
"mcpServices": { "type": "integer", "minimum": 0 },
"httpApis": { "type": "integer", "minimum": 0 },
"externalIntegrations": { "type": "integer", "minimum": 0 }

View File

@@ -18,7 +18,7 @@ if (json) {
} else {
const { counts } = report
process.stdout.write(`Registry 校验:${report.ok ? '通过' : '失败'}\n`)
process.stdout.write(`条目 ${counts.totalEntries}(App ${counts.dockerApps} / MCP ${counts.mcpServices} / HTTP ${counts.httpApis})\n`)
process.stdout.write(`条目 ${counts.totalEntries}(App ${counts.dockerApps + (counts.nativeApps ?? 0)}(原生 ${counts.nativeApps ?? 0}) / MCP ${counts.mcpServices} / HTTP ${counts.httpApis})\n`)
process.stdout.write(`Catalog sidecar ${counts.sidecars},legacy fallback ${counts.legacyOnly}\n`)
for (const item of report.diagnostics) {
process.stdout.write(`${item.level === 'error' ? 'ERROR' : 'WARN '} ${item.file} ${item.path} [${item.code}] ${item.message}\n`)

View File

@@ -1,4 +1,5 @@
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { isDeepStrictEqual } from 'node:util'
import { basename, join, relative, resolve, sep } from 'node:path'
import { validateJsonSchema } from './json-schema.mjs'
@@ -74,7 +75,7 @@ function timestampShapeIsConsistent(timestamp) {
function validateSidecarSemantics(manifest, sidecar, file) {
const errors = []
const add = (code, path, message) => errors.push(diagnostic('error', code, file, path, message))
const expectedKind = manifest.type === 'docker-app' ? 'app' : 'service'
const expectedKind = ['docker-app', 'native-app'].includes(manifest.type) ? 'app' : 'service'
if (sidecar.identity?.id !== manifest.id) add('identity-mismatch', '$.identity.id', 'sidecar identity.id 必须与 legacy manifest.id 一致')
if (sidecar.identity?.kind !== expectedKind) add('kind-mismatch', '$.identity.kind', `legacy ${manifest.type} 必须映射为 ${expectedKind}`)
@@ -133,6 +134,16 @@ function validateSidecarSemantics(manifest, sidecar, file) {
if (manifest.toolCount !== undefined && sidecar.spec?.toolCount !== manifest.toolCount) add('spec-mismatch', '$.spec.toolCount', 'toolCount 必须与 legacy 一致')
}
if (manifest.type === 'native-app') {
for (const field of ['stewardship', 'availability', 'redistribution', 'listingMaintainer', 'upstreamMaintainer', 'branding', 'compliance']) {
if (!isDeepStrictEqual(manifest[field], sidecar.governance?.[field])) add('native-governance-mismatch', '$.governance.' + field, '原生应用 manifest 与 sidecar 的治理事实必须一致')
}
if (manifest.license !== sidecar.governance?.license?.value) add('native-license-mismatch', '$.governance.license', '原生应用许可证必须一致')
if (!isDeepStrictEqual(manifest.source, sidecar.provenance?.content)) add('native-source-mismatch', '$.provenance.content', '制品 URL/ref/SHA-256 必须一致')
if (manifest.requiredClientVersion !== sidecar.compatibility?.requiredClientVersion) add('native-client-mismatch', '$.compatibility.requiredClientVersion', '最低客户端版本必须一致')
if (!isDeepStrictEqual(manifest.placementPolicy, sidecar.spec?.placementPolicy)) add('native-placement-mismatch', '$.spec.placementPolicy', '应用放置策略必须一致')
}
const source = sidecar.provenance?.content
if (source?.path !== undefined && !safeRelativePath(source.path)) add('unsafe-evidence-path', '$.provenance.content.path', '内容 path 必须是安全相对路径')
const compliance = sidecar.governance?.compliance
@@ -213,10 +224,11 @@ export function validateRegistry(repoRoot, options = {}) {
if (!entry) continue
counts.totalEntries += 1
if (entry.type === 'docker-app') counts.dockerApps += 1
if (entry.type === 'native-app') counts.nativeApps = (counts.nativeApps ?? 0) + 1
if (entry.type === 'mcp') counts.mcpServices += 1
if (entry.type === 'http-api') counts.httpApis += 1
if (entry.type === 'external-integration') counts.externalIntegrations += 1
const effectiveEntrySchema = entry.type === 'external-integration' ? externalEntrySchema : entrySchema
const effectiveEntrySchema = ['external-integration', 'native-app'].includes(entry.type) ? externalEntrySchema : entrySchema
if (effectiveEntrySchema) diagnostics.push(...schemaDiagnostics(entry, effectiveEntrySchema, manifestRelative))
if (entry.id !== basename(entryDir)) diagnostics.push(diagnostic('error', 'directory-id-mismatch', manifestRelative, '$.id', 'manifest.id 必须与 entries/<id> 目录名一致'))
if (seenIds.has(entry.id)) diagnostics.push(diagnostic('error', 'duplicate-id', manifestRelative, '$.id', 'Registry 条目 ID 重复'))
@@ -239,9 +251,10 @@ export function validateRegistry(repoRoot, options = {}) {
}
if (rootManifest?.stats) {
for (const key of ['totalEntries', 'dockerApps', 'mcpServices', 'httpApis', 'externalIntegrations']) {
for (const key of ['totalEntries', 'dockerApps', 'nativeApps', 'mcpServices', 'httpApis', 'externalIntegrations']) {
if (key === 'nativeApps' && rootManifest.stats[key] === undefined && !counts.nativeApps) continue
if (rootManifest.stats[key] === undefined && key === 'externalIntegrations' && counts[key] === 0) continue
if (rootManifest.stats[key] !== counts[key]) diagnostics.push(diagnostic('error', 'stats-mismatch', 'manifest.json', `$.stats.${key}`, `声明 ${rootManifest.stats[key]},实际 ${counts[key]}`))
if (rootManifest.stats[key] !== (key === 'nativeApps' ? (counts.nativeApps ?? 0) : counts[key])) diagnostics.push(diagnostic('error', 'stats-mismatch', 'manifest.json', `$.stats.${key}`, `声明 ${rootManifest.stats[key]},实际 ${counts[key]}`))
}
}

View File

@@ -197,18 +197,20 @@ function codes(report) {
return report.errors.map((item) => item.code)
}
test('当前 v4 条目保持可读,21 个 App/Service 具备 sidecar', () => {
test('当前 v4.1 条目保持可读,原生应用与既有 App/Service 具备 sidecar', () => {
const report = validateRegistry(repoRoot)
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
assert.deepEqual({
totalEntries: report.counts.totalEntries,
dockerApps: report.counts.dockerApps,
nativeApps: report.counts.nativeApps,
mcpServices: report.counts.mcpServices,
httpApis: report.counts.httpApis,
externalIntegrations: report.counts.externalIntegrations,
}, {
totalEntries: 22,
totalEntries: 23,
dockerApps: 8,
nativeApps: 1,
mcpServices: 8,
httpApis: 5,
externalIntegrations: 1,

View File

@@ -0,0 +1,63 @@
import assert from 'node:assert/strict'
import { test, afterEach } from 'node:test'
import { cpSync, readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import { validateRegistry } from './catalog/validator.mjs'
const source = join(dirname(fileURLToPath(import.meta.url)), '..')
const temporary = []
afterEach(() => { for (const root of temporary.splice(0)) rmSync(root, { recursive: true, force: true }) })
function fixture() {
const root = mkdtempSync(join(tmpdir(), 'registry-native-'))
temporary.push(root)
cpSync(join(source, 'schemas'), join(root, 'schemas'), { recursive: true })
cpSync(join(source, 'entries/desirecore-control'), join(root, 'entries/desirecore-control'), { recursive: true })
const manifest = JSON.parse(readFileSync(join(source, 'manifest.json'), 'utf8'))
manifest.stats = { totalEntries: 1, dockerApps: 0, nativeApps: 1, mcpServices: 0, httpApis: 0, externalIntegrations: 0 }
writeFileSync(join(root, 'manifest.json'), JSON.stringify(manifest))
writeFileSync(join(root, 'SCHEMA_VERSION'), '4.1.0\n')
return root
}
function mutate(root, file, change) {
const path = join(root, file)
const data = JSON.parse(readFileSync(path, 'utf8'))
change(data)
writeFileSync(path, JSON.stringify(data))
}
const entry = 'entries/desirecore-control/'
test('真实原生应用条目是 App,固定 1.4.0 制品,不包含内部 MCP 配置', () => {
const root = fixture()
const report = validateRegistry(root)
assert.equal(report.ok, true, JSON.stringify(report.errors))
assert.equal(report.counts.nativeApps, 1)
assert.equal(report.counts.mcpServices, 0)
const manifest = JSON.parse(readFileSync(join(root, entry, 'manifest.json'), 'utf8'))
assert.equal(manifest.type, 'native-app')
assert.equal(manifest.install.method, 'native-node')
assert.equal(manifest.install.requirements.docker, false)
assert.equal(manifest.version, '1.4.0')
assert.equal(manifest.connection, undefined)
assert.equal(manifest.exposes, undefined)
})
for (const field of ['connection', 'exposes', 'toolCount', 'capabilities']) test('拒绝原生应用内部服务字段 ' + field, () => {
const root = fixture()
mutate(root, entry + 'manifest.json', data => { data[field] = field === 'toolCount' ? 5 : field === 'connection' ? { transport: 'stdio', command: 'node' } : [] })
assert.equal(validateRegistry(root).ok, false)
})
test('摘要、最低客户端版本和统计必须与目录事实一致', () => {
for (const change of [data => { data.provenance.content.sha256 = '0'.repeat(64) }, data => { data.compatibility.requiredClientVersion = '0.0.0' }]) {
const root = fixture()
mutate(root, entry + 'catalog-metadata.v1.json', change)
assert.equal(validateRegistry(root).ok, false)
}
const root = fixture()
mutate(root, 'manifest.json', data => { data.stats.nativeApps = 0 })
assert.equal(validateRegistry(root).ok, false)
})
test('字段顺序不是治理身份,值相同时允许序列化重排', () => {
const root = fixture()
mutate(root, entry + 'manifest.json', data => { data.source = Object.fromEntries(Object.entries(data.source).reverse()) })
assert.equal(validateRegistry(root).ok, true)
})

View File

@@ -348,6 +348,7 @@ if (rootManifest) {
const expectedStats = {
totalEntries: manifests.length,
dockerApps: manifests.filter((entry) => entry.type === 'docker-app').length,
...(manifests.some((entry) => entry.type === 'native-app') || rootManifest.stats?.nativeApps !== undefined ? { nativeApps: manifests.filter((entry) => entry.type === 'native-app').length } : {}),
mcpServices: manifests.filter((entry) => entry.type === 'mcp').length,
httpApis: manifests.filter((entry) => entry.type === 'http-api').length,
externalIntegrations: manifests.filter((entry) => entry.type === 'external-integration').length,

View File

@@ -47,7 +47,8 @@ test('accepts the checked-in Registry fixture', async () => {
try {
const result = runValidator(root)
assert.equal(result.status, 0, result.stderr)
assert.match(result.stdout, /Registry validation passed: 22 entries/)
const manifest = await readJson(join(root, 'manifest.json'))
assert.ok(result.stdout.includes(`Registry validation passed: ${manifest.stats.totalEntries} entries`))
} finally {
await rm(root, { recursive: true, force: true })
}