release-skill 0.2.3 → 0.2.5
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/CONTRIBUTING.md +27 -0
- package/INSTALL.md +95 -139
- package/INSTALL.zh-CN.md +70 -121
- package/README.md +266 -913
- package/README.zh-CN.md +224 -535
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/claude/schemas/release-plan.schema.json +228 -0
- package/adapters/claude/schemas/release-project.schema.json +93 -0
- package/adapters/claude/schemas/release-run.schema.json +155 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/codex/schemas/release-plan.schema.json +228 -0
- package/adapters/codex/schemas/release-project.schema.json +93 -0
- package/adapters/codex/schemas/release-run.schema.json +155 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/kimi/schemas/release-plan.schema.json +228 -0
- package/adapters/kimi/schemas/release-project.schema.json +93 -0
- package/adapters/kimi/schemas/release-run.schema.json +155 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/workbuddy/schemas/release-plan.schema.json +228 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +155 -2
- package/bin/release-skill.bundle.mjs +19805 -17556
- package/package.json +1 -1
- package/schemas/release-plan.schema.json +228 -0
- package/schemas/release-project.schema.json +93 -0
- package/schemas/release-run.schema.json +155 -2
- package/scripts/sync-public-files.mjs +20 -9
- package/src/adapters/plugin-marketplace.mjs +1237 -403
- package/src/commands/prepare.mjs +454 -40
- package/src/commands/publish.mjs +169 -75
- package/src/commands/reconcile.mjs +92 -327
- package/src/commands/setup.mjs +148 -20
- package/src/commands/verify.mjs +454 -20
- package/src/core/baseline.mjs +8 -1
- package/src/core/checkpoints.mjs +50 -7
- package/src/core/config.mjs +15 -0
- package/src/core/errors.mjs +2 -0
- package/src/core/installation-contract.mjs +341 -0
- package/src/core/plan.mjs +158 -6
- package/src/core/skill-resource-closure.mjs +425 -0
- package/src/platforms/codebuddy.mjs +206 -280
- package/src/platforms/codex.mjs +369 -0
- package/src/platforms/kimi.mjs +191 -119
- package/src/platforms/registry.mjs +24 -6
package/src/platforms/kimi.mjs
CHANGED
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
* the installed managed copy. Missing/expired/mismatched/escaping proof
|
|
16
16
|
* fails closed, so a kimi unit can never reach VERIFIED without it.
|
|
17
17
|
*
|
|
18
|
+
* 统一人工结果:收据仅需 platform, version, planDigest,
|
|
19
|
+
* result(passed|failed), actor, confirmedAt 和可选 note。
|
|
20
|
+
* 不再要求 consumer, plugin, conclusion, confirmedBy、隔离 HOME、
|
|
21
|
+
* 安装路径证明、载荷摘要手填或 24 小时过期。
|
|
22
|
+
*
|
|
18
23
|
* This module is the kimi half of the platform registry's strategy table
|
|
19
24
|
* (registry.mjs references these functions); the plugin-marketplace adapter
|
|
20
25
|
* consumes the attestation path from here. The shared adapter primitives
|
|
@@ -29,7 +34,7 @@
|
|
|
29
34
|
* @module platforms/kimi
|
|
30
35
|
*/
|
|
31
36
|
|
|
32
|
-
import { readFile
|
|
37
|
+
import { readFile } from 'node:fs/promises';
|
|
33
38
|
import { join, resolve, relative, isAbsolute } from 'node:path';
|
|
34
39
|
|
|
35
40
|
import {
|
|
@@ -47,10 +52,6 @@ import { canonicalJson } from '../core/digest.mjs';
|
|
|
47
52
|
export const KIMI_REQUIREMENT_FILE = 'release-skill-kimi-manual-install.json';
|
|
48
53
|
/** Structured human attestation consumed by kimi observe. */
|
|
49
54
|
export const KIMI_ATTESTATION_FILE = 'release-skill-kimi-attestation.json';
|
|
50
|
-
/** Kimi Code managed install layout: $KIMI_CODE_HOME/plugins/managed/<id>/. */
|
|
51
|
-
export const KIMI_MANAGED_SUBPATH = join('plugins', 'managed');
|
|
52
|
-
/** Maximum attestation validity window (mirrors the 24h approval expiry). */
|
|
53
|
-
export const KIMI_MAX_ATTESTATION_VALIDITY_MS = 24 * 60 * 60 * 1000;
|
|
54
55
|
|
|
55
56
|
/** 64-char lowercase hex plan/payload digest pattern. */
|
|
56
57
|
export const HEX_DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
@@ -184,21 +185,28 @@ function buildKimiInstallUrl(repo, ref) {
|
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
/**
|
|
187
|
-
*
|
|
188
|
+
* 统一人工安装说明:面向 Kimi Code 的人工结果流程。
|
|
188
189
|
*
|
|
189
|
-
* @param {{installUrl:string, plugin:string, version:string, ref:string,
|
|
190
|
+
* @param {{installUrl:string, plugin:string, version:string, ref:string, attestationDir:string, requiresInstalledClosure:boolean, managedRoot:string|null}} p
|
|
190
191
|
* @returns {string[]}
|
|
191
192
|
*/
|
|
192
|
-
function buildKimiManualInstructions({
|
|
193
|
+
function buildKimiManualInstructions({
|
|
194
|
+
installUrl,
|
|
195
|
+
plugin,
|
|
196
|
+
version,
|
|
197
|
+
ref,
|
|
198
|
+
attestationDir,
|
|
199
|
+
requiresInstalledClosure,
|
|
200
|
+
managedRoot,
|
|
201
|
+
}) {
|
|
193
202
|
return [
|
|
194
|
-
`Kimi Code
|
|
195
|
-
`1) publish
|
|
196
|
-
`2)
|
|
197
|
-
`3)
|
|
198
|
-
`
|
|
199
|
-
`
|
|
200
|
-
`
|
|
201
|
-
`An install into the ordinary ~/.kimi-code is NOT acceptable proof: the attested installPath must resolve inside this requirement's isolated KIMI_CODE_HOME managed root, otherwise verification fails closed.`,
|
|
203
|
+
`Kimi Code 没有可脚本化的插件安装命令行工具;安装是手动交互步骤。`,
|
|
204
|
+
`1) publish 完成所有远端写入后进入 PUBLISHED 状态(自动化 Git 分支/标签、npm 和 GitHub Release 写入已完成)。此 kimi 检查点标记为需要人工安装。`,
|
|
205
|
+
`2) ${requiresInstalledClosure ? `以 KIMI_CODE_HOME="${resolve(managedRoot, '..', '..')}" 启动 Kimi Code,然后` : '在 Kimi Code 中'}运行: /plugins install ${installUrl}(锁定到冻结 ref "${ref}",版本 ${version})。确认插件 "${plugin}" 的信任提示,然后运行 /plugins reload(或 /new)。`,
|
|
206
|
+
`3) 将人工结果 JSON 写入: ${attestationDir}/${KIMI_ATTESTATION_FILE}`,
|
|
207
|
+
` 必填字段: platform="kimi", version, planDigest(冻结计划摘要), result("passed" 或 "failed"), actor(确认人), confirmedAt(ISO 8601 时间戳)${requiresInstalledClosure ? ',installPath(实际安装后的插件目录,必须位于该证明目录的 kimi-home/plugins/managed/ 内)' : ''}`,
|
|
208
|
+
` 可选字段: note(备注)`,
|
|
209
|
+
`4) 运行 release-skill reconcile(对账远端状态并跳过已完成步骤),然后 release-skill verify(从同一个计划摘要索引的权威目录读取结果,成功后 -> VERIFIED)。`,
|
|
202
210
|
];
|
|
203
211
|
}
|
|
204
212
|
|
|
@@ -236,82 +244,144 @@ export async function readKimiManifest(pluginRootReal) {
|
|
|
236
244
|
}
|
|
237
245
|
|
|
238
246
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
247
|
+
* 统一人工结果验证:验证 kimi 人工结果是否匹配冻结计划。
|
|
248
|
+
*
|
|
249
|
+
* 统一后的结果只需:
|
|
250
|
+
* - 必填:platform, version, planDigest, result(passed|failed), actor, confirmedAt
|
|
251
|
+
* - 可选:note
|
|
252
|
+
*
|
|
253
|
+
* 旧格式兼容(0.2.3 及更早):
|
|
254
|
+
* - consumer → platform (必须是 'kimi')
|
|
255
|
+
* - attestedBy → actor
|
|
256
|
+
* - attestedAt → confirmedAt
|
|
257
|
+
* - payloadDigest → 载荷绑定验证(如果存在)
|
|
241
258
|
*
|
|
242
|
-
*
|
|
243
|
-
* -
|
|
244
|
-
*
|
|
245
|
-
* -
|
|
246
|
-
* snapshot payload digest).
|
|
247
|
-
* - plugin identity, version, entry skill, repo, and frozen ref must match.
|
|
248
|
-
* - Time bounds: `attestedAt` must not be in the future, the validity window
|
|
249
|
-
* (`expiresAt - attestedAt`) must not exceed 24h, and the attestation must
|
|
250
|
-
* not be expired relative to `isoNow`.
|
|
259
|
+
* 绑定验证(任何不匹配都失败):
|
|
260
|
+
* - planDigest 绑定到真正的冻结计划摘要(boundPlanDigest)
|
|
261
|
+
* - version 静态一致性检查
|
|
262
|
+
* - result 只接受 passed 或 failed
|
|
251
263
|
*
|
|
252
|
-
* @param {object} attestation -
|
|
253
|
-
* @param {object} action -
|
|
254
|
-
* @param {string} isoNow -
|
|
255
|
-
* @param {string} boundPlanDigest -
|
|
256
|
-
* @returns {{valid:boolean, error:string|null}}
|
|
264
|
+
* @param {object} attestation - 解析后的人工结果 JSON。
|
|
265
|
+
* @param {object} action - 展开的 kimi 动作(顶层字段)。
|
|
266
|
+
* @param {string} isoNow - 当前 ISO 时间戳(保留签名兼容,不再用于过期检查)。
|
|
267
|
+
* @param {string} boundPlanDigest - 验证过的冻结计划摘要。
|
|
268
|
+
* @returns {{valid:boolean, error:string|null, normalized:object|null}}
|
|
257
269
|
*/
|
|
258
270
|
export function validateKimiAttestation(attestation, action, isoNow, boundPlanDigest) {
|
|
259
271
|
if (!attestation || typeof attestation !== 'object' || Array.isArray(attestation)) {
|
|
260
|
-
return { valid: false, error: 'kimi attestation is not an object' };
|
|
261
|
-
}
|
|
262
|
-
const requiredStrings = ['plugin', 'version', 'entrySkill', 'repo', 'ref', 'installPath', 'payloadDigest', 'planDigest', 'attestedBy', 'attestedAt', 'expiresAt'];
|
|
263
|
-
for (const field of requiredStrings) {
|
|
264
|
-
if (typeof attestation[field] !== 'string' || attestation[field].length === 0) {
|
|
265
|
-
return { valid: false, error: `kimi attestation missing required field "${field}"` };
|
|
266
|
-
}
|
|
272
|
+
return { valid: false, error: 'kimi attestation is not an object', normalized: null };
|
|
267
273
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
274
|
+
|
|
275
|
+
// 旧格式归一化:将旧字段映射到新字段
|
|
276
|
+
const normalized = { ...attestation };
|
|
277
|
+
|
|
278
|
+
// 旧格式识别:完整旧标识组(consumer, attestedBy, attestedAt)全部存在,
|
|
279
|
+
// 且新格式字段组(result, actor, confirmedAt)未混入。
|
|
280
|
+
// 不能通过随意添加一个旧标识字段绕过新格式 result 必填。
|
|
281
|
+
const hasCompleteOldMarkers = !!(normalized.consumer && normalized.attestedBy && normalized.attestedAt);
|
|
282
|
+
const hasNewFormatFields = !!(normalized.result || normalized.actor || normalized.confirmedAt);
|
|
283
|
+
const isOldFormat = hasCompleteOldMarkers && !hasNewFormatFields;
|
|
284
|
+
|
|
285
|
+
// 新格式严格要求 result 字段;仅当确认为旧格式时才允许缺省
|
|
286
|
+
if (!normalized.result && isOldFormat) {
|
|
287
|
+
normalized.result = 'passed';
|
|
273
288
|
}
|
|
274
|
-
|
|
275
|
-
|
|
289
|
+
|
|
290
|
+
// consumer → platform (旧格式使用 consumer)
|
|
291
|
+
if (!normalized.platform && normalized.consumer) {
|
|
292
|
+
normalized.platform = normalized.consumer;
|
|
276
293
|
}
|
|
277
|
-
|
|
278
|
-
|
|
294
|
+
// attestedBy → actor (旧格式使用 attestedBy)
|
|
295
|
+
if (!normalized.actor && normalized.attestedBy) {
|
|
296
|
+
normalized.actor = normalized.attestedBy;
|
|
279
297
|
}
|
|
280
|
-
|
|
281
|
-
|
|
298
|
+
// attestedAt → confirmedAt (旧格式使用 attestedAt)
|
|
299
|
+
if (!normalized.confirmedAt && normalized.attestedAt) {
|
|
300
|
+
normalized.confirmedAt = normalized.attestedAt;
|
|
282
301
|
}
|
|
283
|
-
|
|
284
|
-
|
|
302
|
+
|
|
303
|
+
// 统一必填字段
|
|
304
|
+
const requiredStrings = ['platform', 'version', 'planDigest', 'result', 'actor', 'confirmedAt'];
|
|
305
|
+
for (const field of requiredStrings) {
|
|
306
|
+
if (typeof normalized[field] !== 'string' || normalized[field].length === 0) {
|
|
307
|
+
return { valid: false, error: `kimi attestation missing required field "${field}"`, normalized: null };
|
|
308
|
+
}
|
|
285
309
|
}
|
|
286
|
-
if (
|
|
287
|
-
return { valid: false, error: `kimi attestation
|
|
310
|
+
if (normalized.platform !== 'kimi') {
|
|
311
|
+
return { valid: false, error: `kimi attestation platform "${normalized.platform}" must be "kimi"`, normalized: null };
|
|
288
312
|
}
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
return { valid: false, error: `kimi attestation
|
|
313
|
+
// result 只接受 passed 或 failed
|
|
314
|
+
if (normalized.result !== 'passed' && normalized.result !== 'failed') {
|
|
315
|
+
return { valid: false, error: `kimi attestation result "${normalized.result}" must be "passed" or "failed"`, normalized: null };
|
|
292
316
|
}
|
|
293
|
-
|
|
294
|
-
|
|
317
|
+
// planDigest 绑定验证
|
|
318
|
+
if (!HEX_DIGEST_RE.test(normalized.planDigest)) {
|
|
319
|
+
return { valid: false, error: 'kimi attestation planDigest must be a 64-char lowercase hex digest', normalized: null };
|
|
295
320
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const nowMs = Date.parse(isoNow);
|
|
299
|
-
if (!Number.isFinite(attestedMs) || !Number.isFinite(expiresMs) || !Number.isFinite(nowMs)) {
|
|
300
|
-
return { valid: false, error: 'kimi attestation attestedAt/expiresAt must be valid ISO timestamps' };
|
|
321
|
+
if (normalized.planDigest !== boundPlanDigest) {
|
|
322
|
+
return { valid: false, error: 'kimi attestation planDigest does not match the frozen plan digest', normalized: null };
|
|
301
323
|
}
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
// version 静态一致性检查
|
|
325
|
+
if (normalized.version !== action.version) {
|
|
326
|
+
return { valid: false, error: `kimi attestation version "${normalized.version}" does not match action version "${action.version}"`, normalized: null };
|
|
304
327
|
}
|
|
305
|
-
|
|
306
|
-
|
|
328
|
+
|
|
329
|
+
// 旧格式额外绑定字段校验:plugin, repo, ref, entrySkill, payloadDigest, installPath
|
|
330
|
+
// 旧格式必须包含完整旧字段组;缺任一旧必填字段失败。
|
|
331
|
+
if (isOldFormat) {
|
|
332
|
+
// plugin 必须匹配
|
|
333
|
+
if (normalized.plugin !== action.plugin) {
|
|
334
|
+
return { valid: false, error: `kimi attestation plugin "${normalized.plugin}" does not match action plugin "${action.plugin}"`, normalized: null };
|
|
335
|
+
}
|
|
336
|
+
// repo 必须匹配
|
|
337
|
+
if (normalized.repo !== action.repo) {
|
|
338
|
+
return { valid: false, error: `kimi attestation repo "${normalized.repo}" does not match action repo "${action.repo}"`, normalized: null };
|
|
339
|
+
}
|
|
340
|
+
// ref 必须匹配
|
|
341
|
+
const expectedRef = action.ref ?? `v${action.version}`;
|
|
342
|
+
if (normalized.ref !== expectedRef) {
|
|
343
|
+
return { valid: false, error: `kimi attestation ref "${normalized.ref}" does not match action ref "${expectedRef}"`, normalized: null };
|
|
344
|
+
}
|
|
345
|
+
// entrySkill 必须匹配
|
|
346
|
+
if (normalized.entrySkill !== action.entrySkill) {
|
|
347
|
+
return { valid: false, error: `kimi attestation entrySkill "${normalized.entrySkill}" does not match action entrySkill "${action.entrySkill}"`, normalized: null };
|
|
348
|
+
}
|
|
349
|
+
// payloadDigest 旧格式必填、格式合法且等于冻结动作 manifestDigest
|
|
350
|
+
if (typeof normalized.payloadDigest !== 'string' || normalized.payloadDigest.length === 0) {
|
|
351
|
+
return { valid: false, error: 'kimi attestation payloadDigest is required for old-format receipts', normalized: null };
|
|
352
|
+
}
|
|
353
|
+
// installPath 旧格式必填:旧格式用 installPath 证明实际安装
|
|
354
|
+
if (typeof normalized.installPath !== 'string' || normalized.installPath.length === 0) {
|
|
355
|
+
return { valid: false, error: 'kimi attestation installPath is required for old-format receipts', normalized: null };
|
|
356
|
+
}
|
|
357
|
+
if (!HEX_DIGEST_RE.test(normalized.payloadDigest)) {
|
|
358
|
+
return { valid: false, error: 'kimi attestation payloadDigest must be a 64-char lowercase hex digest', normalized: null };
|
|
359
|
+
}
|
|
360
|
+
if (action.manifestDigest && normalized.payloadDigest !== action.manifestDigest) {
|
|
361
|
+
return { valid: false, error: 'kimi attestation payloadDigest does not match the frozen manifest digest', normalized: null };
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
// 新格式 payloadDigest 可选,但如果存在则必须合法
|
|
365
|
+
if (normalized.payloadDigest !== undefined) {
|
|
366
|
+
if (!HEX_DIGEST_RE.test(normalized.payloadDigest)) {
|
|
367
|
+
return { valid: false, error: 'kimi attestation payloadDigest must be a 64-char lowercase hex digest when present', normalized: null };
|
|
368
|
+
}
|
|
369
|
+
if (action.manifestDigest && normalized.payloadDigest !== action.manifestDigest) {
|
|
370
|
+
return { valid: false, error: 'kimi attestation payloadDigest does not match the frozen manifest digest', normalized: null };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
307
373
|
}
|
|
308
|
-
|
|
309
|
-
|
|
374
|
+
|
|
375
|
+
// confirmedAt 必须是有效的时间戳
|
|
376
|
+
const confirmedMs = Date.parse(normalized.confirmedAt);
|
|
377
|
+
if (!Number.isFinite(confirmedMs)) {
|
|
378
|
+
return { valid: false, error: 'kimi attestation confirmedAt must be a valid ISO timestamp', normalized: null };
|
|
310
379
|
}
|
|
311
|
-
|
|
312
|
-
|
|
380
|
+
// 可选字段 note 如果存在必须是字符串
|
|
381
|
+
if (normalized.note !== undefined && typeof normalized.note !== 'string') {
|
|
382
|
+
return { valid: false, error: 'kimi attestation note must be a string when present', normalized: null };
|
|
313
383
|
}
|
|
314
|
-
return { valid: true, error: null };
|
|
384
|
+
return { valid: true, error: null, normalized };
|
|
315
385
|
}
|
|
316
386
|
|
|
317
387
|
/**
|
|
@@ -323,15 +393,8 @@ export function validateKimiAttestation(attestation, action, isoNow, boundPlanDi
|
|
|
323
393
|
* read-only verification. Without that proof the checkpoint fails closed and can
|
|
324
394
|
* never reach VERIFIED.
|
|
325
395
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* runDir consumer dir. The operator launches Kimi Code with that KIMI_CODE_HOME
|
|
329
|
-
* so the managed copy lands at `<kimiHome>/plugins/managed/<plugin>/`, a
|
|
330
|
-
* location that is identical across publish/reconcile/verify run dirs. execute
|
|
331
|
-
* creates ONLY the managed parent (`plugins/managed`), never `managed/<plugin>`
|
|
332
|
-
* (the operator's interactive install creates that). The requirement write is
|
|
333
|
-
* idempotent: an identical existing requirement is left untouched, a divergent
|
|
334
|
-
* one fails closed.
|
|
396
|
+
* 统一人工判定:不再创建隔离目录,不再要求隔离 HOME。
|
|
397
|
+
* 人工结果只需 platform, version, planDigest, result, actor, confirmedAt。
|
|
335
398
|
*
|
|
336
399
|
* Referenced from the registry as the kimi strategy.buildManualRequirement —
|
|
337
400
|
* the automatable=false manual-requirement path.
|
|
@@ -370,8 +433,8 @@ export async function executeKimiManualRequirement(action, context) {
|
|
|
370
433
|
const ref = action.ref ?? `v${action.version}`;
|
|
371
434
|
const installUrl = buildKimiInstallUrl(action.repo, ref);
|
|
372
435
|
|
|
373
|
-
// (B) Stable, plan-digest-keyed authority dir
|
|
374
|
-
//
|
|
436
|
+
// (B) Stable, plan-digest-keyed authority dir, shared across
|
|
437
|
+
// publish/reconcile/verify run dirs.
|
|
375
438
|
let attestationDir;
|
|
376
439
|
try {
|
|
377
440
|
attestationDir = kimiAuthorityDir(context, planDigest, action.plugin);
|
|
@@ -382,65 +445,78 @@ export async function executeKimiManualRequirement(action, context) {
|
|
|
382
445
|
error: dirErr.message,
|
|
383
446
|
});
|
|
384
447
|
}
|
|
385
|
-
const kimiHome = resolve(attestationDir, 'kimi-home');
|
|
386
|
-
const managedParent = resolve(kimiHome, KIMI_MANAGED_SUBPATH); // plugins/managed
|
|
387
|
-
// plugins/managed/<plugin> — created by the operator's interactive install.
|
|
388
|
-
const managedInstallRoot = resolve(managedParent, action.plugin);
|
|
389
448
|
|
|
449
|
+
const requiresInstalledClosure = Boolean(context.plan?.skillResourceClosure);
|
|
450
|
+
const managedRoot = requiresInstalledClosure
|
|
451
|
+
? resolve(attestationDir, 'kimi-home', 'plugins', 'managed')
|
|
452
|
+
: null;
|
|
390
453
|
const instructions = buildKimiManualInstructions({
|
|
391
454
|
installUrl,
|
|
392
455
|
plugin: action.plugin,
|
|
393
456
|
version: action.version,
|
|
394
457
|
ref,
|
|
395
|
-
isolatedHome: kimiHome,
|
|
396
458
|
attestationDir,
|
|
459
|
+
requiresInstalledClosure,
|
|
460
|
+
managedRoot,
|
|
397
461
|
});
|
|
398
462
|
|
|
463
|
+
// 统一 requirement 结构:不再包含隔离目录信息
|
|
399
464
|
const requirement = {
|
|
400
465
|
kind: 'kimi-manual-install-requirement',
|
|
401
|
-
|
|
466
|
+
platform: 'kimi',
|
|
402
467
|
plugin: action.plugin,
|
|
403
468
|
version: action.version,
|
|
404
|
-
entrySkill: action.entrySkill,
|
|
405
469
|
repo: action.repo,
|
|
406
470
|
ref,
|
|
471
|
+
entrySkill: action.entrySkill,
|
|
407
472
|
installUrl,
|
|
408
|
-
// (A) planDigest binds to the real frozen plan digest;
|
|
409
|
-
// expectedPayloadDigest binds separately to the snapshot payload digest.
|
|
410
473
|
planDigest,
|
|
411
|
-
expectedPayloadDigest: action.manifestDigest,
|
|
412
|
-
isolatedHome: kimiHome,
|
|
413
|
-
kimiCodeHome: kimiHome,
|
|
414
|
-
managedInstallRoot,
|
|
415
474
|
attestationDir,
|
|
416
475
|
attestationFile: KIMI_ATTESTATION_FILE,
|
|
417
476
|
attestationTemplate: {
|
|
418
|
-
|
|
419
|
-
plugin: action.plugin,
|
|
477
|
+
platform: 'kimi',
|
|
420
478
|
version: action.version,
|
|
421
|
-
entrySkill: action.entrySkill,
|
|
422
|
-
repo: action.repo,
|
|
423
|
-
ref,
|
|
424
|
-
installPath: managedInstallRoot,
|
|
425
479
|
planDigest,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
480
|
+
result: '<"passed" or "failed">',
|
|
481
|
+
actor: '<person who confirmed the install>',
|
|
482
|
+
confirmedAt: '<ISO 8601 timestamp>',
|
|
483
|
+
...(requiresInstalledClosure
|
|
484
|
+
? { installPath: resolve(managedRoot, action.plugin) }
|
|
485
|
+
: {}),
|
|
486
|
+
note: '<optional note>',
|
|
430
487
|
},
|
|
431
488
|
instructions,
|
|
432
489
|
};
|
|
433
490
|
|
|
434
|
-
//
|
|
435
|
-
|
|
491
|
+
// Ensure the authority directory exists (no isolated home creation needed).
|
|
492
|
+
const { mkdir } = await import('node:fs/promises');
|
|
436
493
|
try {
|
|
437
|
-
await mkdir(
|
|
494
|
+
await mkdir(attestationDir, { recursive: true, mode: 0o700 });
|
|
438
495
|
} catch (mkdirErr) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
496
|
+
// EEXIST is fine; other errors are fatal.
|
|
497
|
+
if (mkdirErr?.code !== 'EEXIST') {
|
|
498
|
+
return createResult({
|
|
499
|
+
actionType,
|
|
500
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
501
|
+
error: `cannot create kimi attestation directory: ${mkdirErr.message}`,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// New closure plans must scan an actual installed consumer tree. Create only
|
|
507
|
+
// the isolated KIMI_CODE_HOME container; the interactive host remains the
|
|
508
|
+
// sole owner of managed/<plugin>. Legacy plans retain the previous no-home
|
|
509
|
+
// behavior byte-for-byte.
|
|
510
|
+
if (requiresInstalledClosure) {
|
|
511
|
+
try {
|
|
512
|
+
await mkdir(managedRoot, { recursive: true, mode: 0o700 });
|
|
513
|
+
} catch (mkdirErr) {
|
|
514
|
+
return createResult({
|
|
515
|
+
actionType,
|
|
516
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
517
|
+
error: `cannot create kimi resource-closure managed root: ${mkdirErr.message}`,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
444
520
|
}
|
|
445
521
|
|
|
446
522
|
// Idempotent requirement write: an identical existing requirement is left
|
|
@@ -500,14 +576,10 @@ export async function executeKimiManualRequirement(action, context) {
|
|
|
500
576
|
consumer: 'kimi',
|
|
501
577
|
plugin: action.plugin,
|
|
502
578
|
version: action.version,
|
|
503
|
-
entrySkill: action.entrySkill,
|
|
504
|
-
repo: action.repo,
|
|
505
579
|
ref,
|
|
506
580
|
installUrl,
|
|
507
581
|
planDigest,
|
|
508
582
|
attestationDir,
|
|
509
|
-
kimiCodeHome: kimiHome,
|
|
510
|
-
managedInstallRoot,
|
|
511
583
|
instructions,
|
|
512
584
|
},
|
|
513
585
|
});
|
|
@@ -42,6 +42,10 @@ import {
|
|
|
42
42
|
executeCodeBuddyManualRequirement,
|
|
43
43
|
readCodeBuddyManifest,
|
|
44
44
|
} from './codebuddy.mjs';
|
|
45
|
+
import {
|
|
46
|
+
executeCodexManualRequirement,
|
|
47
|
+
readCodexManifest,
|
|
48
|
+
} from './codex.mjs';
|
|
45
49
|
|
|
46
50
|
// --- claude strategy -------------------------------------------------------
|
|
47
51
|
|
|
@@ -201,7 +205,7 @@ const CODEX = Object.freeze({
|
|
|
201
205
|
refStrength: 'commit-sha',
|
|
202
206
|
outputProtocol: 'structured',
|
|
203
207
|
identityEvidence: 'install-output',
|
|
204
|
-
degradationPolicy: '
|
|
208
|
+
degradationPolicy: 'human-attestation-with-fallback',
|
|
205
209
|
cli: Object.freeze({
|
|
206
210
|
binary: 'codex',
|
|
207
211
|
marketplaceAdd: (repo, ref) => ['plugin', 'marketplace', 'add', repo, '--ref', ref, '--json'],
|
|
@@ -243,8 +247,9 @@ const CODEX = Object.freeze({
|
|
|
243
247
|
extractInstallPath: codexExtractInstallPath,
|
|
244
248
|
extractListIdentity: codexExtractListIdentity,
|
|
245
249
|
crossValidateListEntry: codexCrossValidateListEntry,
|
|
246
|
-
|
|
247
|
-
|
|
250
|
+
// codex's human-attestation fallback strategy lives in ./codex.mjs
|
|
251
|
+
buildManualRequirement: executeCodexManualRequirement,
|
|
252
|
+
readManifest: readCodexManifest,
|
|
248
253
|
}),
|
|
249
254
|
});
|
|
250
255
|
|
|
@@ -267,7 +272,7 @@ const KIMI = Object.freeze({
|
|
|
267
272
|
marketplaceAddOutput: null,
|
|
268
273
|
pluginInstallOutput: null,
|
|
269
274
|
}),
|
|
270
|
-
isolationEnv: (home) => ({ HOME: home
|
|
275
|
+
isolationEnv: (home) => ({ HOME: home }),
|
|
271
276
|
// kimi never execs a CLI; its stable home is created by the
|
|
272
277
|
// manual-requirement strategy under the attestation authority.
|
|
273
278
|
isolationSubdirs: Object.freeze([]),
|
|
@@ -348,7 +353,10 @@ const CODEBUDDY = Object.freeze({
|
|
|
348
353
|
manifestPaths: Object.freeze({
|
|
349
354
|
// Single authoritative manifest (no precedence chain, unlike kimi).
|
|
350
355
|
plugin: '.codebuddy-plugin/plugin.json',
|
|
351
|
-
marketplace
|
|
356
|
+
// CodeBuddy bundled-family uses the skill-family marketplace path.
|
|
357
|
+
// This ensures bundled-family installation contracts always include a
|
|
358
|
+
// marketplace entry without requiring an explicit marketplaceIndexPath.
|
|
359
|
+
marketplace: '.claude-plugin/marketplace.json',
|
|
352
360
|
}),
|
|
353
361
|
marketplaceSourceForm: null,
|
|
354
362
|
// codebuddy installs from a unified marketplace but carries no marketplace
|
|
@@ -401,7 +409,7 @@ const VALID_INSTALL_METHODS = new Set(['structured-cli', 'interactive-only', 'hu
|
|
|
401
409
|
const VALID_REF_STRENGTHS = new Set(['commit-sha', 'name-ref', 'unfixable']);
|
|
402
410
|
const VALID_OUTPUT_PROTOCOLS = new Set(['structured', 'text', 'none']);
|
|
403
411
|
const VALID_IDENTITY_EVIDENCE = new Set(['list-record', 'install-output', 'filesystem-payload', 'human-attestation']);
|
|
404
|
-
const VALID_DEGRADATION_POLICIES = new Set(['block', 'human-attestation']);
|
|
412
|
+
const VALID_DEGRADATION_POLICIES = new Set(['block', 'human-attestation', 'human-attestation-with-fallback']);
|
|
405
413
|
|
|
406
414
|
/**
|
|
407
415
|
* Look up a platform descriptor by id.
|
|
@@ -513,6 +521,16 @@ export function assertRegistry(registry = PLATFORMS) {
|
|
|
513
521
|
if (typeof platform.strategy.extractInstallPath !== 'function') {
|
|
514
522
|
throw new Error(`platform registry: automatable platform ${label} needs strategy.extractInstallPath`);
|
|
515
523
|
}
|
|
524
|
+
// Platforms with degradationPolicy 'human-attestation-with-fallback'
|
|
525
|
+
// (codex) also need manual-requirement strategies for the fallback path.
|
|
526
|
+
if (platform.degradationPolicy === 'human-attestation-with-fallback') {
|
|
527
|
+
if (typeof platform.strategy.buildManualRequirement !== 'function') {
|
|
528
|
+
throw new Error(`platform registry: ${label} with human-attestation-with-fallback needs strategy.buildManualRequirement`);
|
|
529
|
+
}
|
|
530
|
+
if (typeof platform.strategy.readManifest !== 'function') {
|
|
531
|
+
throw new Error(`platform registry: ${label} with human-attestation-with-fallback needs strategy.readManifest`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
516
534
|
} else {
|
|
517
535
|
if (platform.cli !== null) {
|
|
518
536
|
throw new Error(`platform registry: non-automatable platform ${label} must have cli === null`);
|