@fitlab-ai/agent-infra 0.9.14 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- 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 +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { locateActivityLog, pairEntries } from '../task/activity-log.js';
|
|
4
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
|
+
import { applyTaskEvent } from '../task/events.js';
|
|
6
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
7
|
+
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
8
|
+
import { createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.js';
|
|
9
|
+
import { archiveManualValidationGeneration, createManualValidationTransaction, readManualValidationTransaction, retryManualValidationTransaction, summaryPreimageDigest, transitionManualValidationTransaction, validateManualValidationGenerationArchive, writeManualValidationTransactionAtomic } from '../task/manual-validation-transaction.js';
|
|
10
|
+
import { sha256File } from '../task/artifact-receipts.js';
|
|
11
|
+
import { summaryCommentState, syncPullRequestSummary } from '../platform/pr-summary.js';
|
|
12
|
+
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
13
|
+
const USAGE = `Usage: agent-infra-internal manual-validation transaction <task-ref> --prepare --artifact <artifact> --summary-file <path> --agent <agent> [--cwd <path>]
|
|
14
|
+
agent-infra-internal manual-validation transaction <task-ref> --artifact <artifact> --summary-file <path> --change-report-file <path> --agent <agent> [--result pr_created|pr_reused|no_op] [--cwd <path>]`;
|
|
15
|
+
function result(status, error = null, extra = {}) {
|
|
16
|
+
return { status, changed: false, error, ...extra };
|
|
17
|
+
}
|
|
18
|
+
function printFailure(format, error) {
|
|
19
|
+
if (format === 'json')
|
|
20
|
+
process.stdout.write(`${JSON.stringify(result('failed', error))}\n`);
|
|
21
|
+
else
|
|
22
|
+
process.stderr.write(`${error.code}: ${error.message}\n`);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
}
|
|
25
|
+
const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
|
|
26
|
+
function manualSummaryBody(body, phase, transactionId, receiptDigest = '', evidenceDigest = '', prHeadSha = '') {
|
|
27
|
+
const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
|
|
28
|
+
const section = phase === 'pending'
|
|
29
|
+
? chinese
|
|
30
|
+
? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
|
|
31
|
+
: `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
|
|
32
|
+
: chinese
|
|
33
|
+
? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
|
|
34
|
+
: `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
|
|
35
|
+
let inserted = false;
|
|
36
|
+
const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
|
|
37
|
+
if (inserted)
|
|
38
|
+
return '';
|
|
39
|
+
inserted = true;
|
|
40
|
+
return `${section}\n\n`;
|
|
41
|
+
});
|
|
42
|
+
return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
|
|
43
|
+
}
|
|
44
|
+
function openManualValidationStarted(taskMdPath) {
|
|
45
|
+
let content;
|
|
46
|
+
try {
|
|
47
|
+
content = fs.readFileSync(taskMdPath, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { present: false, transactionId: null };
|
|
51
|
+
}
|
|
52
|
+
const section = locateActivityLog(content);
|
|
53
|
+
if (!section)
|
|
54
|
+
return { present: false, transactionId: null };
|
|
55
|
+
const row = pairEntries(section.entries)
|
|
56
|
+
.filter((item) => item.step === 'Complete Manual Validation' && item.started && !item.done)
|
|
57
|
+
.at(-1);
|
|
58
|
+
if (!row)
|
|
59
|
+
return { present: false, transactionId: null };
|
|
60
|
+
return {
|
|
61
|
+
present: true,
|
|
62
|
+
transactionId: /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1] ?? null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function executeManualValidationTransaction(taskRef, values, cwd, options = {}) {
|
|
66
|
+
const prepareOnly = values.prepare === 'true';
|
|
67
|
+
const required = prepareOnly
|
|
68
|
+
? ['artifact', 'summaryFile', 'agent']
|
|
69
|
+
: ['artifact', 'summaryFile', 'changeReportFile', 'agent'];
|
|
70
|
+
const missing = required.find((key) => !values[key]);
|
|
71
|
+
if (missing)
|
|
72
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: `transaction requires --${missing.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}` });
|
|
73
|
+
const agent = normalizeAgentToken(values.agent);
|
|
74
|
+
if (!agent)
|
|
75
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: 'transaction agent is invalid' });
|
|
76
|
+
const primaryResult = values.result ?? 'no_op';
|
|
77
|
+
if (!['pr_created', 'pr_reused', 'no_op'].includes(primaryResult))
|
|
78
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: '--result must be pr_created, pr_reused, or no_op' });
|
|
79
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: cwd });
|
|
80
|
+
if (!resolved.ok)
|
|
81
|
+
return result('failed', { code: resolved.code, message: resolved.message });
|
|
82
|
+
try {
|
|
83
|
+
return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `manual-validation:${taskRef}`, () => executeManualValidationTransactionLocked(taskRef, values, cwd, resolved, agent, primaryResult, prepareOnly, options));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof TaskExecutionLockError)
|
|
87
|
+
return result('failed', { code: error.code, message: error.message });
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function executeManualValidationTransactionLocked(taskRef, values, cwd, resolved, agent, primaryResult, prepareOnly, options) {
|
|
92
|
+
const artifactPath = path.resolve(resolved.taskDir, values.artifact);
|
|
93
|
+
if (!prepareOnly && !fs.existsSync(artifactPath))
|
|
94
|
+
return result('failed', { code: 'MANUAL_VALIDATION_ARTIFACT_MISSING', message: `manual-validation artifact is missing: ${values.artifact}` });
|
|
95
|
+
const currentState = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
96
|
+
if (!currentState.pullRequest)
|
|
97
|
+
return result('failed', { code: 'MANUAL_VALIDATION_PR_REQUIRED', message: 'canonical pull-request head is unavailable' });
|
|
98
|
+
const pullRequest = currentState.pullRequest;
|
|
99
|
+
const preimageBody = currentState.comment?.body ?? '';
|
|
100
|
+
const preimage = { commentId: currentState.comment?.id ?? null, body: preimageBody, digest: summaryPreimageDigest(preimageBody) };
|
|
101
|
+
const evidenceDigest = sha256File(path.resolve(cwd, values.summaryFile));
|
|
102
|
+
const archiveGeneration = options.archiveGeneration ?? archiveManualValidationGeneration;
|
|
103
|
+
let transactionResult = readManualValidationTransaction(resolved.taskDir, { taskId: resolved.taskId, prNumber: pullRequest.number, prHeadSha: pullRequest.head.sha, evidenceDigest, artifact: values.artifact });
|
|
104
|
+
let previousTransaction = null;
|
|
105
|
+
if (!transactionResult.ok && transactionResult.error.code === 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH') {
|
|
106
|
+
const existing = readManualValidationTransaction(resolved.taskDir);
|
|
107
|
+
if (!existing.ok)
|
|
108
|
+
return result('failed', existing.error);
|
|
109
|
+
if (!['aborted', 'recovery-required', 'committed'].includes(existing.value.phase))
|
|
110
|
+
return result('failed', transactionResult.error);
|
|
111
|
+
try {
|
|
112
|
+
validateManualValidationGenerationArchive(resolved.taskDir, existing.value, existing.value.phase === 'committed');
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) });
|
|
116
|
+
}
|
|
117
|
+
previousTransaction = existing.value;
|
|
118
|
+
transactionResult = { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_MISSING', message: 'previous failed transaction was archived for a new pull-request head' } };
|
|
119
|
+
}
|
|
120
|
+
if (!transactionResult.ok && transactionResult.error.code !== 'MANUAL_VALIDATION_TRANSACTION_MISSING')
|
|
121
|
+
return result('failed', transactionResult.error);
|
|
122
|
+
if (transactionResult.ok && transactionResult.value.phase === 'committed')
|
|
123
|
+
return result('applied', null, { transaction: transactionResult.value, receipt: transactionResult.value.committedReceipt, idempotent: true });
|
|
124
|
+
const openStarted = transactionResult.ok ? { present: false, transactionId: null } : openManualValidationStarted(resolved.taskMdPath);
|
|
125
|
+
if (openStarted.present && !openStarted.transactionId) {
|
|
126
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'open manual-validation started event has no recoverable transactionId' });
|
|
127
|
+
}
|
|
128
|
+
if (previousTransaction && openStarted.transactionId === previousTransaction.transactionId) {
|
|
129
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'previous manual-validation generation is still open; retry the existing transaction before starting a new generation' });
|
|
130
|
+
}
|
|
131
|
+
const transactionId = transactionResult.ok
|
|
132
|
+
? transactionResult.value.transactionId
|
|
133
|
+
: (openStarted.transactionId ?? values.transactionId ?? `mv-${Date.now()}`);
|
|
134
|
+
const finalBodyWithoutReceipt = fs.readFileSync(path.resolve(cwd, values.summaryFile), 'utf8');
|
|
135
|
+
const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending', transactionId);
|
|
136
|
+
const createTransaction = () => createManualValidationTransaction({
|
|
137
|
+
transactionId,
|
|
138
|
+
taskId: resolved.taskId,
|
|
139
|
+
prNumber: pullRequest.number,
|
|
140
|
+
prHeadSha: pullRequest.head.sha,
|
|
141
|
+
evidenceDigest,
|
|
142
|
+
summaryPreimage: preimage,
|
|
143
|
+
pendingSummaryDigest: summaryPreimageDigest(pendingBody),
|
|
144
|
+
finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final', transactionId, '<receipt>', evidenceDigest, pullRequest.head.sha)),
|
|
145
|
+
artifact: values.artifact,
|
|
146
|
+
attempt: 1,
|
|
147
|
+
createdAt: new Date().toISOString(),
|
|
148
|
+
updatedAt: new Date().toISOString()
|
|
149
|
+
});
|
|
150
|
+
let transaction;
|
|
151
|
+
const idempotent = transactionResult.ok;
|
|
152
|
+
let startedEventApplied = false;
|
|
153
|
+
if (!transactionResult.ok) {
|
|
154
|
+
transaction = createTransaction();
|
|
155
|
+
const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transactionId, reasonCode: 'user-request', transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
|
|
156
|
+
if (started.status === 'failed')
|
|
157
|
+
return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
|
|
158
|
+
startedEventApplied = true;
|
|
159
|
+
if (previousTransaction) {
|
|
160
|
+
try {
|
|
161
|
+
archiveGeneration(resolved.taskDir, previousTransaction, previousTransaction.phase === 'committed');
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
transaction = transactionResult.value;
|
|
171
|
+
if (['aborted', 'recovery-required'].includes(transaction.phase)) {
|
|
172
|
+
const retried = retryManualValidationTransaction(transaction);
|
|
173
|
+
if (!retried.ok)
|
|
174
|
+
return result('failed', retried.error);
|
|
175
|
+
transaction = retried.value;
|
|
176
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (transaction.phase === 'prepared' && !startedEventApplied) {
|
|
180
|
+
const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', transactionId: transaction.transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
|
|
181
|
+
if (started.status === 'failed')
|
|
182
|
+
return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
|
|
183
|
+
}
|
|
184
|
+
if (prepareOnly)
|
|
185
|
+
return result('applied', null, { transaction, prepared: true, idempotent });
|
|
186
|
+
const transactionFailure = (failure) => {
|
|
187
|
+
throw new Error(`${failure.code}: ${failure.message}`);
|
|
188
|
+
};
|
|
189
|
+
const transitionAndPersist = (phase, patch = {}) => {
|
|
190
|
+
const next = transitionManualValidationTransaction(transaction, phase, patch);
|
|
191
|
+
if (!next.ok)
|
|
192
|
+
throw new Error(`${next.error.code}: ${next.error.message}`);
|
|
193
|
+
transaction = next.value;
|
|
194
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
|
|
195
|
+
};
|
|
196
|
+
try {
|
|
197
|
+
if (transaction.phase === 'prepared') {
|
|
198
|
+
const staged = await syncPullRequestSummary(taskRef, {
|
|
199
|
+
cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult: primaryResult, strict: true,
|
|
200
|
+
manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
|
|
201
|
+
});
|
|
202
|
+
if (!['applied', 'no-op'].includes(staged.status))
|
|
203
|
+
transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
|
|
204
|
+
transitionAndPersist('summary-staged');
|
|
205
|
+
}
|
|
206
|
+
let receiptResult = readManualValidationReceipt(resolved.taskDir, {
|
|
207
|
+
transactionId: transaction.transactionId,
|
|
208
|
+
taskId: resolved.taskId,
|
|
209
|
+
prNumber: pullRequest.number,
|
|
210
|
+
prHeadSha: pullRequest.head.sha,
|
|
211
|
+
evidenceDigest: transaction.evidenceDigest,
|
|
212
|
+
artifact: values.artifact
|
|
213
|
+
});
|
|
214
|
+
if (!receiptResult.ok && receiptResult.error.code !== 'MANUAL_VALIDATION_RECEIPT_MISSING')
|
|
215
|
+
transactionFailure(receiptResult.error);
|
|
216
|
+
let receipt;
|
|
217
|
+
if (receiptResult.ok) {
|
|
218
|
+
receipt = receiptResult.value;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
receipt = createManualValidationReceipt({
|
|
222
|
+
transactionId: transaction.transactionId,
|
|
223
|
+
taskId: resolved.taskId,
|
|
224
|
+
prNumber: pullRequest.number,
|
|
225
|
+
prHeadSha: pullRequest.head.sha,
|
|
226
|
+
evidenceDigest: transaction.evidenceDigest,
|
|
227
|
+
artifact: values.artifact,
|
|
228
|
+
artifactSha256: sha256File(artifactPath),
|
|
229
|
+
pendingSummaryDigest: transaction.pendingSummaryDigest,
|
|
230
|
+
finalSummaryDigest: transaction.finalSummaryDigest,
|
|
231
|
+
committedAt: new Date().toISOString()
|
|
232
|
+
});
|
|
233
|
+
writeManualValidationReceiptAtomic(resolved.taskDir, receipt);
|
|
234
|
+
}
|
|
235
|
+
if (transaction.phase === 'summary-staged') {
|
|
236
|
+
transitionAndPersist('receipt-committed', { committedReceipt: receipt.receiptDigest });
|
|
237
|
+
}
|
|
238
|
+
if (transaction.phase === 'receipt-committed' && !transaction.eventAppended) {
|
|
239
|
+
const completed = applyTaskEvent({ taskRef, event: 'manual-validation.completed', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', artifact: values.artifact, summaryResult: 'human-confirmed validation and committed receipt', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha }, { lockAlreadyHeld: true, repoRoot: cwd });
|
|
240
|
+
if (completed.status === 'failed')
|
|
241
|
+
transactionFailure(completed.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation completion event failed' });
|
|
242
|
+
transitionAndPersist('receipt-committed', { eventAppended: true });
|
|
243
|
+
}
|
|
244
|
+
if (transaction.phase === 'receipt-committed') {
|
|
245
|
+
transitionAndPersist('final-promotion-in-progress');
|
|
246
|
+
}
|
|
247
|
+
const promoted = await syncPullRequestSummary(taskRef, {
|
|
248
|
+
cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final', transaction.transactionId, receipt.receiptDigest, receipt.evidenceDigest, receipt.prHeadSha), changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult, strict: true,
|
|
249
|
+
manualValidation: { phase: 'final', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha, authority: 'coordinator' }, lockAlreadyHeld: true
|
|
250
|
+
});
|
|
251
|
+
if (!['applied', 'no-op'].includes(promoted.status))
|
|
252
|
+
transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
|
|
253
|
+
const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
254
|
+
if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt))
|
|
255
|
+
transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
|
|
256
|
+
transitionAndPersist('committed', { postWriteVerified: true });
|
|
257
|
+
return result('applied', null, { transaction, receipt, idempotent: false });
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
261
|
+
if (transaction.phase === 'final-promotion-in-progress') {
|
|
262
|
+
try {
|
|
263
|
+
await syncPullRequestSummary(taskRef, {
|
|
264
|
+
cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult: primaryResult, strict: true,
|
|
265
|
+
manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// Preserve recovery-required when pending compensation cannot be confirmed.
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const failedTransaction = transitionManualValidationTransaction(transaction, 'recovery-required', { error: message.slice(0, 400) });
|
|
273
|
+
if (failedTransaction.ok)
|
|
274
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, failedTransaction.value);
|
|
275
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function parse(args, start) {
|
|
279
|
+
const values = {};
|
|
280
|
+
for (let index = start; index < args.length; index += 1) {
|
|
281
|
+
const flag = args[index];
|
|
282
|
+
if (flag === '--prepare') {
|
|
283
|
+
if (values.prepare)
|
|
284
|
+
return { values, error: `duplicate option '${flag}'` };
|
|
285
|
+
values.prepare = 'true';
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!['--format', '--cwd', '--artifact', '--summary-file', '--change-report-file', '--agent', '--result'].includes(flag))
|
|
289
|
+
return { values, error: `unknown option '${flag}'` };
|
|
290
|
+
const value = args[++index];
|
|
291
|
+
if (!value || value.startsWith('--'))
|
|
292
|
+
return { values, error: `option '${flag}' requires a value` };
|
|
293
|
+
const key = flag.slice(2).replace(/-([a-z])/gu, (_, letter) => letter.toUpperCase());
|
|
294
|
+
if (values[key])
|
|
295
|
+
return { values, error: `duplicate option '${flag}'` };
|
|
296
|
+
values[key] = value;
|
|
297
|
+
}
|
|
298
|
+
return { values };
|
|
299
|
+
}
|
|
300
|
+
async function manualValidation(args = []) {
|
|
301
|
+
if (!ensureInternalHandlerRoute('manual-validation', args))
|
|
302
|
+
return;
|
|
303
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
304
|
+
process.stdout.write(`${USAGE}\n`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const operation = args[0];
|
|
308
|
+
const isTransaction = operation === 'transaction' && internalHandlerRoute('manual-validation', 'transaction', operation);
|
|
309
|
+
if (!isTransaction) {
|
|
310
|
+
printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'transaction operation is required' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const taskRef = args[1];
|
|
314
|
+
if (!taskRef || taskRef.startsWith('--')) {
|
|
315
|
+
printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'task ref is required' });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const parsed = parse(args, 2);
|
|
319
|
+
if (parsed.error) {
|
|
320
|
+
printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: parsed.error });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const values = parsed.values;
|
|
324
|
+
const format = values.format === 'text' ? 'text' : 'json';
|
|
325
|
+
const output = await executeManualValidationTransaction(taskRef, values, path.resolve(values.cwd ?? process.cwd()));
|
|
326
|
+
process.stdout.write(format === 'json' ? `${JSON.stringify(output)}\n` : `${output.status === 'applied' ? 'Manual validation transaction committed.' : `${output.error?.code}: ${output.error?.message}`}\n`);
|
|
327
|
+
if (output.status === 'failed')
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
}
|
|
330
|
+
export { executeManualValidationTransaction, manualValidation };
|
|
331
|
+
//# sourceMappingURL=manual-validation.js.map
|
|
@@ -13,10 +13,10 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
|
|
|
13
13
|
agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
|
|
14
14
|
agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
|
|
15
15
|
agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
|
|
16
|
-
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--strict] [--dry-run] [--cwd <path>]
|
|
16
|
+
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--manual-phase pending|final ...] [--strict] [--dry-run] [--cwd <path>]
|
|
17
17
|
`;
|
|
18
18
|
const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue', '--strict']);
|
|
19
|
-
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result']);
|
|
19
|
+
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result', '--manual-phase', '--transaction-id', '--receipt-digest', '--pr-head-sha']);
|
|
20
20
|
function key(flag) {
|
|
21
21
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
22
22
|
}
|
|
@@ -122,7 +122,7 @@ async function platformPr(args = []) {
|
|
|
122
122
|
'sync-in-labels': ['cwd', 'pr', 'dryRun'],
|
|
123
123
|
'summary-context': ['cwd'],
|
|
124
124
|
'change-report': ['cwd', 'agent', 'mechanicalFile', 'precheckFile', 'dryRun'],
|
|
125
|
-
'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'dryRun', 'strict']
|
|
125
|
+
'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'manualPhase', 'transactionId', 'receiptDigest', 'prHeadSha', 'dryRun', 'strict']
|
|
126
126
|
};
|
|
127
127
|
const unexpected = Object.keys(values).find((name) => !allowed[operation].includes(name));
|
|
128
128
|
if (unexpected) {
|
|
@@ -210,7 +210,21 @@ async function platformPr(args = []) {
|
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
try {
|
|
213
|
-
|
|
213
|
+
const manualPhase = values.manualPhase;
|
|
214
|
+
if (manualPhase !== undefined && manualPhase !== 'pending' && manualPhase !== 'final') {
|
|
215
|
+
fail('--manual-phase must be pending or final');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
finish(await syncPullRequestSummary(taskRef, {
|
|
219
|
+
cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile,
|
|
220
|
+
primaryResult: primaryResult, dryRun: values.dryRun === true, strict: values.strict === true,
|
|
221
|
+
...(manualPhase ? { manualValidation: {
|
|
222
|
+
phase: manualPhase,
|
|
223
|
+
...(typeof values.transactionId === 'string' ? { transactionId: values.transactionId } : {}),
|
|
224
|
+
...(typeof values.receiptDigest === 'string' ? { receiptDigest: values.receiptDigest } : {}),
|
|
225
|
+
...(typeof values.prHeadSha === 'string' ? { prHeadSha: values.prHeadSha } : {})
|
|
226
|
+
} } : {})
|
|
227
|
+
}));
|
|
214
228
|
}
|
|
215
229
|
catch (error) {
|
|
216
230
|
fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1,113 +1,78 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
3
|
import { spawnSync } from 'node:child_process';
|
|
5
4
|
import semver from 'semver';
|
|
6
5
|
import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.js';
|
|
7
6
|
import { inspectPlatformRelease, reconcileReleaseMilestones } from '../platform/releases.js';
|
|
8
7
|
import { inspectHomebrewChannel, inspectNpmChannel } from '../release/channels.js';
|
|
9
8
|
import { releaseSnapshot } from '../release/workflow.js';
|
|
9
|
+
import { collectDemoTranscript } from './demo-transcript.js';
|
|
10
|
+
import { createDemoPromotion, promoteDemoAssets, recoverDemoPromotion } from './demo-promotion.js';
|
|
10
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
11
|
-
function command(cwd, executable, args) {
|
|
12
|
-
return spawnSync(executable, args, {
|
|
12
|
+
function command(cwd, executable, args, options = {}) {
|
|
13
|
+
return spawnSync(executable, args, {
|
|
14
|
+
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...options.env }
|
|
15
|
+
});
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
// lib/init.ts: non-visual config serialization changes must not re-record it.
|
|
16
|
-
const DEMO_INPUT_PATHS = [
|
|
17
|
-
'assets/demo-init.tape',
|
|
18
|
-
'scripts/demo-regen.sh',
|
|
19
|
-
'scripts/normalize-gif-duration.py',
|
|
20
|
-
'bin/cli.ts',
|
|
21
|
-
'lib/log.ts',
|
|
22
|
-
'lib/prompt.ts',
|
|
23
|
-
'lib/paths.ts',
|
|
24
|
-
'lib/render.ts',
|
|
25
|
-
'lib/sandbox/engines/'
|
|
26
|
-
];
|
|
27
|
-
const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
|
|
17
|
+
const DEMO_DIGEST_PATH = 'assets/demo-init.transcript.sha256';
|
|
28
18
|
const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
|
|
29
19
|
const DEMO_MAX_BYTES = 4 * 1024 * 1024;
|
|
30
|
-
function demoInputFiles(cwd) {
|
|
31
|
-
const files = new Set();
|
|
32
|
-
for (const input of DEMO_INPUT_PATHS) {
|
|
33
|
-
if (input.endsWith('/')) {
|
|
34
|
-
const listed = command(cwd, 'git', ['ls-files', '--', input]);
|
|
35
|
-
if (listed.status !== 0)
|
|
36
|
-
throw new Error(String(listed.stderr || `Unable to list ${input}`));
|
|
37
|
-
const directoryFiles = String(listed.stdout).split('\n').filter(Boolean);
|
|
38
|
-
if (!directoryFiles.length)
|
|
39
|
-
throw new Error(`Canonical demo input directory is empty: ${input}`);
|
|
40
|
-
for (const file of directoryFiles)
|
|
41
|
-
files.add(file.replaceAll('\\', '/'));
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
files.add(input);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const sorted = [...files].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
|
|
48
|
-
if (!sorted.length)
|
|
49
|
-
throw new Error('Canonical demo input set is empty');
|
|
50
|
-
for (const file of sorted) {
|
|
51
|
-
const absolute = path.join(cwd, file);
|
|
52
|
-
if (!fs.statSync(absolute).isFile())
|
|
53
|
-
throw new Error(`Canonical demo input is not a file: ${file}`);
|
|
54
|
-
}
|
|
55
|
-
return sorted;
|
|
56
|
-
}
|
|
57
|
-
function computeDemoInputDigest(cwd) {
|
|
58
|
-
const hash = crypto.createHash('sha256');
|
|
59
|
-
for (const file of demoInputFiles(cwd)) {
|
|
60
|
-
const bytes = fs.readFileSync(path.join(cwd, file));
|
|
61
|
-
hash.update(file);
|
|
62
|
-
hash.update('\0');
|
|
63
|
-
hash.update(String(bytes.byteLength));
|
|
64
|
-
hash.update('\0');
|
|
65
|
-
hash.update(bytes);
|
|
66
|
-
hash.update('\0');
|
|
67
|
-
}
|
|
68
|
-
return hash.digest('hex');
|
|
69
|
-
}
|
|
70
20
|
function validGif(filePath) {
|
|
71
21
|
if (!fs.existsSync(filePath))
|
|
72
22
|
return false;
|
|
73
23
|
const header = fs.readFileSync(filePath).subarray(0, 6).toString('ascii');
|
|
74
24
|
return header === 'GIF87a' || header === 'GIF89a';
|
|
75
25
|
}
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
fs.renameSync(temporary, target);
|
|
81
|
-
}
|
|
82
|
-
function runOptionalDemo(cwd, run = command) {
|
|
83
|
-
let digest;
|
|
84
|
-
try {
|
|
85
|
-
digest = computeDemoInputDigest(cwd);
|
|
26
|
+
async function runOptionalDemo(cwd, run = command, collect = collectDemoTranscript) {
|
|
27
|
+
const recovery = recoverDemoPromotion(cwd);
|
|
28
|
+
if (recovery?.status === 'failed') {
|
|
29
|
+
return { status: 'failed', reasonCode: recovery.code === 'DEMO_PROMOTION_CLEANUP_FAILED' ? recovery.code : 'DEMO_PROMOTION_RECOVERY_REQUIRED', message: recovery.message, outputPath: null };
|
|
86
30
|
}
|
|
87
|
-
|
|
88
|
-
|
|
31
|
+
const transcript = await collect(cwd);
|
|
32
|
+
if (transcript.status === 'failed') {
|
|
33
|
+
return { status: 'failed', reasonCode: transcript.reasonCode, message: transcript.message, outputPath: null };
|
|
89
34
|
}
|
|
90
35
|
const digestPath = path.join(cwd, DEMO_DIGEST_PATH);
|
|
91
36
|
const priorDigest = fs.existsSync(digestPath) ? fs.readFileSync(digestPath, 'utf8').trim() : '';
|
|
92
|
-
if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest ===
|
|
93
|
-
return { status: 'skipped', reasonCode: '
|
|
37
|
+
if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === transcript.sha256) {
|
|
38
|
+
return { status: 'skipped', reasonCode: 'DEMO_TRANSCRIPT_UNCHANGED', message: null, outputPath: null };
|
|
94
39
|
}
|
|
40
|
+
const promotion = createDemoPromotion(cwd, transcript.transcript);
|
|
41
|
+
const discardPromotion = () => fs.rmSync(promotion.directory, { recursive: true, force: true });
|
|
95
42
|
const lfs = run(cwd, 'git', ['lfs', 'version']);
|
|
96
43
|
if (lfs.status !== 0) {
|
|
44
|
+
discardPromotion();
|
|
97
45
|
return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: String(lfs.stderr || lfs.stdout), outputPath: null };
|
|
98
46
|
}
|
|
99
47
|
const lfsAttribute = run(cwd, 'git', ['check-attr', 'filter', '--', DEMO_OUTPUT_PATH]);
|
|
100
48
|
if (lfsAttribute.status !== 0 || !String(lfsAttribute.stdout).trim().endsWith(': lfs')) {
|
|
49
|
+
discardPromotion();
|
|
101
50
|
return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: `${DEMO_OUTPUT_PATH} is not tracked by Git LFS`, outputPath: null };
|
|
102
51
|
}
|
|
103
52
|
const vhs = run(cwd, 'vhs', ['--version']);
|
|
104
|
-
if (vhs.status !== 0)
|
|
105
|
-
|
|
53
|
+
if (vhs.status !== 0) {
|
|
54
|
+
discardPromotion();
|
|
55
|
+
return {
|
|
56
|
+
status: 'failed',
|
|
57
|
+
reasonCode: 'VHS_MISSING',
|
|
58
|
+
message: String(vhs.stderr || vhs.stdout) || 'VHS is required to record the changed demo transcript',
|
|
59
|
+
outputPath: null
|
|
60
|
+
};
|
|
61
|
+
}
|
|
106
62
|
const ffmpeg = run(cwd, 'ffmpeg', ['-version']);
|
|
107
|
-
if (ffmpeg.status !== 0)
|
|
108
|
-
|
|
109
|
-
|
|
63
|
+
if (ffmpeg.status !== 0) {
|
|
64
|
+
discardPromotion();
|
|
65
|
+
return {
|
|
66
|
+
status: 'failed',
|
|
67
|
+
reasonCode: 'FFMPEG_MISSING',
|
|
68
|
+
message: String(ffmpeg.stderr || ffmpeg.stdout) || 'ffmpeg is required to record the changed demo transcript',
|
|
69
|
+
outputPath: null
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const stagedOutputPath = path.relative(cwd, promotion.staging.gif).replaceAll(path.sep, '/');
|
|
73
|
+
const demo = run(cwd, 'npm', ['run', 'demo:regen'], { env: { DEMO_OUTPUT_PATH: stagedOutputPath } });
|
|
110
74
|
if (demo.status !== 0) {
|
|
75
|
+
discardPromotion();
|
|
111
76
|
return {
|
|
112
77
|
status: 'failed',
|
|
113
78
|
reasonCode: 'DEMO_COMMAND_FAILED',
|
|
@@ -116,7 +81,8 @@ function runOptionalDemo(cwd, run = command) {
|
|
|
116
81
|
};
|
|
117
82
|
}
|
|
118
83
|
const outputPath = DEMO_OUTPUT_PATH;
|
|
119
|
-
if (!fs.existsSync(
|
|
84
|
+
if (!fs.existsSync(promotion.staging.gif)) {
|
|
85
|
+
discardPromotion();
|
|
120
86
|
return {
|
|
121
87
|
status: 'failed',
|
|
122
88
|
reasonCode: 'DEMO_OUTPUT_MISSING',
|
|
@@ -124,18 +90,24 @@ function runOptionalDemo(cwd, run = command) {
|
|
|
124
90
|
outputPath: null
|
|
125
91
|
};
|
|
126
92
|
}
|
|
127
|
-
const absoluteOutput =
|
|
93
|
+
const absoluteOutput = promotion.staging.gif;
|
|
128
94
|
if (!validGif(absoluteOutput)) {
|
|
95
|
+
discardPromotion();
|
|
129
96
|
return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: `${outputPath} is not a GIF`, outputPath: null };
|
|
130
97
|
}
|
|
131
98
|
if (fs.statSync(absoluteOutput).size > DEMO_MAX_BYTES) {
|
|
99
|
+
discardPromotion();
|
|
132
100
|
return { status: 'failed', reasonCode: 'DEMO_OUTPUT_TOO_LARGE', message: `${outputPath} exceeds 4 MiB`, outputPath: null };
|
|
133
101
|
}
|
|
134
|
-
const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${
|
|
102
|
+
const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${stagedOutputPath}`]);
|
|
135
103
|
if (pointer.status !== 0 || !String(pointer.stdout).includes(`size ${fs.statSync(absoluteOutput).size}`)) {
|
|
104
|
+
discardPromotion();
|
|
136
105
|
return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: 'Git LFS could not produce a matching pointer', outputPath: null };
|
|
137
106
|
}
|
|
138
|
-
|
|
107
|
+
const promoted = promoteDemoAssets(cwd, promotion.staging, `${process.pid}-${Date.now()}`);
|
|
108
|
+
if (promoted.status === 'failed') {
|
|
109
|
+
return { status: 'failed', reasonCode: promoted.code ?? 'DEMO_PROMOTION_FAILED', message: promoted.message, outputPath: null };
|
|
110
|
+
}
|
|
139
111
|
return { status: 'recorded', reasonCode: null, message: null, outputPath };
|
|
140
112
|
}
|
|
141
113
|
function git(cwd, args, run = command, trim = true) {
|
|
@@ -189,7 +161,7 @@ function inspectPostReleaseFacts(cwd, commit, run = command) {
|
|
|
189
161
|
remoteHead,
|
|
190
162
|
newVersion,
|
|
191
163
|
changedPaths,
|
|
192
|
-
|
|
164
|
+
demoTranscriptSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
|
|
193
165
|
worktree: snapshot?.worktree ?? [],
|
|
194
166
|
staged: snapshot?.staged ?? []
|
|
195
167
|
};
|
|
@@ -322,7 +294,8 @@ async function releaseWorkflow(args = []) {
|
|
|
322
294
|
if (internalHandlerRoute('release-workflow', 'prepare', action || '')) {
|
|
323
295
|
if (before.phase !== 'unprepared') {
|
|
324
296
|
const milestones = await reconcileReleaseMilestones(version, { cwd });
|
|
325
|
-
|
|
297
|
+
const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
|
|
298
|
+
process.stdout.write(`${JSON.stringify({ ...milestones, snapshot })}\n`);
|
|
326
299
|
process.exitCode = milestones.status === 'failed' ? 1 : milestones.status === 'blocked' ? 2 : 0;
|
|
327
300
|
return;
|
|
328
301
|
}
|
|
@@ -446,6 +419,12 @@ async function releaseWorkflow(args = []) {
|
|
|
446
419
|
process.exitCode = before.facts.smoke === 'failed' ? 1 : 2;
|
|
447
420
|
return;
|
|
448
421
|
}
|
|
422
|
+
const recovery = recoverDemoPromotion(cwd);
|
|
423
|
+
if (recovery?.status === 'failed') {
|
|
424
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: recovery.code, message: recovery.message } })}\n`);
|
|
425
|
+
process.exitCode = 1;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
449
428
|
const worktreeError = inspectPostWorktree(cwd);
|
|
450
429
|
if (worktreeError) {
|
|
451
430
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
|
|
@@ -458,7 +437,7 @@ async function releaseWorkflow(args = []) {
|
|
|
458
437
|
process.exitCode = 1;
|
|
459
438
|
return;
|
|
460
439
|
}
|
|
461
|
-
const demo = runOptionalDemo(cwd);
|
|
440
|
+
const demo = await runOptionalDemo(cwd);
|
|
462
441
|
if (demo.status === 'failed') {
|
|
463
442
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, demo, error: { code: 'RELEASE_POST_DEMO_FAILED', message: demo.message } })}\n`);
|
|
464
443
|
process.exitCode = 1;
|
|
@@ -487,5 +466,5 @@ async function releaseWorkflow(args = []) {
|
|
|
487
466
|
}
|
|
488
467
|
process.stdout.write(`${JSON.stringify({ status: 'applied', changed: true, demo, snapshot, error: null })}\n`);
|
|
489
468
|
}
|
|
490
|
-
export { changedPaths,
|
|
469
|
+
export { changedPaths, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
|
|
491
470
|
//# sourceMappingURL=release-workflow.js.map
|