@fitlab-ai/agent-infra 0.9.15 → 0.10.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/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 +1 -1
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +0 -1
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/issue-comments.js +16 -10
- 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-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +6 -2
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/workflow-executor.js +4 -14
- 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/task/artifact-command.js +6 -28
- package/dist/lib/task/artifact-operations.js +16 -230
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +73 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +38 -7
- 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 +164 -22
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +66 -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 +148 -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 +1 -0
- package/dist/lib/task/review-finalization.js +104 -77
- 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 +0 -1
- 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 +1 -1
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +0 -1
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/issue-comments.ts +18 -10
- 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-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +6 -2
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/protocol.ts +5 -0
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/workflow-executor.ts +4 -14
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/task/artifact-command.ts +8 -27
- package/lib/task/artifact-operations.ts +21 -251
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +99 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +47 -14
- 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 +187 -23
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +50 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +176 -132
- 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 +1 -0
- package/lib/task/review-finalization.ts +106 -87
- 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 +0 -1
- 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 +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -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 +7 -7
- 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 +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- 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 +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- 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 +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- 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
|
@@ -22,13 +22,14 @@ function copyRegular(source, destination, mode) {
|
|
|
22
22
|
function controlFromEnvironment() {
|
|
23
23
|
const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
24
24
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
25
|
+
const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
25
26
|
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
26
27
|
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
27
28
|
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
28
|
-
if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
|
|
29
|
+
if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
|
|
29
30
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
30
31
|
}
|
|
31
|
-
return { token, generation, channelDir, statusDir, runtimeDir };
|
|
32
|
+
return { token, generation, rootId, channelDir, statusDir, runtimeDir };
|
|
32
33
|
}
|
|
33
34
|
function verifyRuntime(runtimeDir) {
|
|
34
35
|
let stat;
|
|
@@ -188,6 +189,9 @@ function isolatedEnvironment(home, shimDir, contextPath, taskId, control, provid
|
|
|
188
189
|
AGENT_INFRA_TASK_ID: taskId,
|
|
189
190
|
AGENT_INFRA_CONTROL_TOKEN: control.token,
|
|
190
191
|
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
192
|
+
...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
|
|
193
|
+
? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
|
|
194
|
+
: {}),
|
|
191
195
|
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
192
196
|
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
193
197
|
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
@@ -6,9 +6,19 @@ import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js'
|
|
|
6
6
|
const LOCK_RETRY_MS = 10;
|
|
7
7
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
8
8
|
const LOCK_STALE_MS = 30_000;
|
|
9
|
+
const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
|
|
9
10
|
function digest(value) {
|
|
10
11
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
11
12
|
}
|
|
13
|
+
function isRecoveryConsumer(value) {
|
|
14
|
+
return RECOVERY_CONSUMER_RE.test(value);
|
|
15
|
+
}
|
|
16
|
+
function recoveryConsumer(taskId, receiptId) {
|
|
17
|
+
if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
|
|
18
|
+
throw new Error('Codex lifecycle recovery claim identity is invalid');
|
|
19
|
+
}
|
|
20
|
+
return `lifecycle-recovery:${taskId}:${receiptId}`;
|
|
21
|
+
}
|
|
12
22
|
function readRecord(file) {
|
|
13
23
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
14
24
|
if (value.schemaVersion !== 1
|
|
@@ -202,11 +212,13 @@ function createCodexLifecycleStore(options) {
|
|
|
202
212
|
}
|
|
203
213
|
function read(childThreadId) {
|
|
204
214
|
const matches = findByChild(childThreadId);
|
|
205
|
-
if (matches.length
|
|
215
|
+
if (matches.length === 0)
|
|
206
216
|
throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
|
|
217
|
+
if (matches.length > 1)
|
|
218
|
+
throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
|
|
207
219
|
return readRecord(matches[0]);
|
|
208
220
|
}
|
|
209
|
-
function
|
|
221
|
+
function consumeInternal(childThreadId, consumer, expectedHookDefinitionHash) {
|
|
210
222
|
if (!consumer.trim())
|
|
211
223
|
throw new Error('Codex lifecycle consumer is required');
|
|
212
224
|
return withWriteLock(() => {
|
|
@@ -240,6 +252,32 @@ function createCodexLifecycleStore(options) {
|
|
|
240
252
|
return next;
|
|
241
253
|
});
|
|
242
254
|
}
|
|
255
|
+
function consume(childThreadId, consumer, expectedHookDefinitionHash) {
|
|
256
|
+
if (!consumer.trim())
|
|
257
|
+
throw new Error('Codex lifecycle consumer is required');
|
|
258
|
+
if (consumer.startsWith('lifecycle-recovery:')) {
|
|
259
|
+
throw new Error('Codex lifecycle recovery claims require claimRecovery');
|
|
260
|
+
}
|
|
261
|
+
return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
|
|
262
|
+
}
|
|
263
|
+
function claimRecovery(childThreadId, taskId, receiptId, expectedHookDefinitionHash) {
|
|
264
|
+
return consumeInternal(childThreadId, recoveryConsumer(taskId, receiptId), expectedHookDefinitionHash);
|
|
265
|
+
}
|
|
266
|
+
function releaseRecovery(childThreadId, consumer) {
|
|
267
|
+
if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer))
|
|
268
|
+
return false;
|
|
269
|
+
return withWriteLock(() => {
|
|
270
|
+
const matches = findByChild(childThreadId);
|
|
271
|
+
if (matches.length !== 1)
|
|
272
|
+
return false;
|
|
273
|
+
const file = matches[0];
|
|
274
|
+
const current = readRecord(file);
|
|
275
|
+
if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt)
|
|
276
|
+
return false;
|
|
277
|
+
fs.unlinkSync(file);
|
|
278
|
+
return true;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
243
281
|
function expireBefore(cutoff) {
|
|
244
282
|
return withWriteLock(() => {
|
|
245
283
|
let changed = 0;
|
|
@@ -247,6 +285,8 @@ function createCodexLifecycleStore(options) {
|
|
|
247
285
|
const current = readRecord(file);
|
|
248
286
|
if (current.updatedAt >= cutoff)
|
|
249
287
|
continue;
|
|
288
|
+
if (current.consumer && isRecoveryConsumer(current.consumer))
|
|
289
|
+
continue;
|
|
250
290
|
if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
|
|
251
291
|
fs.unlinkSync(file);
|
|
252
292
|
changed += 1;
|
|
@@ -266,7 +306,7 @@ function createCodexLifecycleStore(options) {
|
|
|
266
306
|
return changed;
|
|
267
307
|
});
|
|
268
308
|
}
|
|
269
|
-
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
309
|
+
return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
|
|
270
310
|
}
|
|
271
311
|
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
|
272
312
|
//# sourceMappingURL=store.js.map
|
|
@@ -2,6 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
export function fsyncDirectory(directory) {
|
|
5
|
+
if (process.platform === 'win32')
|
|
6
|
+
return;
|
|
5
7
|
const descriptor = fs.openSync(directory, 'r');
|
|
6
8
|
try {
|
|
7
9
|
fs.fsyncSync(descriptor);
|
|
@@ -54,7 +54,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
54
54
|
'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
|
|
55
55
|
'task-warning': ['list', 'add', 'set-status'],
|
|
56
56
|
'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
|
|
57
|
-
'task-artifact': ['inspect', 'init', '
|
|
57
|
+
'task-artifact': ['inspect', 'init', 'finalize-local'],
|
|
58
58
|
'task-orchestration': ['status', 'progress'],
|
|
59
59
|
'task-review': ['finalize-summary'],
|
|
60
60
|
'task-event': ['event'],
|
|
@@ -22,15 +22,24 @@ function printFailure(format, error) {
|
|
|
22
22
|
process.stderr.write(`${error.code}: ${error.message}\n`);
|
|
23
23
|
process.exitCode = 1;
|
|
24
24
|
}
|
|
25
|
+
const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
|
|
25
26
|
function manualSummaryBody(body, phase, transactionId, receiptDigest = '', evidenceDigest = '', prHeadSha = '') {
|
|
26
|
-
const
|
|
27
|
-
.replace(/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/gmu, '')
|
|
28
|
-
.replace(/^###\s+⏳\s+(?:Manual Validation Pending|人工验证待收尾)\s*$/gmu, '')
|
|
29
|
-
.replace(/Manual validation passed(?:\s+→)?/giu, 'Manual validation pending');
|
|
27
|
+
const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
|
|
30
28
|
const section = phase === 'pending'
|
|
31
|
-
?
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
? chinese
|
|
30
|
+
? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
|
|
31
|
+
: `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
|
|
32
|
+
: chinese
|
|
33
|
+
? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
|
|
34
|
+
: `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
|
|
35
|
+
let inserted = false;
|
|
36
|
+
const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
|
|
37
|
+
if (inserted)
|
|
38
|
+
return '';
|
|
39
|
+
inserted = true;
|
|
40
|
+
return `${section}\n\n`;
|
|
41
|
+
});
|
|
42
|
+
return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
|
|
34
43
|
}
|
|
35
44
|
function openManualValidationStarted(taskMdPath) {
|
|
36
45
|
let content;
|
|
@@ -242,7 +251,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
|
|
|
242
251
|
if (!['applied', 'no-op'].includes(promoted.status))
|
|
243
252
|
transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
|
|
244
253
|
const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
245
|
-
if (
|
|
254
|
+
if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt))
|
|
246
255
|
transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
|
|
247
256
|
transitionAndPersist('committed', { postWriteVerified: true });
|
|
248
257
|
return result('applied', null, { transaction, receipt, idempotent: false });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.js';
|
|
2
2
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
|
|
3
|
-
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id>
|
|
3
|
+
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> finalize-local --family <analysis|plan|code> --artifact <artifact> [--recovery-id <id>]\n\nInspect, initialize, or finalize a workflow artifact without changing task state.\n`;
|
|
4
4
|
function taskArtifact(args = []) {
|
|
5
5
|
if (!ensureInternalHandlerRoute('task-artifact', args))
|
|
6
6
|
return;
|
|
@@ -2,7 +2,7 @@ import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTa
|
|
|
2
2
|
import { lifecycleIntentCatalog } from '../task/lifecycle.js';
|
|
3
3
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
5
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
|
|
5
|
+
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --stage <stage> --round <round> --artifact <artifact> --reason <reason>\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
|
|
6
6
|
function usageFailure(message) {
|
|
7
7
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
8
8
|
process.stderr.write(USAGE);
|
|
@@ -54,7 +54,7 @@ async function taskLifecycle(args = []) {
|
|
|
54
54
|
}
|
|
55
55
|
const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot }), operation);
|
|
56
56
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
57
|
-
if (result.status === 'failed')
|
|
57
|
+
if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict')
|
|
58
58
|
process.exitCode = 1;
|
|
59
59
|
}
|
|
60
60
|
export { taskLifecycle };
|
|
@@ -96,7 +96,6 @@ function internalTaskRoutes() {
|
|
|
96
96
|
descriptor('internal', 'task-activity', 'pr-review-terminate', 'task-bound', 'progress'),
|
|
97
97
|
descriptor('internal', 'task-artifact', 'inspect', 'task-bound', 'diagnostic'),
|
|
98
98
|
descriptor('internal', 'task-artifact', 'init', 'task-bound', 'artifact-write'),
|
|
99
|
-
descriptor('internal', 'task-artifact', 'repair', 'task-bound', 'artifact-write'),
|
|
100
99
|
descriptor('internal', 'task-artifact', 'finalize-local', 'task-bound', 'artifact-write'),
|
|
101
100
|
descriptor('internal', 'task-orchestration', 'status', 'task-bound', 'diagnostic'),
|
|
102
101
|
descriptor('internal', 'task-orchestration', 'progress', 'task-bound', 'progress'),
|
|
@@ -4,7 +4,7 @@ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task
|
|
|
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> 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',
|
|
@@ -30,7 +30,7 @@ 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
34
|
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
35
35
|
if (dryRunConflict) {
|
|
36
36
|
failUsage(dryRunConflict.message);
|
|
@@ -45,7 +45,7 @@ async function taskReview(args = []) {
|
|
|
45
45
|
let humanOverride = null;
|
|
46
46
|
try {
|
|
47
47
|
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', async () => {
|
|
48
|
-
let current = finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
48
|
+
let current = finalizeReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
49
49
|
if (current.status !== 'failed' || !overrideTicket)
|
|
50
50
|
return current;
|
|
51
51
|
if (!overrideTarget || !overrideScope) {
|
|
@@ -58,7 +58,7 @@ async function taskReview(args = []) {
|
|
|
58
58
|
target: overrideTarget, scope: overrideScope
|
|
59
59
|
}, {
|
|
60
60
|
effectExecutor: (capability) => {
|
|
61
|
-
const retried = finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
61
|
+
const retried = finalizeReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
62
62
|
current = retried;
|
|
63
63
|
return retried.status === 'failed' || retried.status === 'planned'
|
|
64
64
|
? { 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'}` }
|
|
@@ -18,6 +18,7 @@ const MARKERS = {
|
|
|
18
18
|
summary: (taskId) => `<!-- sync-issue:${taskId}:summary -->`,
|
|
19
19
|
cancel: (taskId) => `<!-- sync-issue:${taskId}:cancel -->`
|
|
20
20
|
};
|
|
21
|
+
const COMMENT_BYTE_LIMIT = 60_000;
|
|
21
22
|
const ARTIFACT_TITLES = {
|
|
22
23
|
analysis: '需求分析',
|
|
23
24
|
'review-analysis': '需求分析审查',
|
|
@@ -32,6 +33,9 @@ const ARTIFACT_TITLES = {
|
|
|
32
33
|
function normalizeCommentContent(content) {
|
|
33
34
|
return content.replace(/\r\n/g, '\n').replace(/\n+$/, '\n');
|
|
34
35
|
}
|
|
36
|
+
function isTaskCommentTooLarge(content) {
|
|
37
|
+
return Buffer.byteLength(content, 'utf8') > COMMENT_BYTE_LIMIT;
|
|
38
|
+
}
|
|
35
39
|
function splitFrontmatter(content) {
|
|
36
40
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
37
41
|
if (!match)
|
|
@@ -219,7 +223,7 @@ function buildArtifactChunk(taskId, stem, title, agent, content, part, total, ch
|
|
|
219
223
|
return { marker, body, content, part, total };
|
|
220
224
|
}
|
|
221
225
|
function chunkArtifactComment(input) {
|
|
222
|
-
const byteLimit = input.byteLimit ||
|
|
226
|
+
const byteLimit = input.byteLimit || COMMENT_BYTE_LIMIT;
|
|
223
227
|
const identity = artifactIdentity(input.artifact);
|
|
224
228
|
const safeBody = sanitizeCommentBody(input.body, 'artifact body');
|
|
225
229
|
const single = buildArtifactChunk(input.taskId, identity.stem, identity.title, input.agent, safeBody, 1, 1, false, Boolean(input.backfill));
|
|
@@ -257,11 +261,6 @@ function flattenComments(value) {
|
|
|
257
261
|
const flattened = value.flatMap((entry) => Array.isArray(entry) ? entry : [entry]);
|
|
258
262
|
return flattened.filter((entry) => Boolean(entry && typeof entry === 'object' && (typeof entry.id === 'number' || typeof entry.id === 'string') && typeof entry.body === 'string'));
|
|
259
263
|
}
|
|
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
264
|
function listRemoteComments(client, repo, issue, cwd) {
|
|
266
265
|
const result = client.json([
|
|
267
266
|
'api', '--paginate', '--slurp', `repos/${repo}/issues/${issue}/comments?per_page=100`
|
|
@@ -374,14 +373,21 @@ async function syncPlatformComment(taskRef, options) {
|
|
|
374
373
|
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
375
374
|
let issueIdentityFromTask;
|
|
376
375
|
try {
|
|
377
|
-
issueIdentityFromTask = taskIssueIdentity(parseTaskFrontmatter(taskContent)
|
|
376
|
+
issueIdentityFromTask = taskIssueIdentity(parseTaskFrontmatter(taskContent));
|
|
378
377
|
}
|
|
379
378
|
catch (error) {
|
|
380
379
|
return platformResult('failed', { error: { ...taskIssueIdentityError(error), retryable: false } });
|
|
381
380
|
}
|
|
382
381
|
if (!issueIdentityFromTask) {
|
|
383
382
|
return platformResult('no-op', {
|
|
384
|
-
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid
|
|
383
|
+
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
if (options.kind === 'task' && isTaskCommentTooLarge(taskContent)) {
|
|
387
|
+
return platformResult('no-op', {
|
|
388
|
+
resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentityFromTask), identity: issueIdentityFromTask },
|
|
389
|
+
operations: [{ name: `comment:${MARKERS.task(resolved.taskId)}`, status: 'skipped', reasonCode: 'COMMENT_PAYLOAD_TOO_LARGE' }],
|
|
390
|
+
error: null
|
|
385
391
|
});
|
|
386
392
|
}
|
|
387
393
|
let desired;
|
|
@@ -541,7 +547,7 @@ async function checkPlatformCommentOwner(taskRef, options = {}) {
|
|
|
541
547
|
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
542
548
|
let issueIdentity;
|
|
543
549
|
try {
|
|
544
|
-
issueIdentity = taskIssueIdentity(parseTaskFrontmatter(content)
|
|
550
|
+
issueIdentity = taskIssueIdentity(parseTaskFrontmatter(content));
|
|
545
551
|
}
|
|
546
552
|
catch (error) {
|
|
547
553
|
return platformResult('failed', { error: { ...taskIssueIdentityError(error), retryable: false } });
|
|
@@ -572,5 +578,5 @@ async function checkPlatformCommentOwner(taskRef, options = {}) {
|
|
|
572
578
|
}
|
|
573
579
|
return platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity }, error: null });
|
|
574
580
|
}
|
|
575
|
-
export { MARKERS, chunkArtifactComment, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, syncPlatformComment, validateRelatedMarkerSet, writeComment };
|
|
581
|
+
export { COMMENT_BYTE_LIMIT, MARKERS, chunkArtifactComment, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, isTaskCommentTooLarge, syncPlatformComment, validateRelatedMarkerSet, writeComment };
|
|
576
582
|
//# 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
|
},
|
|
@@ -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
|