@fitlab-ai/agent-infra 0.11.15 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
- package/dist/lib/decide.js +2 -7
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/manual-validation.js +19 -33
- package/dist/lib/internal/platform-comment.js +59 -9
- package/dist/lib/internal/task-activity.js +1 -12
- package/dist/lib/internal/task-event.js +1 -12
- package/dist/lib/internal/task-ledger.js +1 -12
- package/dist/lib/internal/task-operation-registry.js +2 -1
- package/dist/lib/internal/task-qualification.js +6 -15
- package/dist/lib/internal/task-review.js +3 -16
- package/dist/lib/internal/task-short-id.js +2 -1
- package/dist/lib/internal/task-verify.js +1 -11
- package/dist/lib/internal/task-warning.js +1 -12
- package/dist/lib/internal/task-workspace.js +43 -0
- package/dist/lib/merge.js +4 -3
- package/dist/lib/platform/issue-comments.js +373 -267
- package/dist/lib/platform/issue-summary-content.js +112 -0
- package/dist/lib/platform/pr-change-report.js +175 -26
- package/dist/lib/platform/pr-summary-content.js +54 -0
- package/dist/lib/platform/pr-summary.js +321 -296
- package/dist/lib/platform/pull-requests.js +3 -15
- package/dist/lib/platform/summary-content-pipeline.js +18 -0
- package/dist/lib/pr-review/evidence-grading.js +3 -2
- package/dist/lib/process-data/index.js +5 -4
- package/dist/lib/process-data/sources.js +6 -0
- package/dist/lib/run/index.js +3 -2
- package/dist/lib/sandbox/commands/create.js +5 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/list-running.js +2 -1
- package/dist/lib/sandbox/control/client.js +8 -13
- package/dist/lib/sandbox/control/completed-reentry.js +2 -1
- package/dist/lib/sandbox/control/task-view.js +4 -2
- package/dist/lib/sandbox/mounts.js +4 -4
- package/dist/lib/sandbox/recovery.js +26 -16
- package/dist/lib/sandbox/removal.js +62 -12
- package/dist/lib/sandbox/task-resolver.js +9 -6
- package/dist/lib/sandbox/workspace-identity.js +6 -5
- package/dist/lib/sandbox/workspace-view.js +29 -22
- package/dist/lib/task/activity-intent.js +2 -12
- package/dist/lib/task/artifact-lifecycle.js +17 -11
- package/dist/lib/task/artifact-operations.js +41 -49
- package/dist/lib/task/capabilities.js +2 -2
- package/dist/lib/task/commands/ls.js +4 -3
- package/dist/lib/task/commands/status.js +1 -1
- package/dist/lib/task/commit-operation.js +6 -6
- package/dist/lib/task/control-authority.js +32 -32
- package/dist/lib/task/create-service.js +2 -4
- package/dist/lib/task/create.js +12 -65
- package/dist/lib/task/delegation-receipts.js +1 -1
- package/dist/lib/task/delivery.js +2 -8
- package/dist/lib/task/events.js +5 -31
- package/dist/lib/task/finalization.js +33 -29
- package/dist/lib/task/lifecycle.js +9 -48
- package/dist/lib/task/local-artifact-finalization.js +1 -2
- package/dist/lib/task/orchestration.js +24 -57
- package/dist/lib/task/platform-operation-recovery.js +87 -88
- package/dist/lib/task/resolve-ref.js +17 -12
- package/dist/lib/task/review-finalization.js +2 -35
- package/dist/lib/task/review-identity.js +3 -1
- package/dist/lib/task/short-id.js +10 -8
- package/dist/lib/task/verification-engine.js +19 -58
- package/dist/lib/task/workspace-root.js +59 -0
- package/dist/lib/task/workspace-snapshot.js +4 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
- package/lib/decide.ts +2 -7
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/manual-validation.ts +20 -37
- package/lib/internal/platform-comment.ts +49 -6
- package/lib/internal/task-activity.ts +1 -10
- package/lib/internal/task-event.ts +1 -10
- package/lib/internal/task-ledger.ts +1 -10
- package/lib/internal/task-operation-registry.ts +2 -1
- package/lib/internal/task-qualification.ts +5 -11
- package/lib/internal/task-review.ts +3 -14
- package/lib/internal/task-short-id.ts +2 -1
- package/lib/internal/task-verify.ts +1 -9
- package/lib/internal/task-warning.ts +1 -10
- package/lib/internal/task-workspace.ts +39 -0
- package/lib/merge.ts +4 -3
- package/lib/platform/issue-comments.ts +478 -280
- package/lib/platform/issue-summary-content.ts +132 -0
- package/lib/platform/pr-change-report.ts +165 -20
- package/lib/platform/pr-summary-content.ts +60 -0
- package/lib/platform/pr-summary.ts +367 -269
- package/lib/platform/pull-requests.ts +3 -24
- package/lib/platform/summary-content-pipeline.ts +23 -0
- package/lib/pr-review/evidence-grading.ts +3 -2
- package/lib/process-data/index.ts +4 -3
- package/lib/process-data/sources.ts +6 -0
- package/lib/run/index.ts +3 -2
- package/lib/sandbox/commands/create.ts +5 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/list-running.ts +2 -1
- package/lib/sandbox/control/client.ts +7 -12
- package/lib/sandbox/control/completed-reentry.ts +2 -1
- package/lib/sandbox/control/task-view.ts +4 -2
- package/lib/sandbox/mounts.ts +4 -4
- package/lib/sandbox/recovery.ts +26 -18
- package/lib/sandbox/removal.ts +60 -27
- package/lib/sandbox/task-resolver.ts +9 -6
- package/lib/sandbox/workspace-identity.ts +6 -5
- package/lib/sandbox/workspace-view.ts +28 -20
- package/lib/task/activity-intent.ts +3 -17
- package/lib/task/artifact-lifecycle.ts +39 -11
- package/lib/task/artifact-operations.ts +28 -35
- package/lib/task/capabilities.ts +2 -2
- package/lib/task/commands/ls.ts +4 -3
- package/lib/task/commands/status.ts +1 -1
- package/lib/task/commit-operation.ts +5 -6
- package/lib/task/control-authority.ts +29 -35
- package/lib/task/create-service.ts +2 -4
- package/lib/task/create.ts +10 -51
- package/lib/task/delegation-receipts.ts +1 -1
- package/lib/task/delivery.ts +2 -10
- package/lib/task/events.ts +5 -32
- package/lib/task/finalization.ts +27 -30
- package/lib/task/lifecycle.ts +9 -50
- package/lib/task/local-artifact-finalization.ts +1 -4
- package/lib/task/orchestration.ts +31 -80
- package/lib/task/platform-operation-recovery.ts +70 -82
- package/lib/task/resolve-ref.ts +18 -12
- package/lib/task/review-finalization.ts +2 -52
- package/lib/task/review-identity.ts +3 -1
- package/lib/task/short-id.ts +10 -8
- package/lib/task/verification-engine.ts +20 -62
- package/lib/task/workspace-root.ts +78 -0
- package/lib/task/workspace-snapshot.ts +4 -3
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +19 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
- package/templates/.agents/README.en.md +10 -15
- package/templates/.agents/README.zh-CN.md +9 -13
- 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/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +2 -2
- package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-sync.en.md +5 -3
- package/templates/.agents/rules/pr-sync.zh-CN.md +7 -10
- package/templates/.agents/rules/release-commands.en.md +3 -0
- package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +0 -2
- package/templates/.agents/rules/task-management.zh-CN.md +0 -2
- package/templates/.agents/rules/task-short-id.en.md +2 -2
- package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
- package/templates/.agents/scripts/task-short-id.js +2 -8
- 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 +0 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/config/verify.json +0 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/config/verify.json +0 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +2 -2
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -30
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -32
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/create-pr/config/verify.json +0 -1
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +10 -3
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +10 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +3 -3
- package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -22
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -22
- package/templates/.agents/skills/create-task/config/verify.json +0 -1
- 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-codescan/config/verify.json +0 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/config/verify.json +0 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/restore-task/config/verify.json +0 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- 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/reference/output-templates.en.md +4 -4
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -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/skills/test/SKILL.en.md +0 -3
- package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
- package/templates/.agents/templates/handoff.en.md +1 -1
- package/templates/.agents/templates/handoff.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +1 -1
- package/templates/.agents/workflows/refactoring.en.yaml +1 -1
- package/dist/lib/task/task-execution-lock.js +0 -250
- package/lib/task/task-execution-lock.ts +0 -320
package/lib/task/lifecycle.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { validateCurrentTaskContract } from './current-contract.ts';
|
|
|
18
18
|
import { isValidAgentInfraVersion } from '../version.ts';
|
|
19
19
|
import { taskIssueIdentity } from '../platform/task-identities.ts';
|
|
20
20
|
import { resourceIdentityEquals } from '../platform/resource-identity.ts';
|
|
21
|
-
import {
|
|
21
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
22
22
|
|
|
23
23
|
const lifecycleIntentCatalog = [
|
|
24
24
|
'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'
|
|
@@ -138,7 +138,7 @@ const DEFAULT_IO: LifecycleFileSystem = {
|
|
|
138
138
|
|
|
139
139
|
function isVirtiofsMountPoint(directory: string, mountInfo: string): boolean {
|
|
140
140
|
const decodeMountInfoPath = (value: string): string => value.replace(/\\([0-7]{3})/gu, (_match, octal: string) => String.fromCharCode(Number.parseInt(octal, 8)));
|
|
141
|
-
const expected =
|
|
141
|
+
const expected = fs.realpathSync.native(directory);
|
|
142
142
|
return mountInfo.split('\n').some((line) => {
|
|
143
143
|
if (!line) return false;
|
|
144
144
|
const fields = line.split(' ');
|
|
@@ -437,14 +437,14 @@ function validateRestoreStaging(request: Extract<TaskLifecycleRequest, { intent:
|
|
|
437
437
|
let workspace: string;
|
|
438
438
|
let stagingDir: string;
|
|
439
439
|
try {
|
|
440
|
-
workspace = fs.realpathSync.native(
|
|
440
|
+
workspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
441
441
|
stagingDir = fs.realpathSync.native(path.resolve(request.stagingDir));
|
|
442
442
|
} catch (error) {
|
|
443
443
|
return { code: 'LIFECYCLE_STAGING_INVALID', message: String(error) };
|
|
444
444
|
}
|
|
445
445
|
const relative = path.relative(workspace, stagingDir);
|
|
446
446
|
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || path.basename(stagingDir) === taskId) {
|
|
447
|
-
return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside
|
|
447
|
+
return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside the project task workspace' };
|
|
448
448
|
}
|
|
449
449
|
let entries: fs.Dirent[];
|
|
450
450
|
try { entries = fs.readdirSync(stagingDir, { withFileTypes: true }); }
|
|
@@ -629,7 +629,7 @@ export function inspectTaskLifecycleProgress(
|
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
function
|
|
632
|
+
function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
|
|
633
633
|
if (requestInput.intent === 'recover-started') {
|
|
634
634
|
return failed(requestInput, { code: 'LIFECYCLE_RECOVERY_REQUIRES_AUTHORITY', message: 'recover-started must be dispatched through task control authority' });
|
|
635
635
|
}
|
|
@@ -671,7 +671,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
671
671
|
} else {
|
|
672
672
|
return failed(request, { code: 'LIFECYCLE_TASK_NOT_FOUND', message: `task ${taskId} has no lifecycle source` }, { taskId });
|
|
673
673
|
}
|
|
674
|
-
const targetPath = path.join(repoRoot,
|
|
674
|
+
const targetPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), spec.target, taskId);
|
|
675
675
|
const requiredSteps = stepsForIntent(request.intent);
|
|
676
676
|
const movingTaskDirectory = sourcePath !== targetPath;
|
|
677
677
|
const currentTaskFile = path.join(sourcePath, 'task.md');
|
|
@@ -709,7 +709,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
709
709
|
if (!contract.ok) return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
710
710
|
}
|
|
711
711
|
const registryRequest = executeShortIdCommand({
|
|
712
|
-
operation: 'list', activeDir: path.join(repoRoot, '
|
|
712
|
+
operation: 'list', activeDir: path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active'),
|
|
713
713
|
shortIdLength: configuredShortIdLength(repoRoot)
|
|
714
714
|
});
|
|
715
715
|
if (registryRequest.status === 'failed') {
|
|
@@ -880,7 +880,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
880
880
|
}
|
|
881
881
|
}
|
|
882
882
|
}
|
|
883
|
-
const activeTaskPath = path.join(repoRoot, '
|
|
883
|
+
const activeTaskPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
|
|
884
884
|
if (request.intent === 'complete' && fs.existsSync(activeTaskPath)) {
|
|
885
885
|
try {
|
|
886
886
|
let sourceIsVirtiofsMount = false;
|
|
@@ -925,50 +925,9 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
925
925
|
};
|
|
926
926
|
}
|
|
927
927
|
|
|
928
|
-
function applyTaskLifecycle(request: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
|
|
929
|
-
const normalized = normalizedRequest(request);
|
|
930
|
-
if ('code' in normalized) return applyTaskLifecycleUnlocked(request, options);
|
|
931
|
-
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
932
|
-
const taskId = normalized.intent === 'restore'
|
|
933
|
-
? normalized.taskRef
|
|
934
|
-
: (() => {
|
|
935
|
-
const resolved = resolveTaskRef(normalized.taskRef, { repoRoot });
|
|
936
|
-
return resolved.ok ? resolved.taskId : null;
|
|
937
|
-
})();
|
|
938
|
-
if (!taskId) return applyTaskLifecycleUnlocked(request, options);
|
|
939
|
-
try {
|
|
940
|
-
return withTaskExecutionLock(repoRoot, taskId, 'task-lifecycle.move', () => applyTaskLifecycleUnlocked(request, options));
|
|
941
|
-
} catch (error) {
|
|
942
|
-
if (error instanceof TaskExecutionLockError) {
|
|
943
|
-
return failed(request, { code: error.code, message: error.message }, { taskId });
|
|
944
|
-
}
|
|
945
|
-
throw error;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
function applyTaskLifecycleWithinTaskLock(
|
|
950
|
-
request: TaskLifecycleRequest,
|
|
951
|
-
options: TaskLifecycleOptions,
|
|
952
|
-
lockedTaskId: string
|
|
953
|
-
): TaskLifecycleResult {
|
|
954
|
-
const normalized = normalizedRequest(request);
|
|
955
|
-
if ('code' in normalized) return failed(request, normalized);
|
|
956
|
-
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
957
|
-
const resolved = normalized.intent === 'restore'
|
|
958
|
-
? { ok: true as const, taskId: normalized.taskRef }
|
|
959
|
-
: resolveTaskRef(normalized.taskRef, { repoRoot });
|
|
960
|
-
if (!resolved.ok || resolved.taskId !== lockedTaskId) {
|
|
961
|
-
return failed(request, {
|
|
962
|
-
code: 'LIFECYCLE_LOCK_SCOPE_MISMATCH',
|
|
963
|
-
message: 'task lifecycle request does not match the held task lock'
|
|
964
|
-
}, { taskId: resolved.ok ? resolved.taskId : resolved.taskId });
|
|
965
|
-
}
|
|
966
|
-
return applyTaskLifecycleUnlocked(request, options);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
928
|
export {
|
|
970
929
|
lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog,
|
|
971
|
-
applyTaskLifecycle,
|
|
930
|
+
applyTaskLifecycle, previewTaskCompletion
|
|
972
931
|
};
|
|
973
932
|
export type {
|
|
974
933
|
TaskLifecycleIntent, TaskLifecycleRequest, TaskLifecycleResult, TaskLifecycleOptions,
|
|
@@ -52,7 +52,6 @@ type LocalArtifactFinalizationRequest = {
|
|
|
52
52
|
artifact: string;
|
|
53
53
|
repoRoot?: string;
|
|
54
54
|
requiredSections?: readonly string[];
|
|
55
|
-
lockAlreadyHeld?: boolean;
|
|
56
55
|
};
|
|
57
56
|
|
|
58
57
|
type LocalArtifactFinalizationResult = {
|
|
@@ -129,7 +128,6 @@ type LocalArtifactPreparation = Readonly<{
|
|
|
129
128
|
result: LocalArtifactFinalizationResult;
|
|
130
129
|
content: string;
|
|
131
130
|
repoRoot?: string;
|
|
132
|
-
lockAlreadyHeld?: boolean;
|
|
133
131
|
}>;
|
|
134
132
|
|
|
135
133
|
function prepareLocalArtifact(
|
|
@@ -180,8 +178,7 @@ function prepareLocalArtifact(
|
|
|
180
178
|
return {
|
|
181
179
|
result: validation.ok && !qualificationError ? { ...result, status: 'passed', error: null } : result,
|
|
182
180
|
content,
|
|
183
|
-
repoRoot: resolved.repoRoot
|
|
184
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
181
|
+
repoRoot: resolved.repoRoot
|
|
185
182
|
};
|
|
186
183
|
|
|
187
184
|
}
|
|
@@ -9,6 +9,7 @@ import { parseReviewSummary } from './review-artifacts.ts';
|
|
|
9
9
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
10
10
|
import type { LedgerDocument } from './ledger.ts';
|
|
11
11
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
12
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
12
13
|
import {
|
|
13
14
|
activateDelegation,
|
|
14
15
|
abortActivatedDelegation,
|
|
@@ -48,14 +49,13 @@ import {
|
|
|
48
49
|
import type { RepositorySnapshot } from './workspace-snapshot.ts';
|
|
49
50
|
import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
|
|
50
51
|
import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
|
|
51
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
52
52
|
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
|
|
53
53
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
|
|
54
54
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
|
|
55
55
|
import { hasOpenLifecycleExecution } from './activity-log.ts';
|
|
56
56
|
import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
|
|
57
57
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
58
|
-
import {
|
|
58
|
+
import { resolveArtifactContextForResolvedTask } from './artifact-lifecycle.ts';
|
|
59
59
|
|
|
60
60
|
type OrchestrationStatus = 'running' | 'paused' | 'completed';
|
|
61
61
|
type ModelPolicySource = Readonly<{
|
|
@@ -441,19 +441,7 @@ function mutateDelegationAdapterEvidence(
|
|
|
441
441
|
): unknown {
|
|
442
442
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
443
443
|
if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
444
|
-
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function mutateDelegationAdapterEvidenceWithinTaskLock(
|
|
448
|
-
taskRef: string,
|
|
449
|
-
receiptId: string,
|
|
450
|
-
adapterId: AgentClientId,
|
|
451
|
-
mutate: (current: unknown) => unknown,
|
|
452
|
-
options: OrchestrationOptions = {}
|
|
453
|
-
): unknown {
|
|
454
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
455
|
-
if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
456
|
-
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, true);
|
|
444
|
+
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options);
|
|
457
445
|
}
|
|
458
446
|
|
|
459
447
|
function mutateDelegationAdapterEvidenceAtResolved(
|
|
@@ -461,8 +449,7 @@ function mutateDelegationAdapterEvidenceAtResolved(
|
|
|
461
449
|
receiptId: string,
|
|
462
450
|
adapterId: AgentClientId,
|
|
463
451
|
mutate: (current: unknown) => unknown,
|
|
464
|
-
options: OrchestrationOptions
|
|
465
|
-
taskLockHeld: boolean
|
|
452
|
+
options: OrchestrationOptions
|
|
466
453
|
): unknown {
|
|
467
454
|
const update = () => {
|
|
468
455
|
try {
|
|
@@ -494,9 +481,7 @@ function mutateDelegationAdapterEvidenceAtResolved(
|
|
|
494
481
|
}
|
|
495
482
|
};
|
|
496
483
|
let mutationError: unknown;
|
|
497
|
-
const result =
|
|
498
|
-
? update()
|
|
499
|
-
: withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `agent-adapter.${adapterId}.evidence`, update);
|
|
484
|
+
const result = update();
|
|
500
485
|
if (mutationError) throw mutationError;
|
|
501
486
|
return result;
|
|
502
487
|
}
|
|
@@ -733,9 +718,13 @@ function routeFromFacts(facts: LifecycleFacts): Omit<OrchestrationNext, 'request
|
|
|
733
718
|
return { action: command, role, stage: action };
|
|
734
719
|
}
|
|
735
720
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
721
|
+
type ResolvedTaskRef = Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>;
|
|
722
|
+
|
|
723
|
+
function routeResolvedTask(
|
|
724
|
+
taskRef: string,
|
|
725
|
+
resolved: ResolvedTaskRef,
|
|
726
|
+
options: OrchestrationOptions
|
|
727
|
+
): OrchestrationResult {
|
|
739
728
|
let gitRoot: string;
|
|
740
729
|
try {
|
|
741
730
|
gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
@@ -772,7 +761,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
772
761
|
resolved.taskDir,
|
|
773
762
|
content,
|
|
774
763
|
resolved.state,
|
|
775
|
-
hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation)
|
|
764
|
+
hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation),
|
|
765
|
+
resolved.repoRoot
|
|
776
766
|
);
|
|
777
767
|
if (!facts.ok) return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
|
|
778
768
|
const routed = routeFromFacts(facts.facts);
|
|
@@ -864,7 +854,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
864
854
|
}
|
|
865
855
|
const action = routed.stage as LifecycleAction;
|
|
866
856
|
const family = action === 'analysis' ? 'analysis' : action;
|
|
867
|
-
const artifactContext =
|
|
857
|
+
const artifactContext = resolveArtifactContextForResolvedTask(resolved, family, {
|
|
868
858
|
repoRoot: resolved.repoRoot
|
|
869
859
|
});
|
|
870
860
|
if (artifactContext.status !== 'ready' || !artifactContext.selection) {
|
|
@@ -898,6 +888,12 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
898
888
|
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
899
889
|
}
|
|
900
890
|
|
|
891
|
+
function routeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
892
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
893
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
894
|
+
return routeResolvedTask(taskRef, resolved, options);
|
|
895
|
+
}
|
|
896
|
+
|
|
901
897
|
function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
902
898
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
903
899
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
@@ -952,7 +948,7 @@ function validateDelegationRun(
|
|
|
952
948
|
return null;
|
|
953
949
|
}
|
|
954
950
|
|
|
955
|
-
function
|
|
951
|
+
function prepareOrchestrationDelegation(
|
|
956
952
|
taskRef: string,
|
|
957
953
|
input: Readonly<{
|
|
958
954
|
client: AgentClientId;
|
|
@@ -978,7 +974,7 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
978
974
|
const runFailure = validateDelegationRun(taskRef, resolved.taskId, run, options);
|
|
979
975
|
if (runFailure) return runFailure;
|
|
980
976
|
const activeRun = run!;
|
|
981
|
-
const routed =
|
|
977
|
+
const routed = routeResolvedTask(taskRef, resolved, options);
|
|
982
978
|
if (!routed.next) return routed;
|
|
983
979
|
const next = routed.next;
|
|
984
980
|
const policyFailure = validatePreparedModelPolicy(resolved.taskId, activeRun, next, input);
|
|
@@ -1010,31 +1006,7 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1010
1006
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
1011
1007
|
}
|
|
1012
1008
|
|
|
1013
|
-
function
|
|
1014
|
-
taskRef: string,
|
|
1015
|
-
input: Readonly<{
|
|
1016
|
-
client: AgentClientId;
|
|
1017
|
-
requestedModel?: string;
|
|
1018
|
-
requestedReasoningEffort?: string;
|
|
1019
|
-
}>,
|
|
1020
|
-
options: OrchestrationOptions = {}
|
|
1021
|
-
): OrchestrationResult {
|
|
1022
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1023
|
-
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1024
|
-
try {
|
|
1025
|
-
return withTaskExecutionLock(
|
|
1026
|
-
resolved.repoRoot,
|
|
1027
|
-
resolved.taskId,
|
|
1028
|
-
'task-orchestration.prepare.task',
|
|
1029
|
-
() => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
|
|
1030
|
-
);
|
|
1031
|
-
} catch (error) {
|
|
1032
|
-
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
1033
|
-
throw error;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
function dispatchOrchestrationDelegationUnlocked(
|
|
1009
|
+
function dispatchOrchestrationDelegation(
|
|
1038
1010
|
taskRef: string,
|
|
1039
1011
|
options: OrchestrationOptions = {}
|
|
1040
1012
|
): OrchestrationResult {
|
|
@@ -1055,25 +1027,6 @@ function dispatchOrchestrationDelegationUnlocked(
|
|
|
1055
1027
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1056
1028
|
}
|
|
1057
1029
|
|
|
1058
|
-
function dispatchOrchestrationDelegation(
|
|
1059
|
-
taskRef: string,
|
|
1060
|
-
options: OrchestrationOptions = {}
|
|
1061
|
-
): OrchestrationResult {
|
|
1062
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1063
|
-
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1064
|
-
try {
|
|
1065
|
-
return withTaskExecutionLock(
|
|
1066
|
-
resolved.repoRoot,
|
|
1067
|
-
resolved.taskId,
|
|
1068
|
-
'task-orchestration.dispatch.task',
|
|
1069
|
-
() => dispatchOrchestrationDelegationUnlocked(taskRef, options)
|
|
1070
|
-
);
|
|
1071
|
-
} catch (error) {
|
|
1072
|
-
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
1073
|
-
throw error;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
1030
|
type MatchingDelegationsResult =
|
|
1078
1031
|
| Readonly<{ ok: true; matches: readonly { taskId: string; run: OrchestrationRun }[] }>
|
|
1079
1032
|
| Readonly<{ ok: false; error: OrchestrationResult }>;
|
|
@@ -1090,7 +1043,7 @@ function matchingDelegations(
|
|
|
1090
1043
|
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
|
|
1091
1044
|
}
|
|
1092
1045
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1093
|
-
const taskDir = path.join(repoRoot, '
|
|
1046
|
+
const taskDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
|
|
1094
1047
|
const run = readRun(taskDir, options);
|
|
1095
1048
|
const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
|
|
1096
1049
|
? [{ taskId, run }]
|
|
@@ -1385,7 +1338,7 @@ function recoverPreparedOrchestrationDelegation(
|
|
|
1385
1338
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1386
1339
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1387
1340
|
try {
|
|
1388
|
-
return
|
|
1341
|
+
return (() => {
|
|
1389
1342
|
const run = readRun(resolved.taskDir, options);
|
|
1390
1343
|
const receipt = run?.pendingDelegation;
|
|
1391
1344
|
if (!run || !receipt) {
|
|
@@ -1430,9 +1383,8 @@ function recoverPreparedOrchestrationDelegation(
|
|
|
1430
1383
|
}, options.now);
|
|
1431
1384
|
saveRun(resolved.taskDir, updated);
|
|
1432
1385
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1433
|
-
});
|
|
1386
|
+
})();
|
|
1434
1387
|
} catch (error) {
|
|
1435
|
-
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
1436
1388
|
return failed(
|
|
1437
1389
|
'ORCHESTRATION_PREPARED_RECOVERY_FAILED',
|
|
1438
1390
|
error instanceof Error ? error.message : String(error),
|
|
@@ -1462,7 +1414,7 @@ function recoveryReceiptMatches(
|
|
|
1462
1414
|
&& normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
|
|
1463
1415
|
}
|
|
1464
1416
|
|
|
1465
|
-
function
|
|
1417
|
+
function recoverActivatedOrchestrationDelegation(
|
|
1466
1418
|
taskRef: string,
|
|
1467
1419
|
event: ActivatedRecoveryEvent,
|
|
1468
1420
|
options: OrchestrationOptions = {}
|
|
@@ -1598,7 +1550,7 @@ function sealOrchestrationDelegation(
|
|
|
1598
1550
|
const orchestrationOptions = options;
|
|
1599
1551
|
const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
|
|
1600
1552
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1601
|
-
return
|
|
1553
|
+
return (() => {
|
|
1602
1554
|
const run = readRun(resolved.taskDir, orchestrationOptions);
|
|
1603
1555
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1604
1556
|
if (expected && (run.runId !== expected.runId || run.pendingDelegation.id !== expected.receiptId)) {
|
|
@@ -1616,7 +1568,7 @@ function sealOrchestrationDelegation(
|
|
|
1616
1568
|
const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
|
|
1617
1569
|
saveRun(resolved.taskDir, updated);
|
|
1618
1570
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1619
|
-
});
|
|
1571
|
+
})();
|
|
1620
1572
|
}
|
|
1621
1573
|
|
|
1622
1574
|
function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
@@ -1672,7 +1624,6 @@ export {
|
|
|
1672
1624
|
orchestrationPath,
|
|
1673
1625
|
readDelegationAdapterEvidence,
|
|
1674
1626
|
mutateDelegationAdapterEvidence,
|
|
1675
|
-
mutateDelegationAdapterEvidenceWithinTaskLock,
|
|
1676
1627
|
pauseMatchingOrchestrationDelegation,
|
|
1677
1628
|
pauseOrchestration,
|
|
1678
1629
|
planOrchestrationStageCompletion,
|
|
@@ -1680,7 +1631,7 @@ export {
|
|
|
1680
1631
|
readRun,
|
|
1681
1632
|
reconcileMatchingOrchestrationDelegation,
|
|
1682
1633
|
recoverPreparedOrchestrationDelegation,
|
|
1683
|
-
|
|
1634
|
+
recoverActivatedOrchestrationDelegation,
|
|
1684
1635
|
routeOrchestration,
|
|
1685
1636
|
sealMatchingOrchestrationDelegation,
|
|
1686
1637
|
sealOrchestrationDelegation,
|
|
@@ -3,13 +3,15 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
|
|
6
|
-
import { inspectPlatformCommentOperation,
|
|
6
|
+
import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.ts';
|
|
7
|
+
import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.ts';
|
|
8
|
+
import { enumerateArtifacts } from './artifacts.ts';
|
|
7
9
|
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
|
|
8
10
|
import { resolvePlatformProviderContext } from '../platform/context.ts';
|
|
9
11
|
import { providerOperationContext, providerError, unsupportedProviderOperation } from '../platform/provider-bridge.ts';
|
|
10
12
|
import { inLabelMappingDigest } from '../platform/in-label-sync.ts';
|
|
11
|
-
import { bindPlatformPullRequest,
|
|
12
|
-
import {
|
|
13
|
+
import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
14
|
+
import { replayPullRequestSummary } from '../platform/pr-summary.ts';
|
|
13
15
|
import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.ts';
|
|
14
16
|
import type { PlatformClient } from '../platform/context.ts';
|
|
15
17
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
@@ -53,33 +55,57 @@ function result(status: RecoveryResult['status'], recovered: string[], pending:
|
|
|
53
55
|
function summaryPayload(taskDir: string, taskId: string): { body: string; sha256: string } | null {
|
|
54
56
|
const file = path.join(taskDir, '.delivery-summary.json');
|
|
55
57
|
if (!fs.existsSync(file)) return null;
|
|
56
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; content?: unknown; contentSha256?: unknown };
|
|
59
|
+
let content;
|
|
60
|
+
try { content = parseDeliverySummaryContent(data.content); }
|
|
61
|
+
catch { content = null; }
|
|
62
|
+
if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
|
|
63
|
+
|| createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
|
|
61
64
|
throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
|
|
67
|
+
if (!canonical?.ok) throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
68
|
+
return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cancellationPayload(taskDir: string, taskId: string): string | null {
|
|
72
|
+
const taskPath = path.join(taskDir, 'task.md');
|
|
73
|
+
if (!fs.existsSync(taskPath)) return null;
|
|
74
|
+
const taskContent = fs.readFileSync(taskPath, 'utf8');
|
|
75
|
+
const frontmatter = parseTaskFrontmatter(taskContent);
|
|
76
|
+
const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
|
|
77
|
+
const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
|
|
78
|
+
const content = parseCancelSummaryContent({
|
|
79
|
+
taskId,
|
|
80
|
+
title,
|
|
81
|
+
outcome: 'cancelled',
|
|
82
|
+
reason: frontmatter.cancel_reason,
|
|
83
|
+
cancelledAt: frontmatter.cancelled_at,
|
|
84
|
+
lastCompletedArtifact: artifacts.at(-1) ?? null
|
|
85
|
+
});
|
|
86
|
+
return renderCancelSummaryContent(content);
|
|
64
87
|
}
|
|
65
88
|
|
|
66
89
|
async function replayComment(taskId: string, taskDir: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
67
90
|
if (operation.kind === 'task-comment') {
|
|
68
|
-
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency,
|
|
91
|
+
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
69
92
|
}
|
|
70
93
|
if (operation.kind === 'artifact-comment') {
|
|
71
|
-
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency,
|
|
94
|
+
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
72
95
|
}
|
|
73
96
|
if (operation.kind === 'summary-comment') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
97
|
+
let summary;
|
|
98
|
+
try { summary = summaryPayload(taskDir, taskId); }
|
|
99
|
+
catch (error) { return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
100
|
+
if (!summary) return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
|
|
101
|
+
return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
|
|
80
102
|
}
|
|
81
103
|
if (operation.kind === 'cancel-comment') {
|
|
82
|
-
|
|
104
|
+
let body: string | null;
|
|
105
|
+
try { body = cancellationPayload(taskDir, taskId); }
|
|
106
|
+
catch (error) { return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
107
|
+
if (!body) return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
|
|
108
|
+
return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
83
109
|
}
|
|
84
110
|
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
|
|
85
111
|
}
|
|
@@ -183,56 +209,6 @@ async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof re
|
|
|
183
209
|
return platformResult('no-op');
|
|
184
210
|
}
|
|
185
211
|
|
|
186
|
-
async function replayPullRequestSummary(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
187
|
-
let identity;
|
|
188
|
-
try { identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity'); }
|
|
189
|
-
catch { return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } }); }
|
|
190
|
-
const summary = operation.pullRequestSummary;
|
|
191
|
-
if (!summary) return platformResult('blocked', { error: {
|
|
192
|
-
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
193
|
-
} });
|
|
194
|
-
const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
|
|
195
|
-
if (manualValidationHeading && !summary.manualValidation) return platformResult('blocked', { error: {
|
|
196
|
-
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
|
|
197
|
-
} });
|
|
198
|
-
const resolved = await resolvePlatformProviderContext({ cwd, client });
|
|
199
|
-
if (!resolved.ok) return resolved.context;
|
|
200
|
-
const listed = resolved.value.provider.comments?.list
|
|
201
|
-
? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
|
|
202
|
-
: unsupportedProviderOperation(resolved.value.provider, 'comments.list');
|
|
203
|
-
if (!listed.ok) return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
|
|
204
|
-
const marker = `<!-- sync-pr:${taskId}:summary -->`;
|
|
205
|
-
const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
|
|
206
|
-
if (summaries.length > 1) return platformResult('blocked', { error: {
|
|
207
|
-
code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
|
|
208
|
-
} });
|
|
209
|
-
if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0]!.body)).digest('hex') === operation.expectedDigest) {
|
|
210
|
-
return platformResult('no-op');
|
|
211
|
-
}
|
|
212
|
-
const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
|
|
213
|
-
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity)) return platformResult('blocked', { error: bound.error ?? {
|
|
214
|
-
code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
|
|
215
|
-
} });
|
|
216
|
-
const replayed = await syncPullRequestSummary(taskId, {
|
|
217
|
-
agent,
|
|
218
|
-
cwd,
|
|
219
|
-
client,
|
|
220
|
-
body: summary.body,
|
|
221
|
-
changeReportFile: summary.changeReportFile,
|
|
222
|
-
...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
|
|
223
|
-
primaryResult: 'no_op',
|
|
224
|
-
strict: true,
|
|
225
|
-
skipQueue: true,
|
|
226
|
-
lockAlreadyHeld: true
|
|
227
|
-
});
|
|
228
|
-
if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
|
|
229
|
-
return platformResult(replayed.status, { changed: replayed.changed });
|
|
230
|
-
}
|
|
231
|
-
return platformResult('blocked', { error: replayed.error ?? {
|
|
232
|
-
code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
|
|
233
|
-
} });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
212
|
async function replayPullRequestReview(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
237
213
|
const intent = operation.pullRequestReview;
|
|
238
214
|
if (!intent) return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request review recovery parameters are missing', retryable: false } });
|
|
@@ -281,6 +257,14 @@ async function replayPullRequest(taskId: string, operation: ReturnType<typeof re
|
|
|
281
257
|
return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
|
|
282
258
|
}
|
|
283
259
|
|
|
260
|
+
function isRecoveryEligible(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], excludeId?: string): boolean {
|
|
261
|
+
if (operation.id === excludeId) return false;
|
|
262
|
+
if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown') return true;
|
|
263
|
+
return operation.state === 'failed'
|
|
264
|
+
&& operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'
|
|
265
|
+
;
|
|
266
|
+
}
|
|
267
|
+
|
|
284
268
|
async function recoverPlatformOperationsOnce(
|
|
285
269
|
taskRef: string,
|
|
286
270
|
selection: 'required' | 'deferred' | 'all',
|
|
@@ -294,9 +278,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
294
278
|
const value = error as { code?: string; message?: string };
|
|
295
279
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
296
280
|
}
|
|
297
|
-
const candidates = journal.operations.filter((operation) => operation
|
|
298
|
-
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
299
|
-
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
|
|
281
|
+
const candidates = journal.operations.filter((operation) => isRecoveryEligible(operation, options.excludeId));
|
|
300
282
|
if (!candidates.length) return result('no-op', [], []);
|
|
301
283
|
const head = candidates[0]!;
|
|
302
284
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
@@ -358,11 +340,18 @@ async function recoverPlatformOperationsOnce(
|
|
|
358
340
|
const commentOptions = operation.kind === 'task-comment'
|
|
359
341
|
? { kind: 'task' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
360
342
|
: operation.kind === 'artifact-comment'
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
343
|
+
? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
344
|
+
: operation.kind === 'summary-comment' ? (() => {
|
|
345
|
+
try {
|
|
346
|
+
const summary = summaryPayload(resolved.taskDir, resolved.taskId);
|
|
347
|
+
return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
|
|
348
|
+
} catch { return null; }
|
|
349
|
+
})() : (() => {
|
|
350
|
+
try {
|
|
351
|
+
const body = cancellationPayload(resolved.taskDir, resolved.taskId);
|
|
352
|
+
return body ? { kind: 'cancel' as const, body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
|
|
353
|
+
} catch { return null; }
|
|
354
|
+
})();
|
|
366
355
|
const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
|
|
367
356
|
if (!current) {
|
|
368
357
|
pending.push(operation.id);
|
|
@@ -384,20 +373,21 @@ async function recoverPlatformOperationsOnce(
|
|
|
384
373
|
remote = operation.kind === 'issue-create'
|
|
385
374
|
? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
|
|
386
375
|
: operation.kind === 'pull-request-summary'
|
|
387
|
-
? await replayPullRequestSummary(resolved.taskId,
|
|
376
|
+
? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
|
|
388
377
|
: operation.kind === 'pull-request-review'
|
|
389
378
|
? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
|
|
390
379
|
: operation.kind === 'issue-metadata'
|
|
391
380
|
? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
392
381
|
: operation.kind === 'pull-request'
|
|
393
382
|
? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
394
|
-
: await replayComment(resolved.taskId, resolved.taskDir,
|
|
383
|
+
: await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
|
|
395
384
|
}
|
|
396
385
|
catch (error) {
|
|
397
386
|
const value = error as { code?: string; message?: string };
|
|
398
387
|
remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
|
|
399
388
|
}
|
|
400
389
|
const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
|
|
390
|
+
const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
|
|
401
391
|
try {
|
|
402
392
|
recordPlatformOperation({
|
|
403
393
|
taskRef: resolved.taskId,
|
|
@@ -412,7 +402,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
412
402
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
413
403
|
dependency: operation.dependency,
|
|
414
404
|
state: succeeded ? 'succeeded' : 'unknown',
|
|
415
|
-
lastCode: remote.error?.code ?? null
|
|
405
|
+
lastCode: remote.error?.code ?? latestOperation?.lastCode ?? null
|
|
416
406
|
});
|
|
417
407
|
} catch (error) {
|
|
418
408
|
const value = error as { code?: string; message?: string };
|
|
@@ -442,9 +432,7 @@ async function recoverPlatformOperations(
|
|
|
442
432
|
const value = error as { code?: string; message?: string };
|
|
443
433
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
444
434
|
}
|
|
445
|
-
const eligible = (operation: typeof initial.operations[number]) => operation
|
|
446
|
-
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
447
|
-
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
|
|
435
|
+
const eligible = (operation: typeof initial.operations[number]) => isRecoveryEligible(operation, options.excludeId);
|
|
448
436
|
const first = initial.operations.filter(eligible);
|
|
449
437
|
if (!first.length) return result('no-op', [], []);
|
|
450
438
|
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|