@dipoleml/dcode-cli 1.2.1 → 1.2.3
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/dist/dcode.cjs +2214 -1891
- package/dist/packages/browser-use-plugin/.dcode-plugin/plugin.json +8 -8
- package/dist/packages/dcode-cua-plugin/.dcode-plugin/plugin.json +10 -0
- package/dist/packages/dcode-cua-plugin/docs/computer-use.md +470 -0
- package/dist/packages/dcode-cua-plugin/package.json +12 -0
- package/dist/packages/dcode-cua-plugin/scripts/bump-dcode-cua-producer.mjs +305 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-cua-baseline.mjs +155 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-sdk.mjs +4 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-version-coherence.mjs +478 -0
- package/dist/packages/dcode-cua-plugin/scripts/computer-use-client.mjs +1208 -0
- package/dist/packages/dcode-cua-plugin/scripts/resolve-pnpm-invocation.mjs +26 -0
- package/dist/packages/dcode-cua-plugin/scripts/sync-cache.mjs +116 -0
- package/dist/packages/dcode-cua-plugin/scripts/sync-skill-from-dcode-cua.mjs +115 -0
- package/dist/packages/dcode-cua-plugin/skills/computer-use/SKILL.md +306 -0
- package/dist/packages/node-repl-host/dist/mcp/server.js +3 -3
- package/dist/packages/node-repl-host/package.json +1 -1
- package/dist/provider/dcode-builtin.json +6748 -6748
- package/node_modules/@dcode/contracts/dist/events/event-reducer-helpers.d.ts +1 -1
- package/node_modules/@dcode/contracts/dist/events/event-reducer-helpers.js +5 -1
- package/node_modules/@dcode/contracts/dist/events/event-reducer.js +26 -5
- package/node_modules/@dcode/contracts/dist/interfaces/session.port.d.ts +5 -1
- package/node_modules/@dcode/contracts/dist/tools/bash.js +4 -1
- package/node_modules/@dcode/i18n/dist/locales/en-US.js +11 -23
- package/node_modules/@dcode/i18n/dist/locales/fr-CA.js +9 -21
- package/node_modules/@dcode/i18n/dist/types.d.ts +0 -12
- package/node_modules/@dcode/model-option-map/package.json +1 -1
- package/node_modules/@dcode/shared/package.json +1 -1
- package/node_modules/@dcode/tui/dist/app-model-ref.js +9 -2
- package/node_modules/@dcode/tui/dist/index.js +57 -65
- package/node_modules/@dcode/tui/dist/theme/defaults.js +21 -15
- package/package.json +3 -3
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CUA 版本一致性校验 —— 单一真相源 + CI gate,防止“多处版本记录漂移”。
|
|
3
|
+
//
|
|
4
|
+
// 背景:CUA 版本以前散落在多处且互不同步,极易忘改:
|
|
5
|
+
// - bundled wrapper: package.json.version / .dcode-plugin/plugin.json.version
|
|
6
|
+
// - producer 依赖锁: pnpm catalog(pnpm-workspace.yaml)里 @dcode/dcode-cua 的 #vX.Y.Z,
|
|
7
|
+
// 被 dcode-cua-plugin + services 共用(catalog: 引用)
|
|
8
|
+
// 历史上只有 skill-sync.node-test.mjs 校验 wrapper 内部一致,**完全没覆盖依赖锁**,导致
|
|
9
|
+
// wrapper 标 0.3.17、而依赖锁已 #v0.5.1 的漂移一路漏到打包。upstream.json 已不再冗余存 version。
|
|
10
|
+
//
|
|
11
|
+
// 本脚本把“producer 版本”作为唯一基准(取依赖锁里出现的版本),断言上述全部记录相等。
|
|
12
|
+
// `--check`(CI 用)发现漂移即 exit 1,把“忘改”变成构建失败而不是静默发版。
|
|
13
|
+
// 默认(无 --check)会把 wrapper、官方插件定义和实现契约对齐到依赖锁(写回),
|
|
14
|
+
// 方便 bump 后一键同步。
|
|
15
|
+
//
|
|
16
|
+
// Usage:
|
|
17
|
+
// node scripts/check-version-coherence.mjs --check # CI / pre-push gate(只读,漂移则非零退出)
|
|
18
|
+
// node scripts/check-version-coherence.mjs # 写回:把版本记录和实现契约对齐到依赖锁
|
|
19
|
+
|
|
20
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
21
|
+
import { dirname, resolve } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
|
|
24
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
const repoRoot = resolve(packageRoot, "../../../..");
|
|
26
|
+
const checkOnly = process.argv.includes("--check");
|
|
27
|
+
|
|
28
|
+
const wrapperPackageJsonPath = resolve(packageRoot, "package.json");
|
|
29
|
+
const wrapperManifestPath = resolve(packageRoot, ".dcode-plugin", "plugin.json");
|
|
30
|
+
const servicesPackageJsonPath = resolve(repoRoot, "packages/services/package.json");
|
|
31
|
+
const upstreamJsonPath = resolve(packageRoot, "upstream.json");
|
|
32
|
+
const pipelineConfigArgIndex = process.argv.indexOf("--pipeline-config");
|
|
33
|
+
const pipelineConfigPath =
|
|
34
|
+
pipelineConfigArgIndex >= 0 && process.argv[pipelineConfigArgIndex + 1]
|
|
35
|
+
? resolve(process.argv[pipelineConfigArgIndex + 1])
|
|
36
|
+
: resolve(repoRoot, ".gitlab/ci/30-build.yml");
|
|
37
|
+
const workspaceArgIndex = process.argv.indexOf("--workspace");
|
|
38
|
+
const workspaceYamlPath =
|
|
39
|
+
workspaceArgIndex >= 0 && process.argv[workspaceArgIndex + 1]
|
|
40
|
+
? resolve(process.argv[workspaceArgIndex + 1])
|
|
41
|
+
: resolve(repoRoot, "pnpm-workspace.yaml");
|
|
42
|
+
const implementationContractArgIndex = process.argv.indexOf("--implementation-contract");
|
|
43
|
+
const implementationContractPath =
|
|
44
|
+
implementationContractArgIndex >= 0 && process.argv[implementationContractArgIndex + 1]
|
|
45
|
+
? resolve(process.argv[implementationContractArgIndex + 1])
|
|
46
|
+
: resolve(repoRoot, "docs/superpowers/specs/2026-08-14-cua-final-raster-integrity-contract.md");
|
|
47
|
+
const lockfileArgIndex = process.argv.indexOf("--lockfile");
|
|
48
|
+
const lockfilePath =
|
|
49
|
+
lockfileArgIndex >= 0 && process.argv[lockfileArgIndex + 1]
|
|
50
|
+
? resolve(process.argv[lockfileArgIndex + 1])
|
|
51
|
+
: resolve(repoRoot, "pnpm-lock.yaml");
|
|
52
|
+
const runtimePackageDirArgIndex = process.argv.indexOf("--runtime-package-dir");
|
|
53
|
+
const explicitRuntimePackageDir =
|
|
54
|
+
runtimePackageDirArgIndex >= 0 && process.argv[runtimePackageDirArgIndex + 1]
|
|
55
|
+
? resolve(process.argv[runtimePackageDirArgIndex + 1])
|
|
56
|
+
: null;
|
|
57
|
+
const officialPluginDefinitionsPath = resolve(
|
|
58
|
+
repoRoot,
|
|
59
|
+
"apps/dcode-cli/packages/bootstrap/src/app/official-plugin-definitions.ts",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
/** 从 `git+https://...dcode-cua.git#vX.Y.Z` 里抽出 `X.Y.Z`;非 git 锁返回 null。 */
|
|
63
|
+
function readProducerPin(spec) {
|
|
64
|
+
if (typeof spec !== "string") return null;
|
|
65
|
+
const match = spec.match(/dcode-cua\.git#v(.+)$/);
|
|
66
|
+
return match ? match[1] : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function readJson(path) {
|
|
70
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function readInstalledRuntimePackage() {
|
|
74
|
+
const candidates = explicitRuntimePackageDir
|
|
75
|
+
? [explicitRuntimePackageDir]
|
|
76
|
+
: [
|
|
77
|
+
resolve(repoRoot, "packages/services/node_modules/@dcode/dcode-cua"),
|
|
78
|
+
resolve(repoRoot, "node_modules/@dcode/dcode-cua"),
|
|
79
|
+
];
|
|
80
|
+
for (const packageDir of candidates) {
|
|
81
|
+
try {
|
|
82
|
+
return await readJson(resolve(packageDir, "package.json"));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error?.code !== "ENOENT") throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.error(
|
|
88
|
+
`[cua-version] installed Helper runtime package is unavailable: ${candidates.join(", ")}`,
|
|
89
|
+
);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasExactKeys(value, keys) {
|
|
94
|
+
return (
|
|
95
|
+
value !== null &&
|
|
96
|
+
typeof value === "object" &&
|
|
97
|
+
!Array.isArray(value) &&
|
|
98
|
+
Object.keys(value).sort().join("\0") === [...keys].sort().join("\0")
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isCanonicalRuntimeArtifactPath(value) {
|
|
103
|
+
return (
|
|
104
|
+
typeof value === "string" &&
|
|
105
|
+
value.length > 0 &&
|
|
106
|
+
!value.includes("\\") &&
|
|
107
|
+
!value.startsWith("/") &&
|
|
108
|
+
!/^[a-z]:/iu.test(value) &&
|
|
109
|
+
!value.split("/").some((segment) => !segment || segment === "." || segment === "..")
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function assertWindowsHelperRuntimeContract(packageJson) {
|
|
114
|
+
const contract = packageJson?.dcodeCuaRuntime;
|
|
115
|
+
const windows = contract?.windows;
|
|
116
|
+
if (
|
|
117
|
+
packageJson?.name !== "@dcode/dcode-cua" ||
|
|
118
|
+
!hasExactKeys(contract, ["schema", "windows"]) ||
|
|
119
|
+
contract.schema !== 1 ||
|
|
120
|
+
!hasExactKeys(windows, ["entry", "nativeAddon"]) ||
|
|
121
|
+
!isCanonicalRuntimeArtifactPath(windows.entry) ||
|
|
122
|
+
!isCanonicalRuntimeArtifactPath(windows.nativeAddon) ||
|
|
123
|
+
windows.entry === windows.nativeAddon
|
|
124
|
+
) {
|
|
125
|
+
// Bug 根因:同一 semver 下的旧评审 SHA 没有 Windows Helper 发布契约,
|
|
126
|
+
// 仅校验版本号会在 macOS build-app 阶段假绿,直到 Windows 准备 runtime 才失败。
|
|
127
|
+
console.error(
|
|
128
|
+
"[cua-version] installed producer is missing the Windows Helper runtime contract",
|
|
129
|
+
);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function assertFinalRasterFrameContract(packageJson) {
|
|
135
|
+
const frameContract = packageJson?.exports?.["./frame-contract"];
|
|
136
|
+
if (
|
|
137
|
+
!hasExactKeys(frameContract, ["types", "import"]) ||
|
|
138
|
+
frameContract.types !== "./dist/frame-contract/index.d.ts" ||
|
|
139
|
+
frameContract.import !== "./dist/frame-contract/index.js"
|
|
140
|
+
) {
|
|
141
|
+
// Bug 根因:v0.5.3 曾从未包含 frame-pixel 合约的旧主线创建,但 semver 更高,
|
|
142
|
+
// 版本同步脚本仍把它当成新 producer 接受,最终让产品安装包静默丢失最终栅格契约。
|
|
143
|
+
// 必须同时验证不可伪装的公开子路径,不能再用版本号替代实现存在性证明。
|
|
144
|
+
console.error("[cua-version] installed producer is missing the final-raster frame contract");
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const wrapperPackageJson = await readJson(wrapperPackageJsonPath);
|
|
150
|
+
const servicesPackageJson = await readJson(servicesPackageJsonPath);
|
|
151
|
+
const installedRuntimePackage = await readInstalledRuntimePackage();
|
|
152
|
+
assertWindowsHelperRuntimeContract(installedRuntimePackage);
|
|
153
|
+
assertFinalRasterFrameContract(installedRuntimePackage);
|
|
154
|
+
|
|
155
|
+
// 基准版本 = pnpm catalog(pnpm-workspace.yaml)里 @dcode/dcode-cua 的 #vX.Y.Z。
|
|
156
|
+
// producer 依赖现在统一走 catalog(package.json 里是 "catalog:"),单一声明处,不再两 pinner。
|
|
157
|
+
// 回退:如果 catalog 里没有,则读 package.json 里的直连 git 锁(兼容尚未迁移的状态)。
|
|
158
|
+
const workspaceYaml = await readFile(workspaceYamlPath, "utf8");
|
|
159
|
+
const lockfileYaml = await readFile(lockfilePath, "utf8");
|
|
160
|
+
const implementationContract = await readFile(implementationContractPath, "utf8");
|
|
161
|
+
function readCatalogRefs(pkg) {
|
|
162
|
+
const pattern = new RegExp(
|
|
163
|
+
`"?${pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"?\\s*:\\s*"[^"]*dcode-cua\\.git#([^"]+)"`,
|
|
164
|
+
"gu",
|
|
165
|
+
);
|
|
166
|
+
return [...workspaceYaml.matchAll(pattern)].map((match) => match[1]);
|
|
167
|
+
}
|
|
168
|
+
function readDepPin(pkgJson, pkg) {
|
|
169
|
+
return readProducerPin(pkgJson.dependencies?.[pkg]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readLockCatalogEntries(pkg) {
|
|
173
|
+
const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
174
|
+
const pattern = new RegExp(
|
|
175
|
+
`^\\s{4}['"]?${escaped}['"]?:\\r?\\n` +
|
|
176
|
+
`\\s{6}specifier:\\s*['"]?[^\\r\\n'"]*dcode-cua\\.git#([^\\s'"]+)['"]?\\r?\\n` +
|
|
177
|
+
`\\s{6}version:\\s*['"]?([^\\s'"]+)['"]?\\s*$`,
|
|
178
|
+
"gmu",
|
|
179
|
+
);
|
|
180
|
+
return [...lockfileYaml.matchAll(pattern)].map((match) => ({
|
|
181
|
+
ref: match[1],
|
|
182
|
+
version: match[2],
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function readLockImporterCommits(pkg) {
|
|
187
|
+
const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
188
|
+
const pattern = new RegExp(
|
|
189
|
+
`^\\s{6}['"]?${escaped}['"]?:\\r?\\n` +
|
|
190
|
+
`\\s{8}specifier:\\s*['"]?catalog:['"]?\\r?\\n` +
|
|
191
|
+
`\\s{8}version:\\s*['"]?(?:@dcode/dcode-cua@)?git\\+https:\\/\\/` +
|
|
192
|
+
`[^\\r\\n]+dcode-cua\\.git#([0-9a-f]{40})['"]?\\s*$`,
|
|
193
|
+
"gmu",
|
|
194
|
+
);
|
|
195
|
+
return [...lockfileYaml.matchAll(pattern)].map((match) => match[1]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readLockProducerResolutionEntries(resolvedRef) {
|
|
199
|
+
const packagesSection = lockfileYaml.match(/^packages:\r?\n([\s\S]*?)(?=^snapshots:)/mu)?.[1];
|
|
200
|
+
if (!packagesSection) return [];
|
|
201
|
+
const escapedRef = resolvedRef.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
202
|
+
const pattern = new RegExp(
|
|
203
|
+
`^\\s{2}['"]?@dcode/dcode-cua@git\\+https:\\/\\/[^\\r\\n]+` +
|
|
204
|
+
`dcode-cua\\.git#${escapedRef}['"]?:\\r?\\n` +
|
|
205
|
+
`((?:\\s{4}[^\\r\\n]*(?:\\r?\\n|$))*)`,
|
|
206
|
+
"gmu",
|
|
207
|
+
);
|
|
208
|
+
return [...packagesSection.matchAll(pattern)].map((match) => ({
|
|
209
|
+
commits: [
|
|
210
|
+
...match[1].matchAll(
|
|
211
|
+
/^\s{4}resolution:\s*\{[^\r\n}]*\bcommit:\s*([0-9a-f]{40})\b[^\r\n}]*\}/gmu,
|
|
212
|
+
),
|
|
213
|
+
].map((resolutionMatch) => resolutionMatch[1]),
|
|
214
|
+
versions: [...match[1].matchAll(/^\s{4}version:\s*['"]?([^\s'"]+)['"]?\s*$/gmu)].map(
|
|
215
|
+
(versionMatch) => versionMatch[1],
|
|
216
|
+
),
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const catalogCuaRefs = readCatalogRefs("@dcode/dcode-cua");
|
|
221
|
+
const hasCatalog = catalogCuaRefs.length > 0;
|
|
222
|
+
if (hasCatalog && catalogCuaRefs.length !== 1) {
|
|
223
|
+
console.error(
|
|
224
|
+
`[cua-version] workspace catalog must contain exactly one canonical producer ref: ` +
|
|
225
|
+
`${catalogCuaRefs.join(",") || "<missing>"}`,
|
|
226
|
+
);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const catalogCuaRef = catalogCuaRefs[0] ?? null;
|
|
230
|
+
// 测试包可精确锁到评审提交 SHA;这种 ref 不携带 semver,因此版本基准必须读取
|
|
231
|
+
// lockfile 的 catalog resolution。不能回退到 wrapper 自己,否则 wrapper、manifest
|
|
232
|
+
// 和官方定义一起写错时门禁仍会自洽通过。
|
|
233
|
+
const catalogRef = catalogCuaRef;
|
|
234
|
+
const lockCuaCatalogEntries = readLockCatalogEntries("@dcode/dcode-cua");
|
|
235
|
+
if (
|
|
236
|
+
catalogRef &&
|
|
237
|
+
(lockCuaCatalogEntries.length !== 1 || lockCuaCatalogEntries[0]?.ref !== catalogRef)
|
|
238
|
+
) {
|
|
239
|
+
console.error(
|
|
240
|
+
`[cua-version] lockfile catalog must contain one canonical producer entry: ` +
|
|
241
|
+
`${lockCuaCatalogEntries.map((entry) => entry.ref).join(",") || "<missing>"} ` +
|
|
242
|
+
`(expected ${catalogRef})`,
|
|
243
|
+
);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
const lockCuaVersion = lockCuaCatalogEntries[0]?.version ?? null;
|
|
247
|
+
if (catalogRef && !lockCuaVersion) {
|
|
248
|
+
console.error("[cua-version] lockfile canonical producer version is missing");
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
if (catalogRef?.startsWith("v") && lockCuaVersion !== catalogRef.slice(1)) {
|
|
252
|
+
console.error(
|
|
253
|
+
`[cua-version] tag catalog must resolve to its matching package version: ` +
|
|
254
|
+
`${catalogRef} != ${lockCuaVersion}`,
|
|
255
|
+
);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
const lockCuaImporterCommits = readLockImporterCommits("@dcode/dcode-cua");
|
|
259
|
+
const importerCommitValues = [...new Set(lockCuaImporterCommits)];
|
|
260
|
+
if (catalogRef && (lockCuaImporterCommits.length === 0 || importerCommitValues.length !== 1)) {
|
|
261
|
+
console.error(
|
|
262
|
+
`[cua-version] lockfile catalog importers must resolve to one producer commit: ` +
|
|
263
|
+
`${lockCuaImporterCommits.join(",") || "<missing>"}`,
|
|
264
|
+
);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
const resolvedProducerCommit = importerCommitValues[0] ?? null;
|
|
268
|
+
const lockProducerResolutionEntries = resolvedProducerCommit
|
|
269
|
+
? readLockProducerResolutionEntries(resolvedProducerCommit)
|
|
270
|
+
: [];
|
|
271
|
+
const producerResolutionEntry = lockProducerResolutionEntries[0];
|
|
272
|
+
if (catalogRef && lockProducerResolutionEntries.length !== 1) {
|
|
273
|
+
console.error(
|
|
274
|
+
`[cua-version] expected exactly one lockfile producer resolution for ` +
|
|
275
|
+
`${resolvedProducerCommit ?? "<missing>"}; found ${lockProducerResolutionEntries.length}`,
|
|
276
|
+
);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
if (
|
|
280
|
+
catalogRef &&
|
|
281
|
+
(producerResolutionEntry?.commits.length !== 1 ||
|
|
282
|
+
producerResolutionEntry.commits[0] !== resolvedProducerCommit ||
|
|
283
|
+
producerResolutionEntry.versions.length !== 1 ||
|
|
284
|
+
producerResolutionEntry.versions[0] !== lockCuaVersion)
|
|
285
|
+
) {
|
|
286
|
+
console.error(
|
|
287
|
+
`[cua-version] lockfile producer resolution metadata must match catalog: ` +
|
|
288
|
+
`commit=${producerResolutionEntry?.commits.join(",") || "<missing>"}, ` +
|
|
289
|
+
`version=${producerResolutionEntry?.versions.join(",") || "<missing>"}, ` +
|
|
290
|
+
`expected=${resolvedProducerCommit}@${lockCuaVersion}`,
|
|
291
|
+
);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
const contractProducerRef = implementationContract.match(
|
|
295
|
+
/producer 实现:`dcode-cua@([^`]+)`/u,
|
|
296
|
+
)?.[1];
|
|
297
|
+
if (catalogRef && /^[0-9a-f]{40}$/u.test(catalogRef) && catalogRef !== resolvedProducerCommit) {
|
|
298
|
+
console.error(
|
|
299
|
+
`[cua-version] SHA catalog must resolve to itself: ${catalogRef} != ${resolvedProducerCommit}`,
|
|
300
|
+
);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
const contractExpectedProducerRef = catalogRef ? resolvedProducerCommit : null;
|
|
304
|
+
const contractProducerMismatch =
|
|
305
|
+
contractExpectedProducerRef !== null && contractProducerRef !== contractExpectedProducerRef;
|
|
306
|
+
// SG-03 根因:曾经 `.gitlab/ci/30-build.yml` 的 baseline bridge 是 GitLab trigger
|
|
307
|
+
// job,无法在运行时读取 upstream.json,ref/SHA 只能是 YAML 字面量,成为第二事实源
|
|
308
|
+
// (re-pin 漏改要到下游三平台基线才暴露)。bridge 随发布信任链剥离后 CI 只剩
|
|
309
|
+
// 运行时派生(从 pnpm-workspace.yaml 读取)。这里保留字面量守卫:一旦未来任何
|
|
310
|
+
// job 重新引入字面量 pin,它必须与 upstream.json(第一事实源)一致,否则在
|
|
311
|
+
// build:desktop:app 的 cua-version-check 就失败,防止第二事实源静默复活。
|
|
312
|
+
const upstreamJson = await readJson(upstreamJsonPath);
|
|
313
|
+
const pipelineConfigYaml = await readFile(pipelineConfigPath, "utf8");
|
|
314
|
+
function readPipelineProducerLiteral(key) {
|
|
315
|
+
const match = pipelineConfigYaml.match(new RegExp(`^\\s*${key}:\\s*"([^"\\s]+)"`, "mu"));
|
|
316
|
+
return match?.[1] ?? null;
|
|
317
|
+
}
|
|
318
|
+
const ciProducerRef = readPipelineProducerLiteral("DCODE_CUA_PRODUCER_REF");
|
|
319
|
+
const ciProducerSha = readPipelineProducerLiteral("DCODE_CUA_PRODUCER_SHA");
|
|
320
|
+
// baseline bridge 已剥离后 CI 不再持有 ref/SHA 字面量(运行时从 pnpm-workspace.yaml
|
|
321
|
+
// 派生)。校验只在字面量存在时生效:一旦未来任何 job 重新引入字面量 pin,
|
|
322
|
+
// 它必须与 upstream.json 一致,防止第二事实源静默复活。
|
|
323
|
+
const ciLiteralMismatch =
|
|
324
|
+
(ciProducerSha !== null || ciProducerRef !== null) &&
|
|
325
|
+
(upstreamJson.commit !== ciProducerSha || upstreamJson.ref !== ciProducerRef);
|
|
326
|
+
// 2026-08-20:pin bump(catalog/lock/契约)与 Skill/provenance 同步曾是两条可拆开的
|
|
327
|
+
// 手工链路,出现过 catalog=e38999b7 / upstream.json=0bf36086 的漂移(skill-sync 测试
|
|
328
|
+
// 有断言但挂在重测试道)。upstream.json 是 provenance 第一事实源:commit/ref 必须与
|
|
329
|
+
// 依赖锁解析出的 producer commit 严格相等。--check 与写回模式都硬失败——写回只对齐
|
|
330
|
+
// 版本记录,provenance 只能由 bump-dcode-cua-producer.mjs 原子更新。
|
|
331
|
+
if (resolvedProducerCommit) {
|
|
332
|
+
if (upstreamJson.commit !== resolvedProducerCommit) {
|
|
333
|
+
console.error(
|
|
334
|
+
`[cua-version] upstream.json provenance 与依赖锁漂移:upstream=${upstreamJson.commit},` +
|
|
335
|
+
`lock=${resolvedProducerCommit}。修复:node apps/dcode-cli/packages/dcode-cua-plugin/scripts/bump-dcode-cua-producer.mjs`,
|
|
336
|
+
);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
if (upstreamJson.ref !== upstreamJson.commit) {
|
|
340
|
+
console.error(
|
|
341
|
+
`[cua-version] upstream.json.ref 必须等于不可变 commit(ref=${upstreamJson.ref},` +
|
|
342
|
+
`commit=${upstreamJson.commit})`,
|
|
343
|
+
);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const catalogVersion = catalogRef?.startsWith("v")
|
|
348
|
+
? catalogRef.slice(1)
|
|
349
|
+
: catalogRef
|
|
350
|
+
? lockCuaVersion
|
|
351
|
+
: null;
|
|
352
|
+
const catalogCua = catalogCuaRef ? catalogVersion : null;
|
|
353
|
+
const producerPins = {};
|
|
354
|
+
if (catalogCua) {
|
|
355
|
+
producerPins["catalog @dcode/dcode-cua"] = catalogCua;
|
|
356
|
+
// catalog 模式下 package.json 依赖必须是 "catalog:",否则有人手改回了直连锁。
|
|
357
|
+
for (const [where, pkg, pkgJson] of [
|
|
358
|
+
["dcode-cua-plugin/package.json", "@dcode/dcode-cua", wrapperPackageJson],
|
|
359
|
+
["services/package.json", "@dcode/dcode-cua", servicesPackageJson],
|
|
360
|
+
]) {
|
|
361
|
+
const spec = pkgJson.dependencies?.[pkg];
|
|
362
|
+
if (spec && spec !== "catalog:") {
|
|
363
|
+
producerPins[`${where} ${pkg} (应为 catalog:)`] = readProducerPin(spec) ?? `<${spec}>`;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
// 回退:直连 git 锁模式。
|
|
368
|
+
producerPins["dcode-cua-plugin/package.json @dcode/dcode-cua"] = readDepPin(
|
|
369
|
+
wrapperPackageJson,
|
|
370
|
+
"@dcode/dcode-cua",
|
|
371
|
+
);
|
|
372
|
+
producerPins["services/package.json @dcode/dcode-cua"] = readDepPin(
|
|
373
|
+
servicesPackageJson,
|
|
374
|
+
"@dcode/dcode-cua",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
const pinValues = [...new Set(Object.values(producerPins))];
|
|
378
|
+
if (pinValues.length !== 1 || !pinValues[0]) {
|
|
379
|
+
console.error("[cua-version] producer 版本记录自相矛盾——catalog / 依赖锁必须指向同一个版本:");
|
|
380
|
+
for (const [where, v] of Object.entries(producerPins)) {
|
|
381
|
+
console.error(` ${where}: ${v ?? "<未声明>"}`);
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
const expected = pinValues[0];
|
|
386
|
+
|
|
387
|
+
function readOfficialPluginVersion(source) {
|
|
388
|
+
const block = source.match(/name:\s*"computer-use"[\s\S]*?version:\s*"([^"]+)"[\s\S]*?\n\s*\},/u);
|
|
389
|
+
return block?.[1] ?? null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const officialPluginDefinitions = await readFile(officialPluginDefinitionsPath, "utf8");
|
|
393
|
+
|
|
394
|
+
// wrapper 两处记录 + 官方插件定义 + 依赖锁,全部必须 == expected。
|
|
395
|
+
const records = {
|
|
396
|
+
"wrapper package.json.version": wrapperPackageJson.version,
|
|
397
|
+
"wrapper .dcode-plugin/plugin.json.version": (await readJson(wrapperManifestPath)).version,
|
|
398
|
+
"official-plugin-definitions computer-use.version":
|
|
399
|
+
readOfficialPluginVersion(officialPluginDefinitions),
|
|
400
|
+
"installed producer package.json.version": installedRuntimePackage.version,
|
|
401
|
+
...producerPins,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const drifted = Object.entries(records).filter(([, v]) => v !== expected);
|
|
405
|
+
|
|
406
|
+
if (drifted.length === 0 && !contractProducerMismatch && !ciLiteralMismatch) {
|
|
407
|
+
console.log(`[cua-version] 一致:全部记录 = ${expected}`);
|
|
408
|
+
process.exit(0);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (checkOnly) {
|
|
412
|
+
console.error(
|
|
413
|
+
`[cua-version] 版本漂移(基准 = 依赖锁 producer ${expected})。CUA 版本必须全部对齐到 ${expected}:`,
|
|
414
|
+
);
|
|
415
|
+
for (const [where, v] of drifted) console.error(` ${where}: ${v}`);
|
|
416
|
+
if (contractProducerMismatch) {
|
|
417
|
+
console.error(
|
|
418
|
+
` implementation contract producer ref: ${contractProducerRef ?? "<missing>"} ` +
|
|
419
|
+
`(expected ${contractExpectedProducerRef})`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (ciLiteralMismatch) {
|
|
423
|
+
console.error(
|
|
424
|
+
` .gitlab/ci/30-build.yml baseline bridge literals: ` +
|
|
425
|
+
`DCODE_CUA_PRODUCER_SHA=${ciProducerSha ?? "<missing>"} ` +
|
|
426
|
+
`(expected ${upstreamJson.commit}), DCODE_CUA_PRODUCER_REF=${ciProducerRef ?? "<missing>"} ` +
|
|
427
|
+
`(expected ${upstreamJson.ref})`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
console.error(
|
|
431
|
+
"修复:node apps/dcode-cli/packages/dcode-cua-plugin/scripts/check-version-coherence.mjs(写回版本记录和实现契约),并确认依赖锁一致。",
|
|
432
|
+
);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 写回模式:把 wrapper 三处记录和实现契约对齐到依赖锁
|
|
437
|
+
// (依赖锁本身不动——改锁要配套 bump + tag + install)。
|
|
438
|
+
async function writeVersionField(path, version) {
|
|
439
|
+
const parsed = await readJson(path);
|
|
440
|
+
if (parsed.version === version) return false;
|
|
441
|
+
parsed.version = version;
|
|
442
|
+
await writeFile(path, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const wrote = [];
|
|
447
|
+
if (await writeVersionField(wrapperPackageJsonPath, expected)) wrote.push("package.json");
|
|
448
|
+
if (await writeVersionField(wrapperManifestPath, expected)) wrote.push(".dcode-plugin/plugin.json");
|
|
449
|
+
const officialVersionPattern =
|
|
450
|
+
/(name:\s*"computer-use"[\s\S]*?version:\s*")([^"]+)("[\s\S]*?\n\s*\},)/u;
|
|
451
|
+
if (!officialVersionPattern.test(officialPluginDefinitions)) {
|
|
452
|
+
throw new Error("[cua-version] 未找到 official-plugin-definitions 中的 computer-use version");
|
|
453
|
+
}
|
|
454
|
+
const updatedOfficialPluginDefinitions = officialPluginDefinitions.replace(
|
|
455
|
+
officialVersionPattern,
|
|
456
|
+
`$1${expected}$3`,
|
|
457
|
+
);
|
|
458
|
+
if (updatedOfficialPluginDefinitions !== officialPluginDefinitions) {
|
|
459
|
+
await writeFile(officialPluginDefinitionsPath, updatedOfficialPluginDefinitions);
|
|
460
|
+
wrote.push("official-plugin-definitions.ts");
|
|
461
|
+
}
|
|
462
|
+
if (contractProducerMismatch) {
|
|
463
|
+
const producerRefPattern = /producer 实现:`dcode-cua@([^`]+)`/u;
|
|
464
|
+
if (!producerRefPattern.test(implementationContract)) {
|
|
465
|
+
throw new Error("[cua-version] 未找到实现契约中的 producer ref");
|
|
466
|
+
}
|
|
467
|
+
await writeFile(
|
|
468
|
+
implementationContractPath,
|
|
469
|
+
implementationContract.replace(
|
|
470
|
+
producerRefPattern,
|
|
471
|
+
`producer 实现:\`dcode-cua@${contractExpectedProducerRef}\``,
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
wrote.push("implementation contract");
|
|
475
|
+
}
|
|
476
|
+
console.log(
|
|
477
|
+
`[cua-version] 已把版本记录对齐到 ${expected}(写回:${wrote.join(", ") || "无变更"})`,
|
|
478
|
+
);
|