@chenmk/superflow 0.4.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.
Files changed (241) hide show
  1. package/INSTALL.en.md +54 -12
  2. package/INSTALL.md +59 -33
  3. package/README.md +178 -54
  4. package/README.zh-CN.md +156 -51
  5. package/assets/brand/managed-review-loop.png +0 -0
  6. package/assets/manifest.json +10 -5
  7. package/assets/scripts/superflow-coding-ready.mjs +135 -0
  8. package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
  9. package/assets/scripts/superflow-document-audit.mjs +240 -0
  10. package/assets/scripts/superflow-environment-preflight.mjs +398 -0
  11. package/assets/scripts/superflow-hook-guard.sh +40 -1
  12. package/assets/scripts/superflow-managed-command-guard.py +309 -0
  13. package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
  14. package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
  15. package/assets/scripts/superflow-managed-work-check.mjs +287 -26
  16. package/assets/scripts/superflow-managed-work-guard.sh +6 -92
  17. package/assets/skills/superflow-clarify/SKILL.md +14 -0
  18. package/assets/skills/superflow-design/SKILL.md +38 -13
  19. package/assets/skills/superflow-docs/SKILL.md +93 -2
  20. package/assets/skills/superflow-pipeline/SKILL.md +167 -10
  21. package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
  22. package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
  23. package/assets/skills/superflow-pipeline/references/document-templates.md +9 -5
  24. package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
  25. package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
  26. package/assets/skills/superflow-pipeline/references/managed-work.md +123 -15
  27. package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
  28. package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
  29. package/assets/skills/superflow-pipeline/references/quality-gate.md +7 -5
  30. package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
  31. package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +205 -9
  32. package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
  33. package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
  34. package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
  35. package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
  36. package/assets/skills-en/superflow-design/SKILL.md +31 -12
  37. package/assets/skills-en/superflow-docs/SKILL.md +99 -0
  38. package/assets/skills-en/superflow-pipeline/SKILL.md +163 -9
  39. package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
  40. package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
  41. package/assets/skills-en/superflow-pipeline/references/document-templates.md +10 -0
  42. package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
  43. package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
  44. package/assets/skills-en/superflow-pipeline/references/managed-work.md +160 -10
  45. package/assets/skills-en/superflow-pipeline/references/quality-gate.md +12 -0
  46. package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
  47. package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +205 -9
  48. package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
  49. package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
  50. package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
  51. package/dist/app/cli.js +64 -9
  52. package/dist/app/cli.js.map +1 -1
  53. package/dist/app/commands/change-guard.js +19 -2
  54. package/dist/app/commands/change-guard.js.map +1 -1
  55. package/dist/app/commands/check.js +107 -45
  56. package/dist/app/commands/check.js.map +1 -1
  57. package/dist/app/commands/docs.js +7 -3
  58. package/dist/app/commands/docs.js.map +1 -1
  59. package/dist/app/commands/doctor.js +174 -198
  60. package/dist/app/commands/doctor.js.map +1 -1
  61. package/dist/app/commands/eval.js +64 -0
  62. package/dist/app/commands/eval.js.map +1 -0
  63. package/dist/app/commands/implement.js +7 -3
  64. package/dist/app/commands/implement.js.map +1 -1
  65. package/dist/app/commands/init.js +12 -24
  66. package/dist/app/commands/init.js.map +1 -1
  67. package/dist/app/commands/mcp.js +167 -0
  68. package/dist/app/commands/mcp.js.map +1 -0
  69. package/dist/app/commands/pipeline.js +652 -18
  70. package/dist/app/commands/pipeline.js.map +1 -1
  71. package/dist/app/commands/requirement-review.js +6 -0
  72. package/dist/app/commands/requirement-review.js.map +1 -0
  73. package/dist/app/commands/status.js +66 -3
  74. package/dist/app/commands/status.js.map +1 -1
  75. package/dist/app/commands/uninstall.js +3 -3
  76. package/dist/app/commands/uninstall.js.map +1 -1
  77. package/dist/app/commands/update.js +83 -9
  78. package/dist/app/commands/update.js.map +1 -1
  79. package/dist/domains/agent.js +3 -11
  80. package/dist/domains/agent.js.map +1 -1
  81. package/dist/domains/config/cli-help.js +54 -12
  82. package/dist/domains/config/cli-help.js.map +1 -1
  83. package/dist/domains/config/i18n.js +6 -8
  84. package/dist/domains/config/i18n.js.map +1 -1
  85. package/dist/domains/config/manifest.js +0 -3
  86. package/dist/domains/config/manifest.js.map +1 -1
  87. package/dist/domains/deps.js +68 -7
  88. package/dist/domains/deps.js.map +1 -1
  89. package/dist/domains/managed-work/completion-policy.js +174 -0
  90. package/dist/domains/managed-work/completion-policy.js.map +1 -0
  91. package/dist/domains/managed-work/context-manifest.js +208 -0
  92. package/dist/domains/managed-work/context-manifest.js.map +1 -0
  93. package/dist/domains/managed-work/contract.js +135 -4
  94. package/dist/domains/managed-work/contract.js.map +1 -1
  95. package/dist/domains/managed-work/control-signals.js +34 -0
  96. package/dist/domains/managed-work/control-signals.js.map +1 -0
  97. package/dist/domains/managed-work/control.js +628 -0
  98. package/dist/domains/managed-work/control.js.map +1 -0
  99. package/dist/domains/managed-work/evaluation.js +497 -0
  100. package/dist/domains/managed-work/evaluation.js.map +1 -0
  101. package/dist/domains/managed-work/evidence-path.js +14 -0
  102. package/dist/domains/managed-work/evidence-path.js.map +1 -0
  103. package/dist/domains/managed-work/execution-contract.js +167 -0
  104. package/dist/domains/managed-work/execution-contract.js.map +1 -0
  105. package/dist/domains/managed-work/executor-policy.js +65 -0
  106. package/dist/domains/managed-work/executor-policy.js.map +1 -0
  107. package/dist/domains/managed-work/failure.js +65 -0
  108. package/dist/domains/managed-work/failure.js.map +1 -0
  109. package/dist/domains/managed-work/host-review.js +77 -0
  110. package/dist/domains/managed-work/host-review.js.map +1 -0
  111. package/dist/domains/managed-work/human-messages.js +31 -0
  112. package/dist/domains/managed-work/human-messages.js.map +1 -0
  113. package/dist/domains/managed-work/input.js +29 -3
  114. package/dist/domains/managed-work/input.js.map +1 -1
  115. package/dist/domains/managed-work/journal.js +77 -3
  116. package/dist/domains/managed-work/journal.js.map +1 -1
  117. package/dist/domains/managed-work/lock.js +67 -1
  118. package/dist/domains/managed-work/lock.js.map +1 -1
  119. package/dist/domains/managed-work/notifications.js +0 -15
  120. package/dist/domains/managed-work/notifications.js.map +1 -1
  121. package/dist/domains/managed-work/pair-admission.js +108 -0
  122. package/dist/domains/managed-work/pair-admission.js.map +1 -0
  123. package/dist/domains/managed-work/prompts.js +257 -22
  124. package/dist/domains/managed-work/prompts.js.map +1 -1
  125. package/dist/domains/managed-work/redaction.js +90 -0
  126. package/dist/domains/managed-work/redaction.js.map +1 -0
  127. package/dist/domains/managed-work/review-convergence.js +41 -0
  128. package/dist/domains/managed-work/review-convergence.js.map +1 -0
  129. package/dist/domains/managed-work/review-facts.js +181 -0
  130. package/dist/domains/managed-work/review-facts.js.map +1 -0
  131. package/dist/domains/managed-work/rule-selection.js +84 -0
  132. package/dist/domains/managed-work/rule-selection.js.map +1 -0
  133. package/dist/domains/managed-work/runner.js +1967 -175
  134. package/dist/domains/managed-work/runner.js.map +1 -1
  135. package/dist/domains/managed-work/schemas.js +72 -2
  136. package/dist/domains/managed-work/schemas.js.map +1 -1
  137. package/dist/domains/managed-work/service.js +82 -13
  138. package/dist/domains/managed-work/service.js.map +1 -1
  139. package/dist/domains/managed-work/state.js +195 -5
  140. package/dist/domains/managed-work/state.js.map +1 -1
  141. package/dist/domains/managed-work/storage.js +66 -24
  142. package/dist/domains/managed-work/storage.js.map +1 -1
  143. package/dist/domains/managed-work/types.js +12 -9
  144. package/dist/domains/managed-work/types.js.map +1 -1
  145. package/dist/domains/managed-work/verification-categories.js +39 -0
  146. package/dist/domains/managed-work/verification-categories.js.map +1 -0
  147. package/dist/domains/managed-work/verification-policy.js +116 -0
  148. package/dist/domains/managed-work/verification-policy.js.map +1 -0
  149. package/dist/domains/managed-work/wait.js +21 -3
  150. package/dist/domains/managed-work/wait.js.map +1 -1
  151. package/dist/domains/managed-work/workspace-binding.js +133 -0
  152. package/dist/domains/managed-work/workspace-binding.js.map +1 -0
  153. package/dist/domains/sdd-readiness.js +44 -0
  154. package/dist/domains/sdd-readiness.js.map +1 -0
  155. package/dist/domains/skill/deploy.js +0 -6
  156. package/dist/domains/skill/deploy.js.map +1 -1
  157. package/dist/domains/skill/scripts.js +0 -8
  158. package/dist/domains/skill/scripts.js.map +1 -1
  159. package/dist/domains/state.js +1 -2
  160. package/dist/domains/state.js.map +1 -1
  161. package/dist/mcp/runtime-identity.js +21 -0
  162. package/dist/mcp/runtime-identity.js.map +1 -0
  163. package/dist/mcp/server.js +369 -0
  164. package/dist/mcp/server.js.map +1 -0
  165. package/dist/platform/agent-process.js +499 -38
  166. package/dist/platform/agent-process.js.map +1 -1
  167. package/dist/platform/executable.js +28 -0
  168. package/dist/platform/executable.js.map +1 -0
  169. package/dist/platform/paths.js +1 -17
  170. package/dist/platform/paths.js.map +1 -1
  171. package/dist/platform/process-tree.js +44 -0
  172. package/dist/platform/process-tree.js.map +1 -0
  173. package/dist/platform/process.js +1 -1
  174. package/dist/platform/process.js.map +1 -1
  175. package/package.json +16 -7
  176. package/scripts/check-design-principles.mjs +107 -0
  177. package/scripts/check-managed-work-design.mjs +507 -0
  178. package/scripts/clean-dist.js +9 -0
  179. package/scripts/ensure-bin-modes.js +11 -0
  180. package/dist/cli/index.js +0 -193
  181. package/dist/cli/index.js.map +0 -1
  182. package/dist/commands/archive.js +0 -6
  183. package/dist/commands/archive.js.map +0 -1
  184. package/dist/commands/clarify.js +0 -6
  185. package/dist/commands/clarify.js.map +0 -1
  186. package/dist/commands/design.js +0 -28
  187. package/dist/commands/design.js.map +0 -1
  188. package/dist/commands/docs.js +0 -6
  189. package/dist/commands/docs.js.map +0 -1
  190. package/dist/commands/doctor.js +0 -494
  191. package/dist/commands/doctor.js.map +0 -1
  192. package/dist/commands/implement.js +0 -6
  193. package/dist/commands/implement.js.map +0 -1
  194. package/dist/commands/init.js +0 -520
  195. package/dist/commands/init.js.map +0 -1
  196. package/dist/commands/pipeline.js +0 -6
  197. package/dist/commands/pipeline.js.map +0 -1
  198. package/dist/commands/scan.js +0 -63
  199. package/dist/commands/scan.js.map +0 -1
  200. package/dist/commands/status.js +0 -173
  201. package/dist/commands/status.js.map +0 -1
  202. package/dist/commands/uninstall.js +0 -214
  203. package/dist/commands/uninstall.js.map +0 -1
  204. package/dist/commands/update.js +0 -193
  205. package/dist/commands/update.js.map +0 -1
  206. package/dist/commands/verify.js +0 -6
  207. package/dist/commands/verify.js.map +0 -1
  208. package/dist/core/assets.js +0 -27
  209. package/dist/core/assets.js.map +0 -1
  210. package/dist/core/cli-help.js +0 -98
  211. package/dist/core/cli-help.js.map +0 -1
  212. package/dist/core/context.js +0 -123
  213. package/dist/core/context.js.map +0 -1
  214. package/dist/core/dependencies.js +0 -146
  215. package/dist/core/dependencies.js.map +0 -1
  216. package/dist/core/detect.js +0 -64
  217. package/dist/core/detect.js.map +0 -1
  218. package/dist/core/i18n.js +0 -105
  219. package/dist/core/i18n.js.map +0 -1
  220. package/dist/core/integrity.js +0 -46
  221. package/dist/core/integrity.js.map +0 -1
  222. package/dist/core/manifest.js +0 -21
  223. package/dist/core/manifest.js.map +0 -1
  224. package/dist/core/prompts.js +0 -20
  225. package/dist/core/prompts.js.map +0 -1
  226. package/dist/core/registry.js +0 -134
  227. package/dist/core/registry.js.map +0 -1
  228. package/dist/core/rules.js +0 -17
  229. package/dist/core/rules.js.map +0 -1
  230. package/dist/core/scripts.js +0 -48
  231. package/dist/core/scripts.js.map +0 -1
  232. package/dist/core/skill-check.js +0 -32
  233. package/dist/core/skill-check.js.map +0 -1
  234. package/dist/core/skills.js +0 -62
  235. package/dist/core/skills.js.map +0 -1
  236. package/dist/core/state.js +0 -54
  237. package/dist/core/state.js.map +0 -1
  238. package/dist/utils/path.js +0 -11
  239. package/dist/utils/path.js.map +0 -1
  240. package/dist/utils/shell.js +0 -29
  241. package/dist/utils/shell.js.map +0 -1
@@ -1,35 +1,69 @@
1
- import { randomUUID } from "crypto";
1
+ import { createHash, randomUUID } from "crypto";
2
2
  import { execFileSync } from "child_process";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "fs";
4
4
  import path from "path";
5
5
  import { appendManagedEvent, readManagedEvents, verifyManagedJournal, } from "./journal.js";
6
- import { acquireManagedLock } from "./lock.js";
6
+ import { acquireManagedLock, acquireManagedProjectClaim } from "./lock.js";
7
7
  import { managedRunDir } from "./paths.js";
8
8
  import { buildExecutorPrompt, buildReviewPrompt } from "./prompts.js";
9
+ import { isAcceptedEvidenceCommand } from "./execution-contract.js";
9
10
  import { writeManagedSchemas } from "./schemas.js";
10
- import { appendTaskReport, loadRegistry, loadManagedRun, loadManagedTask, saveManagedRun, saveManagedTask, upsertRegistryEntry, updateTaskReportStatus, writeJsonAtomic, } from "./storage.js";
11
- import { activeRunMilliseconds, computeWorkspaceFingerprintForRoots, } from "./state.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";
12
16
  import { ASSETS_DIR } from "../../platform/assets.js";
13
17
  import { notifyManagedTask } from "./notifications.js";
14
- import { validateManagedTaskContract, validateManagedTaskPromptSnapshot, } from "./contract.js";
18
+ import { calculateManagedContractHash, validateManagedTaskContract, validateManagedTaskPromptSnapshot, } from "./contract.js";
15
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";
16
32
  export async function runManagedTask(projectRoot, taskId, invoker, env = process.env) {
17
- let contract = loadManagedTask(projectRoot, taskId);
33
+ const contract = loadManagedTask(projectRoot, taskId);
18
34
  let state = loadManagedRun(projectRoot, taskId, registryRunId(contract, env));
19
35
  const runDir = managedRunDir(projectRoot, taskId, state.runId);
20
36
  mkdirSync(runDir, { recursive: true });
21
37
  const lock = acquireManagedLock(path.join(runDir, "run.lock"), randomUUID(), contract.language);
22
38
  let projectLocks = [];
23
39
  try {
24
- projectLocks = acquireProjectLocks(contract);
25
- validateManagedTaskContract(contract);
26
- validateManagedTaskPromptSnapshot(contract);
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
+ }
27
50
  if (state.contractHash !== contract.contractHash) {
28
51
  return finishBlocked(contract, state, mt(contract, "运行状态与任务合同哈希不一致", "Run state and task contract hashes do not match"), env);
29
52
  }
30
53
  if (!verifyManagedJournal(state)) {
31
54
  return finishBlocked(contract, state, mt(contract, "事件账本校验失败,拒绝继续", "Event journal verification failed; refusing to continue"), env);
32
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
+ }
33
67
  state = transition(contract, state, "running", "preflight", env);
34
68
  appendManagedEvent(state, {
35
69
  eventType: "run.started",
@@ -39,33 +73,180 @@ export async function runManagedTask(projectRoot, taskId, invoker, env = process
39
73
  });
40
74
  const schemas = writeManagedSchemas(state);
41
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
+ }
42
84
  while (state.status === "running") {
85
+ state = pauseAtSafeBoundary(contract, state, env);
86
+ if (state.status !== "running")
87
+ break;
43
88
  state = enforceBudgets(contract, state, env);
44
89
  if (state.status !== "running")
45
90
  break;
46
- if (!state.lastExecutorResult) {
91
+ if (!state.lastExecutorResult ||
92
+ hasUndeliveredHumanGuidance(contract, state)) {
47
93
  const findings = loadRepairFindings(state);
48
94
  state = await executeWorker(contract, state, findings, schemas.executor, invoker, env);
49
95
  if (state.status !== "running")
50
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
+ }
51
104
  }
52
- state = await executeReview(contract, state, schemas.reviewer, invoker, env);
53
- if (state.status !== "running")
54
- break;
55
- contract = loadManagedTask(projectRoot, taskId);
56
- validateManagedTaskContract(contract);
105
+ state = prepareExternalHostReview(contract, state, env);
106
+ break;
57
107
  }
58
108
  return state;
59
109
  }
60
110
  catch (error) {
61
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
+ }
62
123
  if (state.activeSince) {
63
124
  state.activeRunMilliseconds += Math.max(0, Date.now() - Date.parse(state.activeSince));
64
125
  state.activeSince = null;
65
126
  saveManagedRun(state);
66
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
+ }
67
245
  if (isConnectivityFailure(message)) {
68
- state = transition(contract, state, "waiting_for_connectivity", "connectivity_wait", env);
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);
69
250
  state.blocker = message;
70
251
  saveManagedRun(state);
71
252
  appendManagedEvent(state, {
@@ -74,6 +255,22 @@ export async function runManagedTask(projectRoot, taskId, invoker, env = process
74
255
  role: "system",
75
256
  summary: mt(contract, `网络或模型服务暂时不可用:${message}`, `Network or model service is temporarily unavailable: ${message}`),
76
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
+ }
77
274
  return state;
78
275
  }
79
276
  return finishBlocked(contract, state, message, env);
@@ -84,6 +281,33 @@ export async function runManagedTask(projectRoot, taskId, invoker, env = process
84
281
  lock.release();
85
282
  }
86
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
+ }
87
311
  function reconcileCompletedReview(contract, state, env) {
88
312
  if (!state.lastReviewResult || !existsSync(state.lastReviewResult))
89
313
  return state;
@@ -104,36 +328,166 @@ function reconcileCompletedReview(contract, state, env) {
104
328
  });
105
329
  }
106
330
  if (result.result === "pass") {
107
- return finishAwaitingGitApproval(contract, state, result.summary, env);
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);
108
338
  }
109
339
  if (result.result === "blocked") {
110
340
  return finishBlocked(contract, state, result.summary, env);
111
341
  }
112
- if (state.reviewRound >= contract.budgets.maxReviewRounds) {
113
- return finishReviewExhausted(contract, state, env);
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;
114
353
  }
115
- if (state.executorInvocations <= state.reviewInvocations) {
116
- state.lastExecutorResult = null;
117
- state.currentStep = "repair_prompt_ready";
118
- saveManagedRun(state);
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) {
119
413
  appendManagedEvent(state, {
120
- eventType: "recovery.checkpoint_restored",
414
+ eventType: "executor.historical_evidence_filtered",
121
415
  actor: "managed-runner",
122
416
  role: "runner",
123
- summary: mt(contract, "根据已落盘的整改结论恢复到执行者整改步骤", "Restored the executor repair step from persisted review findings"),
417
+ summary: mt(contract, `按当前规则过滤 ${filteredCount} 条失效历史命令证据`, `Filtered ${filteredCount} stale historical command evidence item(s) under current rules`),
418
+ evidencePaths: [current],
124
419
  });
125
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
+ });
126
435
  return state;
127
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
+ }
128
459
  async function executeWorker(contract, state, findings, schemaPath, invoker, env) {
129
- if (state.executorInvocations >= contract.budgets.maxExecutorInvocations) {
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) {
130
471
  return finishBudgetExhausted(contract, state, mt(contract, "执行调用次数达到上限", "Executor invocation limit reached"), env);
131
472
  }
132
- if (state.totalAgentInvocations >= contract.budgets.maxTotalAgentInvocations) {
473
+ if (!hasActiveExecutorWindow &&
474
+ !hasUnlimitedAgentBudget(contract) &&
475
+ effectiveTotalAgentInvocations(state) >=
476
+ contract.budgets.maxTotalAgentInvocations) {
133
477
  return finishBudgetExhausted(contract, state, mt(contract, "总 Agent 调用次数达到上限", "Total Agent invocation limit reached"), env);
134
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
+ };
135
487
  state.executorInvocations += 1;
136
488
  state.totalAgentInvocations += 1;
489
+ state.executorStage = initialExecutorStage(findings, state.executorStage);
490
+ state.executorActiveStage = state.executorStage;
137
491
  state.currentStep =
138
492
  findings.length > 0 ? "executor_repairing" : "executor_implementing";
139
493
  state.activeSince = new Date().toISOString();
@@ -144,15 +498,26 @@ async function executeWorker(contract, state, findings, schemaPath, invoker, env
144
498
  role: "executor",
145
499
  summary: mt(contract, `开始第 ${state.executorInvocations} 次执行调用`, `Started executor invocation ${state.executorInvocations}`),
146
500
  });
147
- const prompt = buildExecutorPrompt(contract, state, findings);
501
+ appendExecutorStageEvent(contract, state, state.executorStage);
502
+ const handoffFile = writeManagedExecutorHandoff(contract, state, findings);
503
+ const prompt = buildExecutorPrompt(contract, state, findings, handoffFile);
148
504
  const promptFile = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), findings.length > 0
149
505
  ? `repair-${state.reviewRound}.md`
150
506
  : "executor-initial-prompt.md");
151
507
  writeFileSync(promptFile, prompt, "utf-8");
152
508
  state.lastRepairPrompt =
153
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
+ });
154
517
  const started = Date.now();
155
- const result = await invoker.invoke(invocation(contract, state, "executor", prompt, schemaPath, state.executorSession.sessionId, (sessionId) => {
518
+ const result = await invokeWithFailureEvidence(state, invoker, invocation(contract, state, prompt, promptFile, schemaPath, resumableSessionId, deliveryProtocolRecovery, (sessionId) => {
519
+ if (!acceptsExecutorCallback(state))
520
+ return;
156
521
  state.executorSession = {
157
522
  ...state.executorSession,
158
523
  sessionId,
@@ -161,25 +526,83 @@ async function executeWorker(contract, state, findings, schemaPath, invoker, env
161
526
  lastResumedRound: state.reviewRound,
162
527
  };
163
528
  saveManagedRun(state);
164
- }, (summary) => appendManagedEvent(state, {
165
- eventType: "executor.progress",
166
- actor: contract.executorAgent,
167
- role: "executor",
168
- summary,
169
- })));
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;
170
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;
171
546
  state.executorSession = {
172
547
  ...state.executorSession,
173
548
  sessionId: result.sessionId,
174
549
  createdAt: state.executorSession.createdAt ?? new Date().toISOString(),
175
550
  status: "active",
176
551
  lastResumedRound: state.reviewRound,
552
+ resumingSessionId: null,
177
553
  };
178
- validateExecutorResult(contract, result.output);
179
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
+ }
180
602
  writeJsonAtomic(resultFile, result.output);
181
603
  const logFiles = writeInvocationLogs(state, `executor-${state.executorInvocations}`, result.stdout, result.stderr);
182
604
  state.lastExecutorResult = resultFile;
605
+ state.deliveryProgress = evaluateCompletion(contract, result.output).progress;
183
606
  state.currentStep = "delivery_evidence_checking";
184
607
  state.workspaceFingerprint = computeWorkspaceFingerprintForRoots([
185
608
  contract.projectRoot,
@@ -188,109 +611,838 @@ async function executeWorker(contract, state, findings, schemaPath, invoker, env
188
611
  saveManagedRun(state);
189
612
  appendTaskReport(state, executorReportSection(state, result.output));
190
613
  appendManagedEvent(state, {
191
- eventType: "executor.ended",
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",
192
1335
  actor: contract.executorAgent,
193
1336
  role: "executor",
194
- summary: result.output.summary,
195
- evidencePaths: [resultFile, ...logFiles, ...result.output.evidence],
1337
+ summary: mt(contract, `Executor 已确认接收 ${validIds.length} 条运行中补充指导`, `Executor acknowledged ${validIds.length} in-flight guidance message(s)`),
1338
+ evidencePaths: [machineFile, receiptFile],
196
1339
  });
197
- if (result.output.status === "ready_for_review")
198
- return state;
199
- return finishBlocked(contract, state, managedList(contract.language, result.output.blockers, "", "") || result.output.summary, env);
200
1340
  }
201
- async function executeReview(contract, state, schemaPath, invoker, env) {
202
- if (state.reviewInvocations >= contract.budgets.maxReviewRounds) {
203
- return finishReviewExhausted(contract, state, env);
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("; ")}`);
204
1354
  }
205
- if (state.totalAgentInvocations >= contract.budgets.maxTotalAgentInvocations) {
206
- return finishBudgetExhausted(contract, state, mt(contract, "总 Agent 调用次数达到上限", "Total Agent invocation limit reached"), env);
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
+ }
207
1361
  }
208
- const fingerprint = computeWorkspaceFingerprintForRoots([
209
- contract.projectRoot,
210
- ...contract.relatedProjectRoots,
211
- ]);
212
- state.reviewRound += 1;
213
- state.reviewInvocations += 1;
214
- state.totalAgentInvocations += 1;
215
- state.currentStep = "supervisor_reviewing";
216
- state.activeSince = new Date().toISOString();
217
- saveManagedRun(state);
218
- appendManagedEvent(state, {
219
- eventType: "review.started",
220
- actor: contract.supervisorAgent,
221
- role: "supervisor",
222
- summary: mt(contract, `开始第 ${state.reviewRound} 轮正式检查`, `Started formal review round ${state.reviewRound}`),
223
- });
224
- const prompt = buildReviewPrompt(contract, state);
225
- const started = Date.now();
226
- const result = await invoker.invoke(invocation(contract, state, "supervisor", prompt, schemaPath, state.supervisorSession.sessionId, (sessionId) => {
227
- state.supervisorSession = {
228
- ...state.supervisorSession,
229
- sessionId,
230
- createdAt: state.supervisorSession.createdAt ?? new Date().toISOString(),
231
- status: "active",
232
- lastResumedRound: state.reviewRound,
233
- };
234
- saveManagedRun(state);
235
- }, (summary) => appendManagedEvent(state, {
236
- eventType: "review.progress",
237
- actor: contract.supervisorAgent,
238
- role: "supervisor",
239
- summary,
240
- })));
241
- state = closeActiveTime(state, started);
242
- state.supervisorSession = {
243
- ...state.supervisorSession,
244
- sessionId: result.sessionId,
245
- createdAt: state.supervisorSession.createdAt ?? new Date().toISOString(),
246
- status: "active",
247
- lastResumedRound: state.reviewRound,
248
- };
249
- validateReviewResult(result.output, contract.language);
250
- if (computeWorkspaceFingerprintForRoots([
251
- contract.projectRoot,
252
- ...contract.relatedProjectRoots,
253
- ]) !== fingerprint) {
254
- return finishBlocked(contract, state, mt(contract, "只读检查期间工作区发生变化", "Workspace changed during read-only review"), env);
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: [] };
255
1368
  }
256
- const resultFile = path.join(managedRunDir(state.projectRoot, state.taskId, state.runId), `review-result-${state.reviewRound}.json`);
257
- writeJsonAtomic(resultFile, result.output);
258
- const logFiles = writeInvocationLogs(state, `review-${state.reviewRound}`, result.stdout, result.stderr);
259
- state.lastReviewResult = resultFile;
260
- saveManagedRun(state);
261
- appendTaskReport(state, reviewReportSection(state, result.output));
262
- appendManagedEvent(state, {
263
- eventType: "review.ended",
264
- actor: contract.supervisorAgent,
265
- role: "supervisor",
266
- summary: result.output.summary,
267
- evidencePaths: [resultFile, ...logFiles],
268
- });
269
- if (result.output.result === "pass") {
270
- return finishAwaitingGitApproval(contract, state, result.output.summary, env);
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;
271
1397
  }
272
- if (result.output.result === "blocked") {
273
- return finishBlocked(contract, state, result.output.summary, env);
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();
274
1407
  }
275
- if (state.reviewRound >= contract.budgets.maxReviewRounds) {
276
- return finishReviewExhausted(contract, state, env);
1408
+ catch {
1409
+ return "";
277
1410
  }
278
- state.lastExecutorResult = null;
279
- state.currentStep = "repair_prompt_ready";
280
- saveManagedRun(state);
281
- return state;
282
1411
  }
283
- function invocation(contract, state, role, prompt, schemaPath, sessionId, onSession, onProgress) {
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");
284
1434
  return {
285
1435
  taskId: state.taskId,
286
1436
  runId: state.runId,
287
- role,
1437
+ role: "executor",
288
1438
  language: contract.language,
289
- agent: role === "executor" ? contract.executorAgent : contract.supervisorAgent,
1439
+ agent: contract.executorAgent,
290
1440
  projectRoot: state.projectRoot,
291
1441
  writableRoots: contract.relatedProjectRoots,
292
1442
  prompt,
1443
+ promptPath,
293
1444
  schemaPath,
1445
+ systemPromptPath: policyPath,
294
1446
  sessionId,
295
1447
  timeout: {
296
1448
  warningMs: contract.budgets.noProgressWarningMinutes * 60_000,
@@ -299,13 +1451,111 @@ function invocation(contract, state, role, prompt, schemaPath, sessionId, onSess
299
1451
  },
300
1452
  onSession,
301
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),
302
1522
  };
303
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
+ }
304
1552
  function transition(contract, state, status, currentStep, env) {
305
1553
  state = {
306
1554
  ...state,
307
1555
  status,
308
1556
  currentStep,
1557
+ blocker: status === "running" ? null : state.blocker,
1558
+ failure: status === "running" ? null : state.failure ?? null,
309
1559
  servicePid: process.pid,
310
1560
  updatedAt: new Date().toISOString(),
311
1561
  };
@@ -315,29 +1565,66 @@ function transition(contract, state, status, currentStep, env) {
315
1565
  updateRegistry(contract, state, env);
316
1566
  return state;
317
1567
  }
318
- function finishAwaitingGitApproval(contract, state, summary, env) {
319
- state = transition(contract, state, "awaiting_git_approval", "awaiting_git_approval", env);
320
- state.completedAt = new Date().toISOString();
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";
321
1598
  state.workspaceFingerprint = computeWorkspaceFingerprintForRoots([
322
1599
  contract.projectRoot,
323
1600
  ...contract.relatedProjectRoots,
324
1601
  ]);
325
- saveManagedRun(state);
326
1602
  runManagedDeliveryCheck(state, true);
327
- appendTaskReport(state, mt(contract, `\n## 最终结论\n\n${summary}\n\n状态:等待用户批准 Git 提交。\n`, `\n## Final conclusion\n\n${summary}\n\nStatus: waiting for user approval to commit Git changes.\n`));
328
- updateTaskReportStatus(state, mt(contract, "等待用户批准 Git 提交", "waiting for Git commit approval"));
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);
329
1609
  appendManagedEvent(state, {
330
1610
  eventType: "run.delivery_ready",
331
1611
  actor: "managed-runner",
332
1612
  role: "runner",
333
- summary: mt(contract, "任务已通过检查,等待用户批准 Git 提交", "Task passed review and is waiting for user approval to commit Git changes"),
1613
+ summary: mt(contract, `任务已通过本地检查,进入 ${status}`, `Task passed local review and entered ${status}`),
334
1614
  });
335
- notifyManagedTask({
1615
+ if (notifyManagedTask({
336
1616
  taskId: state.taskId,
337
1617
  type: "delivery_ready",
338
1618
  title: mt(contract, "Superflow 任务已完成", "Superflow task completed"),
339
- message: mt(contract, `${state.taskId} 已通过检查,等待 Git 提交批准`, `${state.taskId} passed review and is waiting for Git commit approval`),
340
- }, env);
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
+ }
341
1628
  return state;
342
1629
  }
343
1630
  function finishBlocked(contract, state, blocker, env) {
@@ -353,12 +1640,19 @@ function finishBlocked(contract, state, blocker, env) {
353
1640
  role: "runner",
354
1641
  summary: blocker,
355
1642
  });
356
- notifyManagedTask({
1643
+ if (notifyManagedTask({
357
1644
  taskId: state.taskId,
358
1645
  type: "human_required",
359
1646
  title: mt(contract, "Superflow 任务需要处理", "Superflow task needs attention"),
360
1647
  message: `${state.taskId}: ${blocker}`,
361
- }, env);
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
+ }
362
1656
  return state;
363
1657
  }
364
1658
  function finishReviewExhausted(contract, state, env) {
@@ -400,6 +1694,21 @@ function finishBudgetExhausted(contract, state, reason, env) {
400
1694
  return state;
401
1695
  }
402
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
+ }
403
1712
  const activeHours = activeRunMilliseconds(state) / 3_600_000;
404
1713
  if (activeHours >= contract.budgets.activeRunWarningHours &&
405
1714
  !readManagedEvents(state).some((event) => event.eventType === "deadline.warning")) {
@@ -423,6 +1732,9 @@ function enforceBudgets(contract, state, env) {
423
1732
  }
424
1733
  return state;
425
1734
  }
1735
+ function hasUnlimitedAgentBudget(contract) {
1736
+ return contract.budgets.unlimitedAgentInvocations === true;
1737
+ }
426
1738
  function closeActiveTime(state, started) {
427
1739
  state.activeRunMilliseconds += Math.max(0, Date.now() - started);
428
1740
  state.activeSince = null;
@@ -435,15 +1747,16 @@ function loadRepairFindings(state) {
435
1747
  const result = JSON.parse(readFileSync(state.lastReviewResult, "utf-8"));
436
1748
  return result.findings.filter((finding) => finding.blocking);
437
1749
  }
438
- function validateExecutorResult(contract, result) {
1750
+ function validateExecutorResult(contract, state, result, options = {}) {
439
1751
  if (!["ready_for_review", "blocked", "failed"].includes(result.status)) {
440
1752
  throw new Error(mt(contract, "执行 Agent 返回了非法状态", "Executor Agent returned an invalid status"));
441
1753
  }
442
1754
  if (!Array.isArray(result.changedFiles) ||
443
1755
  !Array.isArray(result.commands) ||
444
1756
  !Array.isArray(result.evidence) ||
1757
+ !Array.isArray(result.releasePrerequisites) ||
445
1758
  !Array.isArray(result.blockers)) {
446
- throw new Error(mt(contract, "执行 Agent 结果缺少文件、命令、证据或阻塞列表", "Executor Agent result is missing files, commands, evidence, or blockers"));
1759
+ throw new Error(mt(contract, "执行 Agent 结果缺少文件、命令、证据、发布前置条件或阻塞列表", "Executor Agent result is missing files, commands, evidence, release prerequisites, or blockers"));
447
1760
  }
448
1761
  if (!result.summary.trim()) {
449
1762
  throw new Error(mt(contract, "执行 Agent 结果缺少摘要", "Executor Agent result is missing a summary"));
@@ -457,44 +1770,392 @@ function validateExecutorResult(contract, result) {
457
1770
  if (result.blockers.length > 0) {
458
1771
  throw new Error(mt(contract, "执行 Agent 声明可检查,但仍存在阻塞项", "Executor Agent reported ready for review while blockers remain"));
459
1772
  }
460
- if (result.commands.some((command) => command.exitCode !== 0)) {
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) {
461
1779
  throw new Error(mt(contract, "执行 Agent 声明可检查,但命令证据中仍有失败项", "Executor Agent reported ready for review while command evidence still contains failures"));
462
1780
  }
463
1781
  if (contract.profile === "engineering" || contract.profile === "sdd") {
464
- validateEngineeringEvidence(result.commands, contract);
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(", ")}`));
465
1936
  }
466
1937
  }
467
- function validateEngineeringEvidence(commands, contract) {
468
- const successful = commands.filter((command) => command.exitCode === 0);
469
- const categories = new Set(successful.flatMap((command) => verificationCategories(command.command)));
470
- if (categories.size < 2) {
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) {
471
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"));
472
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;
473
2140
  }
474
- function verificationCategories(command) {
475
- const normalized = command.toLowerCase();
476
- const categories = [];
477
- if (/(^|\s)(test|vitest|jest|pytest|phpunit|go\s+test|cargo\s+test)(\s|$)/.test(normalized) ||
478
- /(?:npm|pnpm|yarn)\s+(?:run\s+)?test/.test(normalized) ||
479
- /mvn(?:\s+[^\s]+)*\s+test/.test(normalized) ||
480
- /gradle\w*\s+test/.test(normalized)) {
481
- categories.push("test");
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";
482
2147
  }
483
- if (/(?:npm|pnpm|yarn)\s+(?:run\s+)?build/.test(normalized) ||
484
- /(^|\s)(tsc|make|cmake|compile)(\s|$)/.test(normalized) ||
485
- /mvn(?:\s+[^\s]+)*\s+(?:compile|package|verify)/.test(normalized) ||
486
- /gradle\w*\s+(?:build|assemble)/.test(normalized) ||
487
- /cargo\s+build|go\s+build/.test(normalized)) {
488
- categories.push("build");
2148
+ if (/至少需要两类|evidence categories|启动应用|real HTTP/i.test(message)) {
2149
+ return "verification_categories";
489
2150
  }
490
- if (/(?:npm|pnpm|yarn)\s+(?:run\s+)?(?:start|dev|serve)/.test(normalized) ||
491
- /spring-boot:run|docker\s+compose\s+up|(^|\s)(curl|wget)(\s|$)/.test(normalized) ||
492
- /health(?:check)?|integration|e2e/.test(normalized)) {
493
- categories.push("runtime");
2151
+ if (/命令证据中仍有失败|command evidence.*fail/i.test(message)) {
2152
+ return "command_failure";
494
2153
  }
495
- return categories;
2154
+ if (/写入工具调用|write tool use/i.test(message))
2155
+ return "write_telemetry";
2156
+ return message.replace(/\d+/g, "#").slice(0, 160);
496
2157
  }
497
- function validateReviewResult(result, language = "zh") {
2158
+ export function validateReviewResult(result, language = "zh") {
498
2159
  if (!["pass", "needs_fix", "blocked"].includes(result.result)) {
499
2160
  throw new Error(managedText(language, "监督 Agent 返回了非法状态", "Supervisor Agent returned an invalid status"));
500
2161
  }
@@ -517,7 +2178,11 @@ function executorReportSection(state, result) {
517
2178
  `Status: ${result.status}`,
518
2179
  `Summary: ${result.summary}`,
519
2180
  `Changed files: ${result.changedFiles.join(", ") || "none"}`,
2181
+ `Release prerequisites: ${result.releasePrerequisites.join("; ") || "none"}`,
520
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)}`,
521
2186
  ].join("\n");
522
2187
  }
523
2188
  return [
@@ -526,9 +2191,16 @@ function executorReportSection(state, result) {
526
2191
  `状态:${result.status}`,
527
2192
  `摘要:${result.summary}`,
528
2193
  `修改文件:${result.changedFiles.join("、") || "无"}`,
2194
+ `发布前置条件:${result.releasePrerequisites.join(";") || "无"}`,
529
2195
  `阻塞:${result.blockers.join(";") || "无"}`,
2196
+ `源码任务进度:${formatProgress(state.deliveryProgress?.source)}`,
2197
+ `环境验收进度:${formatProgress(state.deliveryProgress?.environment)}`,
2198
+ `发布签收进度:${formatProgress(state.deliveryProgress?.release)}`,
530
2199
  ].join("\n");
531
2200
  }
2201
+ function formatProgress(progress) {
2202
+ return `${progress?.completed ?? 0}/${progress?.total ?? 0}`;
2203
+ }
532
2204
  function reviewReportSection(state, result) {
533
2205
  if (state.language === "en") {
534
2206
  return [
@@ -569,16 +2241,63 @@ function registryRunId(contract, env) {
569
2241
  return entry.activeRunId;
570
2242
  }
571
2243
  function isConnectivityFailure(message) {
572
- return /(network|socket|connect(?:ion|ed|ing)?|unable to connect|server_error|timed?\s*out|rate.?limit|overloaded|busy|429|503|ECONN|连接|限流|繁忙)/i.test(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);
573
2286
  }
574
- function acquireProjectLocks(contract) {
2287
+ async function acquireProjectLocks(contract, binding) {
575
2288
  const acquired = [];
576
2289
  try {
577
2290
  for (const root of [
578
2291
  contract.projectRoot,
579
2292
  ...contract.relatedProjectRoots,
580
2293
  ].sort()) {
581
- acquired.push(acquireManagedLock(path.join(root, ".superflow", "managed-project.lock"), `${contract.taskId}:${randomUUID()}`, contract.language));
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
+ }));
582
2301
  }
583
2302
  return acquired;
584
2303
  }
@@ -588,6 +2307,30 @@ function acquireProjectLocks(contract) {
588
2307
  throw error;
589
2308
  }
590
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
+ }
591
2334
  function runManagedDeliveryCheck(state, beforeReadyEvent = false) {
592
2335
  const script = path.join(ASSETS_DIR, "scripts", "superflow-managed-work-check.mjs");
593
2336
  const args = [script, state.projectRoot, state.taskId];
@@ -599,27 +2342,76 @@ function runManagedDeliveryCheck(state, beforeReadyEvent = false) {
599
2342
  stdio: ["ignore", "pipe", "pipe"],
600
2343
  });
601
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
+ }
602
2377
  function writeInvocationLogs(state, prefix, stdout, stderr) {
603
2378
  const runDir = managedRunDir(state.projectRoot, state.taskId, state.runId);
604
2379
  const files = [];
605
2380
  if (stdout) {
606
- const file = path.join(runDir, `${prefix}-events.jsonl`);
607
- writeFileSync(file, redactManagedLog(stdout), "utf-8");
608
- files.push(file);
2381
+ files.push(...writeManagedLogParts(runDir, `${prefix}-events`, "jsonl", redactManagedLog(stdout)));
609
2382
  }
610
2383
  if (stderr) {
611
- const file = path.join(runDir, `${prefix}-stderr.log`);
612
- writeFileSync(file, redactManagedLog(stderr), "utf-8");
613
- files.push(file);
2384
+ files.push(...writeManagedLogParts(runDir, `${prefix}-stderr`, "log", redactManagedLog(stderr)));
614
2385
  }
615
2386
  return files;
616
2387
  }
617
- export function redactManagedLog(value) {
618
- return value
619
- .replace(/(authorization\s*[:=]\s*bearer\s+)[^\s"']+/gi, "$1<redacted>")
620
- .replace(/(["']?(?:token|password|secret|cookie|api[_-]?key)["']?\s*[:=]\s*["']?)[^\s"',}]+/gi, "$1<redacted>")
621
- .replace(/(https?:\/\/[^:\s/@]+:)[^@\s/]+@/gi, "$1<redacted>@");
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;
622
2413
  }
2414
+ export { redactManagedLog, redactManagedValue } from "./redaction.js";
623
2415
  function mt(contract, zh, en) {
624
2416
  return managedText(contract.language, zh, en);
625
2417
  }