@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.
- package/bin/cli.ts +26 -9
- package/dist/bin/cli.js +173 -157
- package/dist/bin/internal-cli.js +42 -21
- package/dist/lib/agent-client.js +5 -0
- package/dist/lib/internal/agent-client.js +5 -2
- package/dist/lib/internal/cli-route-inventory.js +152 -0
- package/dist/lib/internal/codex-lifecycle.js +9 -6
- package/dist/lib/internal/codex-sandbox-controller.js +8 -2
- package/dist/lib/internal/git-workflow.js +10 -6
- package/dist/lib/internal/platform-checks.js +16 -4
- package/dist/lib/internal/platform-comment.js +16 -4
- package/dist/lib/internal/platform-context.js +4 -1
- package/dist/lib/internal/platform-issue.js +12 -4
- package/dist/lib/internal/platform-metadata.js +88 -0
- package/dist/lib/internal/platform-pr-review.js +16 -3
- package/dist/lib/internal/platform-pr.js +24 -10
- package/dist/lib/internal/platform-release-notes.js +6 -3
- package/dist/lib/internal/platform-security.js +93 -0
- package/dist/lib/internal/pr-review-grade.js +14 -3
- package/dist/lib/internal/release-workflow.js +19 -5
- package/dist/lib/internal/sandbox-control.js +22 -5
- package/dist/lib/internal/task-activity.js +13 -4
- package/dist/lib/internal/task-artifact.js +186 -27
- package/dist/lib/internal/task-context.js +4 -1
- package/dist/lib/internal/task-create.js +48 -15
- package/dist/lib/internal/task-delivery.js +4 -1
- package/dist/lib/internal/task-event.js +4 -1
- package/dist/lib/internal/task-finalization.js +4 -1
- package/dist/lib/internal/task-invalidation.js +4 -1
- package/dist/lib/internal/task-ledger.js +12 -1
- package/dist/lib/internal/task-lifecycle.js +4 -1
- package/dist/lib/internal/task-operation-registry.js +421 -0
- package/dist/lib/internal/task-orchestration.js +12 -0
- package/dist/lib/internal/task-override.js +10 -3
- package/dist/lib/internal/task-qualification.js +90 -0
- package/dist/lib/internal/task-review.js +4 -1
- package/dist/lib/internal/task-short-id.js +11 -1
- package/dist/lib/internal/task-snapshot.js +4 -1
- package/dist/lib/internal/task-validate.js +7 -2
- package/dist/lib/internal/task-verify.js +5 -1
- package/dist/lib/internal/task-warning.js +8 -1
- package/dist/lib/platform/comment-safety.js +226 -0
- package/dist/lib/platform/github-provider.js +233 -6
- package/dist/lib/platform/issue-comments.js +146 -34
- package/dist/lib/platform/pr-change-report.js +6 -12
- package/dist/lib/platform/pr-summary.js +28 -7
- package/dist/lib/platform/provider-contract.d.ts +67 -1
- package/dist/lib/platform/provider-contract.js +6 -2
- package/dist/lib/platform/provider-validation.js +76 -3
- package/dist/lib/platform/pull-requests.js +115 -170
- package/dist/lib/platform/repository-metadata.js +377 -0
- package/dist/lib/platform/security-alerts.js +155 -0
- package/dist/lib/platform/verification-sync.js +33 -26
- package/dist/lib/process-data/index.js +7 -0
- package/dist/lib/run/index.js +5 -0
- package/dist/lib/sandbox/commands/control-status.js +22 -0
- package/dist/lib/sandbox/commands/create.js +32 -6
- package/dist/lib/sandbox/commands/ls.js +5 -0
- package/dist/lib/sandbox/commands/rm.js +827 -120
- package/dist/lib/sandbox/commands/show.js +16 -0
- package/dist/lib/sandbox/control/client.js +83 -9
- package/dist/lib/sandbox/control/completed-reentry.js +85 -0
- package/dist/lib/sandbox/control/container-identity.js +28 -31
- package/dist/lib/sandbox/control/executor.js +149 -14
- package/dist/lib/sandbox/control/lifecycle.js +621 -127
- package/dist/lib/sandbox/control/native-file-lock.js +177 -0
- package/dist/lib/sandbox/control/server.js +305 -35
- package/dist/lib/sandbox/control/state.js +5 -3
- package/dist/lib/sandbox/control/task-view.js +183 -0
- package/dist/lib/sandbox/engines/authority.js +289 -0
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
- package/dist/lib/sandbox/index.js +5 -0
- package/dist/lib/sandbox/recovery.js +21 -1
- package/dist/lib/sandbox/workspace-view.js +2 -1
- package/dist/lib/sandbox/worktree-safety.js +4 -1
- package/dist/lib/server/index.js +8 -0
- package/dist/lib/task/artifact-lifecycle.js +42 -13
- package/dist/lib/task/artifact-operations.js +520 -0
- package/dist/lib/task/artifact-repair-intent.js +59 -0
- package/dist/lib/task/artifact-schema.js +135 -0
- package/dist/lib/task/capabilities.js +72 -1
- package/dist/lib/task/control-authority.js +4 -0
- package/dist/lib/task/create-service.js +103 -4
- package/dist/lib/task/create.js +25 -0
- package/dist/lib/task/events.js +186 -67
- package/dist/lib/task/index.js +11 -3
- package/dist/lib/task/local-artifact-finalization.js +75 -150
- package/dist/lib/task/orchestration.js +192 -52
- package/dist/lib/task/qualification-audit.js +490 -0
- package/dist/lib/task/qualification-confirmation.js +109 -0
- package/dist/lib/task/qualification-intents.js +148 -0
- package/dist/lib/task/review-artifacts.js +22 -23
- package/dist/lib/task/review-finalization.js +121 -4
- package/dist/lib/task/sections.js +6 -4
- package/dist/lib/task/verification-config.js +11 -0
- package/dist/lib/task/verification-engine.js +54 -4
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +9 -0
- package/lib/internal/agent-client.ts +4 -2
- package/lib/internal/cli-route-inventory.ts +140 -0
- package/lib/internal/codex-lifecycle.ts +8 -6
- package/lib/internal/codex-sandbox-controller.ts +7 -2
- package/lib/internal/git-workflow.ts +9 -6
- package/lib/internal/platform-checks.ts +12 -4
- package/lib/internal/platform-comment.ts +12 -4
- package/lib/internal/platform-context.ts +3 -1
- package/lib/internal/platform-issue.ts +11 -4
- package/lib/internal/platform-metadata.ts +70 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/platform-pr.ts +23 -10
- package/lib/internal/platform-release-notes.ts +5 -3
- package/lib/internal/platform-security.ts +83 -0
- package/lib/internal/pr-review-grade.ts +10 -3
- package/lib/internal/release-workflow.ts +17 -5
- package/lib/internal/sandbox-control.ts +19 -5
- package/lib/internal/task-activity.ts +12 -4
- package/lib/internal/task-artifact.ts +164 -25
- package/lib/internal/task-context.ts +3 -1
- package/lib/internal/task-create.ts +76 -15
- package/lib/internal/task-delivery.ts +3 -1
- package/lib/internal/task-event.ts +3 -1
- package/lib/internal/task-finalization.ts +3 -1
- package/lib/internal/task-invalidation.ts +3 -1
- package/lib/internal/task-ledger.ts +11 -1
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +482 -0
- package/lib/internal/task-orchestration.ts +12 -0
- package/lib/internal/task-override.ts +9 -3
- package/lib/internal/task-qualification.ts +61 -0
- package/lib/internal/task-review.ts +3 -1
- package/lib/internal/task-short-id.ts +10 -1
- package/lib/internal/task-snapshot.ts +3 -1
- package/lib/internal/task-validate.ts +6 -2
- package/lib/internal/task-verify.ts +4 -1
- package/lib/internal/task-warning.ts +7 -1
- package/lib/platform/comment-safety.ts +250 -0
- package/lib/platform/github-provider.ts +229 -7
- package/lib/platform/issue-comments.ts +160 -35
- package/lib/platform/pr-change-report.ts +6 -12
- package/lib/platform/pr-summary.ts +27 -6
- package/lib/platform/provider-contract.ts +78 -3
- package/lib/platform/provider-validation.ts +85 -3
- package/lib/platform/pull-requests.ts +126 -178
- package/lib/platform/repository-metadata.ts +453 -0
- package/lib/platform/security-alerts.ts +191 -0
- package/lib/platform/verification-sync.ts +32 -28
- package/lib/process-data/index.ts +7 -0
- package/lib/run/index.ts +5 -0
- package/lib/sandbox/commands/control-status.ts +26 -0
- package/lib/sandbox/commands/create.ts +46 -6
- package/lib/sandbox/commands/ls.ts +7 -0
- package/lib/sandbox/commands/rm.ts +1000 -87
- package/lib/sandbox/commands/show.ts +16 -0
- package/lib/sandbox/control/client.ts +103 -9
- package/lib/sandbox/control/completed-reentry.ts +98 -0
- package/lib/sandbox/control/container-identity.ts +27 -43
- package/lib/sandbox/control/executor.ts +177 -12
- package/lib/sandbox/control/lifecycle.ts +629 -17
- package/lib/sandbox/control/native-file-lock.ts +209 -0
- package/lib/sandbox/control/protocol.ts +5 -2
- package/lib/sandbox/control/server.ts +319 -37
- package/lib/sandbox/control/state.ts +6 -3
- package/lib/sandbox/control/task-view.ts +240 -0
- package/lib/sandbox/engines/authority.ts +365 -0
- package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/sandbox/recovery.ts +21 -1
- package/lib/sandbox/workspace-view.ts +9 -3
- package/lib/sandbox/worktree-safety.ts +4 -1
- package/lib/server/index.ts +10 -0
- package/lib/task/artifact-lifecycle.ts +38 -16
- package/lib/task/artifact-operations.ts +626 -0
- package/lib/task/artifact-repair-intent.ts +71 -0
- package/lib/task/artifact-schema.ts +192 -0
- package/lib/task/capabilities.ts +70 -1
- package/lib/task/control-authority.ts +11 -1
- package/lib/task/create-service.ts +131 -6
- package/lib/task/create.ts +27 -0
- package/lib/task/events.ts +161 -65
- package/lib/task/index.ts +16 -3
- package/lib/task/local-artifact-finalization.ts +87 -218
- package/lib/task/orchestration.ts +172 -49
- package/lib/task/qualification-audit.ts +599 -0
- package/lib/task/qualification-confirmation.ts +117 -0
- package/lib/task/qualification-intents.ts +150 -0
- package/lib/task/review-artifacts.ts +21 -22
- package/lib/task/review-finalization.ts +133 -4
- package/lib/task/sections.ts +6 -4
- package/lib/task/verification-config.ts +12 -0
- package/lib/task/verification-engine.ts +55 -4
- package/package.json +6 -1
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/decision-qualification.en.md +22 -0
- package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
- package/templates/.agents/rules/evidence-reporting.en.md +28 -0
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
- package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
- package/templates/.agents/rules/security-alerts.en.md +8 -8
- package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
- package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +9 -22
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
- package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
- package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
- package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
- package/templates/.agents/skills/test/SKILL.en.md +13 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
- package/templates/.agents/templates/task.en.md +6 -0
- package/templates/.agents/templates/task.zh-CN.md +6 -0
- package/templates/.agents/scripts/security-alerts.github.sh +0 -151
- package/templates/.agents/scripts/security-alerts.sh +0 -31
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
- 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
|
-
?
|
|
47
|
-
: `<details><summary>元数据 (frontmatter)</summary>\n\n
|
|
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
|
|
73
|
-
while (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
433
|
-
return value
|
|
434
|
-
.replaceAll('&', '&')
|
|
435
|
-
.replaceAll('<', '<')
|
|
436
|
-
.replaceAll('>', '>')
|
|
437
|
-
.replaceAll('"', '"')
|
|
438
|
-
.replaceAll("'", ''')
|
|
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>${
|
|
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>${
|
|
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' ? '通过' : '需复核'}。${
|
|
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, '&')
|
|
20
|
-
.replace(/</g, '<')
|
|
21
|
-
.replace(/>/g, '>')
|
|
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
|
|
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 {
|
|
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;
|