@webskill/sdk 0.21.0 → 0.23.0

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.
Files changed (135) hide show
  1. package/dist/abnfDiagram-O67JEVCF-DtYFjfqO.js +109 -0
  2. package/dist/agent.d.ts +3 -3
  3. package/dist/agent.js +1174 -169
  4. package/dist/{approval-DwN2o2QG.js → approval-7CuZS3Z4.js} +104 -18
  5. package/dist/arc-8U_6NQWm.js +130 -0
  6. package/dist/architecture-7GRP2DOG-RTw1wpAq.js +4 -0
  7. package/dist/architectureDiagram-NJMV4G6O-XmmXrJAK.js +7473 -0
  8. package/dist/array-DQfpQNY8.js +8 -0
  9. package/dist/blockDiagram-BEXU5L5S-D4yjnCD1.js +2582 -0
  10. package/dist/browser.d.ts +455 -49
  11. package/dist/browser.js +2134 -138
  12. package/dist/c4Diagram-YGBWAQC7-BLmA_JSd.js +3668 -0
  13. package/dist/channel-CZxxLrG7.js +9 -0
  14. package/dist/chunk-2Q5K7J3B-Ck03gkFR.js +21 -0
  15. package/dist/chunk-3FUC2YCW-tr6jmbQf.js +530 -0
  16. package/dist/chunk-5DYCD2WN-Bt97YGTd.js +58 -0
  17. package/dist/chunk-5VM5RSS4-BBxa48HL.js +21 -0
  18. package/dist/chunk-6AEJRKK7-JXK89I-O.js +14 -0
  19. package/dist/chunk-742MDFTN-CPmgCGwC.js +466 -0
  20. package/dist/chunk-7INBJB4K-C9jq9o66.js +6552 -0
  21. package/dist/chunk-7M6MHVWA-BrKCWcR1.js +4894 -0
  22. package/dist/chunk-7PRAP22T-BHED6GVs.js +95 -0
  23. package/dist/chunk-DUW6YSOI-DoZj78PO.js +19 -0
  24. package/dist/chunk-FOHPRMQF-Dkbp819z.js +27189 -0
  25. package/dist/chunk-GNY47TPC-Bpvq1T49.js +87 -0
  26. package/dist/chunk-GTNCS2PH-CLmh4I2c.js +686 -0
  27. package/dist/chunk-GWA4HPMP-B6vN3fkL.js +95 -0
  28. package/dist/chunk-J5ZVWO5B-Bi_Pcobw.js +26 -0
  29. package/dist/chunk-JWPE2WC7-D1rC79GS.js +12 -0
  30. package/dist/chunk-LNGE3PJU-D5OIoe3Z.js +2582 -0
  31. package/dist/chunk-MBY4JIJT-C34L7a4f.js +2276 -0
  32. package/dist/chunk-NETBCI7D-Cg84k8N8.js +321 -0
  33. package/dist/chunk-O7XYJQB3-B6K0KOSL.js +8846 -0
  34. package/dist/chunk-UA2S7LBM-DxHM29_K.js +612 -0
  35. package/dist/chunk-WEXAMYUT-Ba6jFWhB.js +33 -0
  36. package/dist/chunk-XXDRQBXY-DPbSy4kg.js +12 -0
  37. package/dist/chunk-Y2CYZVJY-U3QOMqrW.js +15 -0
  38. package/dist/chunk-Z7XXMR3K-oLg1h8F3.js +1038 -0
  39. package/dist/chunk-ZIGJFQKS-DqEFBuHR.js +2215 -0
  40. package/dist/classDiagram-v2-NBCMYWYE-BqnOWIUt.js +3845 -0
  41. package/dist/cose-bilkent-JH36ORCC-C0dTAHVK.js +4270 -0
  42. package/dist/cynefin-OW5HDTMX-B2QvgEQj.js +4 -0
  43. package/dist/cynefinDiagram-VND7K2PF-yAWc6Z3W.js +479 -0
  44. package/dist/cytoscape.esm-C64mRh9B.js +28325 -0
  45. package/dist/dagre-6A5THRUB-D0w2W27v.js +4816 -0
  46. package/dist/defaultLocale-B_EoOkSF.js +249 -0
  47. package/dist/diagram-22UHCM2B-DvgzhUzP.js +6115 -0
  48. package/dist/diagram-3UASUU5V-DWgUB-ef.js +795 -0
  49. package/dist/diagram-ATOU4E4O-P1olqwIs.js +553 -0
  50. package/dist/diagram-CDSNMT55-BUlZuLUe.js +402 -0
  51. package/dist/diagram-MLGK6HIB-6w5qmkGt.js +186 -0
  52. package/dist/diagram-MPIPVDR6-DEFiSo8o.js +289 -0
  53. package/dist/dist-UajCDt3n.js +92 -0
  54. package/dist/ebnfDiagram-ZINNZB2B-CI8liCfR.js +123 -0
  55. package/dist/elk-276RUBZZ-3SvW6OAK.js +90034 -0
  56. package/dist/erDiagram-OPXOYQCR-CPTbKhwP.js +2301 -0
  57. package/dist/eventmodeling-NTZA5JFV-BlyuqGp0.js +4 -0
  58. package/dist/flowDiagram-KWPJA3E3-CcUJ6HbV.js +18 -0
  59. package/dist/ganttDiagram-FUAMR5RP-31UjY0KK.js +3939 -0
  60. package/dist/{toolSource-C9oNuKmr.js → geometry-CRhuySKV.js} +169 -12
  61. package/dist/gitGraph-4MIJSDKK-ChdVlrk7.js +4 -0
  62. package/dist/gitGraphDiagram-X574FWY7-DKuxJymB.js +1153 -0
  63. package/dist/governance.d.ts +22 -4
  64. package/dist/governance.js +100 -10
  65. package/dist/graphlib-tk9gQYuN.js +4194 -0
  66. package/dist/{index-DHopiH_m.d.ts → index-7No55dRb.d.ts} +161 -11
  67. package/dist/{index-B8Tgmpwh.d.ts → index-ShvCB5I9.d.ts} +2 -2
  68. package/dist/{index-CYLt5u4L.d.ts → index-V76_OFj2.d.ts} +789 -21
  69. package/dist/index.d.ts +4 -4
  70. package/dist/index.js +11 -9
  71. package/dist/info-A6RAGUB7-CxRAt9S9.js +4 -0
  72. package/dist/infoDiagram-VRGFBTTK-pRspGfjZ.js +25 -0
  73. package/dist/init-CMOIWdg-.js +16 -0
  74. package/dist/ishikawaDiagram-OU5B5YK6-DKerAPAg.js +975 -0
  75. package/dist/journeyDiagram-ZHPQQLJL-C4MK-YO5.js +1227 -0
  76. package/dist/kanban-definition-PNTS6WVX-BqsZpMXx.js +1244 -0
  77. package/dist/katex-BcRRvyh_.js +26465 -0
  78. package/dist/{kind-DaKqLX2F.js → kind-Dc8x0HWz.js} +54 -7
  79. package/dist/line-LgV8iVMV.js +48 -0
  80. package/dist/linear-BWKR0Tqd.js +393 -0
  81. package/dist/linkedDocument-CZZOl9cO.js +456 -0
  82. package/dist/mcp.d.ts +2 -2
  83. package/dist/{memoryArtifactStore-D6dLeY55.js → memoryArtifactStore-Bs7glPqm.js} +14 -0
  84. package/dist/mermaid-parser.core-pYR5siSx.js +754 -0
  85. package/dist/mermaid.core-DMtwGCHy.js +1756 -0
  86. package/dist/mindmap-definition-NLK3R4M7-CXbD-myG.js +1312 -0
  87. package/dist/node.d.ts +3 -3
  88. package/dist/node.js +12 -5
  89. package/dist/{openUiLibrary-b0i7F_oZ.js → openUiLibrary-B7j3rLrm.js} +1 -1
  90. package/dist/{openUiSpecLang-CLXJEeRU.js → openUiSpecLang-BSYYnjay.js} +1 -1
  91. package/dist/ordinal-CxA6GHQS.js +84 -0
  92. package/dist/packet-AYTQ26CC-BOm08cxl.js +4 -0
  93. package/dist/path-C27Z2Bjv.js +106 -0
  94. package/dist/{pathSecurity-B1owvJAF.js → pathSecurity-CirkQwWP.js} +12 -1
  95. package/dist/pegDiagram-GJSIUBJH-BLGJOewQ.js +115 -0
  96. package/dist/pie-WAS4IAKB-Dvg7glpk.js +4 -0
  97. package/dist/pieDiagram-5QR66LMP-laoaV_PB.js +298 -0
  98. package/dist/quadrantDiagram-O4NWA36T-Db_wFBkT.js +2236 -0
  99. package/dist/radar-RG4KPBEZ-BZDaTu06.js +4 -0
  100. package/dist/railroad-74A4TZTK-C-UhjkOy.js +4 -0
  101. package/dist/railroad-abnf-HS5TGJTU-dUKVA4OS.js +4 -0
  102. package/dist/railroad-ebnf-LZEXJU2U-DzQumk18.js +4 -0
  103. package/dist/railroad-peg-WCYAUIDC-B3vvTWed.js +4 -0
  104. package/dist/railroadDiagram-XR7U4H2S-BY0sTM4X.js +89 -0
  105. package/dist/requirementDiagram-PLB6GJNP-3rkQfEhs.js +2474 -0
  106. package/dist/rough.cjs-AmDd_NQk.js +1395 -0
  107. package/dist/sankeyDiagram-IPEJSGJF-CGAJhyG2.js +3578 -0
  108. package/dist/sequenceDiagram-PO4LG4MO-9FJjFX06.js +5522 -0
  109. package/dist/sizeCapture-INFHLROL-D8PWxdOn.js +56 -0
  110. package/dist/{skill-CAJMsLod.js → skill-WSUDp6A1.js} +1 -1
  111. package/dist/{skillVersionStore-B24Jjjry-BkOk9wI_.d.ts → skillVersionStore-S4_HJ_Fl-Cvy9LqVu.d.ts} +35 -4
  112. package/dist/src-NkhI3UMl.js +2653 -0
  113. package/dist/stateDiagram-v2-GCMORJYK-B0O3wc1-.js +2715 -0
  114. package/dist/swimlanes-2SLR337P-D4EOnrpu.js +6637 -0
  115. package/dist/swimlanesDiagram-TC7HE7FX-BUx29EJ9.js +30 -0
  116. package/dist/testing.d.ts +2 -1
  117. package/dist/testing.js +1 -1
  118. package/dist/timeline-definition-EJHVYXUP-CrgiiB4C.js +1513 -0
  119. package/dist/treeView-Q6P3EWNA-C1by-Gzc.js +4 -0
  120. package/dist/treemap-WGGIJYW6-mC2NZ6ad.js +4 -0
  121. package/dist/{types-BvTV_05--BW8zDuEk.d.ts → types-CpDRZ0rA-BiZBXGm4.d.ts} +66 -8
  122. package/dist/ui-react.d.ts +3 -3
  123. package/dist/ui-react.js +3746 -96
  124. package/dist/ui-vue.d.ts +1 -1
  125. package/dist/ui.d.ts +3 -3
  126. package/dist/ui.js +2 -2
  127. package/dist/usecaseDiagram-POWQR4AR-BUt_TQOY.js +14397 -0
  128. package/dist/vennDiagram-UO4OBE2U-CwXr0wjA.js +2808 -0
  129. package/dist/wardley-WFR3VGLG-CVSYjq_S.js +4 -0
  130. package/dist/wardleyDiagram-VNRHLVJA-Cpfd47dQ.js +874 -0
  131. package/dist/{webSkillApi-Cib6G-94.js → webSkillApi-D5VMUnrv.js} +2 -2
  132. package/dist/{webskillLitCatalog-W06npwej.js → webskillLitCatalog-D5BCiMCU.js} +1 -1
  133. package/dist/xychartDiagram-PMCCYNJV-e1TeVoc6.js +2704 -0
  134. package/package.json +1 -1
  135. package/dist/linkedDocument-C0gj1sMq.js +0 -245
@@ -1,6 +1,6 @@
1
- import { S as UiBridge, U as FileSystemProvider, X as Page, Z as PageQuery, _t as SkillManagerPort, dt as SkillCatalogEntry, f as LlmMessage, l as LlmClient, pt as SkillDocument } from "./types-BvTV_05--BW8zDuEk.js";
2
- import { $ as IntegrityVerdict, ar as WebSkillRuntime, dn as SkillStateGuard, on as SkillIntegrityGuard, sn as SkillOutcomeReporter, zt as RuntimeRun } from "./index-DHopiH_m.js";
3
- import { _ as SkillState, a as AuditLog, b as SkillVersionStore, c as CandidateFile, d as CandidateSkill, f as CandidateSource, g as SKILL_VERSION_PAGE_SIZE, h as CompositeApprovalPolicy, i as AuditEvent, l as CandidatePage, m as CandidateStore, n as ApprovalDecision, o as AuditQueryFilter, p as CandidateStatus, r as ApprovalPolicy, s as CANDIDATE_PAGE_SIZE, t as AlwaysHumanApprovalPolicy, u as CandidateRisk, v as SkillVersion, x as candidateToCatalogEntry, y as SkillVersionPage } from "./skillVersionStore-B24Jjjry-BkOk9wI_.js";
1
+ import { K as FileSystemProvider, S as UiBridge, _t as SkillDocument, et as Page, f as LlmMessage, ht as SkillCatalogEntry, l as LlmClient, tt as PageQuery, xt as SkillManagerPort } from "./types-CpDRZ0rA-BiZBXGm4.js";
2
+ import { Wt as RuntimeRun, dn as SkillIntegrityGuard, fn as SkillOutcomeReporter, gn as SkillStateGuard, hr as WebSkillRuntime, tt as IntegrityVerdict } from "./index-7No55dRb.js";
3
+ import { S as candidateToCatalogEntry, _ as SKILL_VERSION_PAGE_SIZE, a as AuditLog, b as SkillVersionPage, c as CandidateBinaryRef, d as CandidateRisk, f as CandidateSkill, g as CompositeApprovalPolicy, h as CandidateStore, i as AuditEvent, l as CandidateFile, m as CandidateStatus, n as ApprovalDecision, o as AuditQueryFilter, p as CandidateSource, r as ApprovalPolicy, s as CANDIDATE_PAGE_SIZE, t as AlwaysHumanApprovalPolicy, u as CandidatePage, v as SkillState, x as SkillVersionStore, y as SkillVersion } from "./skillVersionStore-S4_HJ_Fl-Cvy9LqVu.js";
4
4
  //#region ../governance/dist/index.d.ts
5
5
  //#region src/candidate/candidateNormalizer.d.ts
6
6
  /** 剥 markdown fence 与 <think> 块、截取首尾 {};非对象 → CANDIDATE_INVALID */
@@ -10,10 +10,14 @@ declare function sanitizeCandidateName(raw: string): string;
10
10
  /**
11
11
  * 文件归一:补 SKILL.md;路径含 `..` / 以 `/` 开头 / 含 `\` → CANDIDATE_INVALID;
12
12
  * scripts/ 下必须是 .ts/.js。
13
+ *
14
+ * 路径校验对两种形态**一视同仁**(0.23.0 分册 20 FR-20.3):这套校验本来就与内容无关,
15
+ * 二进制条目走另一套校验等于给越界路径开第二道门。
13
16
  */
14
17
  declare function normalizeCandidateFiles(files: Array<{
15
18
  path: string;
16
19
  content: string;
20
+ binary?: CandidateBinaryRef;
17
21
  }>, name: string, description: string): CandidateFile[];
18
22
  /** 含脚本且 LLM 自报 low → 强制 medium 并追加"必须人工审批"原因 */
19
23
  declare function normalizeRisk(declared: unknown, files: CandidateFile[], reasons: string[]): {
@@ -53,6 +57,20 @@ interface GeneratedSkillSubmission {
53
57
  sessionId?: string;
54
58
  /** 用户确认的时刻(FR-9.7 的「用户确认事实」) */
55
59
  confirmedAt: string;
60
+ /**
61
+ * 已物化的二进制,键是 `draft.files[].path`(0.23.0 分册 20)。
62
+ * 字节由 agent 侧在用户确认之前取好——候选一旦生成就必须能脱离会话附件独立存活(FR-20.6)。
63
+ */
64
+ binaries?: ReadonlyMap<string, SubmittedBinary>;
65
+ }
66
+ /** 一份随候选提交的字节及其校验信息(0.23.0 分册 20) */
67
+ interface SubmittedBinary {
68
+ bytes: Uint8Array;
69
+ size: number;
70
+ /** 十六进制小写 */
71
+ sha256: string;
72
+ /** 上传时的原始文件名 */
73
+ sourceName: string;
56
74
  }
57
75
  interface CandidateSinkOptions {
58
76
  store: CandidateStore;
@@ -561,4 +579,4 @@ declare function createMissHook(deps: {
561
579
  */
562
580
  declare function completeText(llm: LlmClient, messages: LlmMessage[]): Promise<string>;
563
581
  //#endregion
564
- export { AUDIT_EVENT_TYPES, AUDIT_EVENT_TYPE_LIST, AUDIT_PAGE_SIZE, AlwaysHumanApprovalPolicy, type ApprovalDecision, type ApprovalPolicy, type AuditChainVerification, type AuditEvent, type AuditEventType, type AuditLog, type AuditPage, type AuditQueryFilter, CANDIDATE_PAGE_SIZE, type CandidateFile, type CandidatePage, type CandidateRisk, type CandidateSink, type CandidateSinkOptions, type CandidateSkill, type CandidateSource, type CandidateStatus, CandidateStore, CompositeApprovalPolicy, DependencyGraph, DocumentSkillExtractor, type EvaluationReport, type EvaluationResult, EvaluationRunner, type EvaluationTask, FailureAnalyzer, type FailureDiagnosis, FsAuditLog, type GeneratedSkillSubmission, LlmCandidateGenerator, POLICY_DENIAL_CODES, QUARANTINE_REASONS, type RepairOption, RepairPlanner, SCORING_WEIGHTS, SKILL_VERSION_PAGE_SIZE, type ScoreInput, SimilarityDetector, SkillScorer, type SkillState, SkillStatePolicy, type SkillVersion, type SkillVersionPage, SkillVersionStore, type SourceDocument, USER_CAUSED_CODES, candidateToCatalogEntry, completeText, createCandidateSink, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
582
+ export { AUDIT_EVENT_TYPES, AUDIT_EVENT_TYPE_LIST, AUDIT_PAGE_SIZE, AlwaysHumanApprovalPolicy, type ApprovalDecision, type ApprovalPolicy, type AuditChainVerification, type AuditEvent, type AuditEventType, type AuditLog, type AuditPage, type AuditQueryFilter, CANDIDATE_PAGE_SIZE, type CandidateBinaryRef, type CandidateFile, type CandidatePage, type CandidateRisk, type CandidateSink, type CandidateSinkOptions, type CandidateSkill, type CandidateSource, type CandidateStatus, CandidateStore, CompositeApprovalPolicy, DependencyGraph, DocumentSkillExtractor, type EvaluationReport, type EvaluationResult, EvaluationRunner, type EvaluationTask, FailureAnalyzer, type FailureDiagnosis, FsAuditLog, type GeneratedSkillSubmission, LlmCandidateGenerator, POLICY_DENIAL_CODES, QUARANTINE_REASONS, type RepairOption, RepairPlanner, SCORING_WEIGHTS, SKILL_VERSION_PAGE_SIZE, type ScoreInput, SimilarityDetector, SkillScorer, type SkillState, SkillStatePolicy, type SkillVersion, type SkillVersionPage, SkillVersionStore, type SourceDocument, type SubmittedBinary, USER_CAUSED_CODES, candidateToCatalogEntry, completeText, createCandidateSink, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
@@ -1,6 +1,6 @@
1
1
  import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
2
- import { y as isValidSkillName } from "./skill-CAJMsLod.js";
3
- import { a as atomicWriteText, t as assertSafePathSegment } from "./pathSecurity-B1owvJAF.js";
2
+ import { y as isValidSkillName } from "./skill-WSUDp6A1.js";
3
+ import { a as atomicWriteBinary, o as atomicWriteText, t as assertSafePathSegment } from "./pathSecurity-CirkQwWP.js";
4
4
  import { d as partsToText, p as textParts } from "./llm-eIQNO9tr.js";
5
5
  import { n as AUDIT_EVENT_TYPE_LIST, t as AUDIT_EVENT_TYPES } from "./eventTypes-DcTQXSV1.js";
6
6
 
@@ -36,8 +36,32 @@ const kindOf = (path) => {
36
36
  return "asset";
37
37
  };
38
38
  /**
39
+ * 二进制引用的形状校验(0.23.0 分册 20)。
40
+ *
41
+ * 这条管线的既定前提是「不信任全链路」,所以引用也要逐字段查过才收下:
42
+ * 一条字段缺失的引用会一路活到安装那一步,届时才表现为读不到字节的技能包。
43
+ * `file` 必须是单个文件名——带路径分隔符的值等于一条越出候选旁路目录的通路。
44
+ */
45
+ function readBinaryRef(raw) {
46
+ if (raw === void 0 || raw === null) return void 0;
47
+ if (typeof raw !== "object" || Array.isArray(raw)) return invalid("Candidate file \"binary\" must be an object");
48
+ const { file, size, sha256, sourceName } = raw;
49
+ if (typeof file !== "string" || file === "" || file.includes("/") || file.includes("\\") || file.includes("..")) return invalid(`Candidate binary file name is not allowed: ${JSON.stringify(file)}`);
50
+ if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return invalid(`Candidate binary "${file}" has an invalid size`);
51
+ if (typeof sha256 !== "string" || !/^[0-9a-f]{64}$/.test(sha256)) return invalid(`Candidate binary "${file}" has an invalid sha256`);
52
+ return {
53
+ file,
54
+ size,
55
+ sha256,
56
+ sourceName: typeof sourceName === "string" ? sourceName : file
57
+ };
58
+ }
59
+ /**
39
60
  * 文件归一:补 SKILL.md;路径含 `..` / 以 `/` 开头 / 含 `\` → CANDIDATE_INVALID;
40
61
  * scripts/ 下必须是 .ts/.js。
62
+ *
63
+ * 路径校验对两种形态**一视同仁**(0.23.0 分册 20 FR-20.3):这套校验本来就与内容无关,
64
+ * 二进制条目走另一套校验等于给越界路径开第二道门。
41
65
  */
42
66
  function normalizeCandidateFiles(files, name, description) {
43
67
  const out = [];
@@ -49,7 +73,8 @@ function normalizeCandidateFiles(files, name, description) {
49
73
  out.push({
50
74
  path: p,
51
75
  kind,
52
- content: String(file.content ?? "")
76
+ content: String(file.content ?? ""),
77
+ ...file.binary !== void 0 ? { binary: file.binary } : {}
53
78
  });
54
79
  }
55
80
  if (!out.some((f) => f.kind === "skill-md")) out.unshift({
@@ -77,10 +102,15 @@ function normalizeCandidate(input) {
77
102
  const description = typeof raw["description"] === "string" ? raw["description"] : "";
78
103
  if (description.trim() === "") invalid("Candidate is missing a description");
79
104
  const name = sanitizeCandidateName(typeof raw["name"] === "string" ? raw["name"] : invalid("Candidate is missing a name"));
80
- const files = normalizeCandidateFiles((Array.isArray(raw["files"]) ? raw["files"] : []).map((f) => ({
81
- path: String(f?.["path"] ?? ""),
82
- content: String(f?.["content"] ?? "")
83
- })), name, description);
105
+ const files = normalizeCandidateFiles((Array.isArray(raw["files"]) ? raw["files"] : []).map((f) => {
106
+ const record = f ?? {};
107
+ const binary = readBinaryRef(record["binary"]);
108
+ return {
109
+ path: String(record["path"] ?? ""),
110
+ content: String(record["content"] ?? ""),
111
+ ...binary !== void 0 ? { binary } : {}
112
+ };
113
+ }), name, description);
84
114
  const declaredReasons = Array.isArray(raw["riskReasons"]) ? raw["riskReasons"].map(String) : [];
85
115
  const { risk, reasons } = normalizeRisk(raw["risk"], files, declaredReasons);
86
116
  const now = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -137,6 +167,8 @@ function takeTailPage(all, options, defaultLimit, what) {
137
167
  //#endregion
138
168
  //#region ../governance/src/candidate/candidateStore.ts
139
169
  const dirOf$1 = (root) => `${root}/.webskill/candidates`;
170
+ /** 候选的二进制旁路目录(0.23.0 分册 20 §3):与元数据 JSON 同级,整目录随候选一起删 */
171
+ const candidateBinaryDir = (root, id) => `${dirOf$1(root)}/${id}.files`;
140
172
  /** 候选列表的缺省页长。缺省属于实现,不属于调用方 */
141
173
  const CANDIDATE_PAGE_SIZE = 50;
142
174
  /** 逐文件持久化的候选存储:<managedRoot>/.webskill/candidates/<id>.json */
@@ -149,11 +181,37 @@ var CandidateStore = class {
149
181
  this.#fs = deps.fs;
150
182
  this.#isSkillInstalled = deps.isSkillInstalled;
151
183
  }
152
- async save(candidate) {
184
+ /**
185
+ * 存候选。`binaries` 的键是 `CandidateFile.binary.file`,缺省即本候选全是文本。
186
+ *
187
+ * 落盘顺序是**先字节、后 JSON**(0.23.0 分册 20 §3.2):反过来的话,
188
+ * 崩溃会留下一条指向不存在字节的元数据,而那种候选要到审批安装那一步才暴露——
189
+ * 正是 FR-20.6 要避免的那类「延迟到审批才失败」。
190
+ */
191
+ async save(candidate, binaries) {
153
192
  validateCandidate(candidate);
154
193
  assertSafePathSegment(candidate.id, "candidate id");
194
+ for (const file of candidate.files) {
195
+ if (file.binary === void 0) continue;
196
+ const bytes = binaries?.get(file.binary.file);
197
+ if (bytes === void 0) {
198
+ if (await this.#fs.exists(this.#binaryPath(candidate.id, file.binary.file))) continue;
199
+ throw new WebSkillError("CANDIDATE_INVALID", `Candidate "${candidate.name}" declares binary "${file.path}" but its bytes were not provided`);
200
+ }
201
+ assertSafePathSegment(file.binary.file, "candidate binary file");
202
+ await atomicWriteBinary(this.#fs, this.#binaryPath(candidate.id, file.binary.file), bytes);
203
+ }
155
204
  await atomicWriteText(this.#fs, `${dirOf$1(this.#root)}/${candidate.id}.json`, JSON.stringify(candidate, null, 2));
156
205
  }
206
+ /** 读回候选旁路里的一份字节(安装时按原样写进技能目录,0.23.0 分册 20 FR-20.8) */
207
+ readCandidateBinary(id, file) {
208
+ assertSafePathSegment(id, "candidate id");
209
+ assertSafePathSegment(file, "candidate binary file");
210
+ return this.#fs.readBinary(this.#binaryPath(id, file));
211
+ }
212
+ #binaryPath(id, file) {
213
+ return `${candidateBinaryDir(this.#root, id)}/${file}`;
214
+ }
157
215
  async get(id) {
158
216
  assertSafePathSegment(id, "candidate id");
159
217
  const path = `${dirOf$1(this.#root)}/${id}.json`;
@@ -202,6 +260,8 @@ var CandidateStore = class {
202
260
  if (candidate.status === "approved") throw new WebSkillError("GOVERNANCE_FAILED", `Candidate "${candidate.name}" cannot be removed from status "${candidate.status}"`);
203
261
  if (candidate.status === "published" && await this.#skillStillInstalled(candidate.name)) throw new WebSkillError("GOVERNANCE_FAILED", `Candidate "${candidate.name}" is the provenance record of an installed skill and cannot be removed; uninstall the skill first`);
204
262
  await this.#fs.remove(`${dirOf$1(this.#root)}/${id}.json`);
263
+ const binaryDir = candidateBinaryDir(this.#root, id);
264
+ if (await this.#fs.exists(binaryDir)) await this.#fs.remove(binaryDir, { recursive: true });
205
265
  }
206
266
  async #skillStillInstalled(name) {
207
267
  return this.#isSkillInstalled === void 0 ? true : await this.#isSkillInstalled(name);
@@ -224,6 +284,16 @@ function candidateToCatalogEntry(candidate) {
224
284
  //#endregion
225
285
  //#region ../governance/src/candidate/candidateSink.ts
226
286
  /**
287
+ * 旁路文件名:序号在前保证唯一,原名跟在后面是为了让人在文件管理器里也认得出
288
+ * 哪个是哪个(0.23.0 分册 20 §3)。直接拿技能内路径当文件名不行——
289
+ * 那是一个平铺目录,`references/a/t.xlsx` 与 `references/b/t.xlsx` 会撞在一起。
290
+ * 权威的原名记在 `sourceName` 里,这里只保留能安全落盘的那部分。
291
+ */
292
+ const binaryFileNameOf = (index, path) => {
293
+ const safe = (path.split("/").pop() ?? "").replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+/, "");
294
+ return `${index}-${safe === "" ? "file" : safe}`;
295
+ };
296
+ /**
227
297
  * 把 agent 生成的技能草稿接进既有的候选管线(设计 02 §5)。
228
298
  *
229
299
  * 走的是与文档抽取、runtime-miss 完全相同的 `normalizeCandidate` → `CandidateStore.save`,
@@ -232,6 +302,26 @@ function candidateToCatalogEntry(candidate) {
232
302
  function createCandidateSink(options) {
233
303
  return { async submit(input) {
234
304
  const { draft } = input;
305
+ const bytes = /* @__PURE__ */ new Map();
306
+ const files = (draft.files ?? []).map((file, index) => {
307
+ const payload = input.binaries?.get(file.path);
308
+ if (payload === void 0) return {
309
+ path: file.path,
310
+ content: file.content
311
+ };
312
+ const name = binaryFileNameOf(index, file.path);
313
+ bytes.set(name, payload.bytes);
314
+ return {
315
+ path: file.path,
316
+ content: "",
317
+ binary: {
318
+ file: name,
319
+ size: payload.size,
320
+ sha256: payload.sha256,
321
+ sourceName: payload.sourceName
322
+ }
323
+ };
324
+ });
235
325
  const candidate = normalizeCandidate({
236
326
  raw: {
237
327
  name: draft.name,
@@ -239,7 +329,7 @@ function createCandidateSink(options) {
239
329
  files: [{
240
330
  path: "SKILL.md",
241
331
  content: draft.content
242
- }, ...draft.files ?? []]
332
+ }, ...files]
243
333
  },
244
334
  source: "generated",
245
335
  ...options.now ? { now: options.now } : {},
@@ -250,7 +340,7 @@ function createCandidateSink(options) {
250
340
  confirmedAt: input.confirmedAt,
251
341
  ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {}
252
342
  };
253
- await options.store.save(candidate);
343
+ await options.store.save(candidate, bytes);
254
344
  await options.audit?.append({
255
345
  type: AUDIT_EVENT_TYPES.candidateCreated,
256
346
  target: candidate.id,