@fitlab-ai/agent-infra 0.9.13 → 0.9.14

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 (280) hide show
  1. package/bin/cli.ts +26 -9
  2. package/dist/bin/cli.js +173 -157
  3. package/dist/bin/internal-cli.js +42 -21
  4. package/dist/lib/agent-client.js +5 -0
  5. package/dist/lib/internal/agent-client.js +5 -2
  6. package/dist/lib/internal/cli-route-inventory.js +152 -0
  7. package/dist/lib/internal/codex-lifecycle.js +9 -6
  8. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  9. package/dist/lib/internal/git-workflow.js +10 -6
  10. package/dist/lib/internal/platform-checks.js +16 -4
  11. package/dist/lib/internal/platform-comment.js +16 -4
  12. package/dist/lib/internal/platform-context.js +4 -1
  13. package/dist/lib/internal/platform-issue.js +12 -4
  14. package/dist/lib/internal/platform-metadata.js +88 -0
  15. package/dist/lib/internal/platform-pr-review.js +16 -3
  16. package/dist/lib/internal/platform-pr.js +24 -10
  17. package/dist/lib/internal/platform-release-notes.js +6 -3
  18. package/dist/lib/internal/platform-security.js +93 -0
  19. package/dist/lib/internal/pr-review-grade.js +14 -3
  20. package/dist/lib/internal/release-workflow.js +19 -5
  21. package/dist/lib/internal/sandbox-control.js +22 -5
  22. package/dist/lib/internal/task-activity.js +13 -4
  23. package/dist/lib/internal/task-artifact.js +186 -27
  24. package/dist/lib/internal/task-context.js +4 -1
  25. package/dist/lib/internal/task-create.js +48 -15
  26. package/dist/lib/internal/task-delivery.js +4 -1
  27. package/dist/lib/internal/task-event.js +4 -1
  28. package/dist/lib/internal/task-finalization.js +4 -1
  29. package/dist/lib/internal/task-invalidation.js +4 -1
  30. package/dist/lib/internal/task-ledger.js +12 -1
  31. package/dist/lib/internal/task-lifecycle.js +4 -1
  32. package/dist/lib/internal/task-operation-registry.js +421 -0
  33. package/dist/lib/internal/task-orchestration.js +12 -0
  34. package/dist/lib/internal/task-override.js +10 -3
  35. package/dist/lib/internal/task-qualification.js +90 -0
  36. package/dist/lib/internal/task-review.js +4 -1
  37. package/dist/lib/internal/task-short-id.js +11 -1
  38. package/dist/lib/internal/task-snapshot.js +4 -1
  39. package/dist/lib/internal/task-validate.js +7 -2
  40. package/dist/lib/internal/task-verify.js +5 -1
  41. package/dist/lib/internal/task-warning.js +8 -1
  42. package/dist/lib/platform/comment-safety.js +226 -0
  43. package/dist/lib/platform/github-provider.js +233 -6
  44. package/dist/lib/platform/issue-comments.js +146 -34
  45. package/dist/lib/platform/pr-change-report.js +6 -12
  46. package/dist/lib/platform/pr-summary.js +28 -7
  47. package/dist/lib/platform/provider-contract.d.ts +67 -1
  48. package/dist/lib/platform/provider-contract.js +6 -2
  49. package/dist/lib/platform/provider-validation.js +76 -3
  50. package/dist/lib/platform/pull-requests.js +115 -170
  51. package/dist/lib/platform/repository-metadata.js +377 -0
  52. package/dist/lib/platform/security-alerts.js +155 -0
  53. package/dist/lib/platform/verification-sync.js +33 -26
  54. package/dist/lib/process-data/index.js +7 -0
  55. package/dist/lib/run/index.js +5 -0
  56. package/dist/lib/sandbox/commands/control-status.js +22 -0
  57. package/dist/lib/sandbox/commands/create.js +32 -6
  58. package/dist/lib/sandbox/commands/ls.js +5 -0
  59. package/dist/lib/sandbox/commands/rm.js +827 -120
  60. package/dist/lib/sandbox/commands/show.js +16 -0
  61. package/dist/lib/sandbox/control/client.js +83 -9
  62. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  63. package/dist/lib/sandbox/control/container-identity.js +28 -31
  64. package/dist/lib/sandbox/control/executor.js +149 -14
  65. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  66. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  67. package/dist/lib/sandbox/control/server.js +305 -35
  68. package/dist/lib/sandbox/control/state.js +5 -3
  69. package/dist/lib/sandbox/control/task-view.js +183 -0
  70. package/dist/lib/sandbox/engines/authority.js +289 -0
  71. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  72. package/dist/lib/sandbox/index.js +5 -0
  73. package/dist/lib/sandbox/recovery.js +21 -1
  74. package/dist/lib/sandbox/workspace-view.js +2 -1
  75. package/dist/lib/sandbox/worktree-safety.js +4 -1
  76. package/dist/lib/server/index.js +8 -0
  77. package/dist/lib/task/artifact-lifecycle.js +42 -13
  78. package/dist/lib/task/artifact-operations.js +520 -0
  79. package/dist/lib/task/artifact-repair-intent.js +59 -0
  80. package/dist/lib/task/artifact-schema.js +135 -0
  81. package/dist/lib/task/capabilities.js +72 -1
  82. package/dist/lib/task/control-authority.js +4 -0
  83. package/dist/lib/task/create-service.js +103 -4
  84. package/dist/lib/task/create.js +25 -0
  85. package/dist/lib/task/events.js +186 -67
  86. package/dist/lib/task/index.js +11 -3
  87. package/dist/lib/task/local-artifact-finalization.js +75 -150
  88. package/dist/lib/task/orchestration.js +192 -52
  89. package/dist/lib/task/qualification-audit.js +490 -0
  90. package/dist/lib/task/qualification-confirmation.js +109 -0
  91. package/dist/lib/task/qualification-intents.js +148 -0
  92. package/dist/lib/task/review-artifacts.js +22 -23
  93. package/dist/lib/task/review-finalization.js +121 -4
  94. package/dist/lib/task/sections.js +6 -4
  95. package/dist/lib/task/verification-config.js +11 -0
  96. package/dist/lib/task/verification-engine.js +54 -4
  97. package/dist/lifecycle-build-manifest.json +3 -3
  98. package/lib/agent-client.ts +9 -0
  99. package/lib/internal/agent-client.ts +4 -2
  100. package/lib/internal/cli-route-inventory.ts +140 -0
  101. package/lib/internal/codex-lifecycle.ts +8 -6
  102. package/lib/internal/codex-sandbox-controller.ts +7 -2
  103. package/lib/internal/git-workflow.ts +9 -6
  104. package/lib/internal/platform-checks.ts +12 -4
  105. package/lib/internal/platform-comment.ts +12 -4
  106. package/lib/internal/platform-context.ts +3 -1
  107. package/lib/internal/platform-issue.ts +11 -4
  108. package/lib/internal/platform-metadata.ts +70 -0
  109. package/lib/internal/platform-pr-review.ts +14 -3
  110. package/lib/internal/platform-pr.ts +23 -10
  111. package/lib/internal/platform-release-notes.ts +5 -3
  112. package/lib/internal/platform-security.ts +83 -0
  113. package/lib/internal/pr-review-grade.ts +10 -3
  114. package/lib/internal/release-workflow.ts +17 -5
  115. package/lib/internal/sandbox-control.ts +19 -5
  116. package/lib/internal/task-activity.ts +12 -4
  117. package/lib/internal/task-artifact.ts +164 -25
  118. package/lib/internal/task-context.ts +3 -1
  119. package/lib/internal/task-create.ts +76 -15
  120. package/lib/internal/task-delivery.ts +3 -1
  121. package/lib/internal/task-event.ts +3 -1
  122. package/lib/internal/task-finalization.ts +3 -1
  123. package/lib/internal/task-invalidation.ts +3 -1
  124. package/lib/internal/task-ledger.ts +11 -1
  125. package/lib/internal/task-lifecycle.ts +3 -1
  126. package/lib/internal/task-operation-registry.ts +482 -0
  127. package/lib/internal/task-orchestration.ts +12 -0
  128. package/lib/internal/task-override.ts +9 -3
  129. package/lib/internal/task-qualification.ts +61 -0
  130. package/lib/internal/task-review.ts +3 -1
  131. package/lib/internal/task-short-id.ts +10 -1
  132. package/lib/internal/task-snapshot.ts +3 -1
  133. package/lib/internal/task-validate.ts +6 -2
  134. package/lib/internal/task-verify.ts +4 -1
  135. package/lib/internal/task-warning.ts +7 -1
  136. package/lib/platform/comment-safety.ts +250 -0
  137. package/lib/platform/github-provider.ts +229 -7
  138. package/lib/platform/issue-comments.ts +160 -35
  139. package/lib/platform/pr-change-report.ts +6 -12
  140. package/lib/platform/pr-summary.ts +27 -6
  141. package/lib/platform/provider-contract.ts +78 -3
  142. package/lib/platform/provider-validation.ts +85 -3
  143. package/lib/platform/pull-requests.ts +126 -178
  144. package/lib/platform/repository-metadata.ts +453 -0
  145. package/lib/platform/security-alerts.ts +191 -0
  146. package/lib/platform/verification-sync.ts +32 -28
  147. package/lib/process-data/index.ts +7 -0
  148. package/lib/run/index.ts +5 -0
  149. package/lib/sandbox/commands/control-status.ts +26 -0
  150. package/lib/sandbox/commands/create.ts +46 -6
  151. package/lib/sandbox/commands/ls.ts +7 -0
  152. package/lib/sandbox/commands/rm.ts +1000 -87
  153. package/lib/sandbox/commands/show.ts +16 -0
  154. package/lib/sandbox/control/client.ts +103 -9
  155. package/lib/sandbox/control/completed-reentry.ts +98 -0
  156. package/lib/sandbox/control/container-identity.ts +27 -43
  157. package/lib/sandbox/control/executor.ts +177 -12
  158. package/lib/sandbox/control/lifecycle.ts +629 -17
  159. package/lib/sandbox/control/native-file-lock.ts +209 -0
  160. package/lib/sandbox/control/protocol.ts +5 -2
  161. package/lib/sandbox/control/server.ts +319 -37
  162. package/lib/sandbox/control/state.ts +6 -3
  163. package/lib/sandbox/control/task-view.ts +240 -0
  164. package/lib/sandbox/engines/authority.ts +365 -0
  165. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  166. package/lib/sandbox/index.ts +7 -0
  167. package/lib/sandbox/recovery.ts +21 -1
  168. package/lib/sandbox/workspace-view.ts +9 -3
  169. package/lib/sandbox/worktree-safety.ts +4 -1
  170. package/lib/server/index.ts +10 -0
  171. package/lib/task/artifact-lifecycle.ts +38 -16
  172. package/lib/task/artifact-operations.ts +626 -0
  173. package/lib/task/artifact-repair-intent.ts +71 -0
  174. package/lib/task/artifact-schema.ts +192 -0
  175. package/lib/task/capabilities.ts +70 -1
  176. package/lib/task/control-authority.ts +11 -1
  177. package/lib/task/create-service.ts +131 -6
  178. package/lib/task/create.ts +27 -0
  179. package/lib/task/events.ts +161 -65
  180. package/lib/task/index.ts +16 -3
  181. package/lib/task/local-artifact-finalization.ts +87 -218
  182. package/lib/task/orchestration.ts +172 -49
  183. package/lib/task/qualification-audit.ts +599 -0
  184. package/lib/task/qualification-confirmation.ts +117 -0
  185. package/lib/task/qualification-intents.ts +150 -0
  186. package/lib/task/review-artifacts.ts +21 -22
  187. package/lib/task/review-finalization.ts +133 -4
  188. package/lib/task/sections.ts +6 -4
  189. package/lib/task/verification-config.ts +12 -0
  190. package/lib/task/verification-engine.ts +55 -4
  191. package/package.json +6 -1
  192. package/templates/.agents/rules/README.en.md +2 -0
  193. package/templates/.agents/rules/README.zh-CN.md +2 -0
  194. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  195. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  196. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  197. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  198. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  199. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  200. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  201. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  202. package/templates/.agents/rules/security-alerts.en.md +8 -8
  203. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  204. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  206. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  207. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  208. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  213. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  214. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  215. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  216. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  217. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  218. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  219. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  221. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  222. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  223. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -1
  226. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  227. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  228. package/templates/.agents/skills/complete-task/SKILL.en.md +9 -22
  229. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
  230. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  231. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  232. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  233. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  234. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  235. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  236. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  237. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  238. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  239. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  240. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  241. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  242. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  243. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  244. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  245. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  246. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  247. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  248. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  249. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  250. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  251. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  252. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  253. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  254. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  255. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  256. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  257. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  258. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  259. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  260. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  261. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  262. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  263. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  264. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  265. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
  268. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  269. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  270. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  271. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  272. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  273. package/templates/.agents/templates/task.en.md +6 -0
  274. package/templates/.agents/templates/task.zh-CN.md +6 -0
  275. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  276. package/templates/.agents/scripts/security-alerts.sh +0 -31
  277. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  278. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  279. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  280. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -7,6 +7,7 @@ import { platformResult } from './types.js';
7
7
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
8
8
  import { resourceIdentityNumber } from './resource-identity.js';
9
9
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
10
+ import { CONTROL_MARKER_PATTERN, escapeHtmlText, fenceRanges, renderSafeCodeFence, sanitizeMarkdownDocument } from './comment-safety.js';
10
11
  function providerCommentId(id, provider) {
11
12
  return provider.identity?.comment === 'number' && /^\d+$/.test(id) ? Number(id) : id;
12
13
  }
@@ -37,14 +38,21 @@ function splitFrontmatter(content) {
37
38
  return { frontmatter: null, body: content };
38
39
  return { frontmatter: match[1], body: content.slice(match[0].length).replace(/^\r?\n/, '') };
39
40
  }
41
+ function sanitizeCommentBody(value, label) {
42
+ const result = sanitizeMarkdownDocument(value, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
43
+ if (!result.ok)
44
+ throw new Error(`${result.error.code}: ${label}: ${result.error.message} at offset ${result.error.offset}`);
45
+ return result.value;
46
+ }
40
47
  function footer(agent, taskId) {
41
48
  return `---\n*由 ${agent} 自动生成 · 内部追踪:${taskId}*`;
42
49
  }
43
50
  function renderTaskComment(content, taskId, agent) {
44
51
  const split = splitFrontmatter(content);
52
+ const safeBody = sanitizeCommentBody(split.body, 'task body');
45
53
  const taskBody = split.frontmatter === null
46
- ? split.body
47
- : `<details><summary>元数据 (frontmatter)</summary>\n\n\`\`\`yaml\n---\n${split.frontmatter}\n---\n\`\`\`\n\n</details>\n\n${split.body}`;
54
+ ? safeBody
55
+ : `<details><summary>元数据 (frontmatter)</summary>\n\n${renderSafeCodeFence(`---\n${split.frontmatter}\n---`, 'yaml')}\n\n</details>\n\n${safeBody}`;
48
56
  return normalizeCommentContent([
49
57
  MARKERS.task(taskId),
50
58
  '## 任务文件',
@@ -69,10 +77,11 @@ function chunkByUtf8(content, maxBytes) {
69
77
  if (maxBytes <= 0)
70
78
  throw new Error('comment byte limit is too small');
71
79
  const chunks = [];
72
- let remaining = content;
73
- while (remaining) {
80
+ let start = 0;
81
+ while (start < content.length) {
82
+ const remaining = content.slice(start);
74
83
  if (Buffer.byteLength(remaining, 'utf8') <= maxBytes) {
75
- chunks.push(remaining);
84
+ chunks.push({ content: remaining, start, end: content.length });
76
85
  break;
77
86
  }
78
87
  let bytes = 0;
@@ -90,12 +99,109 @@ function chunkByUtf8(content, maxBytes) {
90
99
  const cut = newlineIndex > 0 ? newlineIndex : index;
91
100
  if (cut === 0)
92
101
  throw new Error('comment byte limit cannot fit one Unicode code point');
93
- chunks.push(remaining.slice(0, cut));
94
- remaining = remaining.slice(cut);
102
+ chunks.push({ content: remaining.slice(0, cut), start, end: start + cut });
103
+ start += cut;
104
+ }
105
+ return chunks.length > 0 ? chunks : [{ content: '', start: 0, end: 0 }];
106
+ }
107
+ function longestFenceLineRun(content, character) {
108
+ const pattern = new RegExp(`^ {0,3}(${character}+)[ \\t]*(?:\\n|$)`, 'gm');
109
+ let longest = 0;
110
+ for (const match of content.matchAll(pattern))
111
+ longest = Math.max(longest, match[1].length);
112
+ return longest;
113
+ }
114
+ function boundedFence(content, sourceCharacter, maxBytes) {
115
+ const candidates = sourceCharacter === '`' ? ['~', '`'] : ['`', '~'];
116
+ for (const character of candidates) {
117
+ const length = Math.max(3, longestFenceLineRun(content, character) + 1);
118
+ const delimiter = character.repeat(length);
119
+ if (Buffer.byteLength(delimiter, 'utf8') * 2 + 2 <= maxBytes)
120
+ return { opening: `${delimiter}\n`, closing: `${delimiter}\n` };
95
121
  }
96
- return chunks.length > 0 ? chunks : [''];
122
+ return null;
123
+ }
124
+ function sourceSlice(piece, start, end) {
125
+ return piece.content.slice(Math.max(0, start - piece.start), Math.max(0, end - piece.start));
97
126
  }
98
- function buildArtifactChunk(taskId, stem, title, agent, content, part, total, chunked, backfill) {
127
+ function renderOversizedFenceChunk(piece, fences, maxBytes) {
128
+ const insertions = new Map();
129
+ const omitted = [];
130
+ const insert = (position, value, ensureLineStart = false) => {
131
+ const values = insertions.get(position) || [];
132
+ values.push({ value, ensureLineStart });
133
+ insertions.set(position, values);
134
+ };
135
+ for (const fence of fences) {
136
+ if (piece.end <= fence.start || piece.start >= fence.end)
137
+ continue;
138
+ const oversized = Buffer.byteLength(fence.opening, 'utf8') > maxBytes
139
+ || Buffer.byteLength(fence.closing, 'utf8') > maxBytes;
140
+ if (!oversized) {
141
+ if (piece.start > fence.start && piece.start < fence.end)
142
+ insert(piece.start, fence.opening);
143
+ if (piece.end > fence.start && piece.end < fence.end)
144
+ insert(piece.end, fence.closing, true);
145
+ continue;
146
+ }
147
+ const code = sourceSlice(piece, Math.max(piece.start, fence.openingEnd), Math.min(piece.end, fence.closingStart));
148
+ const delimiter = boundedFence(code, fence.character, maxBytes);
149
+ if (!delimiter)
150
+ return escapeHtmlText(piece.content);
151
+ insert(Math.max(piece.start, fence.start), delimiter.opening);
152
+ insert(Math.min(piece.end, fence.end), delimiter.closing, true);
153
+ const openingStart = Math.max(piece.start, fence.start);
154
+ const openingEnd = Math.min(piece.end, fence.openingEnd);
155
+ if (openingStart < openingEnd)
156
+ omitted.push({ start: openingStart, end: openingEnd });
157
+ const closingStart = Math.max(piece.start, fence.closingStart);
158
+ const closingEnd = Math.min(piece.end, fence.end);
159
+ if (closingStart < closingEnd)
160
+ omitted.push({ start: closingStart, end: closingEnd });
161
+ }
162
+ const boundaries = new Set([piece.start, piece.end]);
163
+ for (const position of insertions.keys())
164
+ boundaries.add(position);
165
+ for (const range of omitted) {
166
+ boundaries.add(range.start);
167
+ boundaries.add(range.end);
168
+ }
169
+ const points = [...boundaries].sort((left, right) => left - right);
170
+ let content = '';
171
+ for (let index = 0; index < points.length; index += 1) {
172
+ const point = points[index];
173
+ for (const insertion of insertions.get(point) || []) {
174
+ if (insertion.ensureLineStart && content.length > 0 && !content.endsWith('\n'))
175
+ content += '\n';
176
+ content += insertion.value;
177
+ }
178
+ const next = points[index + 1];
179
+ if (next === undefined)
180
+ continue;
181
+ const skipped = omitted.some((range) => range.start <= point && next <= range.end);
182
+ if (!skipped)
183
+ content += sourceSlice(piece, point, next);
184
+ }
185
+ return content;
186
+ }
187
+ function independentChunkContent(piece, fences, maxBytes) {
188
+ const oversizedFence = fences.some((fence) => piece.end > fence.start && piece.start < fence.end
189
+ && (Buffer.byteLength(fence.opening, 'utf8') > maxBytes || Buffer.byteLength(fence.closing, 'utf8') > maxBytes));
190
+ if (oversizedFence)
191
+ return renderOversizedFenceChunk(piece, fences, maxBytes);
192
+ const openingFence = fences.find((fence) => piece.start > fence.start && piece.start < fence.end);
193
+ const closingFence = fences.find((fence) => piece.end > fence.start && piece.end < fence.end);
194
+ let content = piece.content;
195
+ if (openingFence)
196
+ content = openingFence.opening + content;
197
+ if (closingFence) {
198
+ if (!content.endsWith('\n'))
199
+ content += '\n';
200
+ content += closingFence.closing;
201
+ }
202
+ return content;
203
+ }
204
+ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, chunked, backfill, renderedContent = content) {
99
205
  const marker = chunked ? MARKERS.artifactChunk(taskId, stem, part, total) : MARKERS.artifact(taskId, stem);
100
206
  const heading = chunked ? `## ${title}(${part}/${total})` : `## ${title}`;
101
207
  const timelineHint = backfill ? '> 历史产物补发' : null;
@@ -106,7 +212,7 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
106
212
  '',
107
213
  `> **${agent}** · ${taskId}`,
108
214
  '',
109
- content,
215
+ renderedContent,
110
216
  '',
111
217
  footer(agent, taskId)
112
218
  ].join('\n'));
@@ -115,25 +221,32 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
115
221
  function chunkArtifactComment(input) {
116
222
  const byteLimit = input.byteLimit || 60_000;
117
223
  const identity = artifactIdentity(input.artifact);
118
- const single = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, input.body, 1, 1, false, Boolean(input.backfill));
224
+ const safeBody = sanitizeCommentBody(input.body, 'artifact body');
225
+ const single = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, safeBody, 1, 1, false, Boolean(input.backfill));
119
226
  if (Buffer.byteLength(single.body, 'utf8') <= byteLimit)
120
227
  return [single];
121
228
  let total = 2;
122
- let pieces = [];
229
+ let payloadLimit = Number.POSITIVE_INFINITY;
230
+ const ranges = fenceRanges(safeBody);
231
+ if (!ranges.ok)
232
+ throw new Error(`${ranges.error.code}: artifact body: ${ranges.error.message} at offset ${ranges.error.offset}`);
123
233
  for (;;) {
124
234
  const probe = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, '', total, total, true, Boolean(input.backfill));
125
235
  const available = byteLimit - Buffer.byteLength(probe.body, 'utf8');
126
- pieces = chunkByUtf8(input.body, available);
127
- if (pieces.length === total)
128
- break;
129
- total = pieces.length;
236
+ const sourceLimit = Math.min(available, payloadLimit);
237
+ if (sourceLimit <= 0)
238
+ throw new Error('comment byte limit is too small for an artifact chunk');
239
+ const pieces = chunkByUtf8(safeBody, sourceLimit);
240
+ if (pieces.length !== total) {
241
+ total = pieces.length;
242
+ continue;
243
+ }
244
+ const chunks = pieces.map((piece, index) => buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, piece.content, index + 1, total, true, Boolean(input.backfill), independentChunkContent(piece, ranges.value, sourceLimit)));
245
+ const overflow = Math.max(...chunks.map((chunk) => Buffer.byteLength(chunk.body, 'utf8') - byteLimit));
246
+ if (overflow <= 0)
247
+ return chunks;
248
+ payloadLimit = sourceLimit - overflow;
130
249
  }
131
- return pieces.map((content, index) => {
132
- const chunk = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, content, index + 1, total, true, Boolean(input.backfill));
133
- if (Buffer.byteLength(chunk.body, 'utf8') > byteLimit)
134
- throw new Error('rendered comment exceeds byte limit');
135
- return chunk;
136
- });
137
250
  }
138
251
  function findMarkerComments(comments, marker) {
139
252
  return comments.filter((comment) => normalizeCommentContent(String(comment.body || '')).split('\n', 1)[0] === marker);
@@ -166,7 +279,7 @@ function hasResolvedPlatformContext(context) {
166
279
  }
167
280
  function bodyEnvelope(marker, title, taskId, agent, body) {
168
281
  return normalizeCommentContent([
169
- marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '', body.replace(/\n+$/, ''), '', footer(agent, taskId)
282
+ marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '', sanitizeCommentBody(body, `${title} body`).replace(/\n+$/, ''), '', footer(agent, taskId)
170
283
  ].join('\n'));
171
284
  }
172
285
  function resolveArtifactBody(taskDir, artifact) {
@@ -271,6 +384,16 @@ async function syncPlatformComment(taskRef, options) {
271
384
  error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid issue_number', retryable: false }
272
385
  });
273
386
  }
387
+ let desired;
388
+ try {
389
+ desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, options);
390
+ }
391
+ catch (error) {
392
+ return platformResult('failed', {
393
+ resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentityFromTask) },
394
+ error: { code: 'COMMENT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
395
+ });
396
+ }
274
397
  const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
275
398
  const context = loaded.ok ? loaded.value.context : loaded.context;
276
399
  if (!hasResolvedPlatformContext(context) || !loaded.ok)
@@ -293,17 +416,6 @@ async function syncPlatformComment(taskRef, options) {
293
416
  ...contextFields(context), resource: { kind: 'issue', number: issue }, error: listed.error
294
417
  });
295
418
  }
296
- let desired;
297
- try {
298
- desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, options);
299
- }
300
- catch (error) {
301
- return platformResult('failed', {
302
- ...contextFields(context),
303
- resource: { kind: 'issue', number: issue },
304
- error: { code: 'COMMENT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
305
- });
306
- }
307
419
  const existing = relatedComments(listed.value, markerPrefix(resolved.taskId, options));
308
420
  if (!validateRelatedMarkerSet(existing, markerPrefix(resolved.taskId, options)).ok) {
309
421
  return platformResult('failed', {
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { execFileSync } from 'node:child_process';
5
+ import { escapeHtmlText } from './comment-safety.js';
5
6
  const REPORT_FILE_NAME = 'pr-change-report.json';
6
7
  const REPORT_VERSION = 1;
7
8
  const CANONICAL_REPORT_PLACEHOLDER = '<!-- canonical-pr-change-report -->';
@@ -429,21 +430,14 @@ const MAX_REPRESENTATIVE_FILES = 3;
429
430
  function representativePath(file) {
430
431
  return file.newPath || file.oldPath || '';
431
432
  }
432
- function escapeHtml(value) {
433
- return value
434
- .replaceAll('&', '&amp;')
435
- .replaceAll('<', '&lt;')
436
- .replaceAll('>', '&gt;')
437
- .replaceAll('"', '&quot;')
438
- .replaceAll("'", '&#39;')
439
- .replaceAll('\r', '\\r')
440
- .replaceAll('\n', '\\n');
433
+ function escapeReportText(value) {
434
+ return escapeHtmlText(value).replaceAll('\r', '\\r').replaceAll('\n', '\\n');
441
435
  }
442
436
  function renderRepresentativePath(file) {
443
437
  const paths = file.oldPath && file.newPath && file.oldPath !== file.newPath
444
438
  ? [file.oldPath, file.newPath]
445
439
  : [representativePath(file)];
446
- return paths.map((filePath) => `<code>${escapeHtml(filePath)}</code>`).join(' → ');
440
+ return paths.map((filePath) => `<code>${escapeReportText(filePath)}</code>`).join(' → ');
447
441
  }
448
442
  function representativeScore(file, metric) {
449
443
  return metric === 'lines' ? file.additions + file.deletions : Math.abs(file.netBytes);
@@ -492,11 +486,11 @@ function renderEvidence(evidence) {
492
486
  ? `:${evidence.startLine}`
493
487
  : `:${evidence.startLine}-${evidence.endLine}`
494
488
  : `:${evidence.startLine ?? evidence.endLine}`;
495
- return ` - 证据:<code>${escapeHtml(evidence.path)}</code>${location}:${escapeHtml(evidence.detail)}`;
489
+ return ` - 证据:<code>${escapeReportText(evidence.path)}</code>${location}:${escapeReportText(evidence.detail)}`;
496
490
  }
497
491
  function renderPrechecks(checks) {
498
492
  return checks.flatMap((check) => [
499
- `- **${PRECHECK_LABELS[check.id]}(${check.id})**:${check.verdict === 'pass' ? '通过' : '需复核'}。${escapeHtml(check.rationale)}`,
493
+ `- **${PRECHECK_LABELS[check.id]}(${check.id})**:${check.verdict === 'pass' ? '通过' : '需复核'}。${escapeReportText(check.rationale)}`,
500
494
  ...check.evidence.slice(0, 1).map(renderEvidence)
501
495
  ]);
502
496
  }
@@ -8,18 +8,15 @@ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-exec
8
8
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
9
9
  import { resolvePlatformProviderContext } from './context.js';
10
10
  import { normalizeCommentContent } from './issue-comments.js';
11
+ import { CONTROL_MARKER_PATTERN, escapeHtmlText, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.js';
11
12
  import { platformResult } from './types.js';
12
13
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
13
- import { buildPrChangeReport, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
14
+ import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
14
15
  function summaryMarker(taskId) {
15
16
  return `<!-- sync-pr:${taskId}:summary -->`;
16
17
  }
17
18
  function escapeSummaryAuditText(value) {
18
- return value
19
- .replace(/&/g, '&amp;')
20
- .replace(/</g, '&lt;')
21
- .replace(/>/g, '&gt;')
22
- .replace(/\r\n?/g, '\n');
19
+ return escapeHtmlText(value).replace(/\r\n?/g, '\n');
23
20
  }
24
21
  function renderSummaryAuditLine(value) {
25
22
  const safe = escapeSummaryAuditText(value);
@@ -476,7 +473,31 @@ async function syncPullRequestSummary(taskRef, options) {
476
473
  const checked = currentReportCheck(report.value, initial.value, taskContent, resolved.repoRoot);
477
474
  if (!checked.ok)
478
475
  return fail('failed', context, checked.error);
479
- const replaced = replaceCanonicalReportPlaceholder(options.body, report.value);
476
+ const placeholder = CANONICAL_REPORT_PLACEHOLDER;
477
+ const split = splitDocumentPlaceholder(options.body, placeholder);
478
+ if (!split.ok)
479
+ return fail('failed', context, {
480
+ code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
481
+ message: split.error.message,
482
+ retryable: false
483
+ });
484
+ const prefix = sanitizeMarkdownDocument(split.value.prefix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
485
+ const suffix = sanitizeMarkdownDocument(split.value.suffix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
486
+ if (!prefix.ok) {
487
+ return fail('failed', context, {
488
+ code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
489
+ message: `${prefix.error.message} at offset ${prefix.error.offset}`,
490
+ retryable: false
491
+ });
492
+ }
493
+ if (!suffix.ok) {
494
+ return fail('failed', context, {
495
+ code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
496
+ message: `${suffix.error.message} at offset ${suffix.error.offset}`,
497
+ retryable: false
498
+ });
499
+ }
500
+ const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
480
501
  if (!replaced.ok)
481
502
  return fail('failed', context, platformError(replaced.error));
482
503
  const desired = buildPullRequestSummary(resolved.taskId, replaced.value, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
@@ -15,6 +15,7 @@ type ProviderResult<T> = {
15
15
  } | {
16
16
  ok: false;
17
17
  error: PlatformError;
18
+ value?: T;
18
19
  };
19
20
  type ProviderOperationContext = {
20
21
  repositoryRoot: string;
@@ -172,6 +173,35 @@ type MutationReceipt = {
172
173
  changed: boolean;
173
174
  operationId?: string;
174
175
  };
176
+ type SecurityAlertKind = 'dependabot' | 'code-scanning';
177
+ type SecurityAlertSnapshot = {
178
+ kind: SecurityAlertKind;
179
+ number: number;
180
+ state: string;
181
+ data: JsonValue;
182
+ };
183
+ type LabelDefinition = {
184
+ name: string;
185
+ color: string;
186
+ description: string;
187
+ };
188
+ type LabelReconciliation = {
189
+ changed: boolean;
190
+ created: string[];
191
+ updated: string[];
192
+ removed: string[];
193
+ skipped: string[];
194
+ };
195
+ type MilestoneDefinition = {
196
+ title: string;
197
+ description: string;
198
+ state: 'open' | 'closed';
199
+ };
200
+ type MilestoneInitialization = {
201
+ changed: boolean;
202
+ created: string[];
203
+ skipped: string[];
204
+ };
175
205
  type RepositoryMetadataSnapshot = {
176
206
  repository: {
177
207
  identity: ResourceIdentity;
@@ -334,6 +364,14 @@ type PlatformProvider = {
334
364
  context: ProviderOperationContext;
335
365
  target: ResourceIdentity;
336
366
  }): Promise<ProviderResult<ChangeRequestSnapshot>>;
367
+ listFiles?(input: {
368
+ context: ProviderOperationContext;
369
+ target: ResourceIdentity;
370
+ }): Promise<ProviderResult<string[]>>;
371
+ listClosingIssues?(input: {
372
+ context: ProviderOperationContext;
373
+ target: ResourceIdentity;
374
+ }): Promise<ProviderResult<ResourceIdentity[]>>;
337
375
  listClosing(input: {
338
376
  context: ProviderOperationContext;
339
377
  issue: ResourceIdentity;
@@ -468,6 +506,34 @@ type PlatformProvider = {
468
506
  historyLimit: number;
469
507
  }): Promise<ProviderResult<ReleaseNotesFacts>>;
470
508
  };
509
+ securityAlerts?: {
510
+ inspect(input: {
511
+ context: ProviderOperationContext;
512
+ kind: SecurityAlertKind;
513
+ number: number;
514
+ }): Promise<ProviderResult<SecurityAlertSnapshot>>;
515
+ dismiss(input: {
516
+ context: ProviderOperationContext;
517
+ kind: SecurityAlertKind;
518
+ number: number;
519
+ reason: string;
520
+ comment: string;
521
+ mutation: MutationIdentity;
522
+ }): Promise<ProviderResult<MutationReceipt>>;
523
+ };
524
+ repositoryMetadata?: {
525
+ reconcileLabels(input: {
526
+ context: ProviderOperationContext;
527
+ desired: LabelDefinition[];
528
+ cleanupStaleIn: boolean;
529
+ mutation: MutationIdentity;
530
+ }): Promise<ProviderResult<LabelReconciliation>>;
531
+ reconcileMilestones(input: {
532
+ context: ProviderOperationContext;
533
+ desired: MilestoneDefinition[];
534
+ mutation: MutationIdentity;
535
+ }): Promise<ProviderResult<MilestoneInitialization>>;
536
+ };
471
537
  verification?: {
472
538
  fetchRemoteFacts(input: {
473
539
  context: ProviderOperationContext;
@@ -484,4 +550,4 @@ declare const PLATFORM_PROVIDER_CONTRACT_VERSION: 1;
484
550
  declare function isPlatformProviderFactory(value: unknown): value is PlatformProviderFactory;
485
551
  declare function validatePlatformProvider(value: unknown, providerType: string): ProviderResult<PlatformProvider>;
486
552
  export { PLATFORM_PROVIDER_CONTRACT_VERSION, isPlatformProviderFactory, validatePlatformProvider };
487
- export type { ChangeRequestSnapshot, CheckLogSnapshot, CheckRunSnapshot, GitEvidenceSnapshot, IssueSnapshot, JsonValue, MilestoneReconciliation, MutationIdentity, MutationReceipt, PlatformCapabilities, PlatformContextSnapshot, PlatformError, PlatformProvider, PlatformProviderFactory, PlatformProviderFactoryInput, ProviderOperationContext, ProviderResult, ReleaseSnapshot, RemoteCommentSnapshot, RequiredCheckSnapshot, RepositoryMetadataSnapshot, ReleaseNotesFacts, ResourceIdentity, ReviewSnapshot, VerificationRemoteFacts };
553
+ export type { ChangeRequestSnapshot, CheckLogSnapshot, CheckRunSnapshot, GitEvidenceSnapshot, IssueSnapshot, JsonValue, MilestoneReconciliation, MilestoneDefinition, MilestoneInitialization, LabelDefinition, LabelReconciliation, MutationIdentity, MutationReceipt, PlatformCapabilities, PlatformContextSnapshot, PlatformError, PlatformProvider, PlatformProviderFactory, PlatformProviderFactoryInput, ProviderOperationContext, ProviderResult, ReleaseSnapshot, RemoteCommentSnapshot, RequiredCheckSnapshot, RepositoryMetadataSnapshot, ReleaseNotesFacts, ResourceIdentity, SecurityAlertKind, SecurityAlertSnapshot, ReviewSnapshot, VerificationRemoteFacts };
@@ -7,12 +7,14 @@ function isPlatformProviderFactory(value) {
7
7
  }
8
8
  function validatePlatformProvider(value, providerType) {
9
9
  const operationGroups = {
10
- issues: { required: ['inspect', 'create', 'update', 'describeRepository'], optional: [] },
10
+ issues: { required: ['inspect', 'create', 'update', 'describeRepository'], optional: ['listLabels', 'listMilestones'] },
11
11
  comments: { required: ['list', 'write', 'delete'], optional: [] },
12
- changeRequests: { required: ['inspect', 'listClosing', 'create', 'update', 'resolveGitEvidence'], optional: ['verifyHead'] },
12
+ changeRequests: { required: ['inspect', 'listClosing', 'create', 'update', 'resolveGitEvidence'], optional: ['verifyHead', 'listFiles', 'listClosingIssues'] },
13
13
  checks: { required: ['inspectRequired', 'resolveRun', 'fetchLogs'], optional: [] },
14
14
  reviews: { required: ['list', 'publish'], optional: [] },
15
15
  releases: { required: ['inspect', 'create', 'update', 'reconcileMilestones', 'publishNotes', 'collectNotes'], optional: [] },
16
+ securityAlerts: { required: ['inspect', 'dismiss'], optional: [] },
17
+ repositoryMetadata: { required: ['reconcileLabels', 'reconcileMilestones'], optional: [] },
16
18
  verification: { required: ['fetchRemoteFacts'], optional: [] }
17
19
  };
18
20
  const resourceKindsByGroup = {
@@ -22,6 +24,8 @@ function validatePlatformProvider(value, providerType) {
22
24
  checks: ['pull-request'],
23
25
  reviews: ['pull-request'],
24
26
  releases: ['release', 'issue', 'pull-request'],
27
+ securityAlerts: [],
28
+ repositoryMetadata: [],
25
29
  verification: ['issue', 'pull-request']
26
30
  };
27
31
  if (!isRecord(value)
@@ -250,6 +250,64 @@ function validateReceipt(value) {
250
250
  ...(item.operationId === undefined ? {} : { operationId: stringValue(item.operationId, 'receipt.operationId') })
251
251
  };
252
252
  }
253
+ function positiveInteger(value, label) {
254
+ if (!Number.isSafeInteger(value) || value <= 0)
255
+ throw new Error(`${label} must be a positive integer`);
256
+ return value;
257
+ }
258
+ function validateSecurityAlert(value) {
259
+ const item = record(value, 'security alert');
260
+ exactKeys(item, ['kind', 'number', 'state', 'data'], 'security alert');
261
+ if (item.kind !== 'dependabot' && item.kind !== 'code-scanning')
262
+ throw new Error('security alert.kind is invalid');
263
+ return {
264
+ kind: item.kind,
265
+ number: positiveInteger(item.number, 'security alert.number'),
266
+ state: stringValue(item.state, 'security alert.state'),
267
+ data: jsonValue(item.data, 'security alert.data')
268
+ };
269
+ }
270
+ function validateLabelDefinition(value, label) {
271
+ const item = record(value, label);
272
+ exactKeys(item, ['name', 'color', 'description'], label);
273
+ return {
274
+ name: stringValue(item.name, `${label}.name`),
275
+ color: stringValue(item.color, `${label}.color`),
276
+ description: stringValue(item.description, `${label}.description`, true)
277
+ };
278
+ }
279
+ function validateLabelReconciliation(value) {
280
+ const item = record(value, 'label reconciliation');
281
+ exactKeys(item, ['changed', 'created', 'updated', 'removed', 'skipped'], 'label reconciliation');
282
+ const names = (field) => arrayValue(item[field], `label reconciliation.${field}`).map((entry) => stringValue(entry, `${field}[]`));
283
+ return {
284
+ changed: booleanValue(item.changed, 'label reconciliation.changed'),
285
+ created: names('created'),
286
+ updated: names('updated'),
287
+ removed: names('removed'),
288
+ skipped: names('skipped')
289
+ };
290
+ }
291
+ function validateMilestoneDefinition(value, label) {
292
+ const item = record(value, label);
293
+ exactKeys(item, ['title', 'description', 'state'], label);
294
+ if (item.state !== 'open' && item.state !== 'closed')
295
+ throw new Error(`${label}.state is invalid`);
296
+ return {
297
+ title: stringValue(item.title, `${label}.title`),
298
+ description: stringValue(item.description, `${label}.description`, true),
299
+ state: item.state
300
+ };
301
+ }
302
+ function validateMilestoneInitialization(value) {
303
+ const item = record(value, 'milestone initialization');
304
+ exactKeys(item, ['changed', 'created', 'skipped'], 'milestone initialization');
305
+ return {
306
+ changed: booleanValue(item.changed, 'milestone initialization.changed'),
307
+ created: arrayValue(item.created, 'milestone initialization.created').map((entry) => stringValue(entry, 'created[]')),
308
+ skipped: arrayValue(item.skipped, 'milestone initialization.skipped').map((entry) => stringValue(entry, 'skipped[]'))
309
+ };
310
+ }
253
311
  function validateMetadata(value) {
254
312
  const item = record(value, 'repository metadata');
255
313
  exactKeys(item, ['repository', 'labels', 'milestones', 'issueTypes', 'fields'], 'repository metadata');
@@ -352,6 +410,11 @@ const PROVIDER_ERROR_CATALOG = {
352
410
  ISSUE_NUMBER_INVALID: { message: 'The issue number is invalid', retryable: false },
353
411
  ISSUE_CREATE_RESPONSE_INVALID: { message: 'Issue creation returned an invalid response', retryable: false },
354
412
  COMMENT_ID_INVALID: { message: 'The comment identity is invalid', retryable: false },
413
+ SECURITY_KIND_INVALID: { message: 'The security alert kind is invalid', retryable: false },
414
+ SECURITY_NUMBER_INVALID: { message: 'The security alert number is invalid', retryable: false },
415
+ SECURITY_RESPONSE_INVALID: { message: 'The platform returned an invalid security alert', retryable: false },
416
+ LABEL_RESPONSE_INVALID: { message: 'The platform returned an invalid label response', retryable: false },
417
+ MILESTONE_RESPONSE_INVALID: { message: 'The platform returned an invalid milestone response', retryable: false },
355
418
  MILESTONE_IDENTITY_INVALID: { message: 'The milestone identity is invalid', retryable: false },
356
419
  CHECK_RUN_NOT_FOUND: { message: 'The check run was not found', retryable: false },
357
420
  RELEASE_NOTES_AUTHORS_TRUNCATED: { message: 'Release note commit authors exceeded the supported limit', retryable: false },
@@ -390,6 +453,8 @@ function validators(providerType, declaration) {
390
453
  return { sha: stringValue(item.sha, 'change request head.sha') };
391
454
  },
392
455
  'changeRequests.inspect': (value) => validateChangeRequest(value, declaredIdentityKind(declaration, 'pull-request'), true),
456
+ 'changeRequests.listFiles': (value) => arrayValue(value, 'pull request files').map((entry) => stringValue(entry, 'pull request files[]')),
457
+ 'changeRequests.listClosingIssues': (value) => arrayValue(value, 'closing issues').map((entry) => identity(entry, 'closing issues[]', declaredIdentityKind(declaration, 'issue'))),
393
458
  'changeRequests.listClosing': (value) => arrayValue(value, 'changeRequests').map((entry) => validateChangeRequest(entry, declaredIdentityKind(declaration, 'pull-request'), true)),
394
459
  'changeRequests.create': validateReceipt,
395
460
  'changeRequests.update': validateReceipt,
@@ -433,6 +498,10 @@ function validators(providerType, declaration) {
433
498
  },
434
499
  'releases.publishNotes': validateReceipt,
435
500
  'releases.collectNotes': (value) => validateReleaseNotes(value, declaration),
501
+ 'securityAlerts.inspect': validateSecurityAlert,
502
+ 'securityAlerts.dismiss': validateReceipt,
503
+ 'repositoryMetadata.reconcileLabels': validateLabelReconciliation,
504
+ 'repositoryMetadata.reconcileMilestones': validateMilestoneInitialization,
436
505
  'verification.fetchRemoteFacts': (value) => {
437
506
  const item = record(value, 'verification facts');
438
507
  exactKeys(item, ['issue', 'comments', 'changeRequest', 'commit', 'fields'], 'verification facts');
@@ -462,12 +531,16 @@ async function invokeProviderOperation(providerType, operation, call, validate)
462
531
  }
463
532
  if (envelope.ok === false && envelope.error && typeof envelope.error === 'object' && !Array.isArray(envelope.error)) {
464
533
  try {
465
- exactKeys(envelope, ['ok', 'error'], `${operation} result`);
534
+ exactKeys(envelope, Object.hasOwn(envelope, 'value') ? ['ok', 'error', 'value'] : ['ok', 'error'], `${operation} result`);
466
535
  const error = envelope.error;
467
536
  exactKeys(error, ['code', 'message', 'retryable'], `${operation} error`);
468
537
  if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message || typeof error.retryable !== 'boolean')
469
538
  throw new Error('invalid provider error');
470
- return { ok: false, error: safeProviderError(providerType, operation, error) };
539
+ return {
540
+ ok: false,
541
+ error: safeProviderError(providerType, operation, error),
542
+ ...(Object.hasOwn(envelope, 'value') ? { value: validate(envelope.value) } : {})
543
+ };
471
544
  }
472
545
  catch {
473
546
  return { ok: false, error: validationError(providerType, operation, 'Provider operation returned an invalid error') };
@@ -483,7 +556,7 @@ function wrapProviderOperations(provider) {
483
556
  const map = validators(provider.type, provider.identity);
484
557
  const wrapped = { ...provider, context: { ...provider.context } };
485
558
  wrapped.context.resolve = (input) => invokeProviderOperation(provider.type, 'context.resolve', () => provider.context.resolve(input), map['context.resolve']);
486
- for (const groupName of ['issues', 'comments', 'changeRequests', 'checks', 'reviews', 'releases', 'verification']) {
559
+ for (const groupName of ['issues', 'comments', 'changeRequests', 'checks', 'reviews', 'releases', 'securityAlerts', 'repositoryMetadata', 'verification']) {
487
560
  const group = provider[groupName];
488
561
  if (!group)
489
562
  continue;