@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
|
@@ -11,6 +11,7 @@ import { normalizeCommentContent } from './issue-comments.js';
|
|
|
11
11
|
import { CONTROL_MARKER_PATTERN, escapeHtmlText, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.js';
|
|
12
12
|
import { platformResult } from './types.js';
|
|
13
13
|
import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
|
|
14
|
+
import { resourceIdentityEquals } from './resource-identity.js';
|
|
14
15
|
import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
|
|
15
16
|
import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
|
|
16
17
|
import { readManualValidationCompletion } from '../task/manual-validation-completion.js';
|
|
@@ -48,7 +49,8 @@ function readBoundTaskSnapshot(taskMdPath) {
|
|
|
48
49
|
const frontmatter = parseTypedTaskFrontmatter(taskContent);
|
|
49
50
|
const fact = readPrDeliveryFact(frontmatter);
|
|
50
51
|
const boundFact = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact : null;
|
|
51
|
-
|
|
52
|
+
const factError = fact.status === 'invalid' ? { code: fact.error.code, message: fact.error.message } : null;
|
|
53
|
+
return { taskContent, boundFact, factError };
|
|
52
54
|
}
|
|
53
55
|
function boundPrNumberForWarning(taskMdPath) {
|
|
54
56
|
try {
|
|
@@ -117,7 +119,7 @@ function isHardSummaryFailure(error) {
|
|
|
117
119
|
function inspectionError(error) {
|
|
118
120
|
return error.retryable ? 'blocked' : 'failed';
|
|
119
121
|
}
|
|
120
|
-
async function inspectBoundPullRequest(context, repoRoot,
|
|
122
|
+
async function inspectBoundPullRequest(context, repoRoot, prIdentity, loadedContext) {
|
|
121
123
|
if (!context.platform.repository) {
|
|
122
124
|
return { ok: false, status: context.status, error: context.error };
|
|
123
125
|
}
|
|
@@ -126,16 +128,15 @@ async function inspectBoundPullRequest(context, repoRoot, prNumber, loadedContex
|
|
|
126
128
|
: await resolvePlatformProviderContext({ cwd: repoRoot });
|
|
127
129
|
if (!loaded.ok)
|
|
128
130
|
return { ok: false, status: loaded.context.status, error: loaded.context.error };
|
|
129
|
-
const target = providerResourceToken(loaded.value.provider, 'pull-request', String(prNumber));
|
|
130
131
|
const inspected = loaded.value.provider.changeRequests?.inspect
|
|
131
|
-
? await loaded.value.provider.changeRequests.inspect({ context: providerOperationContext(loaded.value), target })
|
|
132
|
+
? await loaded.value.provider.changeRequests.inspect({ context: providerOperationContext(loaded.value), target: prIdentity })
|
|
132
133
|
: unsupportedProviderOperation(loaded.value.provider, 'changeRequests.inspect');
|
|
133
134
|
if (!inspected.ok) {
|
|
134
135
|
const error = providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED');
|
|
135
136
|
return { ok: false, status: inspectionError(error), error };
|
|
136
137
|
}
|
|
137
138
|
const remote = inspected.value;
|
|
138
|
-
const number = remote.number ?? resourceIdentityNumber(remote.identity) ??
|
|
139
|
+
const number = remote.number ?? resourceIdentityNumber(remote.identity) ?? resourceIdentityNumber(prIdentity) ?? 0;
|
|
139
140
|
const head = remote.head || { repository: context.platform.repository, ref: '', sha: remote.headSha || '' };
|
|
140
141
|
const base = remote.base || { repository: context.platform.repository, ref: '', sha: remote.baseSha || '' };
|
|
141
142
|
return {
|
|
@@ -143,6 +144,7 @@ async function inspectBoundPullRequest(context, repoRoot, prNumber, loadedContex
|
|
|
143
144
|
value: {
|
|
144
145
|
repository: context.platform.repository,
|
|
145
146
|
number,
|
|
147
|
+
identity: remote.identity ?? prIdentity,
|
|
146
148
|
nodeId: remote.id,
|
|
147
149
|
url: remote.displayUrl || '',
|
|
148
150
|
state: remote.state === 'closed' ? 'closed' : 'open',
|
|
@@ -163,20 +165,22 @@ async function inspectBoundPullRequest(context, repoRoot, prNumber, loadedContex
|
|
|
163
165
|
function sameIdentity(left, right) {
|
|
164
166
|
return JSON.stringify({
|
|
165
167
|
repository: left.repository,
|
|
166
|
-
|
|
168
|
+
identity: left.identity ?? null,
|
|
167
169
|
base: left.base,
|
|
168
170
|
head: left.head
|
|
169
171
|
}) === JSON.stringify({
|
|
170
172
|
repository: right.repository,
|
|
171
|
-
|
|
173
|
+
identity: right.identity ?? null,
|
|
172
174
|
base: right.base,
|
|
173
175
|
head: right.head
|
|
174
176
|
});
|
|
175
177
|
}
|
|
176
178
|
function identityFromSnapshot(snapshot) {
|
|
179
|
+
if (!snapshot.identity)
|
|
180
|
+
throw new Error('pull-request snapshot has no canonical identity');
|
|
177
181
|
return {
|
|
178
182
|
repository: snapshot.repository,
|
|
179
|
-
|
|
183
|
+
resource: snapshot.identity,
|
|
180
184
|
base: { ...snapshot.base },
|
|
181
185
|
head: { ...snapshot.head }
|
|
182
186
|
};
|
|
@@ -205,7 +209,7 @@ function currentReportCheck(report, snapshot, taskContent, repoRoot) {
|
|
|
205
209
|
if (!digest.ok)
|
|
206
210
|
return { ok: false, error: platformError(digest.error) };
|
|
207
211
|
const reportIdentity = report.identity;
|
|
208
|
-
if (reportIdentity.repository !== snapshot.repository || reportIdentity.
|
|
212
|
+
if (reportIdentity.repository !== snapshot.repository || !resourceIdentityEquals(reportIdentity.resource, snapshot.identity) ||
|
|
209
213
|
JSON.stringify(reportIdentity.base) !== JSON.stringify(snapshot.base) || JSON.stringify(reportIdentity.head) !== JSON.stringify(snapshot.head))
|
|
210
214
|
return {
|
|
211
215
|
ok: false,
|
|
@@ -263,14 +267,23 @@ async function summaryContext(taskRef, options = {}) {
|
|
|
263
267
|
};
|
|
264
268
|
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
265
269
|
const frontmatter = parseTypedTaskFrontmatter(taskContent);
|
|
266
|
-
const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
267
|
-
const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? resourceIdentityNumber(fact.fact.identity.resource) : null;
|
|
268
270
|
const reportPath = taskReportPath(resolved.taskDir);
|
|
271
|
+
const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
272
|
+
if (fact.status === 'invalid')
|
|
273
|
+
return {
|
|
274
|
+
...platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }),
|
|
275
|
+
task: { id: resolved.taskId, prNumber: null },
|
|
276
|
+
pullRequest: null,
|
|
277
|
+
changeReport: { status: 'invalid', path: reportPath, taskIntentSha256: null, reason: fact.error.message },
|
|
278
|
+
artifacts: []
|
|
279
|
+
};
|
|
280
|
+
const prIdentity = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
|
|
281
|
+
const prNumber = resourceIdentityNumber(prIdentity);
|
|
269
282
|
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
270
283
|
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
271
284
|
let pullRequest = null;
|
|
272
|
-
if (loaded.ok &&
|
|
273
|
-
const inspected = await inspectBoundPullRequest(context, resolved.repoRoot,
|
|
285
|
+
if (loaded.ok && prIdentity && context.platform.repository && ['no-op', 'degraded'].includes(context.status)) {
|
|
286
|
+
const inspected = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
274
287
|
if (inspected.ok)
|
|
275
288
|
pullRequest = inspected.value;
|
|
276
289
|
}
|
|
@@ -290,14 +303,15 @@ async function summaryContext(taskRef, options = {}) {
|
|
|
290
303
|
}
|
|
291
304
|
async function summaryCommentState(taskRef, options = {}) {
|
|
292
305
|
const context = await summaryContext(taskRef, options);
|
|
293
|
-
|
|
306
|
+
const prIdentity = context.pullRequest?.identity;
|
|
307
|
+
if (!prIdentity)
|
|
294
308
|
return { ...context, comment: null };
|
|
295
309
|
const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client });
|
|
296
310
|
if (!loaded.ok || !loaded.value.provider.comments?.list)
|
|
297
311
|
return { ...context, comment: null };
|
|
298
312
|
const listed = await loaded.value.provider.comments.list({
|
|
299
313
|
context: providerOperationContext(loaded.value),
|
|
300
|
-
parent:
|
|
314
|
+
parent: prIdentity
|
|
301
315
|
});
|
|
302
316
|
if (!listed.ok)
|
|
303
317
|
return { ...context, comment: null };
|
|
@@ -311,6 +325,21 @@ async function reportWrite(taskRef, options) {
|
|
|
311
325
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
312
326
|
if (!resolved.ok)
|
|
313
327
|
return { ...platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }), report: null };
|
|
328
|
+
let preflight;
|
|
329
|
+
try {
|
|
330
|
+
preflight = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
return {
|
|
334
|
+
...platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false } }),
|
|
335
|
+
report: null
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (preflight.factError)
|
|
339
|
+
return {
|
|
340
|
+
...platformResult('failed', { error: { code: preflight.factError.code, message: preflight.factError.message, retryable: false } }),
|
|
341
|
+
report: null
|
|
342
|
+
};
|
|
314
343
|
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
315
344
|
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
316
345
|
if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status))
|
|
@@ -318,20 +347,21 @@ async function reportWrite(taskRef, options) {
|
|
|
318
347
|
let knownPrNumber = null;
|
|
319
348
|
try {
|
|
320
349
|
return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
|
|
321
|
-
const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
322
|
-
if (
|
|
350
|
+
const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
351
|
+
if (factError)
|
|
323
352
|
return {
|
|
324
|
-
...basePlatformResult('failed', context, resolved.taskId, null, { code:
|
|
353
|
+
...basePlatformResult('failed', context, resolved.taskId, null, { code: factError.code, message: factError.message, retryable: false }),
|
|
325
354
|
report: null
|
|
326
355
|
};
|
|
327
|
-
|
|
328
|
-
if (!boundPrNumber)
|
|
356
|
+
if (!boundFact)
|
|
329
357
|
return {
|
|
330
|
-
...basePlatformResult('failed', context, resolved.taskId, null, { code: '
|
|
358
|
+
...basePlatformResult('failed', context, resolved.taskId, null, { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false }),
|
|
331
359
|
report: null
|
|
332
360
|
};
|
|
361
|
+
const boundPrIdentity = boundFact.identity.resource;
|
|
362
|
+
const boundPrNumber = resourceIdentityNumber(boundPrIdentity);
|
|
333
363
|
knownPrNumber = boundPrNumber;
|
|
334
|
-
const initial = await inspectBoundPullRequest(context, resolved.repoRoot,
|
|
364
|
+
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrIdentity, loaded.value);
|
|
335
365
|
if (!initial.ok)
|
|
336
366
|
return { ...basePlatformResult(initial.status, context, resolved.taskId, boundPrNumber, initial.error), report: null };
|
|
337
367
|
const digest = taskIntentDigest(taskContent);
|
|
@@ -364,7 +394,7 @@ async function reportWrite(taskRef, options) {
|
|
|
364
394
|
const built = buildPrChangeReport(identityFromSnapshot(initial.value), digest.value.sha256, mechanical.value, candidate.value);
|
|
365
395
|
if (!built.ok)
|
|
366
396
|
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(built.error)), report: null };
|
|
367
|
-
const final = await inspectBoundPullRequest(context, resolved.repoRoot,
|
|
397
|
+
const final = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrIdentity, loaded.value);
|
|
368
398
|
if (!final.ok)
|
|
369
399
|
return { ...basePlatformResult(final.status, context, resolved.taskId, boundPrNumber, final.error), report: null };
|
|
370
400
|
if (!sameIdentity(initial.value, final.value))
|
|
@@ -466,7 +496,12 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
466
496
|
}
|
|
467
497
|
try {
|
|
468
498
|
const execute = async () => {
|
|
469
|
-
const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
499
|
+
const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
500
|
+
if (factError)
|
|
501
|
+
return {
|
|
502
|
+
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: factError.code, message: factError.message, retryable: false } }),
|
|
503
|
+
result: null, warnings: []
|
|
504
|
+
};
|
|
470
505
|
if (!boundFact)
|
|
471
506
|
return {
|
|
472
507
|
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
|
|
@@ -474,14 +509,9 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
474
509
|
};
|
|
475
510
|
const prIdentity = boundFact.identity.resource;
|
|
476
511
|
const boundPrNumber = resourceIdentityNumber(prIdentity);
|
|
477
|
-
if (!boundPrNumber)
|
|
478
|
-
return {
|
|
479
|
-
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NUMBER_INVALID', message: 'Bound pull request has no numeric identifier', retryable: false } }),
|
|
480
|
-
result: null, warnings: []
|
|
481
|
-
};
|
|
482
512
|
knownPrNumber = boundPrNumber;
|
|
483
513
|
const prNumber = boundPrNumber;
|
|
484
|
-
const initial = await inspectBoundPullRequest(context, resolved.repoRoot,
|
|
514
|
+
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
485
515
|
if (!initial.ok)
|
|
486
516
|
return fail(initial.status, context, initial.error);
|
|
487
517
|
const manual = options.manualValidation;
|
|
@@ -491,6 +521,8 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
491
521
|
if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation))
|
|
492
522
|
return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
|
|
493
523
|
if (manual) {
|
|
524
|
+
if (prNumber === null)
|
|
525
|
+
return fail('failed', context, { code: 'MANUAL_VALIDATION_PR_NUMBER_REQUIRED', message: 'manual-validation summary requires a numeric pull-request identity', retryable: false }, prNumber);
|
|
494
526
|
if (manual.phase === 'final' && (!manual.transactionId || !manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha))
|
|
495
527
|
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
|
|
496
528
|
if (manual.phase === 'final') {
|
|
@@ -570,7 +602,7 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
570
602
|
? listed.value.find((comment) => comment.id === reconciliation.commentId)?.body ?? null
|
|
571
603
|
: null;
|
|
572
604
|
const info = { precheckVerdict: report.value.precheck.verdict, nextAction: report.value.precheck.route };
|
|
573
|
-
const beforeWrite = await inspectBoundPullRequest(context, resolved.repoRoot,
|
|
605
|
+
const beforeWrite = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
574
606
|
if (!beforeWrite.ok)
|
|
575
607
|
return fail(beforeWrite.status, context, beforeWrite.error, prNumber);
|
|
576
608
|
if (!sameIdentity(initial.value, beforeWrite.value))
|
|
@@ -605,7 +637,7 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
605
637
|
: unsupportedProviderOperation(loaded.value.provider, 'comments.write');
|
|
606
638
|
if (!written.ok)
|
|
607
639
|
return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
|
|
608
|
-
const after = await inspectBoundPullRequest(context, resolved.repoRoot,
|
|
640
|
+
const after = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
609
641
|
const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
|
|
610
642
|
const compensateWrittenSummary = async () => reconciliation.action === 'create'
|
|
611
643
|
? written.value.remoteId && loaded.value.provider.comments?.delete
|
|
@@ -252,7 +252,7 @@ async function resolvedContext(taskRef, options) {
|
|
|
252
252
|
const fact = factRead.status === 'valid' ? factRead.fact : null;
|
|
253
253
|
let issueIdentity;
|
|
254
254
|
try {
|
|
255
|
-
issueIdentity = taskIssueIdentity(frontmatter
|
|
255
|
+
issueIdentity = taskIssueIdentity(frontmatter);
|
|
256
256
|
}
|
|
257
257
|
catch (error) {
|
|
258
258
|
return { ok: false, output: result('failed', resolved.taskId, null, null, { error: { ...taskIssueIdentityError(error), retryable: false } }) };
|
|
@@ -776,7 +776,7 @@ async function resolveExternalPullRequest(taskRef, options) {
|
|
|
776
776
|
return externalResult(result('failed', base.resolved.taskId, null, base.prNumber, {
|
|
777
777
|
platform: base.context.platform,
|
|
778
778
|
capabilities: base.context.capabilities,
|
|
779
|
-
error: { code: 'EXTERNAL_DELIVERY_ISSUE_REQUIRED', message: 'External delivery requires a valid
|
|
779
|
+
error: { code: 'EXTERNAL_DELIVERY_ISSUE_REQUIRED', message: 'External delivery requires a valid platform issue identity', retryable: false }
|
|
780
780
|
}));
|
|
781
781
|
const inspected = base.provider.changeRequests?.listClosing
|
|
782
782
|
? await base.provider.changeRequests.listClosing({
|
|
@@ -1285,7 +1285,7 @@ function skipPlatformPullRequestFactUnlocked(resolved, options) {
|
|
|
1285
1285
|
}
|
|
1286
1286
|
let issueIdentity;
|
|
1287
1287
|
try {
|
|
1288
|
-
issueIdentity = taskIssueIdentity(frontmatter
|
|
1288
|
+
issueIdentity = taskIssueIdentity(frontmatter);
|
|
1289
1289
|
}
|
|
1290
1290
|
catch (error) {
|
|
1291
1291
|
return result('failed', resolved.taskId, null, null, { error: { ...taskIssueIdentityError(error), retryable: false } });
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const SNAPSHOT_FRONTMATTER = new Set([
|
|
3
|
+
'id', 'type', 'branch', 'workflow', 'status', 'created_at', 'updated_at',
|
|
4
|
+
'priority', 'effort', 'start_date', 'target_date', 'current_step', 'assigned_to',
|
|
5
|
+
'platform_issue_identity', 'delivery_remote', 'delivery_base_ref', 'pr_delivery_fact'
|
|
6
|
+
]);
|
|
7
|
+
const PROCESS_SECTION_ALIASES = new Set([
|
|
8
|
+
'活动日志', 'activity log',
|
|
9
|
+
'产物生命周期收据', 'artifact lifecycle receipts',
|
|
10
|
+
'审查分歧账本', 'review dispute ledger', 'review disagreement ledger',
|
|
11
|
+
'人工裁决', '人工裁决待办', 'human decisions', 'human decision pending', 'human decisions pending',
|
|
12
|
+
'实现输入', 'implementation input', 'implementation inputs',
|
|
13
|
+
'工作流告警', 'workflow warning', 'workflow warnings', 'warnings',
|
|
14
|
+
'返工意图', 'rework intent', 'rework intents',
|
|
15
|
+
'产物失效记录', 'artifact invalidation', 'artifact invalidation records',
|
|
16
|
+
'实现备注', 'implementation notes',
|
|
17
|
+
'审查反馈', 'review feedback'
|
|
18
|
+
]);
|
|
19
|
+
function normalize(content) {
|
|
20
|
+
return content.replace(/\r\n/g, '\n').replace(/\n+$/, '\n');
|
|
21
|
+
}
|
|
22
|
+
function splitFrontmatter(content) {
|
|
23
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
24
|
+
if (!match)
|
|
25
|
+
throw new Error('task comment projection requires task frontmatter');
|
|
26
|
+
return { frontmatter: match[1], body: content.slice(match[0].length) };
|
|
27
|
+
}
|
|
28
|
+
function projectFrontmatter(frontmatter) {
|
|
29
|
+
const lines = frontmatter.replace(/\r\n/g, '\n').split('\n');
|
|
30
|
+
const selected = lines.filter((line) => {
|
|
31
|
+
if (!line.trim() || line.trimStart().startsWith('#'))
|
|
32
|
+
return false;
|
|
33
|
+
const match = /^([^:\s][^:]*):/.exec(line);
|
|
34
|
+
return Boolean(match && SNAPSHOT_FRONTMATTER.has(match[1].trim()));
|
|
35
|
+
});
|
|
36
|
+
if (!selected.some((line) => /^id:/.test(line)))
|
|
37
|
+
throw new Error('task comment projection requires frontmatter id');
|
|
38
|
+
return selected.join('\n');
|
|
39
|
+
}
|
|
40
|
+
function projectBody(body) {
|
|
41
|
+
const lines = body.replace(/\r\n/g, '\n').split('\n');
|
|
42
|
+
const kept = [];
|
|
43
|
+
let excludedLevel = null;
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
46
|
+
if (heading) {
|
|
47
|
+
const level = heading[1].length;
|
|
48
|
+
if (excludedLevel !== null && level <= excludedLevel)
|
|
49
|
+
excludedLevel = null;
|
|
50
|
+
if (PROCESS_SECTION_ALIASES.has(heading[2].trim().toLowerCase())) {
|
|
51
|
+
excludedLevel = level;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (excludedLevel === null)
|
|
56
|
+
kept.push(line);
|
|
57
|
+
}
|
|
58
|
+
return kept.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
59
|
+
}
|
|
60
|
+
function projectTaskComment(content) {
|
|
61
|
+
const split = splitFrontmatter(content);
|
|
62
|
+
const projected = normalize(`---\n${projectFrontmatter(split.frontmatter)}\n---\n\n${projectBody(split.body)}`);
|
|
63
|
+
return {
|
|
64
|
+
content: projected,
|
|
65
|
+
byteLength: Buffer.byteLength(projected, 'utf8'),
|
|
66
|
+
sha256: createHash('sha256').update(projected, 'utf8').digest('hex')
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export { projectTaskComment };
|
|
70
|
+
//# sourceMappingURL=task-comment-projection.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { parseResourceIdentity } from './resource-identity.js';
|
|
2
|
-
import { isLegacyCompatibilityEnabled, legacyCompatibilityError, VERSION } from '../version.js';
|
|
3
2
|
import { parse as parseYaml } from 'yaml';
|
|
4
|
-
function taskIssueIdentity(frontmatter
|
|
3
|
+
function taskIssueIdentity(frontmatter) {
|
|
5
4
|
const serialized = frontmatter.platform_issue_identity;
|
|
6
5
|
if (typeof serialized === 'string' && serialized.trim()) {
|
|
7
6
|
try {
|
|
@@ -12,13 +11,7 @@ function taskIssueIdentity(frontmatter, _declaration, runtimeVersion = VERSION)
|
|
|
12
11
|
return null;
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
|
-
|
|
16
|
-
const number = typeof legacy === 'number' ? legacy : typeof legacy === 'string' && /^[1-9]\d*$/u.test(legacy) ? Number(legacy) : NaN;
|
|
17
|
-
// TODO(compat): Remove the numeric issue_number fallback before the first stable v1.0.0 release.
|
|
18
|
-
if (Number.isSafeInteger(number) && number > 0 && !isLegacyCompatibilityEnabled(runtimeVersion)) {
|
|
19
|
-
throw legacyCompatibilityError('issue_number', runtimeVersion);
|
|
20
|
-
}
|
|
21
|
-
return Number.isSafeInteger(number) && number > 0 ? { kind: 'number', value: number } : null;
|
|
14
|
+
return null;
|
|
22
15
|
}
|
|
23
16
|
function taskIssueIdentityError(error) {
|
|
24
17
|
const value = error && typeof error === 'object' ? error : {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { parseActivityRecoveryMetadata } from './activity-recovery.js';
|
|
2
|
+
function normalized(body) {
|
|
3
|
+
return body.replace(/\r\n/g, '\n').replace(/\n+$/, '\n');
|
|
4
|
+
}
|
|
5
|
+
function decodeRenderedText(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/</g, '<')
|
|
8
|
+
.replace(/>/g, '>')
|
|
9
|
+
.replace(/"/g, '"')
|
|
10
|
+
.replace(/'/g, "'")
|
|
11
|
+
.replace(/&/g, '&');
|
|
12
|
+
}
|
|
13
|
+
function taskSnapshot(taskId, body) {
|
|
14
|
+
const marker = `<!-- sync-issue:${taskId}:task -->`;
|
|
15
|
+
if (normalized(body).split('\n', 1)[0] !== marker)
|
|
16
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: task snapshot marker is invalid');
|
|
17
|
+
const frontmatter = body.match(/<details><summary>[^<]*<\/summary>\n\n```yaml\n---\n([\s\S]*?)\n---\n```\n\n<\/details>/);
|
|
18
|
+
if (!frontmatter)
|
|
19
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: task snapshot frontmatter is invalid');
|
|
20
|
+
const afterDetails = (frontmatter.index ?? 0) + frontmatter[0].length;
|
|
21
|
+
const footer = body.lastIndexOf('\n\n---\n*由 ');
|
|
22
|
+
if (footer <= afterDetails)
|
|
23
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: task snapshot envelope is invalid');
|
|
24
|
+
const projectedBody = body.slice(afterDetails, footer).replace(/^\n+|\n+$/g, '');
|
|
25
|
+
return `---\n${decodeRenderedText(frontmatter[1])}\n---\n\n${decodeRenderedText(projectedBody)}\n`;
|
|
26
|
+
}
|
|
27
|
+
function activityLine(entry) {
|
|
28
|
+
return `- ${entry.time} — **${entry.step}** by ${entry.agent} — ${entry.note}`;
|
|
29
|
+
}
|
|
30
|
+
function recoverTaskFromComments(input) {
|
|
31
|
+
const marker = `<!-- sync-issue:${input.taskId}:task -->`;
|
|
32
|
+
const snapshots = input.comments.filter((comment) => normalized(comment.body).split('\n', 1)[0] === marker);
|
|
33
|
+
if (snapshots.length !== 1)
|
|
34
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: exactly one task snapshot comment is required');
|
|
35
|
+
const author = snapshots[0].user?.login;
|
|
36
|
+
if (!author)
|
|
37
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: task snapshot author is unavailable');
|
|
38
|
+
const content = taskSnapshot(input.taskId, normalized(snapshots[0].body));
|
|
39
|
+
const entries = new Map();
|
|
40
|
+
for (const comment of input.comments) {
|
|
41
|
+
if (comment.user?.login !== author)
|
|
42
|
+
continue;
|
|
43
|
+
const record = parseActivityRecoveryMetadata(comment.body);
|
|
44
|
+
if (!record)
|
|
45
|
+
continue;
|
|
46
|
+
if (record.taskId !== input.taskId)
|
|
47
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata task identity is invalid');
|
|
48
|
+
for (const entry of record.entries)
|
|
49
|
+
entries.set(JSON.stringify(entry), entry);
|
|
50
|
+
}
|
|
51
|
+
const restored = [...entries.values()].sort((left, right) => left.time.localeCompare(right.time));
|
|
52
|
+
return `${content.replace(/\n+$/, '')}\n\n## 活动日志\n\n${restored.map(activityLine).join('\n')}\n`;
|
|
53
|
+
}
|
|
54
|
+
export { recoverTaskFromComments };
|
|
55
|
+
//# sourceMappingURL=task-recovery.js.map
|
|
@@ -10,6 +10,7 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.js";
|
|
|
10
10
|
import { readPrDeliveryFact } from "../task/pr-delivery-fact.js";
|
|
11
11
|
import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.js";
|
|
12
12
|
import { taskIssueIdentity } from "./task-identities.js";
|
|
13
|
+
import { renderTaskCommentResult } from "./issue-comments.js";
|
|
13
14
|
import { CONTROL_MARKER_PATTERN, canonicalizeCommentBody, renderSafeCodeFence, sanitizeMarkdownDocument } from "./comment-safety.js";
|
|
14
15
|
const CHECK_TYPE = "platform-sync";
|
|
15
16
|
const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
|
|
@@ -96,8 +97,8 @@ async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
|
|
|
96
97
|
}
|
|
97
98
|
const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
|
|
98
99
|
const prNumber = resourceIdentityNumber(prIdentity);
|
|
99
|
-
if (config.when === "
|
|
100
|
-
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no
|
|
100
|
+
if (config.when === "platform_issue_identity_exists" && !issueIdentity) {
|
|
101
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no platform_issue_identity") };
|
|
101
102
|
}
|
|
102
103
|
if (config.when === "pr_fact_bound" && !prIdentity) {
|
|
103
104
|
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
|
|
@@ -432,11 +433,17 @@ function checkTaskCommentContent(context, remoteData, shared) {
|
|
|
432
433
|
if (!comment) {
|
|
433
434
|
return shared.failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
434
435
|
}
|
|
435
|
-
|
|
436
|
-
|
|
436
|
+
let renderedTask;
|
|
437
|
+
try {
|
|
438
|
+
renderedTask = renderTaskCommentResult(context.task.content, context.task.metadata.id, loadProjectLanguage(shared));
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
437
441
|
return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
|
|
438
442
|
}
|
|
439
|
-
|
|
443
|
+
if (renderedTask.byteLength > 60_000) {
|
|
444
|
+
return shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed");
|
|
445
|
+
}
|
|
446
|
+
const expectedBody = shared.normalizeContent(extractCommentBody(renderedTask.body));
|
|
440
447
|
const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
|
|
441
448
|
if (expectedBody === commentBody) {
|
|
442
449
|
return null;
|
|
@@ -608,12 +615,21 @@ function extractCommentBody(commentBody) {
|
|
|
608
615
|
while (start < lines.length && lines[start].trim() === "") {
|
|
609
616
|
start += 1;
|
|
610
617
|
}
|
|
611
|
-
if (start < lines.length && /^> \*\*.+\*\* · .+$/.test(lines[start].trim())) {
|
|
618
|
+
if (start < lines.length && (/^> \*\*.+\*\* · .+$/.test(lines[start].trim()) || /^> 任务同步 · .+$/.test(lines[start].trim()))) {
|
|
612
619
|
start += 1;
|
|
613
620
|
}
|
|
614
621
|
while (start < lines.length && lines[start].trim() === "") {
|
|
615
622
|
start += 1;
|
|
616
623
|
}
|
|
624
|
+
if (lines[start]?.trim() === "<details><summary>恢复元数据</summary>") {
|
|
625
|
+
const metadataEnd = lines.indexOf("</details>", start + 1);
|
|
626
|
+
if (metadataEnd >= 0) {
|
|
627
|
+
start = metadataEnd + 1;
|
|
628
|
+
while (start < lines.length && lines[start].trim() === "") {
|
|
629
|
+
start += 1;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
617
633
|
let end = lines.length;
|
|
618
634
|
for (let index = lines.length - 1; index >= start; index -= 1) {
|
|
619
635
|
const trimmed = lines[index].trim();
|
|
@@ -785,7 +801,9 @@ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, sha
|
|
|
785
801
|
if (!baseRef) {
|
|
786
802
|
return { ok: false, type: "check_failed", message: "Task has no delivery_base_ref for in-label evidence" };
|
|
787
803
|
}
|
|
788
|
-
const
|
|
804
|
+
const branch = typeof task.metadata?.branch === "string" ? task.metadata.branch.trim() : "";
|
|
805
|
+
const gitCwd = branch ? worktreeForBranch(shared.repoRoot, branch) ?? taskDir : taskDir;
|
|
806
|
+
const changedFilesResult = gitText(["diff", `${baseRef}...HEAD`, "--name-only"], gitCwd);
|
|
789
807
|
if (!changedFilesResult.ok) {
|
|
790
808
|
return { ...changedFilesResult, type: "network_error" };
|
|
791
809
|
}
|
|
@@ -808,6 +826,26 @@ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, sha
|
|
|
808
826
|
}
|
|
809
827
|
return { ok: true, labels: planned.target, mode: "mapped" };
|
|
810
828
|
}
|
|
829
|
+
function worktreeForBranch(repositoryRoot, branch) {
|
|
830
|
+
try {
|
|
831
|
+
const records = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
832
|
+
cwd: repositoryRoot,
|
|
833
|
+
encoding: "utf8",
|
|
834
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
835
|
+
}).trim().split(/\r?\n\r?\n/);
|
|
836
|
+
const expectedRef = `refs/heads/${branch}`;
|
|
837
|
+
for (const record of records) {
|
|
838
|
+
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
839
|
+
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
840
|
+
if (worktree && branchRef === expectedRef)
|
|
841
|
+
return worktree;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
catch {
|
|
845
|
+
// Fall back to taskDir so ordinary in-repository task workspaces retain their behavior.
|
|
846
|
+
}
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
811
849
|
function loadInLabelMapping(shared) {
|
|
812
850
|
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
813
851
|
if (!fs.existsSync(configPath)) {
|
|
@@ -12,10 +12,6 @@ export class HostResolutionError extends Error {
|
|
|
12
12
|
this.code = code;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
function toPositiveNumber(value) {
|
|
16
|
-
const n = Number(value);
|
|
17
|
-
return Number.isInteger(n) && n > 0 ? n : null;
|
|
18
|
-
}
|
|
19
15
|
/**
|
|
20
16
|
* Parse the PR body for closing-issue references. Handles the conventional
|
|
21
17
|
* `Closes #1` / `Fixes: #2, #3` shapes (case-insensitive, comma/space
|
|
@@ -41,7 +37,7 @@ export function extractClosingIssueNumbers(body) {
|
|
|
41
37
|
/**
|
|
42
38
|
* Scan `.agents/workspace/active/{task-id}/task.md` files for tasks bound to the
|
|
43
39
|
* PR number or to one of the PR's closing issues. A task is emitted at most once;
|
|
44
|
-
* a verified fact identity hit takes priority over the issue-
|
|
40
|
+
* a verified fact identity hit takes priority over the issue-identity reverse lookup.
|
|
45
41
|
*/
|
|
46
42
|
export function collectHostCandidates(input) {
|
|
47
43
|
const activeRoot = path.join(input.workspaceRoot, '.agents', 'workspace', 'active');
|
|
@@ -73,7 +69,7 @@ export function collectHostCandidates(input) {
|
|
|
73
69
|
continue;
|
|
74
70
|
}
|
|
75
71
|
const issueIdentity = taskIssueIdentity(frontmatter);
|
|
76
|
-
const issueNumber = resourceIdentityNumber(issueIdentity)
|
|
72
|
+
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
77
73
|
const fact = readPrDeliveryFact(frontmatter);
|
|
78
74
|
const prIdentity = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
|
|
79
75
|
const prNumber = resourceIdentityNumber(prIdentity);
|
|
@@ -133,8 +129,8 @@ function isTrusted(head) {
|
|
|
133
129
|
/**
|
|
134
130
|
* Derive the S1(a) trust factor `taskIssueMatches` (PL-3) mechanically from
|
|
135
131
|
* resolved data: a unique host is trusted on the issue dimension only when the
|
|
136
|
-
* task's bound
|
|
137
|
-
* hosts and tasks without a
|
|
132
|
+
* task's bound issue identity is among the PR's closing issues. Ambiguous/none
|
|
133
|
+
* hosts and tasks without a numeric issue identity cannot be confirmed -> false
|
|
138
134
|
* (fail closed to S2/audit). `resolve-host` emits this value so the prompt layer
|
|
139
135
|
* does not derive the trust factor ad hoc (CD-2).
|
|
140
136
|
*/
|
|
@@ -2,11 +2,27 @@ import { parseArgs } from 'node:util';
|
|
|
2
2
|
import { loadConfig } from '../config.js';
|
|
3
3
|
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
|
|
4
4
|
import { resolveSandboxCleanupTarget } from '../workspace-identity.js';
|
|
5
|
+
import { detectEngine } from '../engine.js';
|
|
6
|
+
import { sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel } from '../constants.js';
|
|
7
|
+
import { fetchSandboxRows } from './list-running.js';
|
|
5
8
|
import { rmOne, rmPurge, rmUnbound } from '../removal.js';
|
|
6
9
|
const USAGE = `Usage:
|
|
7
10
|
ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
|
|
8
|
-
ai sandbox rm --unbound [--dry-run] [--yes] Remove
|
|
11
|
+
ai sandbox rm --unbound [--dry-run] [--yes] Remove discovered task-bound and branch-only sandboxes
|
|
9
12
|
ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
|
|
13
|
+
function resolveMissingTaskSandboxBranch(taskId, config) {
|
|
14
|
+
const engine = detectEngine(config);
|
|
15
|
+
const listed = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
|
|
16
|
+
const branches = new Set([...listed.running, ...listed.nonRunning]
|
|
17
|
+
.filter((row) => row.workspaceMode === 'task-bound' && row.taskId === taskId && row.branch)
|
|
18
|
+
.map((row) => row.branch));
|
|
19
|
+
if (branches.size === 0)
|
|
20
|
+
return null;
|
|
21
|
+
if (branches.size > 1) {
|
|
22
|
+
throw new Error(`SANDBOX_CLEANUP_TASK_ID_AMBIGUOUS: task ${taskId} matches multiple sandbox branches`);
|
|
23
|
+
}
|
|
24
|
+
return branches.values().next().value ?? null;
|
|
25
|
+
}
|
|
10
26
|
export async function rm(args) {
|
|
11
27
|
const { values, positionals } = parseArgs({
|
|
12
28
|
args,
|
|
@@ -53,7 +69,10 @@ export async function rm(args) {
|
|
|
53
69
|
});
|
|
54
70
|
return;
|
|
55
71
|
}
|
|
56
|
-
const cleanupTarget = resolveSandboxCleanupTarget(positionals[0] ?? '', config.repoRoot
|
|
72
|
+
const cleanupTarget = resolveSandboxCleanupTarget(positionals[0] ?? '', config.repoRoot, {
|
|
73
|
+
allowProtected: true,
|
|
74
|
+
resolveMissingTask: (taskId) => resolveMissingTaskSandboxBranch(taskId, config)
|
|
75
|
+
});
|
|
57
76
|
await rmOne(config, tools, cleanupTarget.branch, { cleanupTarget });
|
|
58
77
|
}
|
|
59
78
|
//# sourceMappingURL=rm.js.map
|
|
@@ -125,7 +125,9 @@ export async function prepareSandboxControlExecution(params) {
|
|
|
125
125
|
owner: gateOwner,
|
|
126
126
|
requestId: params.request.id,
|
|
127
127
|
operationId: authority?.operationId ?? params.request.id,
|
|
128
|
-
phase: authority?.phase ?? (params.request.family === 'task-workflow'
|
|
128
|
+
phase: authority?.phase ?? (params.request.family === 'task-workflow'
|
|
129
|
+
&& params.request.workflow?.operation === 'artifact-preflight' ? 'artifact.preflight'
|
|
130
|
+
: params.request.family === 'task-workflow' ? 'artifact.finalize-local' : 'orchestration.prepare'),
|
|
129
131
|
authority
|
|
130
132
|
});
|
|
131
133
|
},
|
|
@@ -163,7 +165,7 @@ function waitForGate(nonce, timeoutMs = 2_000) {
|
|
|
163
165
|
return;
|
|
164
166
|
}
|
|
165
167
|
if (typeof value.requestId !== 'string' || typeof value.operationId !== 'string'
|
|
166
|
-
|| !['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(value.phase)
|
|
168
|
+
|| !['orchestration.prepare', 'artifact.preflight', 'artifact.finalize-local', 'task-event.completed'].includes(value.phase)
|
|
167
169
|
|| (value.authority !== null && value.authority !== undefined && (() => {
|
|
168
170
|
try {
|
|
169
171
|
validateLifecycleRecoveryAttestation(value.authority);
|