@fitlab-ai/agent-infra 0.11.9 → 0.11.11
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/bin/internal-cli.js +0 -6
- package/dist/lib/agent-clients/adapter.js +20 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
- package/dist/lib/agent-clients/adapters/codex.js +4 -0
- package/dist/lib/git/workflow.js +1 -1
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/manual-validation.js +2 -2
- package/dist/lib/internal/platform-pr.js +4 -5
- package/dist/lib/internal/sandbox-control.js +30 -5
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-create.js +15 -1
- package/dist/lib/internal/task-finalization.js +14 -25
- package/dist/lib/internal/task-operation-registry.js +6 -141
- package/dist/lib/internal/task-platform-recovery.js +9 -4
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/context.js +4 -2
- package/dist/lib/platform/github-client.js +20 -17
- package/dist/lib/platform/github-provider.js +42 -22
- package/dist/lib/platform/github-release-notes.js +4 -2
- package/dist/lib/platform/in-label-sync.js +2 -1
- package/dist/lib/platform/issue-comments.js +13 -9
- package/dist/lib/platform/operation-coordinator.js +1 -1
- package/dist/lib/platform/pr-review.js +2 -1
- package/dist/lib/platform/pr-summary.js +45 -8
- package/dist/lib/platform/provider-bridge.js +4 -5
- package/dist/lib/platform/provider-contract.d.ts +3 -6
- package/dist/lib/platform/provider-contract.js +3 -15
- package/dist/lib/platform/provider-validation.js +9 -4
- package/dist/lib/platform/pull-requests.js +83 -16
- package/dist/lib/platform/verification-sync.js +17 -14
- package/dist/lib/sandbox/commands/create.js +18 -2
- package/dist/lib/sandbox/commands/enter.js +47 -33
- package/dist/lib/sandbox/control/client.js +99 -17
- package/dist/lib/sandbox/control/executor.js +12 -14
- package/dist/lib/sandbox/control/host-recovery.js +218 -0
- package/dist/lib/sandbox/control/protocol.js +2 -3
- package/dist/lib/sandbox/control/server.js +42 -2
- package/dist/lib/sandbox/environment.js +150 -0
- package/dist/lib/sandbox/recovery.js +8 -0
- package/dist/lib/sandbox/removal.js +0 -1
- package/dist/lib/sandbox/workspace-identity.js +0 -14
- package/dist/lib/sandbox/workspace-view.js +33 -6
- package/dist/lib/task/artifact-command.js +18 -3
- package/dist/lib/task/artifact-lifecycle.js +121 -63
- package/dist/lib/task/artifact-selection.js +1 -1
- package/dist/lib/task/capabilities.js +59 -22
- package/dist/lib/task/commands/status.js +0 -27
- package/dist/lib/task/commit-identity.js +22 -7
- package/dist/lib/task/commit-intent.js +67 -26
- package/dist/lib/task/commit-operation.js +19 -8
- package/dist/lib/task/completion-facts.js +32 -10
- package/dist/lib/task/control-authority.js +2 -6
- package/dist/lib/task/current-contract.js +1 -5
- package/dist/lib/task/events.js +9 -275
- package/dist/lib/task/execution-inputs.js +14 -0
- package/dist/lib/task/finalization-envelope.js +4 -0
- package/dist/lib/task/finalization.js +43 -86
- package/dist/lib/task/frontmatter.js +25 -2
- package/dist/lib/task/ledger-intents.js +6 -8
- package/dist/lib/task/orchestration.js +11 -10
- package/dist/lib/task/platform-operation-journal.js +29 -10
- package/dist/lib/task/platform-operation-recovery.js +109 -35
- package/dist/lib/task/resolve-ref.js +8 -3
- package/dist/lib/task/review-fingerprint.js +4 -26
- package/dist/lib/task/verification-engine.js +95 -6
- package/dist/lib/task/write.js +0 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +22 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
- package/lib/agent-clients/adapters/codex.ts +4 -0
- package/lib/git/workflow.ts +1 -1
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/manual-validation.ts +2 -2
- package/lib/internal/platform-pr.ts +3 -4
- package/lib/internal/sandbox-control.ts +39 -6
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-create.ts +15 -2
- package/lib/internal/task-finalization.ts +16 -26
- package/lib/internal/task-operation-registry.ts +6 -153
- package/lib/internal/task-platform-recovery.ts +9 -4
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/context.ts +8 -4
- package/lib/platform/github-client.ts +24 -20
- package/lib/platform/github-provider.ts +47 -26
- package/lib/platform/github-release-notes.ts +5 -3
- package/lib/platform/in-label-sync.ts +2 -1
- package/lib/platform/issue-comments.ts +14 -9
- package/lib/platform/operation-coordinator.ts +1 -1
- package/lib/platform/pr-review.ts +2 -1
- package/lib/platform/pr-summary.ts +43 -7
- package/lib/platform/provider-bridge.ts +10 -9
- package/lib/platform/provider-contract.ts +6 -21
- package/lib/platform/provider-loader.ts +7 -6
- package/lib/platform/provider-validation.ts +30 -8
- package/lib/platform/pull-requests.ts +78 -15
- package/lib/platform/repository-metadata.ts +3 -2
- package/lib/platform/security-alerts.ts +1 -1
- package/lib/platform/types.ts +2 -0
- package/lib/platform/verification-sync.ts +18 -14
- package/lib/sandbox/commands/create.ts +24 -2
- package/lib/sandbox/commands/enter.ts +42 -39
- package/lib/sandbox/control/client.ts +107 -19
- package/lib/sandbox/control/executor.ts +13 -15
- package/lib/sandbox/control/host-recovery.ts +195 -0
- package/lib/sandbox/control/protocol.ts +4 -5
- package/lib/sandbox/control/server.ts +42 -1
- package/lib/sandbox/environment.ts +162 -0
- package/lib/sandbox/recovery.ts +10 -0
- package/lib/sandbox/removal.ts +2 -10
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/workspace-identity.ts +0 -26
- package/lib/sandbox/workspace-view.ts +33 -8
- package/lib/task/artifact-command.ts +19 -3
- package/lib/task/artifact-lifecycle.ts +102 -53
- package/lib/task/artifact-selection.ts +1 -1
- package/lib/task/capabilities.ts +52 -26
- package/lib/task/commands/status.ts +0 -31
- package/lib/task/commit-identity.ts +24 -7
- package/lib/task/commit-intent.ts +58 -24
- package/lib/task/commit-operation.ts +17 -8
- package/lib/task/completion-facts.ts +31 -9
- package/lib/task/control-authority.ts +1 -8
- package/lib/task/current-contract.ts +1 -6
- package/lib/task/events.ts +9 -248
- package/lib/task/execution-inputs.ts +25 -0
- package/lib/task/finalization-envelope.ts +21 -0
- package/lib/task/finalization.ts +40 -82
- package/lib/task/frontmatter.ts +26 -2
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/orchestration.ts +11 -17
- package/lib/task/platform-operation-journal.ts +47 -13
- package/lib/task/platform-operation-recovery.ts +102 -37
- package/lib/task/resolve-ref.ts +8 -3
- package/lib/task/review-fingerprint.ts +4 -26
- package/lib/task/verification-engine.ts +103 -9
- package/lib/task/write.ts +0 -19
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/commit-and-pr.en.md +5 -5
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
- package/templates/.agents/rules/decision-qualification.en.md +1 -1
- package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
- package/templates/.agents/rules/review-handshake.en.md +3 -0
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +27 -10
- package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.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 +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +4 -1
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- package/dist/lib/internal/task-invalidation.js +0 -63
- package/dist/lib/task/finalization-handoff.js +0 -93
- package/dist/lib/task/intermediate-cleanup.js +0 -327
- package/dist/lib/task/invalidation-command.js +0 -88
- package/dist/lib/task/invalidation.js +0 -242
- package/lib/internal/task-invalidation.ts +0 -41
- package/lib/task/finalization-handoff.ts +0 -105
- package/lib/task/intermediate-cleanup.ts +0 -393
- package/lib/task/invalidation-command.ts +0 -88
- package/lib/task/invalidation.ts +0 -313
|
@@ -47,12 +47,35 @@ function serializeScalar(value) {
|
|
|
47
47
|
}
|
|
48
48
|
if (value === '')
|
|
49
49
|
return '';
|
|
50
|
-
const serialized = stringify(value).trimEnd();
|
|
50
|
+
const serialized = stringify(value, { lineWidth: 0 }).trimEnd();
|
|
51
51
|
if (serialized.includes('\n') || serialized.includes('\r')) {
|
|
52
52
|
throw new FrontmatterError('MUTATION_INVALID', 'frontmatter values must be scalar');
|
|
53
53
|
}
|
|
54
54
|
return serialized;
|
|
55
55
|
}
|
|
56
|
+
function stripInlineComment(value) {
|
|
57
|
+
const first = value.search(/\S/u);
|
|
58
|
+
let activeQuote = first >= 0 && (value[first] === '"' || value[first] === "'") ? value[first] : null;
|
|
59
|
+
for (let index = activeQuote ? first + 1 : Math.max(first, 0); index < value.length; index += 1) {
|
|
60
|
+
const character = value[index];
|
|
61
|
+
if (activeQuote === '"' && character === '\\') {
|
|
62
|
+
index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (activeQuote && character === activeQuote) {
|
|
66
|
+
if (activeQuote === "'" && value[index + 1] === "'") {
|
|
67
|
+
index += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
activeQuote = null;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!activeQuote && character === '#' && (index === 0 || /\s/u.test(value[index - 1]))) {
|
|
74
|
+
return value.slice(0, index).trimEnd();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
56
79
|
function parseTaskFrontmatter(content) {
|
|
57
80
|
const result = {};
|
|
58
81
|
if (!content.startsWith('---'))
|
|
@@ -69,7 +92,7 @@ function parseTaskFrontmatter(content) {
|
|
|
69
92
|
if (colon === -1)
|
|
70
93
|
continue;
|
|
71
94
|
const key = line.slice(0, colon).trim();
|
|
72
|
-
const value = line.slice(colon + 1).trim();
|
|
95
|
+
const value = stripInlineComment(line.slice(colon + 1).trim());
|
|
73
96
|
if (key)
|
|
74
97
|
result[key] = value;
|
|
75
98
|
}
|
|
@@ -10,6 +10,7 @@ import { parseLegacyReworkIntentDocument, parseReworkIntentDocument, reworkInten
|
|
|
10
10
|
import { extractSection, extractSubSection } from './sections.js';
|
|
11
11
|
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
12
12
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
13
|
+
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
13
14
|
const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
|
|
14
15
|
const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
|
|
15
16
|
const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
|
|
@@ -61,9 +62,8 @@ function findingEvidence(reviewContent, evidence) {
|
|
|
61
62
|
return semanticFindingEvidence(reviewContent, heading, next?.start ?? reviewContent.length);
|
|
62
63
|
}
|
|
63
64
|
function taskFactDigest(taskDir, content) {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latest.name), 'utf8')) : null;
|
|
65
|
+
const latest = inspectArtifactDirectory(taskDir, 'analysis').latest;
|
|
66
|
+
const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(latest.path, 'utf8'), latest.name) : null;
|
|
67
67
|
return semanticDigest(JSON.stringify({
|
|
68
68
|
taskInput: normalizedTaskFact(extractSection(content, ['任务输入', 'Task Input'])),
|
|
69
69
|
requirements: normalizedTaskFact(extractSection(content, ['需求', 'Requirements'])),
|
|
@@ -220,10 +220,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
220
220
|
}
|
|
221
221
|
const sourceIdentity = parseArtifactName(intent.sourceArtifact);
|
|
222
222
|
const expectedFamily = `review-${finding.stage}`;
|
|
223
|
-
const latestSource =
|
|
224
|
-
.map((name) => parseArtifactName(name))
|
|
225
|
-
.filter((identity) => identity?.family === expectedFamily)
|
|
226
|
-
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
|
|
223
|
+
const latestSource = inspectArtifactDirectory(resolved.taskDir, expectedFamily).latest;
|
|
227
224
|
if (!sourceIdentity || sourceIdentity.family !== expectedFamily || latestSource?.name !== sourceIdentity.name) {
|
|
228
225
|
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact is not the latest review for the finding stage', resolved.taskId);
|
|
229
226
|
}
|
|
@@ -339,7 +336,8 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
339
336
|
? new Set(['confirmed', 'open', 'needs-human-decision'])
|
|
340
337
|
: before.status === 'cannot-judge' ? new Set(['open', 'needs-human-decision']) : new Set();
|
|
341
338
|
const sameRoundMinorClose = before.status === 'open' && before.severity === 'minor' && intent.status === 'closed';
|
|
342
|
-
|
|
339
|
+
const refreshOpenEvidence = before.status === 'open' && intent.status === 'open';
|
|
340
|
+
if (!sameRoundMinorClose && !refreshOpenEvidence && !allowed.has(intent.status)) {
|
|
343
341
|
return failed(intent, 'LEDGER_TRANSITION_INVALID', `finding '${intent.id}' cannot transition from ${before.status} to ${intent.status}`, resolved.taskId, intent.id);
|
|
344
342
|
}
|
|
345
343
|
after = { ...before, status: intent.status, evidence };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
-
import {
|
|
4
|
+
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
5
5
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
6
6
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
7
7
|
import { parseReviewSummary } from './review-artifacts.js';
|
|
@@ -20,7 +20,6 @@ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead,
|
|
|
20
20
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
|
|
22
22
|
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
23
|
-
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
24
23
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
25
24
|
import { resolveArtifactContext } from './artifact-lifecycle.js';
|
|
26
25
|
function supportsLifecycleDelegation(client) {
|
|
@@ -441,8 +440,11 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
441
440
|
return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
|
|
442
441
|
}
|
|
443
442
|
function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead) {
|
|
444
|
-
const
|
|
445
|
-
|
|
443
|
+
const reviewInventory = inspectArtifactDirectory(taskDir, 'review-code');
|
|
444
|
+
if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
|
|
445
|
+
return { code: 'ORCHESTRATION_REVIEW_INVALID', message: 'latest completed review-code artifact is unavailable' };
|
|
446
|
+
}
|
|
447
|
+
const reviewContent = fs.readFileSync(reviewInventory.latest.path, 'utf8');
|
|
446
448
|
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
447
449
|
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
448
450
|
const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
@@ -540,8 +542,11 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
540
542
|
if (facts.facts.executionBusy && !run?.pendingDelegation) {
|
|
541
543
|
return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
|
|
542
544
|
}
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
+
const reviewInventory = inspectArtifactDirectory(resolved.taskDir, 'review-code');
|
|
546
|
+
if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
|
|
547
|
+
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest completed review-code artifact is unavailable', resolved.taskId);
|
|
548
|
+
}
|
|
549
|
+
const review = parseReviewSummary(fs.readFileSync(reviewInventory.latest.path, 'utf8'));
|
|
545
550
|
if (!review.ok || review.summary.manualValidation === null) {
|
|
546
551
|
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
547
552
|
}
|
|
@@ -678,10 +683,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
678
683
|
}
|
|
679
684
|
if (run.stepCount >= run.maxSteps)
|
|
680
685
|
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
681
|
-
const invalidation = reconcileTaskInvalidation(taskRef, { repoRoot: resolved.repoRoot });
|
|
682
|
-
if (invalidation.status === 'failed') {
|
|
683
|
-
return failed('ORCHESTRATION_INVALIDATION_RECONCILE_FAILED', `${invalidation.error?.code ?? 'UNKNOWN'}: ${invalidation.error?.message ?? ''}`, resolved.taskId);
|
|
684
|
-
}
|
|
685
686
|
const routed = routeOrchestration(taskRef, options);
|
|
686
687
|
if (!routed.next)
|
|
687
688
|
return routed;
|
|
@@ -4,7 +4,6 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
4
4
|
import { isResourceIdentity } from '../platform/resource-identity.js';
|
|
5
5
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
6
6
|
const JOURNAL_FILE = '.platform-operations.json';
|
|
7
|
-
const MAX_ATTEMPTS = 3;
|
|
8
7
|
function operationId(input) {
|
|
9
8
|
return createHash('sha256')
|
|
10
9
|
.update(`${input.kind}\0${input.target}\0${input.expectedDigest}`)
|
|
@@ -36,8 +35,7 @@ function parseJournal(file, taskId) {
|
|
|
36
35
|
|| !/^[a-f0-9]{64}$/u.test(item.expectedDigest)
|
|
37
36
|
|| !['deferred', 'required'].includes(item.dependency)
|
|
38
37
|
|| !['queued', 'pending', 'unknown', 'succeeded', 'failed'].includes(item.state)
|
|
39
|
-
|| !Number.isSafeInteger(item.attempts) || item.attempts < 0
|
|
40
|
-
|| item.maxAttempts !== MAX_ATTEMPTS
|
|
38
|
+
|| !Number.isSafeInteger(item.attempts) || item.attempts < 0
|
|
41
39
|
|| !(item.lastCode === null || typeof item.lastCode === 'string')
|
|
42
40
|
|| (item.kind === 'issue-metadata'
|
|
43
41
|
? !item.issueMetadata || typeof item.issueMetadata.requirements !== 'boolean'
|
|
@@ -71,6 +69,18 @@ function parseJournal(file, taskId) {
|
|
|
71
69
|
? !item.pullRequestSummary || typeof item.pullRequestSummary.body !== 'string'
|
|
72
70
|
|| !item.pullRequestSummary.body.trim() || typeof item.pullRequestSummary.changeReportFile !== 'string'
|
|
73
71
|
|| !item.pullRequestSummary.changeReportFile.trim()
|
|
72
|
+
|| (item.pullRequestSummary.manualValidation !== undefined && (!item.pullRequestSummary.manualValidation
|
|
73
|
+
|| typeof item.pullRequestSummary.manualValidation !== 'object'
|
|
74
|
+
|| Array.isArray(item.pullRequestSummary.manualValidation)
|
|
75
|
+
|| !['pending', 'final'].includes(item.pullRequestSummary.manualValidation.phase)
|
|
76
|
+
|| typeof item.pullRequestSummary.manualValidation.transactionId !== 'string'
|
|
77
|
+
|| !item.pullRequestSummary.manualValidation.transactionId.trim()
|
|
78
|
+
|| (item.pullRequestSummary.manualValidation.receiptDigest !== undefined && !/^[a-f0-9]{64}$/u.test(item.pullRequestSummary.manualValidation.receiptDigest))
|
|
79
|
+
|| (item.pullRequestSummary.manualValidation.prHeadSha !== undefined && !/^[a-f0-9]{40}$/u.test(item.pullRequestSummary.manualValidation.prHeadSha))
|
|
80
|
+
|| (item.pullRequestSummary.manualValidation.authority !== undefined && item.pullRequestSummary.manualValidation.authority !== 'coordinator')
|
|
81
|
+
|| (item.pullRequestSummary.manualValidation.phase === 'final' && (!/^[a-f0-9]{64}$/u.test(item.pullRequestSummary.manualValidation.receiptDigest ?? '')
|
|
82
|
+
|| !/^[a-f0-9]{40}$/u.test(item.pullRequestSummary.manualValidation.prHeadSha ?? '')
|
|
83
|
+
|| item.pullRequestSummary.manualValidation.authority !== 'coordinator'))))
|
|
74
84
|
: item.pullRequestSummary !== undefined)
|
|
75
85
|
|| (item.kind === 'pull-request-review'
|
|
76
86
|
? !item.pullRequestReview || typeof item.pullRequestReview.prNumber !== 'string'
|
|
@@ -120,7 +130,13 @@ function recordPlatformOperation(input) {
|
|
|
120
130
|
const { taskId, file } = resolveJournal(input.taskRef, input.cwd);
|
|
121
131
|
const journal = parseJournal(file, taskId);
|
|
122
132
|
const id = operationId(input);
|
|
123
|
-
const
|
|
133
|
+
const targetIndex = journal.operations.findIndex((item) => item.id === id);
|
|
134
|
+
const replacementIndex = input.replaceOperationId
|
|
135
|
+
? journal.operations.findIndex((item) => item.id === input.replaceOperationId)
|
|
136
|
+
: -1;
|
|
137
|
+
const target = targetIndex >= 0 ? journal.operations[targetIndex] : undefined;
|
|
138
|
+
const replacement = replacementIndex >= 0 ? journal.operations[replacementIndex] : undefined;
|
|
139
|
+
const previousAttempts = Math.max(target?.attempts ?? 0, replacement?.attempts ?? 0);
|
|
124
140
|
const next = {
|
|
125
141
|
id,
|
|
126
142
|
kind: input.kind,
|
|
@@ -128,8 +144,7 @@ function recordPlatformOperation(input) {
|
|
|
128
144
|
expectedDigest: input.expectedDigest,
|
|
129
145
|
dependency: input.dependency,
|
|
130
146
|
state: input.state,
|
|
131
|
-
attempts:
|
|
132
|
-
maxAttempts: MAX_ATTEMPTS,
|
|
147
|
+
attempts: previousAttempts + (input.state === 'pending' ? 1 : 0),
|
|
133
148
|
lastCode: input.lastCode ?? null,
|
|
134
149
|
...(input.issueMetadata ? { issueMetadata: input.issueMetadata } : {}),
|
|
135
150
|
...(input.issueCreate ? { issueCreate: input.issueCreate } : {}),
|
|
@@ -138,9 +153,13 @@ function recordPlatformOperation(input) {
|
|
|
138
153
|
...(input.pullRequestReview ? { pullRequestReview: input.pullRequestReview } : {}),
|
|
139
154
|
updatedAt: new Date().toISOString()
|
|
140
155
|
};
|
|
141
|
-
const operations =
|
|
142
|
-
? journal.operations.
|
|
143
|
-
|
|
156
|
+
const operations = replacementIndex >= 0
|
|
157
|
+
? journal.operations.flatMap((item, index) => index === replacementIndex
|
|
158
|
+
? [next]
|
|
159
|
+
: index === targetIndex ? [] : [item])
|
|
160
|
+
: targetIndex >= 0
|
|
161
|
+
? journal.operations.map((item, index) => index === targetIndex ? next : item)
|
|
162
|
+
: [...journal.operations, next];
|
|
144
163
|
writeJournal(file, { version: 1, taskId, operations });
|
|
145
164
|
return next;
|
|
146
165
|
}
|
|
@@ -148,5 +167,5 @@ function readPlatformOperationJournal(taskRef, cwd) {
|
|
|
148
167
|
const { taskId, file } = resolveJournal(taskRef, cwd);
|
|
149
168
|
return parseJournal(file, taskId);
|
|
150
169
|
}
|
|
151
|
-
export { JOURNAL_FILE as PLATFORM_OPERATION_JOURNAL_FILE,
|
|
170
|
+
export { JOURNAL_FILE as PLATFORM_OPERATION_JOURNAL_FILE, operationId, recordPlatformOperation, readPlatformOperationJournal };
|
|
152
171
|
//# sourceMappingURL=platform-operation-journal.js.map
|
|
@@ -18,6 +18,8 @@ import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
|
18
18
|
import { platformResult } from '../platform/types.js';
|
|
19
19
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
20
20
|
import { readPlatformOperationJournal, recordPlatformOperation } from './platform-operation-journal.js';
|
|
21
|
+
import { providerErrorRetryable } from '../platform/provider-validation.js';
|
|
22
|
+
const DEFAULT_RECOVERY_ATTEMPTS = 3;
|
|
21
23
|
function labelsMatchOwnedPrefix(actual, expected, prefix) {
|
|
22
24
|
return actual.filter((label) => label.startsWith(prefix)).sort().join('\0')
|
|
23
25
|
=== expected.filter((label) => label.startsWith(prefix)).sort().join('\0');
|
|
@@ -26,7 +28,7 @@ function fieldsMatchExpected(actual, expected) {
|
|
|
26
28
|
return Object.entries(expected).every(([name, value]) => actual[name] === value);
|
|
27
29
|
}
|
|
28
30
|
function result(status, recovered, pending, error = null) {
|
|
29
|
-
return { status, changed: recovered.length > 0, recovered, pending, error };
|
|
31
|
+
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null };
|
|
30
32
|
}
|
|
31
33
|
function summaryPayload(taskDir, taskId) {
|
|
32
34
|
const file = path.join(taskDir, '.delivery-summary.json');
|
|
@@ -191,6 +193,16 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
191
193
|
catch {
|
|
192
194
|
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } });
|
|
193
195
|
}
|
|
196
|
+
const summary = operation.pullRequestSummary;
|
|
197
|
+
if (!summary)
|
|
198
|
+
return platformResult('blocked', { error: {
|
|
199
|
+
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
200
|
+
} });
|
|
201
|
+
const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
|
|
202
|
+
if (manualValidationHeading && !summary.manualValidation)
|
|
203
|
+
return platformResult('blocked', { error: {
|
|
204
|
+
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
|
|
205
|
+
} });
|
|
194
206
|
const resolved = await resolvePlatformProviderContext({ cwd, client });
|
|
195
207
|
if (!resolved.ok)
|
|
196
208
|
return resolved.context;
|
|
@@ -198,20 +210,16 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
198
210
|
? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
|
|
199
211
|
: unsupportedProviderOperation(resolved.value.provider, 'comments.list');
|
|
200
212
|
if (!listed.ok)
|
|
201
|
-
return platformResult(listed.error.
|
|
213
|
+
return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
|
|
202
214
|
const marker = `<!-- sync-pr:${taskId}:summary -->`;
|
|
203
215
|
const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
|
|
204
216
|
if (summaries.length > 1)
|
|
205
217
|
return platformResult('blocked', { error: {
|
|
206
218
|
code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
|
|
207
219
|
} });
|
|
208
|
-
if (summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0].body)).digest('hex') === operation.expectedDigest) {
|
|
220
|
+
if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0].body)).digest('hex') === operation.expectedDigest) {
|
|
209
221
|
return platformResult('no-op');
|
|
210
222
|
}
|
|
211
|
-
if (!operation.pullRequestSummary)
|
|
212
|
-
return platformResult('blocked', { error: {
|
|
213
|
-
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
214
|
-
} });
|
|
215
223
|
const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
|
|
216
224
|
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity))
|
|
217
225
|
return platformResult('blocked', { error: bound.error ?? {
|
|
@@ -221,8 +229,9 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
221
229
|
agent,
|
|
222
230
|
cwd,
|
|
223
231
|
client,
|
|
224
|
-
body:
|
|
225
|
-
changeReportFile:
|
|
232
|
+
body: summary.body,
|
|
233
|
+
changeReportFile: summary.changeReportFile,
|
|
234
|
+
...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
|
|
226
235
|
primaryResult: 'no_op',
|
|
227
236
|
strict: true,
|
|
228
237
|
skipQueue: true,
|
|
@@ -293,7 +302,7 @@ async function replayPullRequest(taskId, operation, agent, cwd) {
|
|
|
293
302
|
}
|
|
294
303
|
return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
|
|
295
304
|
}
|
|
296
|
-
async function
|
|
305
|
+
async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
297
306
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
298
307
|
if (!resolved.ok)
|
|
299
308
|
return result('failed', [], [], { code: resolved.code, message: resolved.message, retryable: false });
|
|
@@ -307,34 +316,23 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
307
316
|
}
|
|
308
317
|
const candidates = journal.operations.filter((operation) => operation.id !== options.excludeId
|
|
309
318
|
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
310
|
-
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')))
|
|
311
|
-
.filter((operation) => selection === 'all' || operation.dependency === selection)
|
|
312
|
-
.sort((left, right) => left.updatedAt.localeCompare(right.updatedAt));
|
|
319
|
+
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
|
|
313
320
|
if (!candidates.length)
|
|
314
321
|
return result('no-op', [], []);
|
|
322
|
+
const head = candidates[0];
|
|
323
|
+
if (selection !== 'all' && head.dependency !== selection) {
|
|
324
|
+
return result('blocked', [], candidates.map((operation) => operation.id), {
|
|
325
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
326
|
+
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
327
|
+
retryable: true
|
|
328
|
+
});
|
|
329
|
+
}
|
|
315
330
|
const limit = options.limit === undefined ? candidates.length : Math.max(1, options.limit);
|
|
316
331
|
const recovered = [];
|
|
317
332
|
const pending = [];
|
|
318
333
|
const selected = candidates.slice(0, limit);
|
|
319
334
|
for (const [index, operation] of selected.entries()) {
|
|
320
|
-
|
|
321
|
-
pending.push(operation.id);
|
|
322
|
-
return result('blocked', recovered, pending, {
|
|
323
|
-
code: operation.lastCode || 'PLATFORM_OPERATION_FAILED',
|
|
324
|
-
message: 'A previous platform operation failed permanently; later writes remain queued',
|
|
325
|
-
retryable: false
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
if (operation.attempts >= operation.maxAttempts) {
|
|
329
|
-
pending.push(operation.id, ...selected.slice(index + 1).map((next) => next.id));
|
|
330
|
-
pending.push(...candidates.slice(limit).map((next) => next.id));
|
|
331
|
-
return result('blocked', recovered, pending, {
|
|
332
|
-
code: operation.lastCode || 'PLATFORM_OPERATION_RETRY_LIMIT_REACHED',
|
|
333
|
-
message: 'A previous platform operation exhausted its recovery attempts; later writes remain queued',
|
|
334
|
-
retryable: false
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
if (operation.kind !== 'issue-create') {
|
|
335
|
+
{
|
|
338
336
|
try {
|
|
339
337
|
recordPlatformOperation({
|
|
340
338
|
taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: operation.kind,
|
|
@@ -368,8 +366,8 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
368
366
|
if (current.id !== operation.id) {
|
|
369
367
|
try {
|
|
370
368
|
recordPlatformOperation({ taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: operation.kind, target: operation.target,
|
|
371
|
-
expectedDigest:
|
|
372
|
-
state: '
|
|
369
|
+
expectedDigest: current.expectedDigest, issueMetadata: current.issueMetadata, dependency: operation.dependency,
|
|
370
|
+
state: 'queued', replaceOperationId: operation.id });
|
|
373
371
|
targetOperation = { ...operation, expectedDigest: current.expectedDigest, issueMetadata: current.issueMetadata, id: current.id };
|
|
374
372
|
}
|
|
375
373
|
catch (error) {
|
|
@@ -396,7 +394,7 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
396
394
|
if (current.id !== operation.id) {
|
|
397
395
|
try {
|
|
398
396
|
recordPlatformOperation({ taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: operation.kind, target: operation.target,
|
|
399
|
-
expectedDigest:
|
|
397
|
+
expectedDigest: current.expectedDigest, dependency: operation.dependency, state: 'queued', replaceOperationId: operation.id });
|
|
400
398
|
targetOperation = { ...operation, expectedDigest: current.expectedDigest, id: current.id };
|
|
401
399
|
}
|
|
402
400
|
catch (error) {
|
|
@@ -437,7 +435,7 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
437
435
|
...(operation.pullRequestSummary ? { pullRequestSummary: operation.pullRequestSummary } : {}),
|
|
438
436
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
439
437
|
dependency: operation.dependency,
|
|
440
|
-
state: succeeded ? 'succeeded' :
|
|
438
|
+
state: succeeded ? 'succeeded' : 'unknown',
|
|
441
439
|
lastCode: remote.error?.code ?? null
|
|
442
440
|
});
|
|
443
441
|
}
|
|
@@ -455,5 +453,81 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
455
453
|
pending.push(...remaining);
|
|
456
454
|
return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null);
|
|
457
455
|
}
|
|
456
|
+
async function recoverPlatformOperations(taskRef, selection, options) {
|
|
457
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
458
|
+
if (!resolved.ok)
|
|
459
|
+
return result('failed', [], [], { code: resolved.code, message: resolved.message, retryable: false });
|
|
460
|
+
let initial;
|
|
461
|
+
try {
|
|
462
|
+
initial = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
const value = error;
|
|
466
|
+
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
467
|
+
}
|
|
468
|
+
const eligible = (operation) => operation.id !== options.excludeId
|
|
469
|
+
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
470
|
+
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
|
|
471
|
+
const first = initial.operations.filter(eligible);
|
|
472
|
+
if (!first.length)
|
|
473
|
+
return result('no-op', [], []);
|
|
474
|
+
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|
|
475
|
+
const attemptLimit = options.attempts ?? DEFAULT_RECOVERY_ATTEMPTS;
|
|
476
|
+
if (!Number.isSafeInteger(attemptLimit) || attemptLimit <= 0) {
|
|
477
|
+
return result('failed', [], [], { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'Recovery attempts must be a positive safe integer', retryable: false });
|
|
478
|
+
}
|
|
479
|
+
const recovered = [];
|
|
480
|
+
for (let operationIndex = 0; operationIndex < operationLimit; operationIndex += 1) {
|
|
481
|
+
let latest;
|
|
482
|
+
try {
|
|
483
|
+
latest = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
const value = error;
|
|
487
|
+
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
488
|
+
}
|
|
489
|
+
const head = latest.operations.find(eligible);
|
|
490
|
+
if (!head)
|
|
491
|
+
break;
|
|
492
|
+
if (selection !== 'all' && head.dependency !== selection) {
|
|
493
|
+
const pending = latest.operations.filter(eligible).map((operation) => operation.id);
|
|
494
|
+
return result('blocked', recovered, pending, {
|
|
495
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
496
|
+
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
497
|
+
retryable: true
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
let last = null;
|
|
501
|
+
for (let attempt = 0; attempt < attemptLimit; attempt += 1) {
|
|
502
|
+
last = await recoverPlatformOperationsOnce(taskRef, selection, { ...options, limit: 1 });
|
|
503
|
+
recovered.push(...last.recovered.filter((id) => !recovered.includes(id)));
|
|
504
|
+
if (last.recovered.length > 0 || last.status === 'applied' || last.status === 'no-op')
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
if (!last || (last.recovered.length === 0 && last.status !== 'applied' && last.status !== 'no-op')) {
|
|
508
|
+
let current;
|
|
509
|
+
try {
|
|
510
|
+
current = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
return result('blocked', recovered, [head.id], last?.error ?? null);
|
|
514
|
+
}
|
|
515
|
+
const pending = current.operations.filter(eligible).map((operation) => operation.id);
|
|
516
|
+
return result(last?.status === 'failed' ? 'failed' : 'blocked', recovered, pending, last?.error ?? {
|
|
517
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
518
|
+
message: 'The first queued platform operation remains unresolved after this recovery run',
|
|
519
|
+
retryable: true
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
let remaining;
|
|
524
|
+
try {
|
|
525
|
+
remaining = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.filter(eligible).map((operation) => operation.id);
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
remaining = [];
|
|
529
|
+
}
|
|
530
|
+
return result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining, remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null);
|
|
531
|
+
}
|
|
458
532
|
export { fieldsMatchExpected, labelsMatchOwnedPrefix, recoverPlatformOperations };
|
|
459
533
|
//# sourceMappingURL=platform-operation-recovery.js.map
|
|
@@ -98,16 +98,21 @@ function enumerateTaskDirs(repoRoot) {
|
|
|
98
98
|
*/
|
|
99
99
|
function enumerateAllTaskDirs(repoRoot) {
|
|
100
100
|
const out = [];
|
|
101
|
-
const seen = new
|
|
101
|
+
const seen = new Map();
|
|
102
102
|
const add = (taskId, taskDir, state) => {
|
|
103
103
|
if (!TASK_ID_RE.test(taskId))
|
|
104
104
|
return;
|
|
105
105
|
const stat = fs.lstatSync(taskDir);
|
|
106
106
|
if (!stat.isDirectory() || stat.isSymbolicLink() || !fs.existsSync(path.join(taskDir, 'task.md')))
|
|
107
107
|
return;
|
|
108
|
-
|
|
108
|
+
const canonicalDir = fs.realpathSync.native(taskDir);
|
|
109
|
+
const previousDir = seen.get(taskId);
|
|
110
|
+
if (previousDir !== undefined) {
|
|
111
|
+
if (previousDir === canonicalDir)
|
|
112
|
+
return;
|
|
109
113
|
throw new Error(`duplicate task identity: ${taskId}`);
|
|
110
|
-
|
|
114
|
+
}
|
|
115
|
+
seen.set(taskId, canonicalDir);
|
|
111
116
|
out.push({ taskId, taskDir, state });
|
|
112
117
|
};
|
|
113
118
|
for (const state of FLAT_WORKSPACE_DIRS) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.js";
|
|
3
|
+
import { inspectArtifactDirectory } from "./artifact-lifecycle.js";
|
|
5
4
|
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
6
5
|
export function parseReviewedGitTree(value) {
|
|
7
6
|
return REVIEWED_TREE_RE.test(value) ? value : null;
|
|
@@ -28,32 +27,11 @@ export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
|
|
|
28
27
|
return [":/", ...excludes.map((p) => `:(top,exclude)${p}`)];
|
|
29
28
|
}
|
|
30
29
|
export function findAuthoritativeReviewCodeArtifact(taskDir) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (round === 0) {
|
|
30
|
+
const inventory = inspectArtifactDirectory(taskDir, 'review-code');
|
|
31
|
+
if (inventory.status !== 'ready' || !inventory.latest) {
|
|
34
32
|
return { ok: false, round: 0, fileName: null, path: null, error: null };
|
|
35
33
|
}
|
|
36
|
-
const fileName =
|
|
37
|
-
let taskContent;
|
|
38
|
-
try {
|
|
39
|
-
taskContent = fs.readFileSync(path.join(taskDir, "task.md"), "utf8");
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false, round, fileName, path: null,
|
|
44
|
-
error: error instanceof Error ? error.message : String(error)
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const invalidation = parseInvalidationDocument(taskContent);
|
|
48
|
-
if (!invalidation.ok) {
|
|
49
|
-
return { ok: false, round, fileName, path: null, error: invalidation.message };
|
|
50
|
-
}
|
|
51
|
-
if (invalidationBlocks(invalidation.document)) {
|
|
52
|
-
return { ok: false, round, fileName, path: null, error: "artifact invalidation is incomplete; reconcile task.md before using review-code" };
|
|
53
|
-
}
|
|
54
|
-
if (isArtifactInvalidated(invalidation.document, "review-code", fileName)) {
|
|
55
|
-
return { ok: false, round, fileName, path: null, error: `${fileName} is invalidated; re-run review-code` };
|
|
56
|
-
}
|
|
34
|
+
const { round, name: fileName } = inventory.latest;
|
|
57
35
|
return {
|
|
58
36
|
ok: true,
|
|
59
37
|
round,
|