@chenmk/superflow 0.3.0 → 0.5.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/INSTALL.en.md +54 -12
- package/INSTALL.md +59 -33
- package/README.md +192 -49
- package/README.zh-CN.md +166 -46
- package/assets/brand/managed-review-loop.png +0 -0
- package/assets/manifest.json +15 -9
- package/assets/prompts/superflow-design.md +3 -0
- package/assets/prompts/superflow-docs.md +4 -1
- package/assets/scripts/superflow-coding-ready.mjs +135 -0
- package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
- package/assets/scripts/superflow-document-audit.mjs +240 -0
- package/assets/scripts/superflow-environment-preflight.mjs +398 -0
- package/assets/scripts/superflow-hook-guard.sh +40 -1
- package/assets/scripts/superflow-managed-command-guard.py +309 -0
- package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
- package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
- package/assets/scripts/superflow-managed-work-check.mjs +455 -0
- package/assets/scripts/superflow-managed-work-guard.sh +17 -0
- package/assets/skills/openspec-propose/SKILL.md +11 -0
- package/assets/skills/superflow-clarify/SKILL.md +14 -0
- package/assets/skills/superflow-design/SKILL.md +46 -12
- package/assets/skills/superflow-docs/SKILL.md +109 -2
- package/assets/skills/superflow-pipeline/SKILL.md +205 -2
- package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
- package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
- package/assets/skills/superflow-pipeline/references/document-templates.md +17 -5
- package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
- package/assets/skills/superflow-pipeline/references/managed-work.md +166 -0
- package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
- package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
- package/assets/skills/superflow-pipeline/references/quality-gate.md +8 -5
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills/superflow-pipeline/references/superpower-technical-design-template.md +10 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-archive.sh +1 -1
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +282 -16
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
- package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
- package/assets/skills-en/openspec-propose/SKILL.md +14 -0
- package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
- package/assets/skills-en/superflow-design/SKILL.md +40 -11
- package/assets/skills-en/superflow-docs/SKILL.md +115 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +202 -0
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
- package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
- package/assets/skills-en/superflow-pipeline/references/document-templates.md +15 -0
- package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/managed-work.md +184 -0
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +20 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/superpower-technical-design-template.md +11 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-archive.sh +1 -1
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +279 -14
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
- package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
- package/dist/app/cli.js +176 -103
- package/dist/app/cli.js.map +1 -1
- package/dist/app/commands/change-guard.js +19 -2
- package/dist/app/commands/change-guard.js.map +1 -1
- package/dist/app/commands/check.js +107 -45
- package/dist/app/commands/check.js.map +1 -1
- package/dist/app/commands/docs.js +7 -3
- package/dist/app/commands/docs.js.map +1 -1
- package/dist/app/commands/doctor.js +174 -198
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.js +64 -0
- package/dist/app/commands/eval.js.map +1 -0
- package/dist/app/commands/implement.js +7 -3
- package/dist/app/commands/implement.js.map +1 -1
- package/dist/app/commands/init.js +16 -24
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/managed-service.js +5 -0
- package/dist/app/commands/managed-service.js.map +1 -0
- package/dist/app/commands/mcp.js +167 -0
- package/dist/app/commands/mcp.js.map +1 -0
- package/dist/app/commands/pipeline.js +757 -3
- package/dist/app/commands/pipeline.js.map +1 -1
- package/dist/app/commands/requirement-review.js +6 -0
- package/dist/app/commands/requirement-review.js.map +1 -0
- package/dist/app/commands/status.js +262 -77
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.js +8 -5
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.js +114 -15
- package/dist/app/commands/update.js.map +1 -1
- package/dist/domains/agent.js +3 -11
- package/dist/domains/agent.js.map +1 -1
- package/dist/domains/config/cli-help.js +145 -73
- package/dist/domains/config/cli-help.js.map +1 -1
- package/dist/domains/config/i18n.js +6 -8
- package/dist/domains/config/i18n.js.map +1 -1
- package/dist/domains/config/manifest.js +0 -3
- package/dist/domains/config/manifest.js.map +1 -1
- package/dist/domains/deps.js +70 -8
- package/dist/domains/deps.js.map +1 -1
- package/dist/domains/hook.js +71 -38
- package/dist/domains/hook.js.map +1 -1
- package/dist/domains/managed-work/completion-policy.js +174 -0
- package/dist/domains/managed-work/completion-policy.js.map +1 -0
- package/dist/domains/managed-work/context-manifest.js +208 -0
- package/dist/domains/managed-work/context-manifest.js.map +1 -0
- package/dist/domains/managed-work/contract.js +349 -0
- package/dist/domains/managed-work/contract.js.map +1 -0
- package/dist/domains/managed-work/control-signals.js +34 -0
- package/dist/domains/managed-work/control-signals.js.map +1 -0
- package/dist/domains/managed-work/control.js +628 -0
- package/dist/domains/managed-work/control.js.map +1 -0
- package/dist/domains/managed-work/evaluation.js +497 -0
- package/dist/domains/managed-work/evaluation.js.map +1 -0
- package/dist/domains/managed-work/evidence-path.js +14 -0
- package/dist/domains/managed-work/evidence-path.js.map +1 -0
- package/dist/domains/managed-work/execution-contract.js +167 -0
- package/dist/domains/managed-work/execution-contract.js.map +1 -0
- package/dist/domains/managed-work/executor-policy.js +65 -0
- package/dist/domains/managed-work/executor-policy.js.map +1 -0
- package/dist/domains/managed-work/failure.js +65 -0
- package/dist/domains/managed-work/failure.js.map +1 -0
- package/dist/domains/managed-work/host-review.js +77 -0
- package/dist/domains/managed-work/host-review.js.map +1 -0
- package/dist/domains/managed-work/human-messages.js +31 -0
- package/dist/domains/managed-work/human-messages.js.map +1 -0
- package/dist/domains/managed-work/i18n.js +12 -0
- package/dist/domains/managed-work/i18n.js.map +1 -0
- package/dist/domains/managed-work/input.js +137 -0
- package/dist/domains/managed-work/input.js.map +1 -0
- package/dist/domains/managed-work/journal.js +196 -0
- package/dist/domains/managed-work/journal.js.map +1 -0
- package/dist/domains/managed-work/lock.js +106 -0
- package/dist/domains/managed-work/lock.js.map +1 -0
- package/dist/domains/managed-work/notifications.js +38 -0
- package/dist/domains/managed-work/notifications.js.map +1 -0
- package/dist/domains/managed-work/pair-admission.js +108 -0
- package/dist/domains/managed-work/pair-admission.js.map +1 -0
- package/dist/domains/managed-work/paths.js +18 -0
- package/dist/domains/managed-work/paths.js.map +1 -0
- package/dist/domains/managed-work/prompts.js +453 -0
- package/dist/domains/managed-work/prompts.js.map +1 -0
- package/dist/domains/managed-work/redaction.js +90 -0
- package/dist/domains/managed-work/redaction.js.map +1 -0
- package/dist/domains/managed-work/review-convergence.js +41 -0
- package/dist/domains/managed-work/review-convergence.js.map +1 -0
- package/dist/domains/managed-work/review-facts.js +181 -0
- package/dist/domains/managed-work/review-facts.js.map +1 -0
- package/dist/domains/managed-work/rule-selection.js +84 -0
- package/dist/domains/managed-work/rule-selection.js.map +1 -0
- package/dist/domains/managed-work/runner.js +2418 -0
- package/dist/domains/managed-work/runner.js.map +1 -0
- package/dist/domains/managed-work/schemas.js +155 -0
- package/dist/domains/managed-work/schemas.js.map +1 -0
- package/dist/domains/managed-work/service.js +190 -0
- package/dist/domains/managed-work/service.js.map +1 -0
- package/dist/domains/managed-work/state.js +280 -0
- package/dist/domains/managed-work/state.js.map +1 -0
- package/dist/domains/managed-work/storage.js +223 -0
- package/dist/domains/managed-work/storage.js.map +1 -0
- package/dist/domains/managed-work/types.js +19 -0
- package/dist/domains/managed-work/types.js.map +1 -0
- package/dist/domains/managed-work/verification-categories.js +39 -0
- package/dist/domains/managed-work/verification-categories.js.map +1 -0
- package/dist/domains/managed-work/verification-policy.js +116 -0
- package/dist/domains/managed-work/verification-policy.js.map +1 -0
- package/dist/domains/managed-work/wait.js +58 -0
- package/dist/domains/managed-work/wait.js.map +1 -0
- package/dist/domains/managed-work/workspace-binding.js +133 -0
- package/dist/domains/managed-work/workspace-binding.js.map +1 -0
- package/dist/domains/sdd-readiness.js +44 -0
- package/dist/domains/sdd-readiness.js.map +1 -0
- package/dist/domains/skill/deploy.js +0 -6
- package/dist/domains/skill/deploy.js.map +1 -1
- package/dist/domains/skill/scripts.js +0 -8
- package/dist/domains/skill/scripts.js.map +1 -1
- package/dist/domains/state.js +1 -2
- package/dist/domains/state.js.map +1 -1
- package/dist/mcp/runtime-identity.js +21 -0
- package/dist/mcp/runtime-identity.js.map +1 -0
- package/dist/mcp/server.js +369 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/platform/agent-process.js +769 -0
- package/dist/platform/agent-process.js.map +1 -0
- package/dist/platform/executable.js +28 -0
- package/dist/platform/executable.js.map +1 -0
- package/dist/platform/paths.js +1 -17
- package/dist/platform/paths.js.map +1 -1
- package/dist/platform/process-liveness.js +10 -0
- package/dist/platform/process-liveness.js.map +1 -0
- package/dist/platform/process-tree.js +44 -0
- package/dist/platform/process-tree.js.map +1 -0
- package/dist/platform/process.js +1 -1
- package/dist/platform/process.js.map +1 -1
- package/dist/platform/version-check.js +30 -9
- package/dist/platform/version-check.js.map +1 -1
- package/package.json +16 -7
- package/scripts/check-design-principles.mjs +107 -0
- package/scripts/check-managed-work-design.mjs +507 -0
- package/scripts/clean-dist.js +9 -0
- package/scripts/ensure-bin-modes.js +11 -0
- package/dist/cli/index.js +0 -193
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/archive.js +0 -6
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/clarify.js +0 -6
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/design.js +0 -28
- package/dist/commands/design.js.map +0 -1
- package/dist/commands/docs.js +0 -6
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js +0 -494
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/implement.js +0 -6
- package/dist/commands/implement.js.map +0 -1
- package/dist/commands/init.js +0 -520
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/pipeline.js +0 -6
- package/dist/commands/pipeline.js.map +0 -1
- package/dist/commands/scan.js +0 -63
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/status.js +0 -173
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/uninstall.js +0 -214
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.js +0 -193
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/verify.js +0 -6
- package/dist/commands/verify.js.map +0 -1
- package/dist/core/assets.js +0 -27
- package/dist/core/assets.js.map +0 -1
- package/dist/core/cli-help.js +0 -98
- package/dist/core/cli-help.js.map +0 -1
- package/dist/core/context.js +0 -123
- package/dist/core/context.js.map +0 -1
- package/dist/core/dependencies.js +0 -146
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/detect.js +0 -64
- package/dist/core/detect.js.map +0 -1
- package/dist/core/i18n.js +0 -105
- package/dist/core/i18n.js.map +0 -1
- package/dist/core/integrity.js +0 -46
- package/dist/core/integrity.js.map +0 -1
- package/dist/core/manifest.js +0 -21
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/prompts.js +0 -20
- package/dist/core/prompts.js.map +0 -1
- package/dist/core/registry.js +0 -134
- package/dist/core/registry.js.map +0 -1
- package/dist/core/rules.js +0 -17
- package/dist/core/rules.js.map +0 -1
- package/dist/core/scripts.js +0 -48
- package/dist/core/scripts.js.map +0 -1
- package/dist/core/skill-check.js +0 -32
- package/dist/core/skill-check.js.map +0 -1
- package/dist/core/skills.js +0 -62
- package/dist/core/skills.js.map +0 -1
- package/dist/core/state.js +0 -54
- package/dist/core/state.js.map +0 -1
- package/dist/utils/path.js +0 -11
- package/dist/utils/path.js.map +0 -1
- package/dist/utils/shell.js +0 -29
- package/dist/utils/shell.js.map +0 -1
|
@@ -0,0 +1,2418 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "crypto";
|
|
2
|
+
import { execFileSync } from "child_process";
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { appendManagedEvent, readManagedEvents, verifyManagedJournal, } from "./journal.js";
|
|
6
|
+
import { acquireManagedLock, acquireManagedProjectClaim } from "./lock.js";
|
|
7
|
+
import { managedRunDir } from "./paths.js";
|
|
8
|
+
import { buildExecutorPrompt, buildReviewPrompt } from "./prompts.js";
|
|
9
|
+
import { isAcceptedEvidenceCommand } from "./execution-contract.js";
|
|
10
|
+
import { writeManagedSchemas } from "./schemas.js";
|
|
11
|
+
import { verificationCategories } from "./verification-categories.js";
|
|
12
|
+
import { assessReviewConvergence } from "./review-convergence.js";
|
|
13
|
+
import { ensureManagedWorkspaceBinding, managedWorkspaceBindingFingerprint, } from "./workspace-binding.js";
|
|
14
|
+
import { appendTaskReport, resolveTaskReportBlockers, loadRegistry, loadManagedRun, loadManagedTask, saveManagedRun, saveManagedTask, upsertRegistryEntry, updateTaskReportStatus, writeJsonAtomic, } from "./storage.js";
|
|
15
|
+
import { activeRunMilliseconds, computeWorkspaceFingerprintForRoots, computeScopedWorkspaceFingerprint, effectiveExecutorInvocations, effectiveTotalAgentInvocations, snapshotChangedWorkspaceFiles, } from "./state.js";
|
|
16
|
+
import { ASSETS_DIR } from "../../platform/assets.js";
|
|
17
|
+
import { notifyManagedTask } from "./notifications.js";
|
|
18
|
+
import { calculateManagedContractHash, validateManagedTaskContract, validateManagedTaskPromptSnapshot, } from "./contract.js";
|
|
19
|
+
import { managedList, managedText } from "./i18n.js";
|
|
20
|
+
import { readManagedHumanMessages } from "./human-messages.js";
|
|
21
|
+
import { clearManagedControlSignal, readManagedControlSignal, } from "./control-signals.js";
|
|
22
|
+
import { evaluateCompletion, readCompletionTasks, } from "./completion-policy.js";
|
|
23
|
+
import { isCommandOutcomeConsistent, validateRiskBasedEvidence, } from "./verification-policy.js";
|
|
24
|
+
import { canonicalEvidencePath } from "./evidence-path.js";
|
|
25
|
+
import { selectManagedRuleFiles } from "./rule-selection.js";
|
|
26
|
+
import { buildExecutorPolicy, stableExecutorPolicyHash, } from "./executor-policy.js";
|
|
27
|
+
import { ensureManagedContextManifest, managedContextManifestPath, } from "./context-manifest.js";
|
|
28
|
+
import { writeManagedReviewFacts } from "./review-facts.js";
|
|
29
|
+
import { redactManagedLog, redactManagedValue } from "./redaction.js";
|
|
30
|
+
import { classifyUnresumableSessionFailure } from "../../platform/agent-process.js";
|
|
31
|
+
import { classifyManagedFailure, ManagedEnvironmentPreparationError, ManagedWorkspaceClaimError, } from "./failure.js";
|
|
32
|
+
export async function runManagedTask(projectRoot, taskId, invoker, env = process.env) {
|
|
33
|
+
const contract = loadManagedTask(projectRoot, taskId);
|
|
34
|
+
let state = loadManagedRun(projectRoot, taskId, registryRunId(contract, env));
|
|
35
|
+
const runDir = managedRunDir(projectRoot, taskId, state.runId);
|
|
36
|
+
mkdirSync(runDir, { recursive: true });
|
|
37
|
+
const lock = acquireManagedLock(path.join(runDir, "run.lock"), randomUUID(), contract.language);
|
|
38
|
+
let projectLocks = [];
|
|
39
|
+
try {
|
|
40
|
+
const prepared = await prepareManagedExecution(contract);
|
|
41
|
+
projectLocks = prepared.projectLocks;
|
|
42
|
+
if (migrateLegacyDataDisclosure(contract, state)) {
|
|
43
|
+
appendManagedEvent(state, {
|
|
44
|
+
eventType: "contract.data_disclosure_migrated",
|
|
45
|
+
actor: "managed-runner",
|
|
46
|
+
role: "runner",
|
|
47
|
+
summary: mt(contract, "历史任务已执行过外部研发 Agent,迁移并冻结原仓库作用域授权", "The legacy task already used an external executor; its repository-scoped approval was migrated and frozen"),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (state.contractHash !== contract.contractHash) {
|
|
51
|
+
return finishBlocked(contract, state, mt(contract, "运行状态与任务合同哈希不一致", "Run state and task contract hashes do not match"), env);
|
|
52
|
+
}
|
|
53
|
+
if (!verifyManagedJournal(state)) {
|
|
54
|
+
return finishBlocked(contract, state, mt(contract, "事件账本校验失败,拒绝继续", "Event journal verification failed; refusing to continue"), env);
|
|
55
|
+
}
|
|
56
|
+
if (!contract.permissions.externalModelDataDisclosure.approved) {
|
|
57
|
+
return finishBlocked(contract, state, mt(contract, "尚未记录外部研发 Agent 读取和处理本地源码的任务级授权;MCP 不会绕过宿主租户 DLP", "Task-scoped approval for the external executor to read and process local source code is missing; MCP does not bypass host tenant DLP"), env);
|
|
58
|
+
}
|
|
59
|
+
if (migrateLegacySupervision(contract, state)) {
|
|
60
|
+
appendManagedEvent(state, {
|
|
61
|
+
eventType: "contract.supervision_migrated",
|
|
62
|
+
actor: "managed-runner",
|
|
63
|
+
role: "runner",
|
|
64
|
+
summary: mt(contract, "旧式后台监督已永久迁移为当前 Host 直接评审", "Legacy background supervision was permanently migrated to direct host review"),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
state = transition(contract, state, "running", "preflight", env);
|
|
68
|
+
appendManagedEvent(state, {
|
|
69
|
+
eventType: "run.started",
|
|
70
|
+
actor: "managed-runner",
|
|
71
|
+
role: "runner",
|
|
72
|
+
summary: mt(contract, "后台开始或恢复托管任务", "Background service started or resumed the managed task"),
|
|
73
|
+
});
|
|
74
|
+
const schemas = writeManagedSchemas(state);
|
|
75
|
+
state = reconcileCompletedReview(contract, state, env);
|
|
76
|
+
if (state.executorInvocations === 0 && !state.baselineCapturedAt) {
|
|
77
|
+
state.baselineWorkspaceFiles = snapshotChangedWorkspaceFiles([
|
|
78
|
+
contract.projectRoot,
|
|
79
|
+
...contract.relatedProjectRoots,
|
|
80
|
+
]);
|
|
81
|
+
state.baselineCapturedAt = new Date().toISOString();
|
|
82
|
+
saveManagedRun(state);
|
|
83
|
+
}
|
|
84
|
+
while (state.status === "running") {
|
|
85
|
+
state = pauseAtSafeBoundary(contract, state, env);
|
|
86
|
+
if (state.status !== "running")
|
|
87
|
+
break;
|
|
88
|
+
state = enforceBudgets(contract, state, env);
|
|
89
|
+
if (state.status !== "running")
|
|
90
|
+
break;
|
|
91
|
+
if (!state.lastExecutorResult ||
|
|
92
|
+
hasUndeliveredHumanGuidance(contract, state)) {
|
|
93
|
+
const findings = loadRepairFindings(state);
|
|
94
|
+
state = await executeWorker(contract, state, findings, schemas.executor, invoker, env);
|
|
95
|
+
if (state.status !== "running")
|
|
96
|
+
break;
|
|
97
|
+
state = pauseAtSafeBoundary(contract, state, env);
|
|
98
|
+
if (state.status !== "running")
|
|
99
|
+
break;
|
|
100
|
+
if (!state.lastExecutorResult ||
|
|
101
|
+
hasUndeliveredHumanGuidance(contract, state)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
state = prepareExternalHostReview(contract, state, env);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
return state;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error.message;
|
|
112
|
+
const failure = classifyManagedFailure(contract, error);
|
|
113
|
+
if (failure) {
|
|
114
|
+
state.failure = failure;
|
|
115
|
+
saveManagedRun(state);
|
|
116
|
+
appendManagedEvent(state, {
|
|
117
|
+
eventType: "run.failure_classified",
|
|
118
|
+
actor: "managed-runner",
|
|
119
|
+
role: "runner",
|
|
120
|
+
summary: `${failure.reason}: ${failure.remediation}`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (state.activeSince) {
|
|
124
|
+
state.activeRunMilliseconds += Math.max(0, Date.now() - Date.parse(state.activeSince));
|
|
125
|
+
state.activeSince = null;
|
|
126
|
+
saveManagedRun(state);
|
|
127
|
+
}
|
|
128
|
+
const pauseSignal = readManagedControlSignal(contract);
|
|
129
|
+
if (pauseSignal?.pauseRequested) {
|
|
130
|
+
clearManagedControlSignal(contract);
|
|
131
|
+
if (state.currentStep.startsWith("executor_")) {
|
|
132
|
+
state.executorInvocationCredits =
|
|
133
|
+
(state.executorInvocationCredits ?? 0) + 1;
|
|
134
|
+
state.totalAgentInvocationCredits =
|
|
135
|
+
(state.totalAgentInvocationCredits ?? 0) + 1;
|
|
136
|
+
}
|
|
137
|
+
state.runningAgentPid = null;
|
|
138
|
+
state.blocker = pauseSignal.reason;
|
|
139
|
+
state = transition(contract, state, "paused", "paused_by_user", env);
|
|
140
|
+
appendManagedEvent(state, {
|
|
141
|
+
eventType: "human.pause_applied",
|
|
142
|
+
actor: pauseSignal.actor,
|
|
143
|
+
role: "system",
|
|
144
|
+
summary: mt(contract, `已中断当前研发 Agent 并暂停任务:${pauseSignal.reason}`, `Interrupted the current executor and paused the task: ${pauseSignal.reason}`),
|
|
145
|
+
});
|
|
146
|
+
appendManagedEvent(state, {
|
|
147
|
+
eventType: "budget.interrupted_invocation_credited",
|
|
148
|
+
actor: "managed-runner",
|
|
149
|
+
role: "system",
|
|
150
|
+
summary: mt(contract, "用户暂停中断的研发调用已抵扣,不消耗有效业务预算", "The executor invocation interrupted by user pause was credited and does not consume effective business budget"),
|
|
151
|
+
});
|
|
152
|
+
return state;
|
|
153
|
+
}
|
|
154
|
+
const unresumableSession = unresumableExecutorSessionFailure(error, state);
|
|
155
|
+
if (unresumableSession) {
|
|
156
|
+
state.executorSession = retireExecutorSession(state.executorSession, unresumableSession);
|
|
157
|
+
appendManagedEvent(state, {
|
|
158
|
+
eventType: "executor.session_retired",
|
|
159
|
+
actor: "managed-runner",
|
|
160
|
+
role: "runner",
|
|
161
|
+
summary: mt(contract, `研发会话无法安全续接,已退役旧会话:${unresumableSession}`, `The executor session cannot be safely resumed and was retired: ${unresumableSession}`),
|
|
162
|
+
});
|
|
163
|
+
if (unresumableSessionRecoveryCount(state) >= 1) {
|
|
164
|
+
appendManagedEvent(state, {
|
|
165
|
+
eventType: "executor.session_recovery_circuit_open",
|
|
166
|
+
actor: "managed-runner",
|
|
167
|
+
role: "runner",
|
|
168
|
+
summary: mt(contract, "研发会话第二次无法安全续接,停止自动创建新会话并转 Host 处理", "The executor session was unresumable for a second time; fresh-session recovery stopped and the task was escalated to Host"),
|
|
169
|
+
});
|
|
170
|
+
return finishBlocked(contract, state, mt(contract, "研发会话第二次无法安全续接;已保留退役记录和原始失败证据,请由 Host 检查 Agent 或切换供应商", "The executor session was unresumable for a second time; retirement records and raw failure evidence were preserved. Ask Host to inspect the agent or switch provider"), env);
|
|
171
|
+
}
|
|
172
|
+
state.runningAgentPid = null;
|
|
173
|
+
state.blocker = null;
|
|
174
|
+
state = transition(contract, state, "queued", "executor_session_recovery_queued", env);
|
|
175
|
+
appendManagedEvent(state, {
|
|
176
|
+
eventType: "executor.session_recovery_queued",
|
|
177
|
+
actor: "managed-runner",
|
|
178
|
+
role: "runner",
|
|
179
|
+
summary: mt(contract, "已保留冻结交接包和有效证据,并仅排队一次不带旧 session 的新会话恢复", "The frozen handoff and valid evidence were preserved; exactly one fresh recovery without the old session was queued"),
|
|
180
|
+
});
|
|
181
|
+
return state;
|
|
182
|
+
}
|
|
183
|
+
if (isMaxTurnContinuationFailure(message)) {
|
|
184
|
+
const continuation = consecutiveMaxTurnFailures(state);
|
|
185
|
+
const hasWorkspaceProgress = changedWorkspaceFilesSinceBaseline(contract, state).length > 0;
|
|
186
|
+
if (!hasWorkspaceProgress && continuation >= 3) {
|
|
187
|
+
state.executorSession = rolloverSession(state.executorSession);
|
|
188
|
+
return finishBlocked(contract, state, mt(contract, "研发 Agent 连续三次因旧版或外部 max-turn 限制退出且零源码进展,已熔断;请检查执行模型能力、Prompt 规模或外部 CLI 配置", "The executor exited three times because of a legacy or external max-turn limit without workspace progress; the circuit opened. Check model capability, prompt size, or external CLI configuration"), env);
|
|
189
|
+
}
|
|
190
|
+
if (continuation < 3 && state.executorSession.sessionId) {
|
|
191
|
+
if (!hasWorkspaceProgress) {
|
|
192
|
+
state.executorSession = rolloverSession(state.executorSession);
|
|
193
|
+
}
|
|
194
|
+
state.runningAgentPid = null;
|
|
195
|
+
state.blocker = null;
|
|
196
|
+
state = transition(contract, state, "queued", "executor_continuation_queued", env);
|
|
197
|
+
appendManagedEvent(state, {
|
|
198
|
+
eventType: "executor.continuation_queued",
|
|
199
|
+
actor: "managed-runner",
|
|
200
|
+
role: "runner",
|
|
201
|
+
summary: mt(contract, hasWorkspaceProgress
|
|
202
|
+
? `研发会话受到旧版或外部 max-turn 限制,检测到源码进展,自动续接同一会话(${continuation}/3)`
|
|
203
|
+
: `研发会话受到旧版或外部 max-turn 限制且零源码进展,切换压缩短会话继续(${continuation}/3)`, hasWorkspaceProgress
|
|
204
|
+
? `Executor hit a legacy or external max-turn limit with workspace progress; automatically resuming the same session (${continuation}/3)`
|
|
205
|
+
: `Executor hit a legacy or external max-turn limit with no workspace progress; continuing in a fresh condensed session (${continuation}/3)`),
|
|
206
|
+
});
|
|
207
|
+
return state;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (isExecutorDeliveryProtocolFailure(message)) {
|
|
211
|
+
const recoveryCount = deliveryProtocolRecoveryCount(state);
|
|
212
|
+
if (recoveryCount < 1) {
|
|
213
|
+
state.executorSession = rolloverSession(state.executorSession);
|
|
214
|
+
state.runningAgentPid = null;
|
|
215
|
+
state.blocker = null;
|
|
216
|
+
state = transition(contract, state, "queued", "executor_delivery_protocol_recovery_queued", env);
|
|
217
|
+
appendManagedEvent(state, {
|
|
218
|
+
eventType: "executor.delivery_protocol_recovery_queued",
|
|
219
|
+
actor: "managed-runner",
|
|
220
|
+
role: "runner",
|
|
221
|
+
summary: mt(contract, "研发 Agent 已完成调用但未按协议返回 JSON;保留原始日志并自动创建一次新的压缩会话补齐交付与受影响验证(1/1)", "The executor completed its call but did not return protocol JSON; raw logs were preserved and one fresh condensed session was queued to complete delivery and affected verification (1/1)"),
|
|
222
|
+
});
|
|
223
|
+
return state;
|
|
224
|
+
}
|
|
225
|
+
return finishBlocked(contract, state, mt(contract, "研发 Agent 连续两次未按协议返回 JSON;已保留原始日志并停止自动恢复,请检查 Agent/适配器输出", "The executor failed to return protocol JSON twice; raw logs were preserved and automatic recovery stopped. Check the agent or adapter output"), env);
|
|
226
|
+
}
|
|
227
|
+
if (isPermanentProviderFailure(message)) {
|
|
228
|
+
state = transition(contract, state, "waiting_for_provider_change", "provider_change_required", env);
|
|
229
|
+
state.blocker = message;
|
|
230
|
+
saveManagedRun(state);
|
|
231
|
+
appendManagedEvent(state, {
|
|
232
|
+
eventType: "provider.permanent_failure",
|
|
233
|
+
actor: contract.executorAgent,
|
|
234
|
+
role: "system",
|
|
235
|
+
summary: mt(contract, `供应商额度或套餐不可用,禁止自动重试:${message}`, `Provider quota or plan is unavailable; automatic retry is forbidden: ${message}`),
|
|
236
|
+
});
|
|
237
|
+
notifyManagedTask({
|
|
238
|
+
taskId: state.taskId,
|
|
239
|
+
type: "provider_change",
|
|
240
|
+
title: mt(contract, "Superflow 需要切换模型供应商", "Superflow requires a provider change"),
|
|
241
|
+
message: `${state.taskId}: ${message}`,
|
|
242
|
+
}, env);
|
|
243
|
+
return state;
|
|
244
|
+
}
|
|
245
|
+
if (isConnectivityFailure(message)) {
|
|
246
|
+
const connectivityFailure = recordExecutorConnectivityFailure(contract, state, message);
|
|
247
|
+
const circuitOpen = connectivityFailure !== null &&
|
|
248
|
+
connectivityFailure.consecutiveFailures >= 3;
|
|
249
|
+
state = transition(contract, state, circuitOpen ? "waiting_for_human" : "waiting_for_connectivity", circuitOpen ? "provider_change_required" : "connectivity_wait", env);
|
|
250
|
+
state.blocker = message;
|
|
251
|
+
saveManagedRun(state);
|
|
252
|
+
appendManagedEvent(state, {
|
|
253
|
+
eventType: "connectivity.lost",
|
|
254
|
+
actor: "managed-runner",
|
|
255
|
+
role: "system",
|
|
256
|
+
summary: mt(contract, `网络或模型服务暂时不可用:${message}`, `Network or model service is temporarily unavailable: ${message}`),
|
|
257
|
+
});
|
|
258
|
+
if (connectivityFailure?.credited) {
|
|
259
|
+
appendManagedEvent(state, {
|
|
260
|
+
eventType: "budget.transient_provider_credited",
|
|
261
|
+
actor: "managed-runner",
|
|
262
|
+
role: "system",
|
|
263
|
+
summary: mt(contract, `临时供应商错误不计入 Claude 预算:返还第 ${connectivityFailure.invocation} 次物理调用,有效执行抵扣 ${state.executorInvocationCredits} 次${connectivityFailure.stopAt === null ? "" : `,临时窗口顺延至 ${connectivityFailure.stopAt}`};连续失败 ${connectivityFailure.consecutiveFailures}/3`, `Transient provider failure excluded from the Claude budget: credited physical invocation ${connectivityFailure.invocation}, effective executor credits ${state.executorInvocationCredits}${connectivityFailure.stopAt === null ? "" : `, temporary window extended to ${connectivityFailure.stopAt}`}; consecutive failures ${connectivityFailure.consecutiveFailures}/3`),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (circuitOpen) {
|
|
267
|
+
appendManagedEvent(state, {
|
|
268
|
+
eventType: "connectivity.circuit_open",
|
|
269
|
+
actor: "managed-runner",
|
|
270
|
+
role: "system",
|
|
271
|
+
summary: mt(contract, "临时供应商错误已连续发生 3 次,停止自动重试并等待切换供应商", "Transient provider failures occurred 3 consecutive times; automatic retries stopped pending a provider change"),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return state;
|
|
275
|
+
}
|
|
276
|
+
return finishBlocked(contract, state, message, env);
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
for (const projectLock of projectLocks.reverse())
|
|
280
|
+
projectLock.release();
|
|
281
|
+
lock.release();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function migrateLegacyDataDisclosure(contract, state) {
|
|
285
|
+
if (contract.permissions.externalModelDataDisclosure)
|
|
286
|
+
return false;
|
|
287
|
+
contract.permissions.externalModelDataDisclosure = {
|
|
288
|
+
approved: state.executorInvocations > 0,
|
|
289
|
+
approvedBy: state.executorInvocations > 0 ? "legacy_migration" : null,
|
|
290
|
+
approvedAt: state.executorInvocations > 0 ? contract.createdAt : null,
|
|
291
|
+
scope: [contract.projectRoot, ...contract.relatedProjectRoots],
|
|
292
|
+
};
|
|
293
|
+
contract.contractHash = calculateManagedContractHash(contract);
|
|
294
|
+
state.contractHash = contract.contractHash;
|
|
295
|
+
saveManagedTask(contract);
|
|
296
|
+
saveManagedRun(state);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
function migrateLegacySupervision(contract, state) {
|
|
300
|
+
const legacy = contract;
|
|
301
|
+
if (legacy.supervisorExecution === "external_host")
|
|
302
|
+
return false;
|
|
303
|
+
legacy.supervisorExecution = "external_host";
|
|
304
|
+
contract.contractHash = calculateManagedContractHash(contract);
|
|
305
|
+
state.contractHash = contract.contractHash;
|
|
306
|
+
state.supervisorSession = rolloverSession(state.supervisorSession);
|
|
307
|
+
saveManagedTask(contract);
|
|
308
|
+
saveManagedRun(state);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
function reconcileCompletedReview(contract, state, env) {
|
|
312
|
+
if (!state.lastReviewResult || !existsSync(state.lastReviewResult))
|
|
313
|
+
return state;
|
|
314
|
+
if (!state.lastReviewResult.endsWith(`review-result-${state.reviewRound}.json`)) {
|
|
315
|
+
return state;
|
|
316
|
+
}
|
|
317
|
+
const result = JSON.parse(readFileSync(state.lastReviewResult, "utf-8"));
|
|
318
|
+
validateReviewResult(result, contract.language);
|
|
319
|
+
const endedReviews = readManagedEvents(state).filter((event) => event.eventType === "review.ended").length;
|
|
320
|
+
if (endedReviews < state.reviewInvocations) {
|
|
321
|
+
appendTaskReport(state, reviewReportSection(state, result));
|
|
322
|
+
appendManagedEvent(state, {
|
|
323
|
+
eventType: "review.ended",
|
|
324
|
+
actor: contract.supervisorAgent,
|
|
325
|
+
role: "supervisor",
|
|
326
|
+
summary: mt(contract, `恢复已落盘的检查结果:${result.summary}`, `Recovered persisted review result: ${result.summary}`),
|
|
327
|
+
evidencePaths: [state.lastReviewResult],
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (result.result === "pass") {
|
|
331
|
+
try {
|
|
332
|
+
state = promoteRevalidatedExecutorResult(contract, state);
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return finishBlocked(contract, state, mt(contract, `Host 评审通过,但最后一次执行结果按当前规则重验仍失败:${error.message}`, `The host review passed, but the latest executor result still failed current deterministic validation: ${error.message}`), env);
|
|
336
|
+
}
|
|
337
|
+
return finishDeliveryReady(contract, state, result.summary, env);
|
|
338
|
+
}
|
|
339
|
+
if (result.result === "blocked") {
|
|
340
|
+
return finishBlocked(contract, state, result.summary, env);
|
|
341
|
+
}
|
|
342
|
+
const convergence = assessReviewConvergence(readReviewHistory(state));
|
|
343
|
+
if (!convergence.shouldContinue) {
|
|
344
|
+
state.blocker = mt(contract, `连续 ${convergence.stagnantTransitions} 次评审转换没有关闭既有阻断项,已提前止损并保留整改证据`, `${convergence.stagnantTransitions} consecutive review transitions closed no existing blocker; repair stopped early with evidence preserved`);
|
|
345
|
+
state = transition(contract, state, "repair_pending", "repair_convergence_stalled", env);
|
|
346
|
+
appendManagedEvent(state, {
|
|
347
|
+
eventType: "review.convergence_stalled",
|
|
348
|
+
actor: "managed-runner",
|
|
349
|
+
role: "runner",
|
|
350
|
+
summary: state.blocker ?? "review convergence stalled",
|
|
351
|
+
});
|
|
352
|
+
return state;
|
|
353
|
+
}
|
|
354
|
+
if (!hasUnlimitedAgentBudget(contract) &&
|
|
355
|
+
state.reviewRound >= contract.budgets.maxReviewRounds) {
|
|
356
|
+
state.blocker = mt(contract, "仍有阻断 finding,但当前评审预算已达上限;整改结论已保留,等待用户扩展预算", "Blocking findings remain but the review budget is exhausted; the repair decision is preserved pending a user budget extension");
|
|
357
|
+
state = transition(contract, state, "repair_pending", "repair_budget_approval_required", env);
|
|
358
|
+
appendManagedEvent(state, {
|
|
359
|
+
eventType: "review.repair_pending",
|
|
360
|
+
actor: "managed-runner",
|
|
361
|
+
role: "runner",
|
|
362
|
+
summary: state.blocker ?? "repair pending",
|
|
363
|
+
});
|
|
364
|
+
return state;
|
|
365
|
+
}
|
|
366
|
+
state.lastExecutorResult = null;
|
|
367
|
+
state.currentStep = "repair_prompt_ready";
|
|
368
|
+
saveManagedRun(state);
|
|
369
|
+
appendManagedEvent(state, {
|
|
370
|
+
eventType: "recovery.checkpoint_restored",
|
|
371
|
+
actor: "managed-runner",
|
|
372
|
+
role: "runner",
|
|
373
|
+
summary: mt(contract, "根据已落盘的整改结论恢复到执行者整改步骤", "Restored the executor repair step from persisted review findings"),
|
|
374
|
+
});
|
|
375
|
+
return state;
|
|
376
|
+
}
|
|
377
|
+
function readReviewHistory(state) {
|
|
378
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
379
|
+
if (!existsSync(runDir))
|
|
380
|
+
return [];
|
|
381
|
+
return readdirSync(runDir)
|
|
382
|
+
.filter((name) => /^review-result-\d+\.json$/.test(name))
|
|
383
|
+
.sort((left, right) => reviewFileRound(left) - reviewFileRound(right))
|
|
384
|
+
.flatMap((name) => {
|
|
385
|
+
try {
|
|
386
|
+
return [
|
|
387
|
+
JSON.parse(readFileSync(path.join(runDir, name), "utf-8")),
|
|
388
|
+
];
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function reviewFileRound(name) {
|
|
396
|
+
return Number.parseInt(name.match(/\d+/)?.[0] ?? "0", 10);
|
|
397
|
+
}
|
|
398
|
+
function promoteRevalidatedExecutorResult(contract, state) {
|
|
399
|
+
const current = state.lastExecutorResult;
|
|
400
|
+
if (!current?.endsWith("-invalid.json"))
|
|
401
|
+
return state;
|
|
402
|
+
const result = JSON.parse(readFileSync(current, "utf-8"));
|
|
403
|
+
const historical = historicalEngineeringCommands(state, true);
|
|
404
|
+
const reusableHistorical = historical.filter((command) => !isBlockingCommandFailure(command) && isCommandOutcomeConsistent(command));
|
|
405
|
+
const currentCommandCount = result.commands.length;
|
|
406
|
+
const reusableCurrent = result.commands.filter((command) => !isBlockingCommandFailure(command) && isCommandOutcomeConsistent(command));
|
|
407
|
+
result.commands = uniqueCommands([...reusableHistorical, ...reusableCurrent]);
|
|
408
|
+
promoteHostApprovedVerificationMetadata(contract, state, result, current);
|
|
409
|
+
const filteredCount = historical.length -
|
|
410
|
+
reusableHistorical.length +
|
|
411
|
+
(currentCommandCount - reusableCurrent.length);
|
|
412
|
+
if (filteredCount > 0) {
|
|
413
|
+
appendManagedEvent(state, {
|
|
414
|
+
eventType: "executor.historical_evidence_filtered",
|
|
415
|
+
actor: "managed-runner",
|
|
416
|
+
role: "runner",
|
|
417
|
+
summary: mt(contract, `按当前规则过滤 ${filteredCount} 条失效历史命令证据`, `Filtered ${filteredCount} stale historical command evidence item(s) under current rules`),
|
|
418
|
+
evidencePaths: [current],
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
validateWorkspaceSecrets(contract, state);
|
|
422
|
+
validateExecutorResult(contract, state, result, { hostApproved: true });
|
|
423
|
+
const accepted = current.replace(/-invalid\.json$/, ".json");
|
|
424
|
+
writeJsonAtomic(accepted, result);
|
|
425
|
+
state.lastExecutorResult = accepted;
|
|
426
|
+
state.deliveryProgress = evaluateCompletion(contract, result).progress;
|
|
427
|
+
saveManagedRun(state);
|
|
428
|
+
appendManagedEvent(state, {
|
|
429
|
+
eventType: "executor.result_revalidated",
|
|
430
|
+
actor: "managed-runner",
|
|
431
|
+
role: "runner",
|
|
432
|
+
summary: mt(contract, "最后一次执行结果已按当前确定性规则重验并晋升为正式证据", "The latest executor result passed current deterministic validation and was promoted to accepted evidence"),
|
|
433
|
+
evidencePaths: [current, accepted],
|
|
434
|
+
});
|
|
435
|
+
return state;
|
|
436
|
+
}
|
|
437
|
+
function promoteHostApprovedVerificationMetadata(contract, state, result, invalidResultPath) {
|
|
438
|
+
const escalated = readManagedEvents(state).some((event) => event.eventType === "executor.verification_metadata_escalated_to_host" &&
|
|
439
|
+
event.evidencePaths.includes(invalidResultPath));
|
|
440
|
+
if (!escalated)
|
|
441
|
+
return;
|
|
442
|
+
const command = result.commands.find((item) => item.exitCode === 0 &&
|
|
443
|
+
item.categories?.includes("startup") &&
|
|
444
|
+
item.categories.includes("runtime"));
|
|
445
|
+
if (!command)
|
|
446
|
+
return;
|
|
447
|
+
command.categories = uniqueCategories([
|
|
448
|
+
...(command.categories ?? []),
|
|
449
|
+
"invocation",
|
|
450
|
+
]);
|
|
451
|
+
appendManagedEvent(state, {
|
|
452
|
+
eventType: "executor.verification_metadata_host_promoted",
|
|
453
|
+
actor: contract.supervisorAgent,
|
|
454
|
+
role: "supervisor",
|
|
455
|
+
summary: mt(contract, "Host 已核对原始运行证据并批准把缺失的真实调用类别审计式晋升,未重跑研发环境", "The Host inspected the raw runtime evidence and approved audit promotion of the missing real-invocation category without rerunning the engineering environment"),
|
|
456
|
+
evidencePaths: [invalidResultPath],
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
async function executeWorker(contract, state, findings, schemaPath, invoker, env) {
|
|
460
|
+
const deliveryProtocolRecovery = state.currentStep === "executor_delivery_protocol_recovery_queued";
|
|
461
|
+
if (contract.budgets.executorPhysicalStopAt !== undefined &&
|
|
462
|
+
state.executorInvocations >= contract.budgets.executorPhysicalStopAt) {
|
|
463
|
+
return finishBudgetExhausted(contract, state, mt(contract, "Claude 临时调用窗口已用尽", "The temporary Claude invocation window is exhausted"), env);
|
|
464
|
+
}
|
|
465
|
+
const hasActiveExecutorWindow = contract.budgets.executorPhysicalStopAt !== undefined &&
|
|
466
|
+
state.executorInvocations < contract.budgets.executorPhysicalStopAt;
|
|
467
|
+
if (!hasActiveExecutorWindow &&
|
|
468
|
+
!hasUnlimitedAgentBudget(contract) &&
|
|
469
|
+
effectiveExecutorInvocations(state) >=
|
|
470
|
+
contract.budgets.maxExecutorInvocations) {
|
|
471
|
+
return finishBudgetExhausted(contract, state, mt(contract, "执行调用次数达到上限", "Executor invocation limit reached"), env);
|
|
472
|
+
}
|
|
473
|
+
if (!hasActiveExecutorWindow &&
|
|
474
|
+
!hasUnlimitedAgentBudget(contract) &&
|
|
475
|
+
effectiveTotalAgentInvocations(state) >=
|
|
476
|
+
contract.budgets.maxTotalAgentInvocations) {
|
|
477
|
+
return finishBudgetExhausted(contract, state, mt(contract, "总 Agent 调用次数达到上限", "Total Agent invocation limit reached"), env);
|
|
478
|
+
}
|
|
479
|
+
const resumableSessionId = maxTurnSessionToResume(state);
|
|
480
|
+
if (!resumableSessionId) {
|
|
481
|
+
state.executorSession = rolloverSession(state.executorSession);
|
|
482
|
+
}
|
|
483
|
+
state.executorSession = {
|
|
484
|
+
...state.executorSession,
|
|
485
|
+
resumingSessionId: resumableSessionId,
|
|
486
|
+
};
|
|
487
|
+
state.executorInvocations += 1;
|
|
488
|
+
state.totalAgentInvocations += 1;
|
|
489
|
+
state.executorStage = initialExecutorStage(findings, state.executorStage);
|
|
490
|
+
state.executorActiveStage = state.executorStage;
|
|
491
|
+
state.currentStep =
|
|
492
|
+
findings.length > 0 ? "executor_repairing" : "executor_implementing";
|
|
493
|
+
state.activeSince = new Date().toISOString();
|
|
494
|
+
saveManagedRun(state);
|
|
495
|
+
appendManagedEvent(state, {
|
|
496
|
+
eventType: "executor.started",
|
|
497
|
+
actor: contract.executorAgent,
|
|
498
|
+
role: "executor",
|
|
499
|
+
summary: mt(contract, `开始第 ${state.executorInvocations} 次执行调用`, `Started executor invocation ${state.executorInvocations}`),
|
|
500
|
+
});
|
|
501
|
+
appendExecutorStageEvent(contract, state, state.executorStage);
|
|
502
|
+
const handoffFile = writeManagedExecutorHandoff(contract, state, findings);
|
|
503
|
+
const prompt = buildExecutorPrompt(contract, state, findings, handoffFile);
|
|
504
|
+
const promptFile = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), findings.length > 0
|
|
505
|
+
? `repair-${state.reviewRound}.md`
|
|
506
|
+
: "executor-initial-prompt.md");
|
|
507
|
+
writeFileSync(promptFile, prompt, "utf-8");
|
|
508
|
+
state.lastRepairPrompt =
|
|
509
|
+
findings.length > 0 ? promptFile : state.lastRepairPrompt;
|
|
510
|
+
appendManagedEvent(state, {
|
|
511
|
+
eventType: "executor.dispatch_recorded",
|
|
512
|
+
actor: "managed-runner",
|
|
513
|
+
role: "runner",
|
|
514
|
+
summary: mt(contract, `已下发第 ${state.executorInvocations} 次执行,handoff hash=${fileHash(handoffFile)}`, `Recorded executor dispatch ${state.executorInvocations}, handoff hash=${fileHash(handoffFile)}`),
|
|
515
|
+
evidencePaths: [handoffFile, promptFile],
|
|
516
|
+
});
|
|
517
|
+
const started = Date.now();
|
|
518
|
+
const result = await invokeWithFailureEvidence(state, invoker, invocation(contract, state, prompt, promptFile, schemaPath, resumableSessionId, deliveryProtocolRecovery, (sessionId) => {
|
|
519
|
+
if (!acceptsExecutorCallback(state))
|
|
520
|
+
return;
|
|
521
|
+
state.executorSession = {
|
|
522
|
+
...state.executorSession,
|
|
523
|
+
sessionId,
|
|
524
|
+
createdAt: state.executorSession.createdAt ?? new Date().toISOString(),
|
|
525
|
+
status: "active",
|
|
526
|
+
lastResumedRound: state.reviewRound,
|
|
527
|
+
};
|
|
528
|
+
saveManagedRun(state);
|
|
529
|
+
recordHumanGuidanceHandoffDelivery(contract, state, handoffFile, sessionId);
|
|
530
|
+
if (!readManagedEvents(state).some((event) => event.eventType === "executor.handoff_acknowledged" &&
|
|
531
|
+
event.evidencePaths.includes(handoffFile))) {
|
|
532
|
+
appendManagedEvent(state, {
|
|
533
|
+
eventType: "executor.handoff_acknowledged",
|
|
534
|
+
actor: contract.executorAgent,
|
|
535
|
+
role: "executor",
|
|
536
|
+
summary: mt(contract, `Executor 已确认第 ${state.executorInvocations} 次 handoff 并返回会话标识`, `Executor acknowledged handoff for invocation ${state.executorInvocations} with a session identifier`),
|
|
537
|
+
evidencePaths: [handoffFile],
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}, (summary) => recordExecutorProgress(contract, state, summary)), `executor-${state.executorInvocations}-failed`);
|
|
541
|
+
state.consecutiveTransientProviderFailures = 0;
|
|
542
|
+
state = closeActiveTime(state, started);
|
|
543
|
+
state.executorUsage = mergeAgentUsage(state.executorUsage, result.usage);
|
|
544
|
+
state.runtimeTelemetry = result.telemetry ?? state.runtimeTelemetry ?? null;
|
|
545
|
+
state.runningAgentPid = null;
|
|
546
|
+
state.executorSession = {
|
|
547
|
+
...state.executorSession,
|
|
548
|
+
sessionId: result.sessionId,
|
|
549
|
+
createdAt: state.executorSession.createdAt ?? new Date().toISOString(),
|
|
550
|
+
status: "active",
|
|
551
|
+
lastResumedRound: state.reviewRound,
|
|
552
|
+
resumingSessionId: null,
|
|
553
|
+
};
|
|
554
|
+
const resultFile = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), `executor-result-${state.executorInvocations}.json`);
|
|
555
|
+
let finalPreflightLogs = [];
|
|
556
|
+
appendManagedEvent(state, {
|
|
557
|
+
eventType: "executor.delivery_received",
|
|
558
|
+
actor: "managed-runner",
|
|
559
|
+
role: "runner",
|
|
560
|
+
summary: mt(contract, `Runner 已接收第 ${state.executorInvocations} 次交付,开始确定性校验`, `Runner received executor delivery ${state.executorInvocations} and started deterministic validation`),
|
|
561
|
+
});
|
|
562
|
+
try {
|
|
563
|
+
result.output = redactManagedValue(normalizeExecutorResult(contract, state, result.output));
|
|
564
|
+
validateInvocationTelemetry(contract, state, result, result.output);
|
|
565
|
+
validateWorkspaceSecrets(contract, state);
|
|
566
|
+
validateExecutorResult(contract, state, result.output);
|
|
567
|
+
finalPreflightLogs = runExecutorFinalPreflight(contract, state, result.output);
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
const invalidFile = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), `executor-result-${state.executorInvocations}-invalid.json`);
|
|
571
|
+
writeJsonAtomic(invalidFile, result.output);
|
|
572
|
+
const invalidLogs = writeInvocationLogs(state, `executor-${state.executorInvocations}-invalid`, result.stdout, result.stderr);
|
|
573
|
+
const gateLogs = error instanceof ExecutorFinalPreflightError ? error.evidencePaths : [];
|
|
574
|
+
appendManagedEvent(state, {
|
|
575
|
+
eventType: "executor.result_rejected",
|
|
576
|
+
actor: contract.executorAgent,
|
|
577
|
+
role: "executor",
|
|
578
|
+
summary: error.message,
|
|
579
|
+
evidencePaths: [
|
|
580
|
+
invalidFile,
|
|
581
|
+
...invalidLogs,
|
|
582
|
+
...finalPreflightLogs,
|
|
583
|
+
...gateLogs,
|
|
584
|
+
],
|
|
585
|
+
});
|
|
586
|
+
if (error instanceof VerificationMetadataAmbiguityError) {
|
|
587
|
+
state.lastExecutorResult = invalidFile;
|
|
588
|
+
state.lastReviewResult = null;
|
|
589
|
+
state.currentStep = "verification_metadata_host_review_required";
|
|
590
|
+
saveManagedRun(state);
|
|
591
|
+
appendManagedEvent(state, {
|
|
592
|
+
eventType: "executor.verification_metadata_escalated_to_host",
|
|
593
|
+
actor: "managed-runner",
|
|
594
|
+
role: "runner",
|
|
595
|
+
summary: mt(contract, `运行证据类别存在结构化歧义,保留原始证据交 Host 语义判断,不启动完整 Executor 整改:${error.message}`, `Verification categories are structurally ambiguous; raw evidence was preserved for Host semantic review without starting a full Executor repair: ${error.message}`),
|
|
596
|
+
evidencePaths: [invalidFile, ...invalidLogs],
|
|
597
|
+
});
|
|
598
|
+
return state;
|
|
599
|
+
}
|
|
600
|
+
return queueExecutorSelfRepair(contract, state, error.message, invalidFile);
|
|
601
|
+
}
|
|
602
|
+
writeJsonAtomic(resultFile, result.output);
|
|
603
|
+
const logFiles = writeInvocationLogs(state, `executor-${state.executorInvocations}`, result.stdout, result.stderr);
|
|
604
|
+
state.lastExecutorResult = resultFile;
|
|
605
|
+
state.deliveryProgress = evaluateCompletion(contract, result.output).progress;
|
|
606
|
+
state.currentStep = "delivery_evidence_checking";
|
|
607
|
+
state.workspaceFingerprint = computeWorkspaceFingerprintForRoots([
|
|
608
|
+
contract.projectRoot,
|
|
609
|
+
...contract.relatedProjectRoots,
|
|
610
|
+
]);
|
|
611
|
+
saveManagedRun(state);
|
|
612
|
+
appendTaskReport(state, executorReportSection(state, result.output));
|
|
613
|
+
appendManagedEvent(state, {
|
|
614
|
+
eventType: "executor.ended",
|
|
615
|
+
actor: contract.executorAgent,
|
|
616
|
+
role: "executor",
|
|
617
|
+
summary: result.output.summary,
|
|
618
|
+
evidencePaths: [
|
|
619
|
+
resultFile,
|
|
620
|
+
...logFiles,
|
|
621
|
+
...finalPreflightLogs,
|
|
622
|
+
...result.output.evidence,
|
|
623
|
+
],
|
|
624
|
+
});
|
|
625
|
+
const completionGap = executorCompletionGap(contract, result.output);
|
|
626
|
+
if (result.output.status === "ready_for_review" && completionGap) {
|
|
627
|
+
return queueExecutorSelfRepair(contract, state, completionGap, resultFile);
|
|
628
|
+
}
|
|
629
|
+
if (result.output.status !== "failed")
|
|
630
|
+
return state;
|
|
631
|
+
return finishBlocked(contract, state, managedList(contract.language, result.output.blockers, "", "") ||
|
|
632
|
+
result.output.summary, env);
|
|
633
|
+
}
|
|
634
|
+
function maxTurnSessionToResume(state) {
|
|
635
|
+
if (state.executorSession.status !== "active")
|
|
636
|
+
return null;
|
|
637
|
+
if (!state.executorSession.sessionId)
|
|
638
|
+
return null;
|
|
639
|
+
if (state.executorSession.retiredSessions?.some((session) => session.sessionId === state.executorSession.sessionId)) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const terminal = readManagedEvents(state)
|
|
643
|
+
.slice()
|
|
644
|
+
.reverse()
|
|
645
|
+
.find((event) => ["executor.invocation_failed", "executor.ended"].includes(event.eventType));
|
|
646
|
+
if (terminal?.eventType !== "executor.invocation_failed" ||
|
|
647
|
+
!/max[_ -]?turns|maximum number of turns/i.test(terminal.summary)) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
return state.executorSession.sessionId;
|
|
651
|
+
}
|
|
652
|
+
function isMaxTurnContinuationFailure(message) {
|
|
653
|
+
return /max[_ -]?turns|maximum number of turns/i.test(message);
|
|
654
|
+
}
|
|
655
|
+
function unresumableExecutorSessionFailure(error, state) {
|
|
656
|
+
const sessionId = state.executorSession.resumingSessionId;
|
|
657
|
+
if (!sessionId || state.executorSession.sessionId !== sessionId)
|
|
658
|
+
return null;
|
|
659
|
+
const raw = error;
|
|
660
|
+
return classifyUnresumableSessionFailure({
|
|
661
|
+
message: raw.message,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function unresumableSessionRecoveryCount(state) {
|
|
665
|
+
return readManagedEvents(state).filter((event) => event.eventType === "executor.session_recovery_queued").length;
|
|
666
|
+
}
|
|
667
|
+
function isExecutorDeliveryProtocolFailure(message) {
|
|
668
|
+
return /(?:Agent 最终输出不是 JSON|Agent final output is not JSON|Agent 未返回结构化结果|Agent did not return structured output|Agent 输出中缺少可恢复 session ID|Agent output is missing a resumable session ID)/i.test(message);
|
|
669
|
+
}
|
|
670
|
+
function deliveryProtocolRecoveryCount(state) {
|
|
671
|
+
return readManagedEvents(state).filter((event) => event.eventType === "executor.delivery_protocol_recovery_queued").length;
|
|
672
|
+
}
|
|
673
|
+
function consecutiveMaxTurnFailures(state) {
|
|
674
|
+
let count = 0;
|
|
675
|
+
for (const event of readManagedEvents(state).slice().reverse()) {
|
|
676
|
+
if ([
|
|
677
|
+
"executor.ended",
|
|
678
|
+
"human.pause_applied",
|
|
679
|
+
"human.guidance_resumed",
|
|
680
|
+
"human.resume_requested",
|
|
681
|
+
"provider.permanent_failure",
|
|
682
|
+
"connectivity.lost",
|
|
683
|
+
].includes(event.eventType)) {
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
if (event.eventType === "executor.invocation_failed" &&
|
|
687
|
+
isMaxTurnContinuationFailure(event.summary)) {
|
|
688
|
+
count += 1;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return count;
|
|
692
|
+
}
|
|
693
|
+
function recordExecutorProgress(contract, state, summary) {
|
|
694
|
+
if (!acceptsExecutorCallback(state))
|
|
695
|
+
return;
|
|
696
|
+
if (summary.startsWith("superflow-supervision:")) {
|
|
697
|
+
appendSupervisionCheckpoint(contract, state, summary);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
recordExecutorRework(contract, state, summary);
|
|
701
|
+
const explicitStage = explicitExecutorStage(summary);
|
|
702
|
+
if (explicitStage && explicitStage !== state.executorActiveStage) {
|
|
703
|
+
state.executorActiveStage = explicitStage;
|
|
704
|
+
saveManagedRun(state);
|
|
705
|
+
}
|
|
706
|
+
const stage = inferExecutorStage(summary, state.executorStage);
|
|
707
|
+
if (stage && stage !== state.executorStage) {
|
|
708
|
+
state.executorStage = stage;
|
|
709
|
+
saveManagedRun(state);
|
|
710
|
+
appendExecutorStageEvent(contract, state, stage);
|
|
711
|
+
}
|
|
712
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
713
|
+
const evidenceFile = path.join(runDir, `executor-progress-${state.executorInvocations}.jsonl`);
|
|
714
|
+
const persistedSummary = redactManagedLog(summary);
|
|
715
|
+
appendFileSync(evidenceFile, `${JSON.stringify({ timestamp: new Date().toISOString(), summary: persistedSummary })}\n`, "utf-8");
|
|
716
|
+
appendManagedEvent(state, {
|
|
717
|
+
eventType: "executor.progress",
|
|
718
|
+
actor: contract.executorAgent,
|
|
719
|
+
role: "executor",
|
|
720
|
+
summary: persistedSummary.slice(0, 240),
|
|
721
|
+
evidencePaths: [evidenceFile],
|
|
722
|
+
});
|
|
723
|
+
const fingerprint = computeWorkspaceFingerprintForRoots([
|
|
724
|
+
contract.projectRoot,
|
|
725
|
+
...contract.relatedProjectRoots,
|
|
726
|
+
]);
|
|
727
|
+
if (fingerprint === state.workspaceFingerprint)
|
|
728
|
+
return;
|
|
729
|
+
state.workspaceFingerprint = fingerprint;
|
|
730
|
+
saveManagedRun(state);
|
|
731
|
+
const diffStats = [contract.projectRoot, ...contract.relatedProjectRoots]
|
|
732
|
+
.map((root) => `${root}\n${safeGitOutput(root, ["diff", "--stat"]) || "无差异统计"}`)
|
|
733
|
+
.join("\n");
|
|
734
|
+
const workspaceFile = path.join(runDir, `workspace-change-${Date.now()}.txt`);
|
|
735
|
+
writeFileSync(workspaceFile, `${diffStats}\n`, "utf-8");
|
|
736
|
+
appendManagedEvent(state, {
|
|
737
|
+
eventType: "workspace.changed",
|
|
738
|
+
actor: contract.executorAgent,
|
|
739
|
+
role: "executor",
|
|
740
|
+
summary: mt(contract, "检测到目标工作区内容变化,完整 diff stat 已留档", "Detected target workspace changes; the full diff stat was persisted"),
|
|
741
|
+
evidencePaths: [workspaceFile],
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
function appendSupervisionCheckpoint(contract, state, summary) {
|
|
745
|
+
const events = readManagedEvents(state);
|
|
746
|
+
const latestAttention = events
|
|
747
|
+
.slice()
|
|
748
|
+
.reverse()
|
|
749
|
+
.find((event) => event.eventType === "executor.supervision_attention_required");
|
|
750
|
+
const previous = events
|
|
751
|
+
.slice()
|
|
752
|
+
.reverse()
|
|
753
|
+
.find((event) => event.sequence > (latestAttention?.sequence ?? 0) &&
|
|
754
|
+
(event.eventType === "executor.supervision_checkpoint" ||
|
|
755
|
+
event.eventType === "executor.supervision_checkpoint_idle"));
|
|
756
|
+
const supervisionWindowStart = previous?.sequence ?? latestAttention?.sequence ?? 0;
|
|
757
|
+
const recent = events.filter((event) => event.sequence > supervisionWindowStart);
|
|
758
|
+
const stageChanges = recent.filter((event) => ["executor.stage_changed", "executor.stage_rework"].includes(event.eventType)).length;
|
|
759
|
+
const commandCompletions = recent.filter((event) => event.eventType === "executor.progress" &&
|
|
760
|
+
/(?:background command|command).*(?:completed|exit code|完成|退出码)/i.test(event.summary)).length;
|
|
761
|
+
const workspaceChanges = recent.filter((event) => event.eventType === "workspace.changed").length;
|
|
762
|
+
const raw = summary.slice("superflow-supervision:".length);
|
|
763
|
+
let telemetry = raw;
|
|
764
|
+
try {
|
|
765
|
+
telemetry = JSON.stringify(JSON.parse(raw));
|
|
766
|
+
}
|
|
767
|
+
catch {
|
|
768
|
+
// Preserve bounded raw telemetry when an older adapter emits plain text.
|
|
769
|
+
}
|
|
770
|
+
const hasEffectiveMilestone = stageChanges + commandCompletions > 0;
|
|
771
|
+
appendManagedEvent(state, {
|
|
772
|
+
eventType: hasEffectiveMilestone
|
|
773
|
+
? "executor.supervision_checkpoint"
|
|
774
|
+
: "executor.supervision_checkpoint_idle",
|
|
775
|
+
actor: contract.executorAgent,
|
|
776
|
+
role: "executor",
|
|
777
|
+
summary: mt(contract, `10 分钟监督点:当前阶段 ${state.executorActiveStage ?? state.executorStage ?? "unknown"};已达里程碑 ${state.executorStage ?? "unknown"};阶段变化 ${stageChanges};命令完成 ${commandCompletions};工作区变化 ${workspaceChanges};遥测 ${telemetry}`, `10-minute supervision checkpoint: active stage ${state.executorActiveStage ?? state.executorStage ?? "unknown"}; reached milestone ${state.executorStage ?? "unknown"}; stage changes ${stageChanges}; command completions ${commandCompletions}; workspace changes ${workspaceChanges}; telemetry ${telemetry}`).slice(0, 500),
|
|
778
|
+
evidencePaths: [],
|
|
779
|
+
});
|
|
780
|
+
if (!hasEffectiveMilestone &&
|
|
781
|
+
previous?.eventType === "executor.supervision_checkpoint_idle") {
|
|
782
|
+
appendManagedEvent(state, {
|
|
783
|
+
eventType: "executor.supervision_attention_required",
|
|
784
|
+
actor: "managed-runner",
|
|
785
|
+
role: "runner",
|
|
786
|
+
summary: mt(contract, "连续两个 10 分钟监督点没有阶段推进或权威命令完成,需要 Host 轻量判断是否偏航", "Two consecutive 10-minute checkpoints had no stage advance or authoritative command completion; the Host must perform a lightweight drift check"),
|
|
787
|
+
evidencePaths: [],
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function acceptsExecutorCallback(state) {
|
|
792
|
+
const persisted = loadManagedRun(state.projectRoot, state.taskId, state.runId);
|
|
793
|
+
return (persisted.status === "running" &&
|
|
794
|
+
persisted.executorInvocations === state.executorInvocations);
|
|
795
|
+
}
|
|
796
|
+
function recordExecutorRework(contract, state, summary) {
|
|
797
|
+
const current = state.executorStage;
|
|
798
|
+
const explicit = explicitExecutorStage(summary);
|
|
799
|
+
if (!current || !explicit || explicit === current)
|
|
800
|
+
return;
|
|
801
|
+
const order = [
|
|
802
|
+
"source_discovery",
|
|
803
|
+
"implementation",
|
|
804
|
+
"unit_test",
|
|
805
|
+
"package",
|
|
806
|
+
"application_startup",
|
|
807
|
+
"http_e2e",
|
|
808
|
+
"cleanup",
|
|
809
|
+
"delivery_self_check",
|
|
810
|
+
];
|
|
811
|
+
if (order.indexOf(explicit) >= order.indexOf(current))
|
|
812
|
+
return;
|
|
813
|
+
const summaryText = mt(contract, `Executor 在 ${current} 后回到 ${explicit} 修复并重跑受影响验证`, `Executor returned from ${current} to ${explicit} to repair and rerun affected validation`);
|
|
814
|
+
const events = readManagedEvents(state);
|
|
815
|
+
let duplicate = false;
|
|
816
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
817
|
+
const event = events[index];
|
|
818
|
+
if (event.eventType === "executor.stage_changed")
|
|
819
|
+
break;
|
|
820
|
+
if (event.eventType === "executor.stage_rework" &&
|
|
821
|
+
event.summary === summaryText) {
|
|
822
|
+
duplicate = true;
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (duplicate)
|
|
827
|
+
return;
|
|
828
|
+
appendManagedEvent(state, {
|
|
829
|
+
eventType: "executor.stage_rework",
|
|
830
|
+
actor: contract.executorAgent,
|
|
831
|
+
role: "executor",
|
|
832
|
+
summary: summaryText,
|
|
833
|
+
evidencePaths: [],
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
function explicitExecutorStage(summary) {
|
|
837
|
+
return summary
|
|
838
|
+
.toLowerCase()
|
|
839
|
+
.match(/superflow-stage:(source_discovery|implementation|unit_test|package|application_startup|http_e2e|cleanup|delivery_self_check)/)?.[1];
|
|
840
|
+
}
|
|
841
|
+
function inferExecutorStage(summary, current) {
|
|
842
|
+
const value = summary.toLowerCase();
|
|
843
|
+
const explicit = value.match(/superflow-stage:(source_discovery|implementation|unit_test|package|application_startup|http_e2e|cleanup|delivery_self_check)/)?.[1];
|
|
844
|
+
if (explicit)
|
|
845
|
+
return advanceExecutorStage(current, explicit);
|
|
846
|
+
if (/structuredoutput|preflight|diff --check|交付前|final result/.test(value)) {
|
|
847
|
+
return advanceExecutorStage(current, "delivery_self_check");
|
|
848
|
+
}
|
|
849
|
+
if (/drop table|redis.*(?:del|exists)|kill|sigterm|pgrep|cleanup|清理|no listener|无监听/.test(value)) {
|
|
850
|
+
return advanceExecutorStage(current, "cleanup");
|
|
851
|
+
}
|
|
852
|
+
if (/curl|http|e2e|接口调用/.test(value)) {
|
|
853
|
+
return advanceExecutorStage(current, "http_e2e");
|
|
854
|
+
}
|
|
855
|
+
if (/java -jar|spring-boot:run|app(?:lication)? startup|startup.*ports?|wait for application|启动应用/.test(value)) {
|
|
856
|
+
return advanceExecutorStage(current, "application_startup");
|
|
857
|
+
}
|
|
858
|
+
if (/mvn.*package|gradle.*build|npm.*build|package jar|打包/.test(value)) {
|
|
859
|
+
return advanceExecutorStage(current, "package");
|
|
860
|
+
}
|
|
861
|
+
if (/mvn.*test|unit test|vitest|jest|pytest|单元测试/.test(value)) {
|
|
862
|
+
return advanceExecutorStage(current, "unit_test");
|
|
863
|
+
}
|
|
864
|
+
if (/\bedit\b|\bwrite\b|workspace_writing|修改源码|编码/.test(value)) {
|
|
865
|
+
return advanceExecutorStage(current, "implementation");
|
|
866
|
+
}
|
|
867
|
+
if (/\bread\b|\brg\b|\bgrep\b|搜索|检索/.test(value)) {
|
|
868
|
+
return current ?? "source_discovery";
|
|
869
|
+
}
|
|
870
|
+
return current;
|
|
871
|
+
}
|
|
872
|
+
function initialExecutorStage(findings, current) {
|
|
873
|
+
if (findings.length === 0)
|
|
874
|
+
return "source_discovery";
|
|
875
|
+
if (findings.some((finding) => /^(?:correctness|security|maintainability|style|code_quality|implementation)$/i.test(finding.category))) {
|
|
876
|
+
return "implementation";
|
|
877
|
+
}
|
|
878
|
+
const content = findings
|
|
879
|
+
.flatMap((finding) => [
|
|
880
|
+
finding.category,
|
|
881
|
+
finding.target,
|
|
882
|
+
finding.evidence,
|
|
883
|
+
finding.requiredFix,
|
|
884
|
+
...finding.acceptanceChecks,
|
|
885
|
+
])
|
|
886
|
+
.join("\n");
|
|
887
|
+
return inferRepairStage(content, current);
|
|
888
|
+
}
|
|
889
|
+
function inferRepairStage(content, current) {
|
|
890
|
+
const value = content.toLowerCase();
|
|
891
|
+
if (/cleanup|清理|drop table|redis.*del|进程残留/.test(value)) {
|
|
892
|
+
return "cleanup";
|
|
893
|
+
}
|
|
894
|
+
if (/curl|http|e2e|接口|页面测试/.test(value))
|
|
895
|
+
return "http_e2e";
|
|
896
|
+
if (/startup|启动|端口|listener/.test(value)) {
|
|
897
|
+
return "application_startup";
|
|
898
|
+
}
|
|
899
|
+
if (/package|build|compile|构建|编译|打包/.test(value))
|
|
900
|
+
return "package";
|
|
901
|
+
if (/test|测试|覆盖率/.test(value))
|
|
902
|
+
return "unit_test";
|
|
903
|
+
if (/preflight|交付|证据|报告/.test(value))
|
|
904
|
+
return "delivery_self_check";
|
|
905
|
+
if (current && current !== "source_discovery")
|
|
906
|
+
return current;
|
|
907
|
+
return "implementation";
|
|
908
|
+
}
|
|
909
|
+
function advanceExecutorStage(current, candidate) {
|
|
910
|
+
const order = [
|
|
911
|
+
"source_discovery",
|
|
912
|
+
"implementation",
|
|
913
|
+
"unit_test",
|
|
914
|
+
"package",
|
|
915
|
+
"application_startup",
|
|
916
|
+
"http_e2e",
|
|
917
|
+
"cleanup",
|
|
918
|
+
"delivery_self_check",
|
|
919
|
+
];
|
|
920
|
+
if (!current)
|
|
921
|
+
return candidate;
|
|
922
|
+
return order.indexOf(candidate) >= order.indexOf(current)
|
|
923
|
+
? candidate
|
|
924
|
+
: current;
|
|
925
|
+
}
|
|
926
|
+
function appendExecutorStageEvent(contract, state, stage) {
|
|
927
|
+
const labels = {
|
|
928
|
+
source_discovery: ["源码与规则检索", "source and rule discovery"],
|
|
929
|
+
implementation: ["编码实现", "implementation"],
|
|
930
|
+
unit_test: ["单元测试", "unit tests"],
|
|
931
|
+
package: ["构建打包", "build and package"],
|
|
932
|
+
application_startup: ["应用启动", "application startup"],
|
|
933
|
+
http_e2e: ["真实 HTTP E2E", "real HTTP E2E"],
|
|
934
|
+
cleanup: ["环境清理", "environment cleanup"],
|
|
935
|
+
delivery_self_check: [
|
|
936
|
+
"交付前确定性自检",
|
|
937
|
+
"deterministic delivery preflight",
|
|
938
|
+
],
|
|
939
|
+
};
|
|
940
|
+
appendManagedEvent(state, {
|
|
941
|
+
eventType: "executor.stage_changed",
|
|
942
|
+
actor: contract.executorAgent,
|
|
943
|
+
role: "executor",
|
|
944
|
+
summary: mt(contract, `研发阶段:${labels[stage][0]}`, `Executor stage: ${labels[stage][1]}`),
|
|
945
|
+
evidencePaths: [],
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
function validateInvocationTelemetry(contract, state, invocationResult, result) {
|
|
949
|
+
const telemetry = invocationResult.telemetry;
|
|
950
|
+
if (!telemetry)
|
|
951
|
+
return;
|
|
952
|
+
if (contract.executorAgent === "claude") {
|
|
953
|
+
if (!telemetry.model || telemetry.tools.length === 0) {
|
|
954
|
+
throw new Error(mt(contract, "Claude system/init 未提供实际模型或工具清单", "Claude system/init did not provide the actual model or tool list"));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (result.changedFiles.length > 0 &&
|
|
958
|
+
!telemetry.toolUses.some((tool) => /write|edit|apply_patch/i.test(tool))) {
|
|
959
|
+
if (!declaredFilesChangedSinceBaseline(contract, state, result.changedFiles)) {
|
|
960
|
+
throw new Error(mt(contract, "执行结果声明修改文件,但工作区基线无法证明任何对应变更", "The executor reported changed files but the workspace baseline proves no corresponding change"));
|
|
961
|
+
}
|
|
962
|
+
appendManagedEvent(state, {
|
|
963
|
+
eventType: "executor.write_telemetry_inferred",
|
|
964
|
+
actor: "managed-runner",
|
|
965
|
+
role: "runner",
|
|
966
|
+
summary: mt(contract, "流式事件缺少 Write/Edit,但冻结基线已证明交付文件真实变化;转交 Host 语义评审,不重启 Executor", "Write/Edit stream telemetry was absent, but the frozen baseline proves real delivery changes; forwarding to Host review without restarting the Executor"),
|
|
967
|
+
evidencePaths: [],
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
const claimsNoTool = [...result.blockers, result.summary].some((item) => /工具.*不可用|no tools?|tool.*unavailable/i.test(item));
|
|
971
|
+
if (claimsNoTool &&
|
|
972
|
+
telemetry.tools.length > 0 &&
|
|
973
|
+
telemetry.toolUses.length === 0) {
|
|
974
|
+
throw new Error(mt(contract, "执行者在未尝试已声明工具的情况下声称工具不可用", "The executor claimed tools were unavailable without attempting the declared tools"));
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function declaredFilesChangedSinceBaseline(contract, state, files) {
|
|
978
|
+
const roots = [contract.projectRoot, ...contract.relatedProjectRoots].map((root) => path.resolve(root));
|
|
979
|
+
const current = snapshotChangedWorkspaceFiles(roots);
|
|
980
|
+
const baseline = state.baselineWorkspaceFiles ?? {};
|
|
981
|
+
return files.some((file) => {
|
|
982
|
+
const normalized = file.replaceAll("\\", "/");
|
|
983
|
+
return roots.some((root) => {
|
|
984
|
+
const absolute = path.isAbsolute(file)
|
|
985
|
+
? path.resolve(file)
|
|
986
|
+
: path.resolve(root, file);
|
|
987
|
+
if (absolute !== root && !absolute.startsWith(`${root}${path.sep}`)) {
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
const relative = path.relative(root, absolute).replaceAll("\\", "/");
|
|
991
|
+
if (relative !== normalized && path.isAbsolute(file))
|
|
992
|
+
return false;
|
|
993
|
+
const key = `${root}::${relative}`;
|
|
994
|
+
return current[key] !== undefined && baseline[key] !== current[key];
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
function prepareExternalHostReview(contract, state, env) {
|
|
999
|
+
if (!hasUnlimitedAgentBudget(contract) &&
|
|
1000
|
+
state.reviewInvocations >= contract.budgets.maxReviewRounds) {
|
|
1001
|
+
return finishReviewExhausted(contract, state, env);
|
|
1002
|
+
}
|
|
1003
|
+
state.reviewRound += 1;
|
|
1004
|
+
state.reviewInvocations += 1;
|
|
1005
|
+
const reviewedResult = state.lastExecutorResult
|
|
1006
|
+
? JSON.parse(readFileSync(state.lastExecutorResult, "utf-8"))
|
|
1007
|
+
: null;
|
|
1008
|
+
const workspacePaths = hostReviewWorkspacePaths(contract, reviewedResult);
|
|
1009
|
+
const promptPath = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), `host-review-${state.reviewRound}.md`);
|
|
1010
|
+
const reviewFacts = writeManagedReviewFacts(contract, state);
|
|
1011
|
+
state.lastReviewedWorkspaceFiles = reviewFacts.workspaceSnapshot;
|
|
1012
|
+
writeFileSync(promptPath, buildReviewPrompt(contract, state, reviewFacts.jsonPath), "utf-8");
|
|
1013
|
+
state.pendingExternalReview = {
|
|
1014
|
+
round: state.reviewRound,
|
|
1015
|
+
promptPath,
|
|
1016
|
+
factsPath: reviewFacts.jsonPath,
|
|
1017
|
+
workspaceFingerprint: computeScopedWorkspaceFingerprint([contract.projectRoot, ...contract.relatedProjectRoots], workspacePaths),
|
|
1018
|
+
requestedAt: new Date().toISOString(),
|
|
1019
|
+
workspacePaths,
|
|
1020
|
+
};
|
|
1021
|
+
const reviewMessage = mt(contract, `等待当前 Host ${contract.supervisorAgent} 提交评审结果`, `Waiting for the current ${contract.supervisorAgent} host to submit its review result`);
|
|
1022
|
+
state.blocker = null;
|
|
1023
|
+
state = transition(contract, state, "waiting_for_host_review", "external_supervisor_review_required", env);
|
|
1024
|
+
saveManagedRun(state);
|
|
1025
|
+
appendManagedEvent(state, {
|
|
1026
|
+
eventType: "review.external_requested",
|
|
1027
|
+
actor: contract.supervisorAgent,
|
|
1028
|
+
role: "supervisor",
|
|
1029
|
+
summary: reviewMessage,
|
|
1030
|
+
evidencePaths: [promptPath, reviewFacts.jsonPath, reviewFacts.markdownPath],
|
|
1031
|
+
});
|
|
1032
|
+
if (notifyManagedTask({
|
|
1033
|
+
taskId: state.taskId,
|
|
1034
|
+
type: "review_required",
|
|
1035
|
+
title: mt(contract, "Superflow 等待主 Agent 评审", "Superflow is waiting for host review"),
|
|
1036
|
+
message: mt(contract, `${state.taskId} 已完成研发执行,等待第 ${state.reviewRound} 轮全量评审`, `${state.taskId} completed executor work and awaits full review round ${state.reviewRound}`),
|
|
1037
|
+
}, env)) {
|
|
1038
|
+
appendManagedEvent(state, {
|
|
1039
|
+
eventType: "host.review_required_notified",
|
|
1040
|
+
actor: "managed-runner",
|
|
1041
|
+
role: "runner",
|
|
1042
|
+
summary: `已通知 Host 处理第 ${state.reviewRound} 轮评审`,
|
|
1043
|
+
evidencePaths: [promptPath],
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
return state;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Host 验收会重跑已声明的验证命令,因此只允许明确 taskEvidence 指向
|
|
1050
|
+
* 的证据文件在评审期间更新;其他源码与合同文件仍必须保持不变。
|
|
1051
|
+
*/
|
|
1052
|
+
function hostReviewWorkspacePaths(contract, result) {
|
|
1053
|
+
if (!result)
|
|
1054
|
+
return [];
|
|
1055
|
+
const roots = [contract.projectRoot, ...contract.relatedProjectRoots].map((root) => path.resolve(root));
|
|
1056
|
+
const evidencePaths = new Set((result.taskEvidence ?? []).flatMap((item) => item.evidencePaths.flatMap((evidencePath) => {
|
|
1057
|
+
const canonicalPath = canonicalEvidencePath(evidencePath);
|
|
1058
|
+
return path.isAbsolute(canonicalPath)
|
|
1059
|
+
? [path.resolve(canonicalPath)]
|
|
1060
|
+
: roots.map((root) => path.resolve(root, canonicalPath));
|
|
1061
|
+
})));
|
|
1062
|
+
return result.changedFiles.filter((changedFile) => {
|
|
1063
|
+
const candidates = path.isAbsolute(changedFile)
|
|
1064
|
+
? [path.resolve(changedFile)]
|
|
1065
|
+
: roots.map((root) => path.resolve(root, changedFile));
|
|
1066
|
+
return !candidates.some((candidate) => evidencePaths.has(candidate));
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
function rolloverSession(session) {
|
|
1070
|
+
const previousSessionIds = [...(session.previousSessionIds ?? [])];
|
|
1071
|
+
if (session.sessionId && !previousSessionIds.includes(session.sessionId)) {
|
|
1072
|
+
previousSessionIds.push(session.sessionId);
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
...session,
|
|
1076
|
+
sessionId: null,
|
|
1077
|
+
createdAt: null,
|
|
1078
|
+
status: "pending",
|
|
1079
|
+
previousSessionIds,
|
|
1080
|
+
resumingSessionId: null,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function retireExecutorSession(session, reason) {
|
|
1084
|
+
const sessionId = session.resumingSessionId;
|
|
1085
|
+
if (!sessionId)
|
|
1086
|
+
return rolloverSession(session);
|
|
1087
|
+
const retiredSessions = [...(session.retiredSessions ?? [])];
|
|
1088
|
+
if (!retiredSessions.some((retired) => retired.sessionId === sessionId)) {
|
|
1089
|
+
retiredSessions.push({
|
|
1090
|
+
sessionId,
|
|
1091
|
+
reason,
|
|
1092
|
+
retiredAt: new Date().toISOString(),
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return {
|
|
1096
|
+
...rolloverSession(session),
|
|
1097
|
+
retiredSessions,
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
export function writeManagedExecutorHandoff(contract, state, findings) {
|
|
1101
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
1102
|
+
const reportPath = path.join(runDir, "task-report.md");
|
|
1103
|
+
const report = existsSync(reportPath)
|
|
1104
|
+
? readFileSync(reportPath, "utf-8").slice(-6_000)
|
|
1105
|
+
: "";
|
|
1106
|
+
const lastResult = state.lastExecutorResult
|
|
1107
|
+
? JSON.parse(readFileSync(state.lastExecutorResult, "utf-8"))
|
|
1108
|
+
: null;
|
|
1109
|
+
const progress = readOpenSpecTaskProgress(contract, completionResultWithHistory(state, lastResult));
|
|
1110
|
+
const diffStat = safeGitOutput(contract.projectRoot, ["diff", "--stat"]);
|
|
1111
|
+
const changedFiles = changedWorkspaceFilesSinceBaseline(contract, state).map(({ root, relative }) => path.resolve(root) === path.resolve(contract.projectRoot)
|
|
1112
|
+
? relative.replaceAll("\\", "/")
|
|
1113
|
+
: path.join(root, relative).replaceAll("\\", "/"));
|
|
1114
|
+
const evidenceTasks = readCompletionTasks(contract, completionResultWithHistory(state, lastResult)).filter((task) => task.completed && task.category === "local_required");
|
|
1115
|
+
const humanMessages = undeliveredHumanGuidance(contract, state);
|
|
1116
|
+
const displayedHumanMessages = humanMessages.slice(-20);
|
|
1117
|
+
const deliveryMessageIds = humanMessages.map((message) => message.messageId);
|
|
1118
|
+
const contextManifest = ensureManagedContextManifest(contract);
|
|
1119
|
+
const ruleSelection = frozenRuleSelection(contract, contextManifest);
|
|
1120
|
+
const ruleFiles = ruleSelection.files;
|
|
1121
|
+
const preflightCommand = [
|
|
1122
|
+
process.execPath,
|
|
1123
|
+
path.join(ASSETS_DIR, "scripts", "superflow-managed-executor-preflight.mjs"),
|
|
1124
|
+
contract.projectRoot,
|
|
1125
|
+
state.taskId,
|
|
1126
|
+
].join(" ");
|
|
1127
|
+
const file = path.join(runDir, `executor-handoff-${state.executorInvocations}.md`);
|
|
1128
|
+
const machineFile = path.join(runDir, `executor-handoff-${state.executorInvocations}.json`);
|
|
1129
|
+
writeJsonAtomic(machineFile, {
|
|
1130
|
+
protocolVersion: "superflow.handoff.v2",
|
|
1131
|
+
messageType: "executor_handoff",
|
|
1132
|
+
taskId: state.taskId,
|
|
1133
|
+
runId: state.runId,
|
|
1134
|
+
invocation: state.executorInvocations,
|
|
1135
|
+
language: contract.language ?? "zh",
|
|
1136
|
+
frozenPrompt: {
|
|
1137
|
+
path: contract.taskPrompt?.snapshotPath ?? null,
|
|
1138
|
+
sha256: contract.taskPrompt?.sha256 ?? null,
|
|
1139
|
+
},
|
|
1140
|
+
workspace: { changedFiles, diffStat: diffStat || null },
|
|
1141
|
+
executionHints: {
|
|
1142
|
+
stablePolicySha256: stableExecutorPolicyHash(),
|
|
1143
|
+
contextManifest: {
|
|
1144
|
+
path: managedContextManifestPath(contract),
|
|
1145
|
+
sha256: contextManifest.manifestHash,
|
|
1146
|
+
},
|
|
1147
|
+
ruleScenarios: ruleSelection.scenarios,
|
|
1148
|
+
applicableRuleFiles: ruleFiles,
|
|
1149
|
+
mandatoryEngineeringRules: contract.mandatoryEngineeringRules ?? [],
|
|
1150
|
+
deterministicPreflightCommand: preflightCommand,
|
|
1151
|
+
},
|
|
1152
|
+
tasks: {
|
|
1153
|
+
total: progress.total,
|
|
1154
|
+
completed: progress.completed,
|
|
1155
|
+
localPending: progress.localPending,
|
|
1156
|
+
externalPending: progress.externalPending,
|
|
1157
|
+
checkedLocalTaskIds: evidenceTasks.map((task) => task.taskId),
|
|
1158
|
+
},
|
|
1159
|
+
findings,
|
|
1160
|
+
humanGuidance: {
|
|
1161
|
+
messages: humanMessages,
|
|
1162
|
+
deliveryMessageIds,
|
|
1163
|
+
},
|
|
1164
|
+
budget: {
|
|
1165
|
+
executorInvocations: state.executorInvocations,
|
|
1166
|
+
effectiveExecutorInvocations: effectiveExecutorInvocations(state),
|
|
1167
|
+
maxExecutorInvocations: contract.budgets.maxExecutorInvocations,
|
|
1168
|
+
tokenUnitsUsed: executorOutputTokenUnits(state),
|
|
1169
|
+
maxExecutorTokenUnits: contract.budgets.maxExecutorTokenUnits ?? null,
|
|
1170
|
+
outputTokensUsed: executorOutputTokenUnits(state),
|
|
1171
|
+
maxExecutorOutputTokens: contract.budgets.maxExecutorTokenUnits ?? null,
|
|
1172
|
+
costUsdUsed: state.executorUsage?.costUsd ?? null,
|
|
1173
|
+
maxExecutorCostUsd: contract.budgets.maxExecutorCostUsd ?? null,
|
|
1174
|
+
},
|
|
1175
|
+
});
|
|
1176
|
+
const lines = contract.language === "en"
|
|
1177
|
+
? [
|
|
1178
|
+
"# Condensed Executor Handoff",
|
|
1179
|
+
"",
|
|
1180
|
+
`Task: ${state.taskId}`,
|
|
1181
|
+
`Frozen prompt: ${contract.taskPrompt?.snapshotPath ?? "none"}`,
|
|
1182
|
+
`Prompt SHA-256: ${contract.taskPrompt?.sha256 ?? "none"}`,
|
|
1183
|
+
`Machine handoff: ${machineFile}`,
|
|
1184
|
+
`Applicable rule files: ${ruleFiles.join(", ") || "none"}`,
|
|
1185
|
+
`Mandatory host rules: ${contract.mandatoryEngineeringRules?.join("; ") || "none"}`,
|
|
1186
|
+
`Deterministic delivery preflight: ${preflightCommand}`,
|
|
1187
|
+
`Task progress: ${progress.completed}/${progress.total}; local pending ${progress.localPending.length}; external pending ${progress.externalPending.length}`,
|
|
1188
|
+
`Current diff stat:\n${diffStat || "none"}`,
|
|
1189
|
+
"",
|
|
1190
|
+
"## Required Complete changedFiles",
|
|
1191
|
+
"The Runner owns the authoritative changedFiles list below. The executor may report hints, but must not spend a repair round reproducing this list.",
|
|
1192
|
+
...(changedFiles.length > 0
|
|
1193
|
+
? changedFiles.map((changedFile) => `- ${changedFile}`)
|
|
1194
|
+
: ["none"]),
|
|
1195
|
+
"",
|
|
1196
|
+
"## Required Exact taskEvidence IDs",
|
|
1197
|
+
"The Runner derives baseline evidence for every exact ID below from tasks.md, workspace changes, and successful commands. Add taskEvidence only when a task needs distinct evidence; do not duplicate entries merely to satisfy formatting.",
|
|
1198
|
+
...(evidenceTasks.length > 0
|
|
1199
|
+
? evidenceTasks.map((task) => `- ${task.taskId}: ${task.text}`)
|
|
1200
|
+
: ["none"]),
|
|
1201
|
+
"",
|
|
1202
|
+
"## Current Findings",
|
|
1203
|
+
...handoffFindingLines(findings, "en"),
|
|
1204
|
+
"",
|
|
1205
|
+
"## Latest User Guidance",
|
|
1206
|
+
...(displayedHumanMessages.length > 0
|
|
1207
|
+
? displayedHumanMessages.map((message) => `- ${message.timestamp} [${message.actor}] ${message.content}`)
|
|
1208
|
+
: ["none"]),
|
|
1209
|
+
"",
|
|
1210
|
+
"## Local Pending Tasks",
|
|
1211
|
+
...(progress.localPending.length > 0
|
|
1212
|
+
? progress.localPending
|
|
1213
|
+
: ["none"]),
|
|
1214
|
+
"",
|
|
1215
|
+
"## External Prerequisites",
|
|
1216
|
+
...(progress.externalPending.length > 0
|
|
1217
|
+
? progress.externalPending
|
|
1218
|
+
: ["none"]),
|
|
1219
|
+
"",
|
|
1220
|
+
"## Recent Persisted Report",
|
|
1221
|
+
report || "none",
|
|
1222
|
+
]
|
|
1223
|
+
: [
|
|
1224
|
+
"# 研发执行压缩交接包",
|
|
1225
|
+
"",
|
|
1226
|
+
`任务:${state.taskId}`,
|
|
1227
|
+
`冻结 Prompt:${contract.taskPrompt?.snapshotPath ?? "无"}`,
|
|
1228
|
+
`Prompt SHA-256:${contract.taskPrompt?.sha256 ?? "无"}`,
|
|
1229
|
+
`机器交接协议:${machineFile}`,
|
|
1230
|
+
`适用规则文件:${ruleFiles.join("、") || "无"}`,
|
|
1231
|
+
`Host 强制规则:${contract.mandatoryEngineeringRules?.join(";") || "无"}`,
|
|
1232
|
+
`交付前确定性门禁:${preflightCommand}`,
|
|
1233
|
+
`任务进度:${progress.completed}/${progress.total};本地剩余 ${progress.localPending.length};外部前置 ${progress.externalPending.length}`,
|
|
1234
|
+
`当前差异统计:\n${diffStat || "无"}`,
|
|
1235
|
+
"",
|
|
1236
|
+
"## 必须完整上报的 changedFiles",
|
|
1237
|
+
"以下 changedFiles 由 Runner 负责生成并作为事实源;Executor 可提供提示,但不得为重复抄写本清单消耗整改轮次。",
|
|
1238
|
+
...(changedFiles.length > 0
|
|
1239
|
+
? changedFiles.map((changedFile) => `- ${changedFile}`)
|
|
1240
|
+
: ["无"]),
|
|
1241
|
+
"",
|
|
1242
|
+
"## 必须逐项上报的精确 taskEvidence ID",
|
|
1243
|
+
"Runner 会根据 tasks.md、工作区差异和成功命令为以下精确 ID 推导基础证据。只有某项需要独立证据时才补充 taskEvidence,禁止为了满足格式重复填写全部任务。",
|
|
1244
|
+
...(evidenceTasks.length > 0
|
|
1245
|
+
? evidenceTasks.map((task) => `- ${task.taskId}: ${task.text}`)
|
|
1246
|
+
: ["无"]),
|
|
1247
|
+
"",
|
|
1248
|
+
"## 本轮全部 Finding",
|
|
1249
|
+
...handoffFindingLines(findings, "zh"),
|
|
1250
|
+
"",
|
|
1251
|
+
"## 用户最新补充",
|
|
1252
|
+
...(displayedHumanMessages.length > 0
|
|
1253
|
+
? displayedHumanMessages.map((message) => `- ${message.timestamp} [${message.actor}] ${message.content}`)
|
|
1254
|
+
: ["无"]),
|
|
1255
|
+
"",
|
|
1256
|
+
"## 本地剩余任务",
|
|
1257
|
+
...(progress.localPending.length > 0
|
|
1258
|
+
? progress.localPending
|
|
1259
|
+
: ["无"]),
|
|
1260
|
+
"",
|
|
1261
|
+
"## 外部发布前置",
|
|
1262
|
+
...(progress.externalPending.length > 0
|
|
1263
|
+
? progress.externalPending
|
|
1264
|
+
: ["无"]),
|
|
1265
|
+
"",
|
|
1266
|
+
"## 最近落盘报告",
|
|
1267
|
+
report || "无",
|
|
1268
|
+
];
|
|
1269
|
+
writeFileSync(file, `${lines.join("\n")}\n`, "utf-8");
|
|
1270
|
+
appendManagedEvent(state, {
|
|
1271
|
+
eventType: "executor.handoff_created",
|
|
1272
|
+
actor: "managed-runner",
|
|
1273
|
+
role: "runner",
|
|
1274
|
+
summary: mt(contract, `已生成第 ${state.executorInvocations} 次研发执行压缩交接包`, `Created condensed handoff for executor invocation ${state.executorInvocations}`),
|
|
1275
|
+
evidencePaths: [file, machineFile],
|
|
1276
|
+
});
|
|
1277
|
+
return file;
|
|
1278
|
+
}
|
|
1279
|
+
function hasUndeliveredHumanGuidance(contract, state) {
|
|
1280
|
+
return undeliveredHumanGuidance(contract, state).length > 0;
|
|
1281
|
+
}
|
|
1282
|
+
function undeliveredHumanGuidance(contract, state) {
|
|
1283
|
+
const delivered = deliveredHumanGuidanceIds(state);
|
|
1284
|
+
return readManagedHumanMessages(contract)
|
|
1285
|
+
.filter((message) => !delivered.has(message.messageId));
|
|
1286
|
+
}
|
|
1287
|
+
function deliveredHumanGuidanceIds(state) {
|
|
1288
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
1289
|
+
if (!existsSync(runDir))
|
|
1290
|
+
return new Set();
|
|
1291
|
+
const delivered = new Set();
|
|
1292
|
+
for (const file of readdirSync(runDir)) {
|
|
1293
|
+
if (!/^executor-guidance-delivery-\d+\.json$/.test(file))
|
|
1294
|
+
continue;
|
|
1295
|
+
try {
|
|
1296
|
+
const receipt = JSON.parse(readFileSync(path.join(runDir, file), "utf-8"));
|
|
1297
|
+
if (!Array.isArray(receipt.messageIds))
|
|
1298
|
+
continue;
|
|
1299
|
+
for (const messageId of receipt.messageIds) {
|
|
1300
|
+
if (typeof messageId === "string")
|
|
1301
|
+
delivered.add(messageId);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
catch {
|
|
1305
|
+
// A malformed receipt is not proof of delivery and is therefore retried.
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return delivered;
|
|
1309
|
+
}
|
|
1310
|
+
function recordHumanGuidanceHandoffDelivery(contract, state, handoffFile, sessionId) {
|
|
1311
|
+
const machineFile = handoffFile.replace(/\.md$/, ".json");
|
|
1312
|
+
const handoff = JSON.parse(readFileSync(machineFile, "utf-8"));
|
|
1313
|
+
const messageIds = handoff.humanGuidance?.deliveryMessageIds;
|
|
1314
|
+
if (!Array.isArray(messageIds) || messageIds.length === 0)
|
|
1315
|
+
return;
|
|
1316
|
+
const validIds = messageIds.filter((messageId) => typeof messageId === "string");
|
|
1317
|
+
if (validIds.length === 0)
|
|
1318
|
+
return;
|
|
1319
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
1320
|
+
const receiptFile = path.join(runDir, `executor-guidance-delivery-${state.executorInvocations}.json`);
|
|
1321
|
+
if (existsSync(receiptFile))
|
|
1322
|
+
return;
|
|
1323
|
+
writeJsonAtomic(receiptFile, {
|
|
1324
|
+
protocolVersion: "superflow.guidance-receipt.v1",
|
|
1325
|
+
taskId: state.taskId,
|
|
1326
|
+
runId: state.runId,
|
|
1327
|
+
invocation: state.executorInvocations,
|
|
1328
|
+
handoffFile,
|
|
1329
|
+
sessionId,
|
|
1330
|
+
messageIds: validIds,
|
|
1331
|
+
deliveredAt: new Date().toISOString(),
|
|
1332
|
+
});
|
|
1333
|
+
appendManagedEvent(state, {
|
|
1334
|
+
eventType: "executor.guidance_handoff_delivered",
|
|
1335
|
+
actor: contract.executorAgent,
|
|
1336
|
+
role: "executor",
|
|
1337
|
+
summary: mt(contract, `Executor 已确认接收 ${validIds.length} 条运行中补充指导`, `Executor acknowledged ${validIds.length} in-flight guidance message(s)`),
|
|
1338
|
+
evidencePaths: [machineFile, receiptFile],
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
function handoffFindingLines(findings, language) {
|
|
1342
|
+
if (findings.length === 0)
|
|
1343
|
+
return [language === "en" ? "none" : "无"];
|
|
1344
|
+
return findings.map((finding) => language === "en"
|
|
1345
|
+
? `- ${finding.id} ${finding.target}: ${finding.requiredFix}; checks: ${finding.acceptanceChecks.join("; ")}`
|
|
1346
|
+
: `- ${finding.id} ${finding.target}:${finding.requiredFix};验收:${finding.acceptanceChecks.join(";")}`);
|
|
1347
|
+
}
|
|
1348
|
+
function executorCompletionGap(contract, result) {
|
|
1349
|
+
if (contract.source !== "sdd")
|
|
1350
|
+
return null;
|
|
1351
|
+
const decision = evaluateCompletion(contract, result);
|
|
1352
|
+
if (decision.localGaps.length > 0) {
|
|
1353
|
+
return mt(contract, `本地交付裁决未通过:${decision.localGaps.join(";")}`, `Local completion policy failed: ${decision.localGaps.join("; ")}`);
|
|
1354
|
+
}
|
|
1355
|
+
const tasksFile = findOpenSpecTasksFile(contract);
|
|
1356
|
+
if (tasksFile) {
|
|
1357
|
+
const report = path.join(path.dirname(tasksFile), "test-report.md");
|
|
1358
|
+
if (!existsSync(report)) {
|
|
1359
|
+
return mt(contract, "SDD 任务缺少 test-report.md,研发 Agent 必须填写真实测试和运行证据", "The SDD task is missing test-report.md; the executor must record real test and runtime evidence");
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
function readOpenSpecTaskProgress(contract, result) {
|
|
1365
|
+
const tasks = readCompletionTasks(contract, result);
|
|
1366
|
+
if (tasks.length === 0) {
|
|
1367
|
+
return { total: 0, completed: 0, localPending: [], externalPending: [] };
|
|
1368
|
+
}
|
|
1369
|
+
const pending = tasks.filter((task) => !task.completed);
|
|
1370
|
+
return {
|
|
1371
|
+
total: tasks.length,
|
|
1372
|
+
completed: tasks.length - pending.length,
|
|
1373
|
+
localPending: pending
|
|
1374
|
+
.filter((task) => task.category === "local_required")
|
|
1375
|
+
.map((task) => `${task.taskId}: ${task.text}`),
|
|
1376
|
+
externalPending: pending
|
|
1377
|
+
.filter((task) => task.category !== "local_required")
|
|
1378
|
+
.map((task) => `${task.taskId}: ${task.text}`),
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
export function isExternalPendingTask(line) {
|
|
1382
|
+
return /\[(?:environment_required|release_required)\]/i.test(line);
|
|
1383
|
+
}
|
|
1384
|
+
function findOpenSpecTasksFile(contract) {
|
|
1385
|
+
if (!contract.taskPrompt?.originalPath)
|
|
1386
|
+
return null;
|
|
1387
|
+
let current = path.dirname(path.resolve(contract.taskPrompt.originalPath));
|
|
1388
|
+
const projectRoot = path.resolve(contract.projectRoot);
|
|
1389
|
+
while (current.startsWith(projectRoot)) {
|
|
1390
|
+
const file = path.join(current, "tasks.md");
|
|
1391
|
+
if (existsSync(file))
|
|
1392
|
+
return file;
|
|
1393
|
+
const parent = path.dirname(current);
|
|
1394
|
+
if (parent === current)
|
|
1395
|
+
break;
|
|
1396
|
+
current = parent;
|
|
1397
|
+
}
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
function safeGitOutput(projectRoot, args) {
|
|
1401
|
+
try {
|
|
1402
|
+
return execFileSync("git", args, {
|
|
1403
|
+
cwd: projectRoot,
|
|
1404
|
+
encoding: "utf-8",
|
|
1405
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1406
|
+
}).trim();
|
|
1407
|
+
}
|
|
1408
|
+
catch {
|
|
1409
|
+
return "";
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function invocation(contract, state, prompt, promptPath, schemaPath, sessionId, deliveryProtocolRecovery, onSession, onProgress) {
|
|
1413
|
+
const freshMaxTurnRecovery = !sessionId && consecutiveMaxTurnFailures(state) > 0;
|
|
1414
|
+
const policyPath = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), "managed-executor-policy.md");
|
|
1415
|
+
const contextManifest = ensureManagedContextManifest(contract);
|
|
1416
|
+
const ruleSelection = frozenRuleSelection(contract, contextManifest);
|
|
1417
|
+
const preflightScript = path.join(ASSETS_DIR, "scripts", "superflow-managed-executor-preflight.mjs");
|
|
1418
|
+
const ownerTemplate = path.join(ASSETS_DIR, "scripts", "superflow-managed-owner-verification.sh");
|
|
1419
|
+
const preflightCommand = [
|
|
1420
|
+
process.execPath,
|
|
1421
|
+
preflightScript,
|
|
1422
|
+
contract.projectRoot,
|
|
1423
|
+
state.taskId,
|
|
1424
|
+
].join(" ");
|
|
1425
|
+
writeFileSync(policyPath, buildExecutorPolicy({
|
|
1426
|
+
ruleSelection,
|
|
1427
|
+
toolchainFacts: managedToolchainFacts(),
|
|
1428
|
+
ownerTemplate,
|
|
1429
|
+
preflightCommand,
|
|
1430
|
+
compatibilityContinuation: Boolean(sessionId),
|
|
1431
|
+
freshRecovery: freshMaxTurnRecovery,
|
|
1432
|
+
deliveryProtocolRecovery,
|
|
1433
|
+
}), "utf-8");
|
|
1434
|
+
return {
|
|
1435
|
+
taskId: state.taskId,
|
|
1436
|
+
runId: state.runId,
|
|
1437
|
+
role: "executor",
|
|
1438
|
+
language: contract.language,
|
|
1439
|
+
agent: contract.executorAgent,
|
|
1440
|
+
projectRoot: state.projectRoot,
|
|
1441
|
+
writableRoots: contract.relatedProjectRoots,
|
|
1442
|
+
prompt,
|
|
1443
|
+
promptPath,
|
|
1444
|
+
schemaPath,
|
|
1445
|
+
systemPromptPath: policyPath,
|
|
1446
|
+
sessionId,
|
|
1447
|
+
timeout: {
|
|
1448
|
+
warningMs: contract.budgets.noProgressWarningMinutes * 60_000,
|
|
1449
|
+
stalledMs: contract.budgets.stalledTimeoutMinutes * 60_000,
|
|
1450
|
+
hardMs: contract.budgets.maxSingleInvocationHours * 3_600_000,
|
|
1451
|
+
},
|
|
1452
|
+
onSession,
|
|
1453
|
+
onProgress,
|
|
1454
|
+
onTelemetry: (telemetry) => {
|
|
1455
|
+
if (!acceptsExecutorCallback(state))
|
|
1456
|
+
return;
|
|
1457
|
+
state.runtimeTelemetry = telemetry;
|
|
1458
|
+
saveManagedRun(state);
|
|
1459
|
+
},
|
|
1460
|
+
onProcess: (pid) => {
|
|
1461
|
+
if (!acceptsExecutorCallback(state))
|
|
1462
|
+
return;
|
|
1463
|
+
state.runningAgentPid = pid;
|
|
1464
|
+
saveManagedRun(state);
|
|
1465
|
+
},
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
function managedToolchainFacts() {
|
|
1469
|
+
const facts = [
|
|
1470
|
+
[
|
|
1471
|
+
"java",
|
|
1472
|
+
process.env.JAVA_HOME
|
|
1473
|
+
? path.join(process.env.JAVA_HOME, "bin", "java")
|
|
1474
|
+
: executablePath("java"),
|
|
1475
|
+
],
|
|
1476
|
+
["maven", executablePath("mvn")],
|
|
1477
|
+
["node", executablePath("node")],
|
|
1478
|
+
["npm", executablePath("npm")],
|
|
1479
|
+
]
|
|
1480
|
+
.filter((item) => Boolean(item[1]))
|
|
1481
|
+
.map(([name, executable]) => `- ${name}: ${executable}`);
|
|
1482
|
+
if (facts.length === 0)
|
|
1483
|
+
return [];
|
|
1484
|
+
return [
|
|
1485
|
+
"Superflow preflight found these executable paths. Use them directly instead of spending turns searching for the toolchain:",
|
|
1486
|
+
...facts,
|
|
1487
|
+
];
|
|
1488
|
+
}
|
|
1489
|
+
function frozenRuleSelection(contract, manifest) {
|
|
1490
|
+
const selected = selectManagedRuleFiles(contract);
|
|
1491
|
+
const frozenRules = new Set(manifest.entries
|
|
1492
|
+
.filter((entry) => entry.role === "rule")
|
|
1493
|
+
.map((entry) => path.resolve(entry.path)));
|
|
1494
|
+
return {
|
|
1495
|
+
scenarios: selected.scenarios,
|
|
1496
|
+
files: selected.files.filter((file) => frozenRules.has(path.resolve(file))),
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function executablePath(command) {
|
|
1500
|
+
try {
|
|
1501
|
+
return execFileSync("which", [command], {
|
|
1502
|
+
encoding: "utf-8",
|
|
1503
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1504
|
+
}).trim();
|
|
1505
|
+
}
|
|
1506
|
+
catch {
|
|
1507
|
+
return "";
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function executorOutputTokenUnits(state) {
|
|
1511
|
+
return state.executorUsage?.outputTokens ?? null;
|
|
1512
|
+
}
|
|
1513
|
+
function mergeAgentUsage(current, next) {
|
|
1514
|
+
if (!next)
|
|
1515
|
+
return current;
|
|
1516
|
+
return {
|
|
1517
|
+
inputTokens: sumNullable(current?.inputTokens, next.inputTokens),
|
|
1518
|
+
outputTokens: sumNullable(current?.outputTokens, next.outputTokens),
|
|
1519
|
+
cacheReadTokens: sumNullable(current?.cacheReadTokens, next.cacheReadTokens),
|
|
1520
|
+
cacheWriteTokens: sumNullable(current?.cacheWriteTokens, next.cacheWriteTokens),
|
|
1521
|
+
costUsd: sumNullable(current?.costUsd, next.costUsd),
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
function sumNullable(left, right) {
|
|
1525
|
+
if (left == null)
|
|
1526
|
+
return right ?? null;
|
|
1527
|
+
if (right == null)
|
|
1528
|
+
return null;
|
|
1529
|
+
return left + right;
|
|
1530
|
+
}
|
|
1531
|
+
async function invokeWithFailureEvidence(state, invoker, agentInvocation, prefix) {
|
|
1532
|
+
try {
|
|
1533
|
+
return await invoker.invoke(agentInvocation);
|
|
1534
|
+
}
|
|
1535
|
+
catch (error) {
|
|
1536
|
+
const raw = error;
|
|
1537
|
+
const stdout = typeof raw.stdout === "string" ? raw.stdout : "";
|
|
1538
|
+
const stderr = typeof raw.stderr === "string" ? raw.stderr : "";
|
|
1539
|
+
const logs = stdout || stderr
|
|
1540
|
+
? writeInvocationLogs(state, prefix, stdout, stderr)
|
|
1541
|
+
: [];
|
|
1542
|
+
appendManagedEvent(state, {
|
|
1543
|
+
eventType: "executor.invocation_failed",
|
|
1544
|
+
actor: agentInvocation.agent,
|
|
1545
|
+
role: "executor",
|
|
1546
|
+
summary: raw.message,
|
|
1547
|
+
evidencePaths: logs,
|
|
1548
|
+
});
|
|
1549
|
+
throw error;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function transition(contract, state, status, currentStep, env) {
|
|
1553
|
+
state = {
|
|
1554
|
+
...state,
|
|
1555
|
+
status,
|
|
1556
|
+
currentStep,
|
|
1557
|
+
blocker: status === "running" ? null : state.blocker,
|
|
1558
|
+
failure: status === "running" ? null : state.failure ?? null,
|
|
1559
|
+
servicePid: process.pid,
|
|
1560
|
+
updatedAt: new Date().toISOString(),
|
|
1561
|
+
};
|
|
1562
|
+
contract.status = status;
|
|
1563
|
+
saveManagedRun(state);
|
|
1564
|
+
saveManagedTask(contract);
|
|
1565
|
+
updateRegistry(contract, state, env);
|
|
1566
|
+
return state;
|
|
1567
|
+
}
|
|
1568
|
+
function pauseAtSafeBoundary(contract, state, env) {
|
|
1569
|
+
const signal = readManagedControlSignal(contract);
|
|
1570
|
+
if (!signal?.pauseRequested)
|
|
1571
|
+
return state;
|
|
1572
|
+
clearManagedControlSignal(contract);
|
|
1573
|
+
state.blocker = signal.reason;
|
|
1574
|
+
state.activeSince = null;
|
|
1575
|
+
state = transition(contract, state, "paused", "paused_by_user", env);
|
|
1576
|
+
appendManagedEvent(state, {
|
|
1577
|
+
eventType: "human.pause_applied",
|
|
1578
|
+
actor: signal.actor,
|
|
1579
|
+
role: "system",
|
|
1580
|
+
summary: mt(contract, `已在安全边界暂停:${signal.reason}`, `Paused at a safe boundary: ${signal.reason}`),
|
|
1581
|
+
});
|
|
1582
|
+
return state;
|
|
1583
|
+
}
|
|
1584
|
+
function finishDeliveryReady(contract, state, summary, env) {
|
|
1585
|
+
const executorResult = state.lastExecutorResult
|
|
1586
|
+
? JSON.parse(readFileSync(state.lastExecutorResult, "utf-8"))
|
|
1587
|
+
: null;
|
|
1588
|
+
const decision = evaluateCompletion(contract, completionResultWithHistory(state, executorResult));
|
|
1589
|
+
if (!decision.localReady) {
|
|
1590
|
+
return finishBlocked(contract, state, decision.localGaps.join(";"), env);
|
|
1591
|
+
}
|
|
1592
|
+
state.deliveryProgress = decision.progress;
|
|
1593
|
+
const status = !decision.environmentReady
|
|
1594
|
+
? "environment_validation_blocked"
|
|
1595
|
+
: !decision.releaseReady
|
|
1596
|
+
? "local_delivery_ready"
|
|
1597
|
+
: "release_ready";
|
|
1598
|
+
state.workspaceFingerprint = computeWorkspaceFingerprintForRoots([
|
|
1599
|
+
contract.projectRoot,
|
|
1600
|
+
...contract.relatedProjectRoots,
|
|
1601
|
+
]);
|
|
1602
|
+
runManagedDeliveryCheck(state, true);
|
|
1603
|
+
state = transition(contract, state, status, status, env);
|
|
1604
|
+
state.completedAt = new Date().toISOString();
|
|
1605
|
+
saveManagedRun(state);
|
|
1606
|
+
appendTaskReport(state, mt(contract, `\n## 最终结论\n\n${summary}\n\n状态:${status}。\n`, `\n## Final conclusion\n\n${summary}\n\nStatus: ${status}.\n`));
|
|
1607
|
+
resolveTaskReportBlockers(state);
|
|
1608
|
+
updateTaskReportStatus(state, status);
|
|
1609
|
+
appendManagedEvent(state, {
|
|
1610
|
+
eventType: "run.delivery_ready",
|
|
1611
|
+
actor: "managed-runner",
|
|
1612
|
+
role: "runner",
|
|
1613
|
+
summary: mt(contract, `任务已通过本地检查,进入 ${status}`, `Task passed local review and entered ${status}`),
|
|
1614
|
+
});
|
|
1615
|
+
if (notifyManagedTask({
|
|
1616
|
+
taskId: state.taskId,
|
|
1617
|
+
type: "delivery_ready",
|
|
1618
|
+
title: mt(contract, "Superflow 任务已完成", "Superflow task completed"),
|
|
1619
|
+
message: mt(contract, `${state.taskId} 已通过本地检查并进入 ${status};Git、环境和发布动作仍需用户批准`, `${state.taskId} passed local review and entered ${status}; Git, environment, and release actions still require user approval`),
|
|
1620
|
+
}, env)) {
|
|
1621
|
+
appendManagedEvent(state, {
|
|
1622
|
+
eventType: "host.delivery_ready_notified",
|
|
1623
|
+
actor: "managed-runner",
|
|
1624
|
+
role: "runner",
|
|
1625
|
+
summary: mt(contract, "已发送可交付通知", "Recorded delivery-ready notification"),
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
return state;
|
|
1629
|
+
}
|
|
1630
|
+
function finishBlocked(contract, state, blocker, env) {
|
|
1631
|
+
state = transition(contract, state, "waiting_for_human", "waiting_for_human", env);
|
|
1632
|
+
state.blocker = blocker;
|
|
1633
|
+
state.completedAt = null;
|
|
1634
|
+
saveManagedRun(state);
|
|
1635
|
+
appendTaskReport(state, mt(contract, `\n## 当前阻塞\n\n${blocker}\n`, `\n## Current blocker\n\n${blocker}\n`));
|
|
1636
|
+
updateTaskReportStatus(state, mt(contract, "等待人工处理", "waiting for human action"));
|
|
1637
|
+
appendManagedEvent(state, {
|
|
1638
|
+
eventType: "human_input.required",
|
|
1639
|
+
actor: "managed-runner",
|
|
1640
|
+
role: "runner",
|
|
1641
|
+
summary: blocker,
|
|
1642
|
+
});
|
|
1643
|
+
if (notifyManagedTask({
|
|
1644
|
+
taskId: state.taskId,
|
|
1645
|
+
type: "human_required",
|
|
1646
|
+
title: mt(contract, "Superflow 任务需要处理", "Superflow task needs attention"),
|
|
1647
|
+
message: `${state.taskId}: ${blocker}`,
|
|
1648
|
+
}, env)) {
|
|
1649
|
+
appendManagedEvent(state, {
|
|
1650
|
+
eventType: "host.attention_notified",
|
|
1651
|
+
actor: "managed-runner",
|
|
1652
|
+
role: "runner",
|
|
1653
|
+
summary: mt(contract, "已发送人工处理通知", "Recorded attention notification"),
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
return state;
|
|
1657
|
+
}
|
|
1658
|
+
function finishReviewExhausted(contract, state, env) {
|
|
1659
|
+
state = transition(contract, state, "review_exhausted", "review_exhausted", env);
|
|
1660
|
+
state.blocker = mt(contract, "正式检查达到五轮上限", "Formal review reached the five-round limit");
|
|
1661
|
+
saveManagedRun(state);
|
|
1662
|
+
updateTaskReportStatus(state, mt(contract, "检查轮次耗尽", "review rounds exhausted"));
|
|
1663
|
+
appendManagedEvent(state, {
|
|
1664
|
+
eventType: "budget.exhausted",
|
|
1665
|
+
actor: "managed-runner",
|
|
1666
|
+
role: "runner",
|
|
1667
|
+
summary: state.blocker,
|
|
1668
|
+
});
|
|
1669
|
+
notifyManagedTask({
|
|
1670
|
+
taskId: state.taskId,
|
|
1671
|
+
type: "budget_exhausted",
|
|
1672
|
+
title: mt(contract, "Superflow 检查轮次已耗尽", "Superflow review rounds exhausted"),
|
|
1673
|
+
message: `${state.taskId}: ${state.blocker}`,
|
|
1674
|
+
}, env);
|
|
1675
|
+
return state;
|
|
1676
|
+
}
|
|
1677
|
+
function finishBudgetExhausted(contract, state, reason, env) {
|
|
1678
|
+
state = transition(contract, state, "budget_exhausted", "budget_exhausted", env);
|
|
1679
|
+
state.blocker = reason;
|
|
1680
|
+
saveManagedRun(state);
|
|
1681
|
+
updateTaskReportStatus(state, mt(contract, "调用预算耗尽", "invocation budget exhausted"));
|
|
1682
|
+
appendManagedEvent(state, {
|
|
1683
|
+
eventType: "budget.exhausted",
|
|
1684
|
+
actor: "managed-runner",
|
|
1685
|
+
role: "runner",
|
|
1686
|
+
summary: reason,
|
|
1687
|
+
});
|
|
1688
|
+
notifyManagedTask({
|
|
1689
|
+
taskId: state.taskId,
|
|
1690
|
+
type: "budget_exhausted",
|
|
1691
|
+
title: mt(contract, "Superflow 调用预算已耗尽", "Superflow invocation budget exhausted"),
|
|
1692
|
+
message: `${state.taskId}: ${reason}`,
|
|
1693
|
+
}, env);
|
|
1694
|
+
return state;
|
|
1695
|
+
}
|
|
1696
|
+
function enforceBudgets(contract, state, env) {
|
|
1697
|
+
if (!hasUnlimitedAgentBudget(contract)) {
|
|
1698
|
+
const usage = state.executorUsage;
|
|
1699
|
+
const outputTokens = executorOutputTokenUnits(state);
|
|
1700
|
+
if (contract.budgets.maxExecutorTokenUnits !== undefined &&
|
|
1701
|
+
outputTokens !== null &&
|
|
1702
|
+
outputTokens >= contract.budgets.maxExecutorTokenUnits) {
|
|
1703
|
+
return finishBudgetExhausted(contract, state, mt(contract, `研发 Agent 输出 Token 熔断:累计 ${outputTokens},上限 ${contract.budgets.maxExecutorTokenUnits}`, `Executor output-token circuit opened at ${outputTokens}; limit ${contract.budgets.maxExecutorTokenUnits}`), env);
|
|
1704
|
+
}
|
|
1705
|
+
if (contract.budgets.maxExecutorCostUsd !== undefined &&
|
|
1706
|
+
usage?.costUsd !== null &&
|
|
1707
|
+
usage?.costUsd !== undefined &&
|
|
1708
|
+
usage.costUsd >= contract.budgets.maxExecutorCostUsd) {
|
|
1709
|
+
return finishBudgetExhausted(contract, state, mt(contract, `研发 Agent 成本熔断:累计 $${usage?.costUsd?.toFixed(2)},上限 $${contract.budgets.maxExecutorCostUsd.toFixed(2)}`, `Executor cost circuit opened at $${usage?.costUsd?.toFixed(2)}; limit $${contract.budgets.maxExecutorCostUsd.toFixed(2)}`), env);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
const activeHours = activeRunMilliseconds(state) / 3_600_000;
|
|
1713
|
+
if (activeHours >= contract.budgets.activeRunWarningHours &&
|
|
1714
|
+
!readManagedEvents(state).some((event) => event.eventType === "deadline.warning")) {
|
|
1715
|
+
appendManagedEvent(state, {
|
|
1716
|
+
eventType: "deadline.warning",
|
|
1717
|
+
actor: "managed-runner",
|
|
1718
|
+
role: "runner",
|
|
1719
|
+
summary: mt(contract, `实际工作时间已达到 ${contract.budgets.activeRunWarningHours} 小时,任务继续运行但已接近兜底上限`, `Active work time reached ${contract.budgets.activeRunWarningHours} hours; the task continues but is approaching the safety limit`),
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
if (activeHours >= contract.budgets.maxActiveRunHours) {
|
|
1723
|
+
state = transition(contract, state, "deadline_exhausted", "deadline_exhausted", env);
|
|
1724
|
+
state.blocker = mt(contract, "实际工作时间达到防失控上限", "Active work time reached the safety limit");
|
|
1725
|
+
saveManagedRun(state);
|
|
1726
|
+
appendManagedEvent(state, {
|
|
1727
|
+
eventType: "deadline.exhausted",
|
|
1728
|
+
actor: "managed-runner",
|
|
1729
|
+
role: "runner",
|
|
1730
|
+
summary: state.blocker,
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
return state;
|
|
1734
|
+
}
|
|
1735
|
+
function hasUnlimitedAgentBudget(contract) {
|
|
1736
|
+
return contract.budgets.unlimitedAgentInvocations === true;
|
|
1737
|
+
}
|
|
1738
|
+
function closeActiveTime(state, started) {
|
|
1739
|
+
state.activeRunMilliseconds += Math.max(0, Date.now() - started);
|
|
1740
|
+
state.activeSince = null;
|
|
1741
|
+
saveManagedRun(state);
|
|
1742
|
+
return state;
|
|
1743
|
+
}
|
|
1744
|
+
function loadRepairFindings(state) {
|
|
1745
|
+
if (!state.lastReviewResult || !existsSync(state.lastReviewResult))
|
|
1746
|
+
return [];
|
|
1747
|
+
const result = JSON.parse(readFileSync(state.lastReviewResult, "utf-8"));
|
|
1748
|
+
return result.findings.filter((finding) => finding.blocking);
|
|
1749
|
+
}
|
|
1750
|
+
function validateExecutorResult(contract, state, result, options = {}) {
|
|
1751
|
+
if (!["ready_for_review", "blocked", "failed"].includes(result.status)) {
|
|
1752
|
+
throw new Error(mt(contract, "执行 Agent 返回了非法状态", "Executor Agent returned an invalid status"));
|
|
1753
|
+
}
|
|
1754
|
+
if (!Array.isArray(result.changedFiles) ||
|
|
1755
|
+
!Array.isArray(result.commands) ||
|
|
1756
|
+
!Array.isArray(result.evidence) ||
|
|
1757
|
+
!Array.isArray(result.releasePrerequisites) ||
|
|
1758
|
+
!Array.isArray(result.blockers)) {
|
|
1759
|
+
throw new Error(mt(contract, "执行 Agent 结果缺少文件、命令、证据、发布前置条件或阻塞列表", "Executor Agent result is missing files, commands, evidence, release prerequisites, or blockers"));
|
|
1760
|
+
}
|
|
1761
|
+
if (!result.summary.trim()) {
|
|
1762
|
+
throw new Error(mt(contract, "执行 Agent 结果缺少摘要", "Executor Agent result is missing a summary"));
|
|
1763
|
+
}
|
|
1764
|
+
if (result.status !== "ready_for_review") {
|
|
1765
|
+
if (result.blockers.length === 0) {
|
|
1766
|
+
throw new Error(mt(contract, "执行 Agent 声明阻塞或失败,但没有提供阻塞原因", "Executor Agent reported blocked or failed without a blocker"));
|
|
1767
|
+
}
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
if (result.blockers.length > 0) {
|
|
1771
|
+
throw new Error(mt(contract, "执行 Agent 声明可检查,但仍存在阻塞项", "Executor Agent reported ready for review while blockers remain"));
|
|
1772
|
+
}
|
|
1773
|
+
const blockingCommands = result.commands.filter(isBlockingCommandFailure);
|
|
1774
|
+
if (blockingCommands.length > 0 &&
|
|
1775
|
+
blockingCommands.every((command) => command.assertion === "negative")) {
|
|
1776
|
+
throw new Error(mt(contract, "负向命令证据不符合检查型断言协议,需要 Host 判断原始失败是否为预期行为", "Negative command evidence violates the inspection-only assertion protocol and requires Host judgment of whether the raw failure was expected"));
|
|
1777
|
+
}
|
|
1778
|
+
if (blockingCommands.length > 0) {
|
|
1779
|
+
throw new Error(mt(contract, "执行 Agent 声明可检查,但命令证据中仍有失败项", "Executor Agent reported ready for review while command evidence still contains failures"));
|
|
1780
|
+
}
|
|
1781
|
+
if (contract.profile === "engineering" || contract.profile === "sdd") {
|
|
1782
|
+
validateDeclaredChangedFiles(contract, state, result, options);
|
|
1783
|
+
validateEngineeringEvidence(result, contract, state);
|
|
1784
|
+
// Risk-based evidence sufficiency is intentionally semantic: after the
|
|
1785
|
+
// external Host has inspected the raw evidence and returned pass, the
|
|
1786
|
+
// Runner must not overrule that decision with command-text heuristics.
|
|
1787
|
+
// Schema, changed files, command outcomes, and engineering categories
|
|
1788
|
+
// remain deterministic gates above.
|
|
1789
|
+
if (!options.hostApproved)
|
|
1790
|
+
validateRiskBasedEvidence(contract, result);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
function normalizeExecutorResult(contract, state, result) {
|
|
1794
|
+
if (!result || typeof result !== "object")
|
|
1795
|
+
return result;
|
|
1796
|
+
result.protocolVersion = "superflow.executor.v2";
|
|
1797
|
+
result.messageType = "executor_delivery";
|
|
1798
|
+
const machineChangedFiles = changedWorkspaceFilesSinceBaseline(contract, state).map(({ root, relative }) => path.resolve(root) === path.resolve(contract.projectRoot)
|
|
1799
|
+
? relative.replaceAll("\\", "/")
|
|
1800
|
+
: path.join(root, relative).replaceAll("\\", "/"));
|
|
1801
|
+
result.changedFiles = uniqueStrings([
|
|
1802
|
+
...(Array.isArray(result.changedFiles) ? result.changedFiles : []),
|
|
1803
|
+
...machineChangedFiles,
|
|
1804
|
+
]);
|
|
1805
|
+
result.blockers = normalizedStrings(result.blockers);
|
|
1806
|
+
result.releasePrerequisites = normalizedStrings(result.releasePrerequisites);
|
|
1807
|
+
result.evidence = normalizedStrings(result.evidence);
|
|
1808
|
+
if (Array.isArray(result.commands)) {
|
|
1809
|
+
const currentCommands = result.commands.map((command) => ({
|
|
1810
|
+
...command,
|
|
1811
|
+
categories: uniqueCategories([
|
|
1812
|
+
...(command.categories ?? []),
|
|
1813
|
+
...verificationCategories(command.command, command.result),
|
|
1814
|
+
]),
|
|
1815
|
+
}));
|
|
1816
|
+
result.commands = uniqueCommands([
|
|
1817
|
+
...historicalEngineeringCommands(state, true).filter((command) => !isBlockingCommandFailure(command) &&
|
|
1818
|
+
isCommandOutcomeConsistent(command)),
|
|
1819
|
+
...currentCommands,
|
|
1820
|
+
]);
|
|
1821
|
+
}
|
|
1822
|
+
return result;
|
|
1823
|
+
}
|
|
1824
|
+
function uniqueStrings(values) {
|
|
1825
|
+
return [...new Set(values.filter(Boolean))];
|
|
1826
|
+
}
|
|
1827
|
+
function normalizedStrings(values) {
|
|
1828
|
+
if (!Array.isArray(values))
|
|
1829
|
+
return [];
|
|
1830
|
+
return uniqueStrings(values
|
|
1831
|
+
.filter((value) => typeof value === "string")
|
|
1832
|
+
.map((value) => value.trim()));
|
|
1833
|
+
}
|
|
1834
|
+
function uniqueCategories(values) {
|
|
1835
|
+
return [...new Set(values)];
|
|
1836
|
+
}
|
|
1837
|
+
function uniqueCommands(commands) {
|
|
1838
|
+
const seen = new Set();
|
|
1839
|
+
return commands.filter((command) => {
|
|
1840
|
+
const key = JSON.stringify([
|
|
1841
|
+
command.command,
|
|
1842
|
+
command.exitCode,
|
|
1843
|
+
command.result,
|
|
1844
|
+
command.assertion ?? "positive",
|
|
1845
|
+
]);
|
|
1846
|
+
if (seen.has(key))
|
|
1847
|
+
return false;
|
|
1848
|
+
seen.add(key);
|
|
1849
|
+
return true;
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
function isBlockingCommandFailure(command) {
|
|
1853
|
+
if (isAcceptedEvidenceCommand(command))
|
|
1854
|
+
return false;
|
|
1855
|
+
if (isExpectedStartupShutdown(command))
|
|
1856
|
+
return false;
|
|
1857
|
+
return true;
|
|
1858
|
+
}
|
|
1859
|
+
function isExpectedStartupShutdown(command) {
|
|
1860
|
+
if (command.exitCode !== 143 || !command.categories?.includes("startup")) {
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1863
|
+
return (/(?:started|undertow.*port|application.*started)/i.test(command.result) &&
|
|
1864
|
+
/(?:sigterm|exit(?:\s*code)?\s*=?\s*143|退出码\s*=?\s*143)/i.test(command.result));
|
|
1865
|
+
}
|
|
1866
|
+
function validateWorkspaceSecrets(contract, state) {
|
|
1867
|
+
const leaked = changedWorkspaceFilesSinceBaseline(contract, state)
|
|
1868
|
+
.map(({ root, relative }) => ({
|
|
1869
|
+
relative,
|
|
1870
|
+
absolute: path.join(root, relative),
|
|
1871
|
+
}))
|
|
1872
|
+
.filter(({ absolute }) => existsSync(absolute))
|
|
1873
|
+
.filter(({ absolute }) => {
|
|
1874
|
+
try {
|
|
1875
|
+
const content = readFileSync(absolute, "utf-8");
|
|
1876
|
+
return containsPlaintextCredential(content);
|
|
1877
|
+
}
|
|
1878
|
+
catch {
|
|
1879
|
+
return false;
|
|
1880
|
+
}
|
|
1881
|
+
})
|
|
1882
|
+
.map(({ relative }) => relative.replaceAll("\\", "/"));
|
|
1883
|
+
if (leaked.length === 0)
|
|
1884
|
+
return;
|
|
1885
|
+
throw new Error(mt(contract, `工作区存在疑似明文凭据:${leaked.slice(0, 10).join("、")}`, `Workspace contains suspected plaintext credentials: ${leaked
|
|
1886
|
+
.slice(0, 10)
|
|
1887
|
+
.join(", ")}`));
|
|
1888
|
+
}
|
|
1889
|
+
export function containsPlaintextCredential(value) {
|
|
1890
|
+
const literal = /\b(?:[a-z0-9]+_)*(?:password|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\b\s*[:=]\s*["']([^"']+)["']/gi;
|
|
1891
|
+
for (const match of value.matchAll(literal)) {
|
|
1892
|
+
if (isPlaintextSecretValue(match[1]))
|
|
1893
|
+
return true;
|
|
1894
|
+
}
|
|
1895
|
+
const mysqlPassword = /(?:\s-p(?:'([^']*)'|"([^"]*)"|([^\s'"-][^\s]*))|--password=(?:'([^']*)'|"([^"]*)"|([^\s]+)))/g;
|
|
1896
|
+
for (const match of value.matchAll(mysqlPassword)) {
|
|
1897
|
+
const secret = match.slice(1).find((item) => item !== undefined) ?? "";
|
|
1898
|
+
if (isPlaintextSecretValue(secret)) {
|
|
1899
|
+
return true;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return /authorization\s*[:=]\s*bearer\s+(?!<redacted>)[^\s"']+/i.test(value);
|
|
1903
|
+
}
|
|
1904
|
+
function isPlaintextSecretValue(value) {
|
|
1905
|
+
const normalized = value.trim().toLowerCase();
|
|
1906
|
+
return (normalized.length >= 6 &&
|
|
1907
|
+
!normalized.includes("<redacted>") &&
|
|
1908
|
+
!isDocumentationSecretPlaceholder(normalized) &&
|
|
1909
|
+
!normalized.startsWith("$") &&
|
|
1910
|
+
!isNonceDerivedTemporarySecret(normalized));
|
|
1911
|
+
}
|
|
1912
|
+
function isDocumentationSecretPlaceholder(value) {
|
|
1913
|
+
return /^<(?:pass(?:word)?|secret|api[_-]?key|access[_-]?token|auth[_-]?token)>$/i.test(value);
|
|
1914
|
+
}
|
|
1915
|
+
function isNonceDerivedTemporarySecret(value) {
|
|
1916
|
+
return /^[a-z0-9_-]*\$(?:\{[a-z0-9_]*nonce\}|[a-z0-9_]*nonce)$/.test(value);
|
|
1917
|
+
}
|
|
1918
|
+
function validateDeclaredChangedFiles(contract, state, result, options = {}) {
|
|
1919
|
+
// Host approval judges the Executor's delivery at the Host-review snapshot.
|
|
1920
|
+
// The Host may create later acceptance reports while independently verifying
|
|
1921
|
+
// the delivery; those reports are not omitted Executor changes. Normal
|
|
1922
|
+
// Executor intake still compares the live workspace against its baseline.
|
|
1923
|
+
const reviewedSnapshot = options.hostApproved && state.lastReviewedWorkspaceFiles
|
|
1924
|
+
? state.lastReviewedWorkspaceFiles
|
|
1925
|
+
: undefined;
|
|
1926
|
+
const changed = changedWorkspaceFilesSinceBaseline(contract, state, reviewedSnapshot).map(({ key }) => key);
|
|
1927
|
+
const declared = result.changedFiles.map((file) => file.replaceAll("\\", "/"));
|
|
1928
|
+
const missing = changed.filter((key) => {
|
|
1929
|
+
const [root, relative] = key.split("::");
|
|
1930
|
+
const absolute = path.join(root, relative).replaceAll("\\", "/");
|
|
1931
|
+
return (!declared.includes(relative.replaceAll("\\", "/")) &&
|
|
1932
|
+
!declared.includes(absolute));
|
|
1933
|
+
});
|
|
1934
|
+
if (missing.length > 0) {
|
|
1935
|
+
throw new Error(mt(contract, `执行结果漏报真实工作区变更:${missing.join("、")}`, `Executor result omitted real workspace changes: ${missing.join(", ")}`));
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
function changedWorkspaceFilesSinceBaseline(contract, state, snapshot) {
|
|
1939
|
+
const current = snapshot ??
|
|
1940
|
+
snapshotChangedWorkspaceFiles([
|
|
1941
|
+
contract.projectRoot,
|
|
1942
|
+
...contract.relatedProjectRoots,
|
|
1943
|
+
]);
|
|
1944
|
+
const baseline = state.baselineWorkspaceFiles ?? {};
|
|
1945
|
+
return Object.entries(current)
|
|
1946
|
+
.filter(([key, hash]) => baseline[key] !== hash)
|
|
1947
|
+
.map(([key]) => {
|
|
1948
|
+
const separator = key.indexOf("::");
|
|
1949
|
+
return {
|
|
1950
|
+
key,
|
|
1951
|
+
root: key.slice(0, separator),
|
|
1952
|
+
relative: key.slice(separator + 2),
|
|
1953
|
+
};
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
function validateEngineeringEvidence(result, contract, state) {
|
|
1957
|
+
const commands = result.commands;
|
|
1958
|
+
const allCommands = [...historicalEngineeringCommands(state), ...commands];
|
|
1959
|
+
const successful = allCommands.filter((command) => command.exitCode === 0 || isExpectedStartupShutdown(command));
|
|
1960
|
+
const categories = new Set(successful.flatMap((command) => command.categories ??
|
|
1961
|
+
verificationCategories(command.command, command.result)));
|
|
1962
|
+
const requiredCategories = requiredVerificationCategories(contract.projectRoot);
|
|
1963
|
+
if (categories.size < requiredCategories) {
|
|
1964
|
+
throw new Error(mt(contract, "工程任务至少需要两类成功验证证据(构建、测试、启动或真实调用),只编译或只跑单测不能交付", "Engineering tasks require at least two successful evidence categories (build, tests, startup, or real calls); compilation-only or unit-test-only evidence cannot be delivered"));
|
|
1965
|
+
}
|
|
1966
|
+
if (isSpringBootProject(contract.projectRoot)) {
|
|
1967
|
+
const missing = [
|
|
1968
|
+
!categories.has("startup") ? "startup" : null,
|
|
1969
|
+
!categories.has("invocation") ? "invocation" : null,
|
|
1970
|
+
].filter((value) => value !== null);
|
|
1971
|
+
if (missing.length > 0) {
|
|
1972
|
+
const labels = missing.map((category) => category === "startup"
|
|
1973
|
+
? mt(contract, "启动应用", "application startup")
|
|
1974
|
+
: mt(contract, "真实 HTTP 调用", "real HTTP invocation"));
|
|
1975
|
+
const message = mt(contract, `Spring Boot 工程交付前必须完成启动应用和真实 HTTP 调用,当前缺少:${labels.join("、")}`, `Spring Boot delivery requires application startup and a real HTTP invocation; missing: ${labels.join(", ")}`);
|
|
1976
|
+
if (missing.length === 1 &&
|
|
1977
|
+
missing[0] === "invocation" &&
|
|
1978
|
+
categories.has("startup") &&
|
|
1979
|
+
categories.has("runtime") &&
|
|
1980
|
+
result.evidence.length > 0) {
|
|
1981
|
+
throw new VerificationMetadataAmbiguityError(message);
|
|
1982
|
+
}
|
|
1983
|
+
throw new Error(message);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
class VerificationMetadataAmbiguityError extends Error {
|
|
1988
|
+
}
|
|
1989
|
+
function historicalEngineeringCommands(state, includeInvalid = false) {
|
|
1990
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
1991
|
+
if (!existsSync(runDir))
|
|
1992
|
+
return [];
|
|
1993
|
+
const resultPattern = includeInvalid
|
|
1994
|
+
? /^executor-result-\d+(?:-invalid)?\.json$/
|
|
1995
|
+
: /^executor-result-\d+\.json$/;
|
|
1996
|
+
return readdirSync(runDir)
|
|
1997
|
+
.filter((name) => resultPattern.test(name))
|
|
1998
|
+
.flatMap((name) => {
|
|
1999
|
+
try {
|
|
2000
|
+
const result = JSON.parse(readFileSync(path.join(runDir, name), "utf-8"));
|
|
2001
|
+
return Array.isArray(result.commands) ? result.commands : [];
|
|
2002
|
+
}
|
|
2003
|
+
catch {
|
|
2004
|
+
return [];
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
function completionResultWithHistory(state, current) {
|
|
2009
|
+
if (!current)
|
|
2010
|
+
return null;
|
|
2011
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
2012
|
+
const history = existsSync(runDir)
|
|
2013
|
+
? readdirSync(runDir)
|
|
2014
|
+
.filter((name) => /^executor-result-\d+(?:-invalid)?\.json$/.test(name))
|
|
2015
|
+
.flatMap((name) => {
|
|
2016
|
+
try {
|
|
2017
|
+
return [
|
|
2018
|
+
JSON.parse(readFileSync(path.join(runDir, name), "utf-8")),
|
|
2019
|
+
];
|
|
2020
|
+
}
|
|
2021
|
+
catch {
|
|
2022
|
+
return [];
|
|
2023
|
+
}
|
|
2024
|
+
})
|
|
2025
|
+
: [];
|
|
2026
|
+
const results = [...history, current];
|
|
2027
|
+
const commands = uniqueCommands(results
|
|
2028
|
+
.flatMap((result) => result.commands)
|
|
2029
|
+
.filter((command) => !isBlockingCommandFailure(command) &&
|
|
2030
|
+
isCommandOutcomeConsistent(command)));
|
|
2031
|
+
return {
|
|
2032
|
+
...current,
|
|
2033
|
+
changedFiles: [
|
|
2034
|
+
...new Set(results.flatMap((result) => result.changedFiles)),
|
|
2035
|
+
],
|
|
2036
|
+
commands,
|
|
2037
|
+
evidence: [...new Set(results.flatMap((result) => result.evidence))],
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
function requiredVerificationCategories(projectRoot) {
|
|
2041
|
+
const packageFile = path.join(projectRoot, "package.json");
|
|
2042
|
+
if (!existsSync(packageFile))
|
|
2043
|
+
return 2;
|
|
2044
|
+
try {
|
|
2045
|
+
const packageJson = JSON.parse(readFileSync(packageFile, "utf-8"));
|
|
2046
|
+
const scripts = packageJson.scripts ?? {};
|
|
2047
|
+
const hasTest = typeof scripts.test === "string";
|
|
2048
|
+
const hasRunnableTarget = ["build", "start", "dev", "serve"].some((name) => typeof scripts[name] === "string");
|
|
2049
|
+
return hasTest && !hasRunnableTarget ? 1 : 2;
|
|
2050
|
+
}
|
|
2051
|
+
catch {
|
|
2052
|
+
return 2;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function isSpringBootProject(projectRoot) {
|
|
2056
|
+
const pom = path.join(projectRoot, "pom.xml");
|
|
2057
|
+
return existsSync(pom) && /spring-boot/i.test(readFileSync(pom, "utf-8"));
|
|
2058
|
+
}
|
|
2059
|
+
function queueExecutorSelfRepair(contract, state, message, evidencePath) {
|
|
2060
|
+
const rejectionKey = executorRejectionKey(message);
|
|
2061
|
+
const rejectionCounts = state.executorRejectionCounts ?? {};
|
|
2062
|
+
const repeated = (rejectionCounts[rejectionKey] ?? 0) >= 1;
|
|
2063
|
+
const repairCount = state.executorSelfRepairCount ?? 0;
|
|
2064
|
+
const permissionBlocked = (state.runtimeTelemetry?.permissionDenials ?? 0) >= 3;
|
|
2065
|
+
const semanticHostJudgment = /疑似明文凭据|suspected plaintext credentials/i.test(message);
|
|
2066
|
+
const structuredMetadataFailure = /JSON|Expected ',' or '}'|structured (?:output|metadata)|结构化.{0,12}(?:格式|解析)/i.test(message);
|
|
2067
|
+
const protocolEvidenceFailure = /负向命令证据不符合检查型断言协议|Negative command evidence violates the inspection-only assertion protocol/i.test(message);
|
|
2068
|
+
state.executorRejectionCounts = {
|
|
2069
|
+
...rejectionCounts,
|
|
2070
|
+
[rejectionKey]: (rejectionCounts[rejectionKey] ?? 0) + 1,
|
|
2071
|
+
};
|
|
2072
|
+
if (semanticHostJudgment ||
|
|
2073
|
+
structuredMetadataFailure ||
|
|
2074
|
+
protocolEvidenceFailure ||
|
|
2075
|
+
permissionBlocked ||
|
|
2076
|
+
repeated ||
|
|
2077
|
+
repairCount >= 2) {
|
|
2078
|
+
const stopReason = semanticHostJudgment
|
|
2079
|
+
? mt(contract, "(需要语义判断)", " because semantic judgment is required")
|
|
2080
|
+
: structuredMetadataFailure
|
|
2081
|
+
? mt(contract, "(结构化元数据不得触发完整研发重跑)", " because structured metadata must not trigger a full engineering rerun")
|
|
2082
|
+
: protocolEvidenceFailure
|
|
2083
|
+
? mt(contract, "(预期失败证据需要 Host 语义判断,不得触发完整研发重跑)", " because expected-failure evidence requires Host semantic judgment and must not trigger a full engineering rerun")
|
|
2084
|
+
: repeated
|
|
2085
|
+
? mt(contract, "(同类机械退回已达上限)", " after the same rejection repeated")
|
|
2086
|
+
: permissionBlocked
|
|
2087
|
+
? mt(contract, "(本轮权限拒绝过多)", " after repeated permission denials")
|
|
2088
|
+
: mt(contract, "(自动整改已达上限)", " after automatic repairs were exhausted");
|
|
2089
|
+
state.lastExecutorResult = evidencePath;
|
|
2090
|
+
state.lastReviewResult = null;
|
|
2091
|
+
state.currentStep = "executor_rejection_escalated";
|
|
2092
|
+
saveManagedRun(state);
|
|
2093
|
+
appendManagedEvent(state, {
|
|
2094
|
+
eventType: "executor.rejection_escalated_to_host",
|
|
2095
|
+
actor: "managed-runner",
|
|
2096
|
+
role: "runner",
|
|
2097
|
+
summary: semanticHostJudgment
|
|
2098
|
+
? mt(contract, `脚本不裁决疑似语义问题${stopReason},转交 Host 判断:${message}`, `The script does not decide ambiguous semantic issues${stopReason}; escalated to the host: ${message}`)
|
|
2099
|
+
: structuredMetadataFailure
|
|
2100
|
+
? mt(contract, `结构化元数据无法由 Runner 本地恢复${stopReason},转交 Host 判断:${message}`, `The runner could not recover structured metadata locally${stopReason}; escalated to the host: ${message}`)
|
|
2101
|
+
: protocolEvidenceFailure
|
|
2102
|
+
? mt(contract, `负向命令协议违规已止损${stopReason},转交 Host 核对原始日志:${message}`, `The negative-command protocol violation was stopped${stopReason} and escalated to the Host for raw-log review: ${message}`)
|
|
2103
|
+
: mt(contract, `自动整改已止损${stopReason},转交 Host 判断:${message}`, `Automatic repair stopped${stopReason} and escalated to the host: ${message}`),
|
|
2104
|
+
evidencePaths: [evidencePath],
|
|
2105
|
+
});
|
|
2106
|
+
return state;
|
|
2107
|
+
}
|
|
2108
|
+
const result = {
|
|
2109
|
+
result: "needs_fix",
|
|
2110
|
+
summary: message,
|
|
2111
|
+
findings: [
|
|
2112
|
+
{
|
|
2113
|
+
id: `AUTO-EXECUTOR-${state.executorInvocations}`,
|
|
2114
|
+
severity: "high",
|
|
2115
|
+
blocking: true,
|
|
2116
|
+
category: "delivery_gate",
|
|
2117
|
+
target: "完整研发交付与验证证据",
|
|
2118
|
+
evidence: message,
|
|
2119
|
+
risk: mt(contract, "研发 Agent 过早返回会增加评审轮次并留下未完成任务", "An early executor return increases review rounds and leaves work incomplete"),
|
|
2120
|
+
requiredFix: mt(contract, "继续完成全部本地任务、验证和测试报告后再返回 ready_for_review", "Continue all local work, verification, and test reporting before returning ready_for_review"),
|
|
2121
|
+
acceptanceChecks: [message],
|
|
2122
|
+
},
|
|
2123
|
+
],
|
|
2124
|
+
};
|
|
2125
|
+
const file = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), `executor-self-repair-${state.executorInvocations}.json`);
|
|
2126
|
+
writeJsonAtomic(file, result);
|
|
2127
|
+
state.lastReviewResult = file;
|
|
2128
|
+
state.lastExecutorResult = null;
|
|
2129
|
+
state.executorSelfRepairCount = repairCount + 1;
|
|
2130
|
+
state.currentStep = "executor_self_repair_required";
|
|
2131
|
+
saveManagedRun(state);
|
|
2132
|
+
appendManagedEvent(state, {
|
|
2133
|
+
eventType: "executor.self_repair_queued",
|
|
2134
|
+
actor: contract.executorAgent,
|
|
2135
|
+
role: "executor",
|
|
2136
|
+
summary: message,
|
|
2137
|
+
evidencePaths: [evidencePath, file],
|
|
2138
|
+
});
|
|
2139
|
+
return state;
|
|
2140
|
+
}
|
|
2141
|
+
function executorRejectionKey(message) {
|
|
2142
|
+
if (/漏报真实工作区变更|omitted real workspace changes/i.test(message)) {
|
|
2143
|
+
return "changed_files";
|
|
2144
|
+
}
|
|
2145
|
+
if (/结构化证据|taskEvidence|completion policy/i.test(message)) {
|
|
2146
|
+
return "task_evidence";
|
|
2147
|
+
}
|
|
2148
|
+
if (/至少需要两类|evidence categories|启动应用|real HTTP/i.test(message)) {
|
|
2149
|
+
return "verification_categories";
|
|
2150
|
+
}
|
|
2151
|
+
if (/命令证据中仍有失败|command evidence.*fail/i.test(message)) {
|
|
2152
|
+
return "command_failure";
|
|
2153
|
+
}
|
|
2154
|
+
if (/写入工具调用|write tool use/i.test(message))
|
|
2155
|
+
return "write_telemetry";
|
|
2156
|
+
return message.replace(/\d+/g, "#").slice(0, 160);
|
|
2157
|
+
}
|
|
2158
|
+
export function validateReviewResult(result, language = "zh") {
|
|
2159
|
+
if (!["pass", "needs_fix", "blocked"].includes(result.result)) {
|
|
2160
|
+
throw new Error(managedText(language, "监督 Agent 返回了非法状态", "Supervisor Agent returned an invalid status"));
|
|
2161
|
+
}
|
|
2162
|
+
if (!Array.isArray(result.findings))
|
|
2163
|
+
throw new Error(managedText(language, "监督 Agent 结果缺少 findings", "Supervisor Agent result is missing findings"));
|
|
2164
|
+
if (result.result === "pass" &&
|
|
2165
|
+
result.findings.some((finding) => finding.blocking)) {
|
|
2166
|
+
throw new Error(managedText(language, "监督结果声明通过,但仍存在阻断 finding", "Supervisor result reported pass while blocking findings remain"));
|
|
2167
|
+
}
|
|
2168
|
+
if (result.result === "needs_fix" &&
|
|
2169
|
+
!result.findings.some((finding) => finding.blocking)) {
|
|
2170
|
+
throw new Error(managedText(language, "监督结果要求整改,但没有提供阻断 finding", "Supervisor result requested fixes without a blocking finding"));
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
function executorReportSection(state, result) {
|
|
2174
|
+
if (state.language === "en") {
|
|
2175
|
+
return [
|
|
2176
|
+
`## Executor invocation ${state.executorInvocations}`,
|
|
2177
|
+
"",
|
|
2178
|
+
`Status: ${result.status}`,
|
|
2179
|
+
`Summary: ${result.summary}`,
|
|
2180
|
+
`Changed files: ${result.changedFiles.join(", ") || "none"}`,
|
|
2181
|
+
`Release prerequisites: ${result.releasePrerequisites.join("; ") || "none"}`,
|
|
2182
|
+
`Blockers: ${result.blockers.join("; ") || "none"}`,
|
|
2183
|
+
`Source progress: ${formatProgress(state.deliveryProgress?.source)}`,
|
|
2184
|
+
`Environment progress: ${formatProgress(state.deliveryProgress?.environment)}`,
|
|
2185
|
+
`Release progress: ${formatProgress(state.deliveryProgress?.release)}`,
|
|
2186
|
+
].join("\n");
|
|
2187
|
+
}
|
|
2188
|
+
return [
|
|
2189
|
+
`## 执行调用 ${state.executorInvocations}`,
|
|
2190
|
+
"",
|
|
2191
|
+
`状态:${result.status}`,
|
|
2192
|
+
`摘要:${result.summary}`,
|
|
2193
|
+
`修改文件:${result.changedFiles.join("、") || "无"}`,
|
|
2194
|
+
`发布前置条件:${result.releasePrerequisites.join(";") || "无"}`,
|
|
2195
|
+
`阻塞:${result.blockers.join(";") || "无"}`,
|
|
2196
|
+
`源码任务进度:${formatProgress(state.deliveryProgress?.source)}`,
|
|
2197
|
+
`环境验收进度:${formatProgress(state.deliveryProgress?.environment)}`,
|
|
2198
|
+
`发布签收进度:${formatProgress(state.deliveryProgress?.release)}`,
|
|
2199
|
+
].join("\n");
|
|
2200
|
+
}
|
|
2201
|
+
function formatProgress(progress) {
|
|
2202
|
+
return `${progress?.completed ?? 0}/${progress?.total ?? 0}`;
|
|
2203
|
+
}
|
|
2204
|
+
function reviewReportSection(state, result) {
|
|
2205
|
+
if (state.language === "en") {
|
|
2206
|
+
return [
|
|
2207
|
+
`## Formal review R${state.reviewRound}`,
|
|
2208
|
+
"",
|
|
2209
|
+
`Result: ${result.result}`,
|
|
2210
|
+
`Summary: ${result.summary}`,
|
|
2211
|
+
`Blocking findings: ${result.findings.filter((finding) => finding.blocking).length}`,
|
|
2212
|
+
].join("\n");
|
|
2213
|
+
}
|
|
2214
|
+
return [
|
|
2215
|
+
`## 正式检查 R${state.reviewRound}`,
|
|
2216
|
+
"",
|
|
2217
|
+
`结果:${result.result}`,
|
|
2218
|
+
`摘要:${result.summary}`,
|
|
2219
|
+
`阻断问题:${result.findings.filter((finding) => finding.blocking).length}`,
|
|
2220
|
+
].join("\n");
|
|
2221
|
+
}
|
|
2222
|
+
function updateRegistry(contract, state, env) {
|
|
2223
|
+
upsertRegistryEntry({
|
|
2224
|
+
taskId: contract.taskId,
|
|
2225
|
+
projectRoot: contract.projectRoot,
|
|
2226
|
+
status: state.status,
|
|
2227
|
+
profile: state.profile,
|
|
2228
|
+
language: contract.language,
|
|
2229
|
+
activeRunId: state.runId,
|
|
2230
|
+
createdAt: contract.createdAt,
|
|
2231
|
+
updatedAt: new Date().toISOString(),
|
|
2232
|
+
servicePid: state.servicePid,
|
|
2233
|
+
}, env);
|
|
2234
|
+
}
|
|
2235
|
+
function registryRunId(contract, env) {
|
|
2236
|
+
const registry = loadRegistry(env);
|
|
2237
|
+
const entry = registry.tasks.find((item) => item.taskId === contract.taskId);
|
|
2238
|
+
if (!entry) {
|
|
2239
|
+
throw new Error(mt(contract, `全局任务索引缺少 ${contract.taskId}`, `Global task registry is missing ${contract.taskId}`));
|
|
2240
|
+
}
|
|
2241
|
+
return entry.activeRunId;
|
|
2242
|
+
}
|
|
2243
|
+
function isConnectivityFailure(message) {
|
|
2244
|
+
return /(network|socket|unable to connect|connection (?:refused|reset|closed)|server_error|timed?\s*out|rate.?limit|overloaded|busy|\b(?:429|503|529)\b|\bECONN[A-Z]*\b|网络|无法连接|连接被拒绝|限流|繁忙)/i.test(message);
|
|
2245
|
+
}
|
|
2246
|
+
export function isPermanentProviderFailure(message) {
|
|
2247
|
+
return /(token plan|用量上限|套餐|quota.?exhaust|insufficient.?quota|error.?code\s*2056|\b2056\b|billing|payment required)/i.test(message);
|
|
2248
|
+
}
|
|
2249
|
+
function recordExecutorConnectivityFailure(contract, state, message) {
|
|
2250
|
+
if (!state.currentStep.startsWith("executor_"))
|
|
2251
|
+
return null;
|
|
2252
|
+
const credited = isCreditableTransientProviderFailure(message);
|
|
2253
|
+
if (credited) {
|
|
2254
|
+
state.executorInvocationCredits =
|
|
2255
|
+
(state.executorInvocationCredits ?? 0) + 1;
|
|
2256
|
+
state.totalAgentInvocationCredits =
|
|
2257
|
+
(state.totalAgentInvocationCredits ?? 0) + 1;
|
|
2258
|
+
}
|
|
2259
|
+
state.consecutiveTransientProviderFailures =
|
|
2260
|
+
(state.consecutiveTransientProviderFailures ?? 0) + 1;
|
|
2261
|
+
state.connectivityRetryCount = (state.connectivityRetryCount ?? 0) + 1;
|
|
2262
|
+
let stopAt = null;
|
|
2263
|
+
if (credited && contract.budgets.executorPhysicalStopAt !== undefined) {
|
|
2264
|
+
stopAt = contract.budgets.executorPhysicalStopAt + 1;
|
|
2265
|
+
contract.budgets = {
|
|
2266
|
+
...contract.budgets,
|
|
2267
|
+
executorPhysicalStopAt: stopAt,
|
|
2268
|
+
};
|
|
2269
|
+
contract.contractHash = calculateManagedContractHash(contract);
|
|
2270
|
+
state.contractHash = contract.contractHash;
|
|
2271
|
+
saveManagedTask(contract);
|
|
2272
|
+
}
|
|
2273
|
+
saveManagedRun(state);
|
|
2274
|
+
return {
|
|
2275
|
+
invocation: state.executorInvocations,
|
|
2276
|
+
stopAt,
|
|
2277
|
+
consecutiveFailures: state.consecutiveTransientProviderFailures,
|
|
2278
|
+
credited,
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function isCreditableTransientProviderFailure(message) {
|
|
2282
|
+
if (/(token plan|用量上限|套餐|quota.?exhaust|insufficient.?quota|error.?code\s*2056|\b2056\b)/i.test(message)) {
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
2285
|
+
return /(\b429\b|\b529\b|rate.?limit|overloaded|访问量过大|暂时.*限流|temporar(?:y|ily).*(?:busy|unavailable)|connection (?:refused|reset|closed)|unable to connect|socket hang up|\bECONN[A-Z]*\b|连接(?:被拒绝|重置|关闭)|无法连接)/i.test(message);
|
|
2286
|
+
}
|
|
2287
|
+
async function acquireProjectLocks(contract, binding) {
|
|
2288
|
+
const acquired = [];
|
|
2289
|
+
try {
|
|
2290
|
+
for (const root of [
|
|
2291
|
+
contract.projectRoot,
|
|
2292
|
+
...contract.relatedProjectRoots,
|
|
2293
|
+
].sort()) {
|
|
2294
|
+
acquired.push(await acquireManagedProjectClaim(path.join(root, ".superflow", "managed-project.lock"), {
|
|
2295
|
+
taskId: contract.taskId,
|
|
2296
|
+
projectRoot: root,
|
|
2297
|
+
bindingFingerprint: managedWorkspaceBindingFingerprint(binding),
|
|
2298
|
+
token: `${contract.taskId}:${randomUUID()}`,
|
|
2299
|
+
language: contract.language,
|
|
2300
|
+
}));
|
|
2301
|
+
}
|
|
2302
|
+
return acquired;
|
|
2303
|
+
}
|
|
2304
|
+
catch (error) {
|
|
2305
|
+
for (const lock of acquired.reverse())
|
|
2306
|
+
lock.release();
|
|
2307
|
+
throw error;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
async function prepareManagedExecution(contract) {
|
|
2311
|
+
let projectLocks = [];
|
|
2312
|
+
let stage = "workspace_binding";
|
|
2313
|
+
try {
|
|
2314
|
+
const workspaceBinding = ensureManagedWorkspaceBinding(contract);
|
|
2315
|
+
stage = "project_claim";
|
|
2316
|
+
projectLocks = await acquireProjectLocks(contract, workspaceBinding);
|
|
2317
|
+
stage = "contract_validation";
|
|
2318
|
+
validateManagedTaskContract(contract);
|
|
2319
|
+
stage = "frozen_prompt_validation";
|
|
2320
|
+
validateManagedTaskPromptSnapshot(contract);
|
|
2321
|
+
stage = "context_manifest";
|
|
2322
|
+
ensureManagedContextManifest(contract);
|
|
2323
|
+
return { workspaceBinding, projectLocks };
|
|
2324
|
+
}
|
|
2325
|
+
catch (error) {
|
|
2326
|
+
for (const projectLock of projectLocks.reverse())
|
|
2327
|
+
projectLock.release();
|
|
2328
|
+
if (error instanceof ManagedWorkspaceClaimError)
|
|
2329
|
+
throw error;
|
|
2330
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2331
|
+
throw new ManagedEnvironmentPreparationError(message, stage);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function runManagedDeliveryCheck(state, beforeReadyEvent = false) {
|
|
2335
|
+
const script = path.join(ASSETS_DIR, "scripts", "superflow-managed-work-check.mjs");
|
|
2336
|
+
const args = [script, state.projectRoot, state.taskId];
|
|
2337
|
+
if (beforeReadyEvent)
|
|
2338
|
+
args.push("--before-ready-event");
|
|
2339
|
+
execFileSync(process.execPath, args, {
|
|
2340
|
+
cwd: state.projectRoot,
|
|
2341
|
+
encoding: "utf-8",
|
|
2342
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
class ExecutorFinalPreflightError extends Error {
|
|
2346
|
+
evidencePaths;
|
|
2347
|
+
constructor(message, evidencePaths) {
|
|
2348
|
+
super(message);
|
|
2349
|
+
this.evidencePaths = evidencePaths;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
function runExecutorFinalPreflight(contract, state, result) {
|
|
2353
|
+
if (result.status !== "ready_for_review" ||
|
|
2354
|
+
safeGitOutput(contract.projectRoot, [
|
|
2355
|
+
"rev-parse",
|
|
2356
|
+
"--is-inside-work-tree",
|
|
2357
|
+
]) !== "true") {
|
|
2358
|
+
return [];
|
|
2359
|
+
}
|
|
2360
|
+
const script = path.join(ASSETS_DIR, "scripts", "superflow-managed-executor-preflight.mjs");
|
|
2361
|
+
const prefix = `executor-${state.executorInvocations}-final-preflight`;
|
|
2362
|
+
try {
|
|
2363
|
+
const stdout = execFileSync(process.execPath, [script, contract.projectRoot, state.taskId], {
|
|
2364
|
+
cwd: contract.projectRoot,
|
|
2365
|
+
encoding: "utf-8",
|
|
2366
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2367
|
+
});
|
|
2368
|
+
return writeInvocationLogs(state, prefix, stdout, "");
|
|
2369
|
+
}
|
|
2370
|
+
catch (error) {
|
|
2371
|
+
const execution = error;
|
|
2372
|
+
const logs = writeInvocationLogs(state, prefix, execution.stdout ?? "", execution.stderr ?? "");
|
|
2373
|
+
const detail = (execution.stderr ?? execution.message).trim();
|
|
2374
|
+
throw new ExecutorFinalPreflightError(mt(contract, `Runner 最终交付前门禁失败:${detail}`, `Runner final delivery preflight failed: ${detail}`), logs);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
function writeInvocationLogs(state, prefix, stdout, stderr) {
|
|
2378
|
+
const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
|
|
2379
|
+
const files = [];
|
|
2380
|
+
if (stdout) {
|
|
2381
|
+
files.push(...writeManagedLogParts(runDir, `${prefix}-events`, "jsonl", redactManagedLog(stdout)));
|
|
2382
|
+
}
|
|
2383
|
+
if (stderr) {
|
|
2384
|
+
files.push(...writeManagedLogParts(runDir, `${prefix}-stderr`, "log", redactManagedLog(stderr)));
|
|
2385
|
+
}
|
|
2386
|
+
return files;
|
|
2387
|
+
}
|
|
2388
|
+
function fileHash(file) {
|
|
2389
|
+
return createHash("sha256").update(readFileSync(file)).digest("hex");
|
|
2390
|
+
}
|
|
2391
|
+
const MANAGED_LOG_PART_BYTES = 8 * 1024 * 1024;
|
|
2392
|
+
export function writeManagedLogParts(directory, basename, extension, content) {
|
|
2393
|
+
const bytes = Buffer.from(content, "utf-8");
|
|
2394
|
+
if (bytes.length <= MANAGED_LOG_PART_BYTES) {
|
|
2395
|
+
const file = path.join(directory, `${basename}.${extension}`);
|
|
2396
|
+
writeFileSync(file, bytes);
|
|
2397
|
+
return [file];
|
|
2398
|
+
}
|
|
2399
|
+
const files = [];
|
|
2400
|
+
for (let offset = 0, part = 1; offset < bytes.length; part += 1) {
|
|
2401
|
+
let end = Math.min(offset + MANAGED_LOG_PART_BYTES, bytes.length);
|
|
2402
|
+
if (end < bytes.length) {
|
|
2403
|
+
const newline = bytes.lastIndexOf(10, end);
|
|
2404
|
+
if (newline > offset)
|
|
2405
|
+
end = newline + 1;
|
|
2406
|
+
}
|
|
2407
|
+
const file = path.join(directory, `${basename}-part-${String(part).padStart(3, "0")}.${extension}`);
|
|
2408
|
+
writeFileSync(file, bytes.subarray(offset, end));
|
|
2409
|
+
files.push(file);
|
|
2410
|
+
offset = end;
|
|
2411
|
+
}
|
|
2412
|
+
return files;
|
|
2413
|
+
}
|
|
2414
|
+
export { redactManagedLog, redactManagedValue } from "./redaction.js";
|
|
2415
|
+
function mt(contract, zh, en) {
|
|
2416
|
+
return managedText(contract.language, zh, en);
|
|
2417
|
+
}
|
|
2418
|
+
//# sourceMappingURL=runner.js.map
|