@fitlab-ai/agent-infra 0.9.15 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +4 -4
- package/dist/lib/internal/task-operation-registry.js +22 -13
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +12 -7
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +89 -31
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +45 -7
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +87 -35
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +11 -30
- package/dist/lib/task/artifact-operations.js +17 -231
- package/dist/lib/task/artifact-recovery.js +476 -0
- package/dist/lib/task/artifact-repair-intent.js +90 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +39 -8
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +165 -23
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +91 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +212 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +3 -2
- package/dist/lib/task/review-finalization.js +294 -74
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +3 -14
- package/dist/lib/task/workflow-command.js +2 -1
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +5 -4
- package/lib/internal/task-operation-registry.ts +22 -13
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +11 -7
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +105 -30
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +48 -11
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +6 -1
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +106 -35
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +12 -29
- package/lib/task/artifact-operations.ts +22 -252
- package/lib/task/artifact-recovery.ts +572 -0
- package/lib/task/artifact-repair-intent.ts +126 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +48 -15
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +188 -24
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +66 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +249 -131
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +2 -1
- package/lib/task/review-finalization.ts +298 -84
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +2 -1
- package/lib/task/workflow-dispatch.ts +171 -0
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +12 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
- package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { parseReviewCommand } from '../task/review-command.js';
|
|
2
|
-
import { finalizeReviewSummary } from '../task/review-finalization.js';
|
|
2
|
+
import { finalizeReviewSummary, preflightReviewSummary } from '../task/review-finalization.js';
|
|
3
3
|
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
4
4
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
5
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
6
6
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
|
|
7
|
-
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
7
|
+
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--recovery-id <id>] [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
8
8
|
function failUsage(message) {
|
|
9
9
|
process.stdout.write(`${JSON.stringify({
|
|
10
10
|
status: 'failed',
|
|
11
11
|
changed: false,
|
|
12
|
-
intent: '
|
|
12
|
+
intent: 'review-operation',
|
|
13
13
|
error: { code: 'REVIEW_PAYLOAD_INVALID', message }
|
|
14
14
|
})}\n`);
|
|
15
15
|
process.stderr.write(USAGE);
|
|
@@ -30,7 +30,8 @@ async function taskReview(args = []) {
|
|
|
30
30
|
failUsage(error instanceof Error ? error.message : String(error));
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
const { stage, artifact, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
|
|
33
|
+
const { stage, artifact, recoveryId, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
|
|
34
|
+
const operation = args[1];
|
|
34
35
|
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
35
36
|
if (dryRunConflict) {
|
|
36
37
|
failUsage(dryRunConflict.message);
|
|
@@ -44,8 +45,12 @@ async function taskReview(args = []) {
|
|
|
44
45
|
let result;
|
|
45
46
|
let humanOverride = null;
|
|
46
47
|
try {
|
|
47
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId,
|
|
48
|
-
let current =
|
|
48
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-review.${operation}`, async () => {
|
|
49
|
+
let current = operation === 'preflight'
|
|
50
|
+
? preflightReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true })
|
|
51
|
+
: finalizeReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
52
|
+
if (operation === 'preflight')
|
|
53
|
+
return current;
|
|
49
54
|
if (current.status !== 'failed' || !overrideTicket)
|
|
50
55
|
return current;
|
|
51
56
|
if (!overrideTarget || !overrideScope) {
|
|
@@ -58,7 +63,7 @@ async function taskReview(args = []) {
|
|
|
58
63
|
target: overrideTarget, scope: overrideScope
|
|
59
64
|
}, {
|
|
60
65
|
effectExecutor: (capability) => {
|
|
61
|
-
const retried = finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
66
|
+
const retried = finalizeReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
62
67
|
current = retried;
|
|
63
68
|
return retried.status === 'failed' || retried.status === 'planned'
|
|
64
69
|
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { locateActivityLog } from '../task/activity-log.js';
|
|
3
|
+
const METADATA_PREFIX = '<!-- activity-recovery:v1:';
|
|
4
|
+
const METADATA_SUFFIX = ' -->';
|
|
5
|
+
function canonicalJson(value) {
|
|
6
|
+
if (value === null || typeof value !== 'object')
|
|
7
|
+
return JSON.stringify(value);
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
10
|
+
const record = value;
|
|
11
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
12
|
+
}
|
|
13
|
+
function sha256(value) {
|
|
14
|
+
return createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex');
|
|
15
|
+
}
|
|
16
|
+
function baseStep(step) {
|
|
17
|
+
return step.replace(/\s*\[(?:started|aborted)\]\s*$/, '');
|
|
18
|
+
}
|
|
19
|
+
function entriesForArtifact(taskContent, artifact) {
|
|
20
|
+
const log = locateActivityLog(taskContent);
|
|
21
|
+
if (!log)
|
|
22
|
+
return [];
|
|
23
|
+
const selected = new Set();
|
|
24
|
+
for (let index = 0; index < log.entries.length; index += 1) {
|
|
25
|
+
const entry = log.entries[index];
|
|
26
|
+
if (!entry.note.includes(`→ ${artifact}`))
|
|
27
|
+
continue;
|
|
28
|
+
selected.add(index);
|
|
29
|
+
const step = baseStep(entry.step);
|
|
30
|
+
let startedAt = index;
|
|
31
|
+
for (let prior = index - 1; prior >= 0; prior -= 1) {
|
|
32
|
+
const candidate = log.entries[prior];
|
|
33
|
+
if (baseStep(candidate.step) !== step)
|
|
34
|
+
continue;
|
|
35
|
+
if (/\[started\]\s*$/.test(candidate.step)) {
|
|
36
|
+
selected.add(prior);
|
|
37
|
+
startedAt = prior;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (/^code(?:-r\d+)?\.md$/.test(artifact)) {
|
|
42
|
+
for (let between = startedAt + 1; between < index; between += 1) {
|
|
43
|
+
if (baseStep(log.entries[between].step) === 'Commit')
|
|
44
|
+
selected.add(between);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return log.entries.filter((_entry, index) => selected.has(index));
|
|
49
|
+
}
|
|
50
|
+
function recordForEntries(taskId, entries, artifact) {
|
|
51
|
+
if (entries.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const base = { version: 1, taskId, ...(artifact ? { artifact } : {}), entries: [...entries] };
|
|
54
|
+
return { ...base, sha256: sha256(base) };
|
|
55
|
+
}
|
|
56
|
+
function recordForArtifact(taskId, taskContent, artifact) {
|
|
57
|
+
return recordForEntries(taskId, entriesForArtifact(taskContent, artifact), artifact);
|
|
58
|
+
}
|
|
59
|
+
function entriesForTask(taskContent) {
|
|
60
|
+
const log = locateActivityLog(taskContent);
|
|
61
|
+
if (!log)
|
|
62
|
+
return [];
|
|
63
|
+
const linked = new Set(entriesForArtifactNames(log.entries));
|
|
64
|
+
return log.entries.filter((entry, index) => !linked.has(index)
|
|
65
|
+
&& !['Create PR', 'Complete Task', 'Cancel Task'].includes(baseStep(entry.step)));
|
|
66
|
+
}
|
|
67
|
+
function entriesForSummary(taskContent) {
|
|
68
|
+
const log = locateActivityLog(taskContent);
|
|
69
|
+
return log?.entries.filter((entry) => ['Create PR', 'Complete Task'].includes(baseStep(entry.step))) ?? [];
|
|
70
|
+
}
|
|
71
|
+
function entriesForCancel(taskContent) {
|
|
72
|
+
const log = locateActivityLog(taskContent);
|
|
73
|
+
return log?.entries.filter((entry) => baseStep(entry.step) === 'Cancel Task') ?? [];
|
|
74
|
+
}
|
|
75
|
+
function recordForTask(taskId, taskContent) {
|
|
76
|
+
return recordForEntries(taskId, entriesForTask(taskContent));
|
|
77
|
+
}
|
|
78
|
+
function recordForSummary(taskId, taskContent) {
|
|
79
|
+
return recordForEntries(taskId, entriesForSummary(taskContent));
|
|
80
|
+
}
|
|
81
|
+
function recordForCancel(taskId, taskContent) {
|
|
82
|
+
return recordForEntries(taskId, entriesForCancel(taskContent));
|
|
83
|
+
}
|
|
84
|
+
function entriesForArtifactNames(entries) {
|
|
85
|
+
const selected = new Set();
|
|
86
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
87
|
+
const entry = entries[index];
|
|
88
|
+
const artifact = /→ ([A-Za-z0-9][A-Za-z0-9._-]*\.md)(?:\s|$)/.exec(entry.note)?.[1];
|
|
89
|
+
if (!artifact)
|
|
90
|
+
continue;
|
|
91
|
+
selected.add(index);
|
|
92
|
+
const step = baseStep(entry.step);
|
|
93
|
+
let startedAt = index;
|
|
94
|
+
for (let prior = index - 1; prior >= 0; prior -= 1) {
|
|
95
|
+
const candidate = entries[prior];
|
|
96
|
+
if (baseStep(candidate.step) !== step)
|
|
97
|
+
continue;
|
|
98
|
+
if (/\[started\]\s*$/.test(candidate.step)) {
|
|
99
|
+
selected.add(prior);
|
|
100
|
+
startedAt = prior;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
if (/^code(?:-r\d+)?\.md$/.test(artifact)) {
|
|
105
|
+
for (let between = startedAt + 1; between < index; between += 1) {
|
|
106
|
+
if (baseStep(entries[between].step) === 'Commit')
|
|
107
|
+
selected.add(between);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...selected];
|
|
112
|
+
}
|
|
113
|
+
function renderActivityRecoveryMetadata(record) {
|
|
114
|
+
if (!record)
|
|
115
|
+
return '';
|
|
116
|
+
const encoded = Buffer.from(canonicalJson(record), 'utf8').toString('base64url');
|
|
117
|
+
const first = record.entries[0];
|
|
118
|
+
const last = record.entries.at(-1);
|
|
119
|
+
return [
|
|
120
|
+
'<details><summary>恢复元数据</summary>',
|
|
121
|
+
'',
|
|
122
|
+
`- 动作:${baseStep(last.step)}`,
|
|
123
|
+
`- 记录:${record.entries.length} 条(${first.time} 至 ${last.time})`,
|
|
124
|
+
...(record.artifact ? [`- 产物:${record.artifact}`] : []),
|
|
125
|
+
`- 校验:\`${record.sha256}\``,
|
|
126
|
+
'',
|
|
127
|
+
`${METADATA_PREFIX}${encoded}${METADATA_SUFFIX}`,
|
|
128
|
+
'</details>',
|
|
129
|
+
''
|
|
130
|
+
].join('\n');
|
|
131
|
+
}
|
|
132
|
+
function parseActivityRecoveryMetadata(body) {
|
|
133
|
+
const match = body.match(/<!-- activity-recovery:v1:([A-Za-z0-9_-]+) -->/);
|
|
134
|
+
if (!match)
|
|
135
|
+
return null;
|
|
136
|
+
let value;
|
|
137
|
+
try {
|
|
138
|
+
value = JSON.parse(Buffer.from(match[1], 'base64url').toString('utf8'));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata is not valid');
|
|
142
|
+
}
|
|
143
|
+
if (!value || typeof value !== 'object')
|
|
144
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata is invalid');
|
|
145
|
+
const record = value;
|
|
146
|
+
if (record.version !== 1 || typeof record.taskId !== 'string' || (record.artifact !== undefined && typeof record.artifact !== 'string')
|
|
147
|
+
|| !Array.isArray(record.entries) || typeof record.sha256 !== 'string') {
|
|
148
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata shape is invalid');
|
|
149
|
+
}
|
|
150
|
+
if (record.entries.some((entry) => !entry || typeof entry.time !== 'string' || typeof entry.step !== 'string' || typeof entry.agent !== 'string' || typeof entry.note !== 'string')) {
|
|
151
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata entries are invalid');
|
|
152
|
+
}
|
|
153
|
+
const base = { version: 1, taskId: record.taskId, ...(record.artifact ? { artifact: record.artifact } : {}), entries: record.entries };
|
|
154
|
+
if (sha256(base) !== record.sha256)
|
|
155
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata checksum does not match');
|
|
156
|
+
return { ...base, sha256: record.sha256 };
|
|
157
|
+
}
|
|
158
|
+
export { entriesForArtifact, entriesForCancel, entriesForSummary, entriesForTask, parseActivityRecoveryMetadata, recordForArtifact, recordForCancel, recordForEntries, recordForSummary, recordForTask, renderActivityRecoveryMetadata };
|
|
159
|
+
//# sourceMappingURL=activity-recovery.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { parseTaskFrontmatter } from '../task/frontmatter.js';
|
|
4
5
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
6
|
import { resolvePlatformProviderContext } from './context.js';
|
|
@@ -7,6 +8,8 @@ import { platformResult } from './types.js';
|
|
|
7
8
|
import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
|
|
8
9
|
import { resourceIdentityNumber } from './resource-identity.js';
|
|
9
10
|
import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
|
|
11
|
+
import { recordForArtifact, recordForCancel, recordForSummary, recordForTask, renderActivityRecoveryMetadata } from './activity-recovery.js';
|
|
12
|
+
import { projectTaskComment } from './task-comment-projection.js';
|
|
10
13
|
import { canonicalizeCommentBody, escapeHtmlText, fenceRanges, renderSafeCodeFence } from './comment-safety.js';
|
|
11
14
|
function providerCommentId(id, provider) {
|
|
12
15
|
return provider.identity?.comment === 'number' && /^\d+$/.test(id) ? Number(id) : id;
|
|
@@ -16,8 +19,9 @@ const MARKERS = {
|
|
|
16
19
|
artifact: (taskId, stem) => `<!-- sync-issue:${taskId}:${stem} -->`,
|
|
17
20
|
artifactChunk: (taskId, stem, part, total) => `<!-- sync-issue:${taskId}:${stem}:${part}/${total} -->`,
|
|
18
21
|
summary: (taskId) => `<!-- sync-issue:${taskId}:summary -->`,
|
|
19
|
-
cancel: (taskId) => `<!-- sync-issue:${taskId}:cancel
|
|
22
|
+
cancel: (taskId) => `<!-- sync-issue:${taskId}:cancel -->`,
|
|
20
23
|
};
|
|
24
|
+
const COMMENT_BYTE_LIMIT = 60_000;
|
|
21
25
|
const ARTIFACT_TITLES = {
|
|
22
26
|
analysis: '需求分析',
|
|
23
27
|
'review-analysis': '需求分析审查',
|
|
@@ -47,22 +51,52 @@ function sanitizeCommentBody(value, label) {
|
|
|
47
51
|
function footer(agent, taskId) {
|
|
48
52
|
return `---\n*由 ${agent} 自动生成 · 内部追踪:${taskId}*`;
|
|
49
53
|
}
|
|
50
|
-
function
|
|
54
|
+
function taskFrontmatterSummary(language = '') {
|
|
55
|
+
return language === 'en' || language === 'en-US'
|
|
56
|
+
? 'Metadata (frontmatter)'
|
|
57
|
+
: '元数据 (frontmatter)';
|
|
58
|
+
}
|
|
59
|
+
function renderTaskCommentResult(content, taskId, language = '') {
|
|
51
60
|
const split = splitFrontmatter(content);
|
|
52
|
-
const
|
|
53
|
-
const
|
|
61
|
+
const projectedContent = split.frontmatter === null ? content : projectTaskComment(content).content;
|
|
62
|
+
const projected = splitFrontmatter(projectedContent);
|
|
63
|
+
const safeBody = sanitizeCommentBody(projected.body, 'task body');
|
|
64
|
+
const taskBody = projected.frontmatter === null
|
|
54
65
|
? safeBody
|
|
55
|
-
: `<details><summary
|
|
56
|
-
|
|
66
|
+
: `<details><summary>${taskFrontmatterSummary(language)}</summary>\n\n${renderSafeCodeFence(`---\n${projected.frontmatter}\n---`, 'yaml')}\n\n</details>\n\n${safeBody}`;
|
|
67
|
+
const recoveryMetadata = renderActivityRecoveryMetadata(recordForTask(taskId, content));
|
|
68
|
+
const body = normalizeCommentContent([
|
|
57
69
|
MARKERS.task(taskId),
|
|
58
70
|
'## 任务文件',
|
|
59
71
|
'',
|
|
60
|
-
`>
|
|
72
|
+
`> 任务同步 · ${taskId}`,
|
|
61
73
|
'',
|
|
74
|
+
...(recoveryMetadata ? [recoveryMetadata.replace(/\n+$/, ''), ''] : []),
|
|
62
75
|
taskBody.replace(/\n+$/, ''),
|
|
63
76
|
'',
|
|
64
|
-
|
|
77
|
+
'---',
|
|
78
|
+
`*由 agent-infra 自动生成 · 内部追踪:${taskId}*`
|
|
65
79
|
].join('\n'));
|
|
80
|
+
return {
|
|
81
|
+
body,
|
|
82
|
+
byteLength: Buffer.byteLength(body, 'utf8'),
|
|
83
|
+
sha256: createHash('sha256').update(body, 'utf8').digest('hex')
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function renderTaskComment(content, taskId, _agent, language = '') {
|
|
87
|
+
return renderTaskCommentResult(content, taskId, language).body;
|
|
88
|
+
}
|
|
89
|
+
function isTaskCommentTooLarge(content, taskId = 'TASK-UNKNOWN', _agent = 'codex') {
|
|
90
|
+
return renderTaskCommentResult(content, taskId).byteLength > COMMENT_BYTE_LIMIT;
|
|
91
|
+
}
|
|
92
|
+
function taskCommentLanguage(repoRoot) {
|
|
93
|
+
try {
|
|
94
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
95
|
+
return typeof config.language === 'string' ? config.language.trim() : '';
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
66
100
|
}
|
|
67
101
|
function artifactIdentity(artifact) {
|
|
68
102
|
const stem = path.basename(artifact, '.md');
|
|
@@ -201,7 +235,7 @@ function independentChunkContent(piece, fences, maxBytes) {
|
|
|
201
235
|
}
|
|
202
236
|
return content;
|
|
203
237
|
}
|
|
204
|
-
function buildArtifactChunk(taskId, stem, title, agent, content, part, total, chunked, backfill, renderedContent = content) {
|
|
238
|
+
function buildArtifactChunk(taskId, stem, title, agent, content, part, total, chunked, backfill, renderedContent = content, recoveryMetadata = '') {
|
|
205
239
|
const marker = chunked ? MARKERS.artifactChunk(taskId, stem, part, total) : MARKERS.artifact(taskId, stem);
|
|
206
240
|
const heading = chunked ? `## ${title}(${part}/${total})` : `## ${title}`;
|
|
207
241
|
const timelineHint = backfill ? '> 历史产物补发' : null;
|
|
@@ -212,6 +246,7 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
|
|
|
212
246
|
'',
|
|
213
247
|
`> **${agent}** · ${taskId}`,
|
|
214
248
|
'',
|
|
249
|
+
...(recoveryMetadata ? [recoveryMetadata] : []),
|
|
215
250
|
renderedContent,
|
|
216
251
|
'',
|
|
217
252
|
footer(agent, taskId)
|
|
@@ -219,10 +254,10 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
|
|
|
219
254
|
return { marker, body, content, part, total };
|
|
220
255
|
}
|
|
221
256
|
function chunkArtifactComment(input) {
|
|
222
|
-
const byteLimit = input.byteLimit ||
|
|
257
|
+
const byteLimit = input.byteLimit || COMMENT_BYTE_LIMIT;
|
|
223
258
|
const identity = artifactIdentity(input.artifact);
|
|
224
259
|
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));
|
|
260
|
+
const single = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, safeBody, 1, 1, false, Boolean(input.backfill), safeBody, input.recoveryMetadata);
|
|
226
261
|
if (Buffer.byteLength(single.body, 'utf8') <= byteLimit)
|
|
227
262
|
return [single];
|
|
228
263
|
let total = 2;
|
|
@@ -231,7 +266,7 @@ function chunkArtifactComment(input) {
|
|
|
231
266
|
if (!ranges.ok)
|
|
232
267
|
throw new Error(`${ranges.error.code}: artifact body: ${ranges.error.message} at offset ${ranges.error.offset}`);
|
|
233
268
|
for (;;) {
|
|
234
|
-
const probe = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, '',
|
|
269
|
+
const probe = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, '', 1, total, true, Boolean(input.backfill), '', input.recoveryMetadata);
|
|
235
270
|
const available = byteLimit - Buffer.byteLength(probe.body, 'utf8');
|
|
236
271
|
const sourceLimit = Math.min(available, payloadLimit);
|
|
237
272
|
if (sourceLimit <= 0)
|
|
@@ -241,7 +276,7 @@ function chunkArtifactComment(input) {
|
|
|
241
276
|
total = pieces.length;
|
|
242
277
|
continue;
|
|
243
278
|
}
|
|
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)));
|
|
279
|
+
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), index === 0 ? input.recoveryMetadata : ''));
|
|
245
280
|
const overflow = Math.max(...chunks.map((chunk) => Buffer.byteLength(chunk.body, 'utf8') - byteLimit));
|
|
246
281
|
if (overflow <= 0)
|
|
247
282
|
return chunks;
|
|
@@ -257,11 +292,6 @@ function flattenComments(value) {
|
|
|
257
292
|
const flattened = value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]);
|
|
258
293
|
return flattened.filter((entry) => Boolean(entry && typeof entry === 'object' && (typeof entry.id === 'number' || typeof entry.id === 'string') && typeof entry.body === 'string'));
|
|
259
294
|
}
|
|
260
|
-
function issueNumberFromTask(content) {
|
|
261
|
-
const value = parseTaskFrontmatter(content).issue_number;
|
|
262
|
-
const number = Number(value);
|
|
263
|
-
return Number.isInteger(number) && number > 0 ? number : null;
|
|
264
|
-
}
|
|
265
295
|
function listRemoteComments(client, repo, issue, cwd) {
|
|
266
296
|
const result = client.json([
|
|
267
297
|
'api', '--paginate', '--slurp', `repos/${repo}/issues/${issue}/comments?per_page=100`
|
|
@@ -277,9 +307,11 @@ function contextFields(context) {
|
|
|
277
307
|
function hasResolvedPlatformContext(context) {
|
|
278
308
|
return context.status === 'degraded' || (context.status === 'no-op' && context.error === null);
|
|
279
309
|
}
|
|
280
|
-
function bodyEnvelope(marker, title, taskId, agent, body) {
|
|
310
|
+
function bodyEnvelope(marker, title, taskId, agent, body, recoveryMetadata = '') {
|
|
281
311
|
return normalizeCommentContent([
|
|
282
|
-
marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '',
|
|
312
|
+
marker, `## ${title}`, '', `> **${agent}** · ${taskId}`, '',
|
|
313
|
+
...(recoveryMetadata ? [recoveryMetadata.replace(/\n+$/, ''), ''] : []),
|
|
314
|
+
sanitizeCommentBody(body, `${title} body`).replace(/\n+$/, ''), '', footer(agent, taskId)
|
|
283
315
|
].join('\n'));
|
|
284
316
|
}
|
|
285
317
|
function resolveArtifactBody(taskDir, artifact) {
|
|
@@ -296,9 +328,9 @@ function resolveArtifactBody(taskDir, artifact) {
|
|
|
296
328
|
throw new Error('artifact must stay inside the task directory');
|
|
297
329
|
return fs.readFileSync(realArtifact, 'utf8');
|
|
298
330
|
}
|
|
299
|
-
function expectedComments(taskId, taskContent, taskDir, options) {
|
|
331
|
+
function expectedComments(taskId, taskContent, taskDir, repoRoot, options) {
|
|
300
332
|
if (options.kind === 'task') {
|
|
301
|
-
const body = renderTaskComment(taskContent, taskId, options.agent);
|
|
333
|
+
const body = renderTaskComment(taskContent, taskId, options.agent, taskCommentLanguage(repoRoot));
|
|
302
334
|
return [{ marker: MARKERS.task(taskId), body, content: taskContent, part: 1, total: 1 }];
|
|
303
335
|
}
|
|
304
336
|
if (options.kind === 'artifact') {
|
|
@@ -309,14 +341,18 @@ function expectedComments(taskId, taskContent, taskDir, options) {
|
|
|
309
341
|
artifact: options.artifact,
|
|
310
342
|
agent: options.agent,
|
|
311
343
|
body: resolveArtifactBody(taskDir, options.artifact),
|
|
312
|
-
backfill: options.backfill
|
|
344
|
+
backfill: options.backfill,
|
|
345
|
+
recoveryMetadata: renderActivityRecoveryMetadata(recordForArtifact(taskId, taskContent, options.artifact))
|
|
313
346
|
});
|
|
314
347
|
}
|
|
315
348
|
if (options.body === undefined)
|
|
316
349
|
throw new Error(`${options.kind} sync requires a body`);
|
|
317
350
|
const marker = options.kind === 'summary' ? MARKERS.summary(taskId) : MARKERS.cancel(taskId);
|
|
318
351
|
const title = options.kind === 'summary' ? '交付摘要' : '任务取消';
|
|
319
|
-
const
|
|
352
|
+
const recoveryMetadata = options.kind === 'summary'
|
|
353
|
+
? renderActivityRecoveryMetadata(recordForSummary(taskId, taskContent))
|
|
354
|
+
: renderActivityRecoveryMetadata(recordForCancel(taskId, taskContent));
|
|
355
|
+
const body = bodyEnvelope(marker, title, taskId, options.agent, options.body, recoveryMetadata);
|
|
320
356
|
return [{ marker, body, content: options.body, part: 1, total: 1 }];
|
|
321
357
|
}
|
|
322
358
|
function markerPrefix(taskId, options) {
|
|
@@ -374,19 +410,19 @@ async function syncPlatformComment(taskRef, options) {
|
|
|
374
410
|
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
375
411
|
let issueIdentityFromTask;
|
|
376
412
|
try {
|
|
377
|
-
issueIdentityFromTask = taskIssueIdentity(parseTaskFrontmatter(taskContent)
|
|
413
|
+
issueIdentityFromTask = taskIssueIdentity(parseTaskFrontmatter(taskContent));
|
|
378
414
|
}
|
|
379
415
|
catch (error) {
|
|
380
416
|
return platformResult('failed', { error: { ...taskIssueIdentityError(error), retryable: false } });
|
|
381
417
|
}
|
|
382
418
|
if (!issueIdentityFromTask) {
|
|
383
419
|
return platformResult('no-op', {
|
|
384
|
-
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid
|
|
420
|
+
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
|
|
385
421
|
});
|
|
386
422
|
}
|
|
387
423
|
let desired;
|
|
388
424
|
try {
|
|
389
|
-
desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, options);
|
|
425
|
+
desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, resolved.repoRoot, options);
|
|
390
426
|
}
|
|
391
427
|
catch (error) {
|
|
392
428
|
return platformResult('failed', {
|
|
@@ -394,6 +430,13 @@ async function syncPlatformComment(taskRef, options) {
|
|
|
394
430
|
error: { code: 'COMMENT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
395
431
|
});
|
|
396
432
|
}
|
|
433
|
+
if (options.kind === 'task' && Buffer.byteLength(desired[0].body, 'utf8') > COMMENT_BYTE_LIMIT) {
|
|
434
|
+
return platformResult('failed', {
|
|
435
|
+
resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentityFromTask), identity: issueIdentityFromTask },
|
|
436
|
+
operations: [{ name: `comment:${MARKERS.task(resolved.taskId)}`, status: 'skipped', reasonCode: 'COMMENT_PAYLOAD_TOO_LARGE' }],
|
|
437
|
+
error: { code: 'COMMENT_PAYLOAD_TOO_LARGE', message: 'Projected task comment exceeds the platform byte limit', retryable: false }
|
|
438
|
+
});
|
|
439
|
+
}
|
|
397
440
|
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
398
441
|
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
399
442
|
if (!hasResolvedPlatformContext(context) || !loaded.ok)
|
|
@@ -424,6 +467,20 @@ async function syncPlatformComment(taskRef, options) {
|
|
|
424
467
|
error: { code: 'COMMENT_MARKER_CONFLICT', message: 'Multiple comments use the same registered marker', retryable: false }
|
|
425
468
|
});
|
|
426
469
|
}
|
|
470
|
+
if (options.kind === 'task' && existing.length === 1) {
|
|
471
|
+
const owner = existing[0].user?.login;
|
|
472
|
+
if (!owner || !context.platform.currentUser || owner !== context.platform.currentUser) {
|
|
473
|
+
return platformResult('blocked', {
|
|
474
|
+
...contextFields(context),
|
|
475
|
+
resource: { kind: 'issue', number: issue, identity: issueIdentityFromTask },
|
|
476
|
+
error: {
|
|
477
|
+
code: 'COMMENT_OWNER_CONFLICT',
|
|
478
|
+
message: owner ? `Task comment is owned by '${owner}'` : 'Task comment owner is unavailable',
|
|
479
|
+
retryable: false
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
427
484
|
// Backfill only supplies missing artifact comments; valid existing marker sets stay untouched.
|
|
428
485
|
if (options.kind === 'artifact' && options.backfill && existing.length > 0) {
|
|
429
486
|
const operations = existing.map((comment) => ({
|
|
@@ -499,14 +556,15 @@ async function syncPlatformComment(taskRef, options) {
|
|
|
499
556
|
operations.push({ name: `comment:${comment.id}`, status: 'applied', reasonCode: 'STALE_CHUNK_DELETED' });
|
|
500
557
|
}
|
|
501
558
|
const changed = operations.some((operation) => operation.status === 'applied');
|
|
502
|
-
|
|
559
|
+
const result = platformResult(changed ? 'applied' : 'no-op', {
|
|
503
560
|
...contextFields(context),
|
|
504
561
|
changed,
|
|
505
562
|
resource: { kind: 'issue', number: issue },
|
|
506
563
|
operations,
|
|
507
|
-
comment: { kind: options.kind, marker: desired[0].
|
|
564
|
+
comment: { kind: options.kind, marker: desired[0]?.marker ?? markerPrefix(resolved.taskId, options), ids, parts: desired.length },
|
|
508
565
|
error: null
|
|
509
566
|
});
|
|
567
|
+
return result;
|
|
510
568
|
}
|
|
511
569
|
async function listPlatformComments(issue, cwd = process.cwd(), client) {
|
|
512
570
|
const loaded = await resolvePlatformProviderContext({ cwd, client });
|
|
@@ -541,7 +599,7 @@ async function checkPlatformCommentOwner(taskRef, options = {}) {
|
|
|
541
599
|
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
542
600
|
let issueIdentity;
|
|
543
601
|
try {
|
|
544
|
-
issueIdentity = taskIssueIdentity(parseTaskFrontmatter(content)
|
|
602
|
+
issueIdentity = taskIssueIdentity(parseTaskFrontmatter(content));
|
|
545
603
|
}
|
|
546
604
|
catch (error) {
|
|
547
605
|
return platformResult('failed', { error: { ...taskIssueIdentityError(error), retryable: false } });
|
|
@@ -572,5 +630,5 @@ async function checkPlatformCommentOwner(taskRef, options = {}) {
|
|
|
572
630
|
}
|
|
573
631
|
return platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity }, error: null });
|
|
574
632
|
}
|
|
575
|
-
export { MARKERS, chunkArtifactComment, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, syncPlatformComment, validateRelatedMarkerSet, writeComment };
|
|
633
|
+
export { COMMENT_BYTE_LIMIT, MARKERS, chunkArtifactComment, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, renderTaskCommentResult, isTaskCommentTooLarge, syncPlatformComment, validateRelatedMarkerSet, writeComment };
|
|
576
634
|
//# sourceMappingURL=issue-comments.js.map
|
|
@@ -32,7 +32,7 @@ async function resolvedContext(taskRef, options) {
|
|
|
32
32
|
const frontmatter = parseTaskFrontmatter(content);
|
|
33
33
|
let issueIdentity;
|
|
34
34
|
try {
|
|
35
|
-
issueIdentity = taskIssueIdentity(frontmatter
|
|
35
|
+
issueIdentity = taskIssueIdentity(frontmatter);
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
return { ok: false, output: result('failed', resolved.taskId, null, { error: { ...taskIssueIdentityError(error), retryable: false } }) };
|
|
@@ -3,8 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { escapeHtmlText } from './comment-safety.js';
|
|
6
|
+
import { isResourceIdentity } from './resource-identity.js';
|
|
6
7
|
const REPORT_FILE_NAME = 'pr-change-report.json';
|
|
7
|
-
const REPORT_VERSION =
|
|
8
|
+
const REPORT_VERSION = 2;
|
|
8
9
|
const CANONICAL_REPORT_PLACEHOLDER = '<!-- canonical-pr-change-report -->';
|
|
9
10
|
const CANONICAL_REPORT_HEADING = '### PR 代码增减';
|
|
10
11
|
const PRECHECK_IDS = [
|
|
@@ -211,8 +212,8 @@ function validatePrChangeReport(value) {
|
|
|
211
212
|
}
|
|
212
213
|
const identity = value.identity;
|
|
213
214
|
const identityRepository = identity.repository;
|
|
214
|
-
const
|
|
215
|
-
if (!isNonEmptyString(identityRepository) ||
|
|
215
|
+
const identityResource = identity.resource;
|
|
216
|
+
if (!isNonEmptyString(identityRepository) || !isResourceIdentity(identityResource))
|
|
216
217
|
return invalid('change report identity is invalid');
|
|
217
218
|
const parseIdentityPart = (part, name) => {
|
|
218
219
|
if (!isRecord(part) || !isNonEmptyString(part.repository) || !isNonEmptyString(part.ref) || !isSha(part.sha))
|
|
@@ -259,7 +260,7 @@ function validatePrChangeReport(value) {
|
|
|
259
260
|
version: REPORT_VERSION,
|
|
260
261
|
identity: {
|
|
261
262
|
repository: identityRepository,
|
|
262
|
-
|
|
263
|
+
resource: identityResource,
|
|
263
264
|
base: base.value,
|
|
264
265
|
head: head.value
|
|
265
266
|
},
|